/src/samba/source3/smbd/dfree.c
Line | Count | Source |
1 | | /* |
2 | | Unix SMB/CIFS implementation. |
3 | | functions to calculate the free disk space |
4 | | Copyright (C) Andrew Tridgell 1998 |
5 | | |
6 | | This program is free software; you can redistribute it and/or modify |
7 | | it under the terms of the GNU General Public License as published by |
8 | | the Free Software Foundation; either version 3 of the License, or |
9 | | (at your option) any later version. |
10 | | |
11 | | This program is distributed in the hope that it will be useful, |
12 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | GNU General Public License for more details. |
15 | | |
16 | | You should have received a copy of the GNU General Public License |
17 | | along with this program. If not, see <http://www.gnu.org/licenses/>. |
18 | | */ |
19 | | |
20 | | #include "includes.h" |
21 | | #include "smbd/smbd.h" |
22 | | #include "smbd/globals.h" |
23 | | #include "lib/util/util_file.h" |
24 | | #include "lib/util/memcache.h" |
25 | | |
26 | | /**************************************************************************** |
27 | | Normalise for DOS usage. |
28 | | ****************************************************************************/ |
29 | | |
30 | | static void disk_norm(uint64_t *bsize, uint64_t *dfree, uint64_t *dsize) |
31 | 0 | { |
32 | | /* check if the disk is beyond the max disk size */ |
33 | 0 | uint64_t maxdisksize = lp_max_disk_size(); |
34 | 0 | if (maxdisksize) { |
35 | | /* convert to blocks - and don't overflow */ |
36 | 0 | maxdisksize = ((maxdisksize*1024)/(*bsize))*1024; |
37 | 0 | if (*dsize > maxdisksize) { |
38 | 0 | *dsize = maxdisksize; |
39 | 0 | } |
40 | 0 | if (*dfree > maxdisksize) { |
41 | 0 | *dfree = maxdisksize - 1; |
42 | 0 | } |
43 | | /* the -1 should stop applications getting div by 0 |
44 | | errors */ |
45 | 0 | } |
46 | 0 | } |
47 | | |
48 | | |
49 | | |
50 | | /**************************************************************************** |
51 | | Return number of 1K blocks available on a path and total number. |
52 | | ****************************************************************************/ |
53 | | |
54 | | static uint64_t sys_disk_free(connection_struct *conn, |
55 | | struct smb_filename *fname, |
56 | | uint64_t *bsize, |
57 | | uint64_t *dfree, |
58 | | uint64_t *dsize) |
59 | 0 | { |
60 | 0 | const struct loadparm_substitution *lp_sub = |
61 | 0 | loadparm_s3_global_substitution(); |
62 | 0 | uint64_t dfree_retval; |
63 | 0 | uint64_t dfree_q = 0; |
64 | 0 | uint64_t bsize_q = 0; |
65 | 0 | uint64_t dsize_q = 0; |
66 | 0 | const char *dfree_command; |
67 | 0 | static bool dfree_broken = false; |
68 | 0 | char *path = fname->base_name; |
69 | |
|
70 | 0 | (*dfree) = (*dsize) = 0; |
71 | 0 | (*bsize) = 512; |
72 | | |
73 | | /* |
74 | | * If external disk calculation specified, use it. |
75 | | */ |
76 | |
|
77 | 0 | dfree_command = lp_dfree_command(talloc_tos(), lp_sub, SNUM(conn)); |
78 | 0 | if (dfree_command && *dfree_command) { |
79 | 0 | const char *p; |
80 | 0 | char **lines = NULL; |
81 | 0 | char **argl = NULL; |
82 | |
|
83 | 0 | argl = str_list_make_empty(talloc_tos()); |
84 | 0 | str_list_add_printf(&argl, "%s", dfree_command); |
85 | 0 | str_list_add_printf(&argl, "%s", path); |
86 | 0 | if (argl == NULL) { |
87 | 0 | return (uint64_t)-1; |
88 | 0 | } |
89 | | |
90 | 0 | DBG_NOTICE("Running command '%s %s'\n", |
91 | 0 | dfree_command, |
92 | 0 | path); |
93 | |
|
94 | 0 | lines = file_lines_ploadv(talloc_tos(), argl, NULL); |
95 | |
|
96 | 0 | TALLOC_FREE(argl); |
97 | |
|
98 | 0 | if (lines != NULL) { |
99 | 0 | char *line = lines[0]; |
100 | |
|
101 | 0 | DEBUG (3, ("Read input from dfree, \"%s\"\n", line)); |
102 | |
|
103 | 0 | *dsize = STR_TO_SMB_BIG_UINT(line, &p); |
104 | 0 | while (p && *p && isspace(*p)) |
105 | 0 | p++; |
106 | 0 | if (p && *p) |
107 | 0 | *dfree = STR_TO_SMB_BIG_UINT(p, &p); |
108 | 0 | while (p && *p && isspace(*p)) |
109 | 0 | p++; |
110 | 0 | if (p && *p) |
111 | 0 | *bsize = STR_TO_SMB_BIG_UINT(p, NULL); |
112 | 0 | else |
113 | 0 | *bsize = 1024; |
114 | 0 | TALLOC_FREE(lines); |
115 | 0 | DEBUG (3, ("Parsed output of dfree, dsize=%u, dfree=%u, bsize=%u\n", |
116 | 0 | (unsigned int)*dsize, (unsigned int)*dfree, (unsigned int)*bsize)); |
117 | |
|
118 | 0 | if (!*dsize) |
119 | 0 | *dsize = 2048; |
120 | 0 | if (!*dfree) |
121 | 0 | *dfree = 1024; |
122 | |
|
123 | 0 | goto dfree_done; |
124 | 0 | } |
125 | 0 | DBG_ERR("file_lines_load() failed for " |
126 | 0 | "command '%s %s'. Error was : %s\n", |
127 | 0 | dfree_command, path, strerror(errno)); |
128 | 0 | } |
129 | | |
130 | 0 | if (SMB_VFS_DISK_FREE(conn, fname, bsize, dfree, dsize) == |
131 | 0 | (uint64_t)-1) { |
132 | 0 | DBG_ERR("VFS disk_free failed. Error was : %s\n", |
133 | 0 | strerror(errno)); |
134 | 0 | return (uint64_t)-1; |
135 | 0 | } |
136 | | |
137 | 0 | if (disk_quotas(conn, fname, &bsize_q, &dfree_q, &dsize_q)) { |
138 | 0 | uint64_t min_bsize = MIN(*bsize, bsize_q); |
139 | |
|
140 | 0 | (*dfree) = (*dfree) * (*bsize) / min_bsize; |
141 | 0 | (*dsize) = (*dsize) * (*bsize) / min_bsize; |
142 | 0 | dfree_q = dfree_q * bsize_q / min_bsize; |
143 | 0 | dsize_q = dsize_q * bsize_q / min_bsize; |
144 | |
|
145 | 0 | (*bsize) = min_bsize; |
146 | 0 | (*dfree) = MIN(*dfree,dfree_q); |
147 | 0 | (*dsize) = MIN(*dsize,dsize_q); |
148 | 0 | } |
149 | | |
150 | | /* FIXME : Any reason for this assumption ? */ |
151 | 0 | if (*bsize < 256) { |
152 | 0 | DEBUG(5,("disk_free:Warning: bsize == %d < 256 . Changing to assumed correct bsize = 512\n",(int)*bsize)); |
153 | 0 | *bsize = 512; |
154 | 0 | } |
155 | |
|
156 | 0 | if ((*dsize)<1) { |
157 | 0 | if (!dfree_broken) { |
158 | 0 | DEBUG(0,("WARNING: dfree is broken on this system\n")); |
159 | 0 | dfree_broken=true; |
160 | 0 | } |
161 | 0 | *dsize = 20*1024*1024/(*bsize); |
162 | 0 | *dfree = MAX(1,*dfree); |
163 | 0 | } |
164 | |
|
165 | 0 | dfree_done: |
166 | 0 | disk_norm(bsize, dfree, dsize); |
167 | |
|
168 | 0 | if ((*bsize) < 1024) { |
169 | 0 | dfree_retval = (*dfree)/(1024/(*bsize)); |
170 | 0 | } else { |
171 | 0 | dfree_retval = ((*bsize)/1024)*(*dfree); |
172 | 0 | } |
173 | |
|
174 | 0 | return(dfree_retval); |
175 | 0 | } |
176 | | |
177 | | /**************************************************************************** |
178 | | Potentially returned cached dfree info. |
179 | | |
180 | | Depending on the file system layout and file system features, the free space |
181 | | information can be different for different sub directories underneath a SMB |
182 | | share. Store the cache information in memcache using the query path as the |
183 | | key to accommodate this. |
184 | | ****************************************************************************/ |
185 | | |
186 | | struct dfree_cached_info { |
187 | | time_t last_dfree_time; |
188 | | uint64_t dfree_ret; |
189 | | uint64_t bsize; |
190 | | uint64_t dfree; |
191 | | uint64_t dsize; |
192 | | }; |
193 | | |
194 | | uint64_t get_dfree_info(connection_struct *conn, struct smb_filename *fname, |
195 | | uint64_t *bsize, uint64_t *dfree, uint64_t *dsize) |
196 | 0 | { |
197 | 0 | int dfree_cache_time = lp_dfree_cache_time(SNUM(conn)); |
198 | 0 | struct dfree_cached_info *dfc = NULL; |
199 | 0 | struct dfree_cached_info dfc_new = { 0 }; |
200 | 0 | uint64_t dfree_ret; |
201 | 0 | char tmpbuf[PATH_MAX]; |
202 | 0 | char *full_path = NULL; |
203 | 0 | char *to_free = NULL; |
204 | 0 | char *key_path = NULL; |
205 | 0 | size_t len; |
206 | 0 | DATA_BLOB key, value; |
207 | 0 | bool found; |
208 | |
|
209 | 0 | if (!dfree_cache_time) { |
210 | 0 | return sys_disk_free(conn, fname, bsize, dfree, dsize); |
211 | 0 | } |
212 | | |
213 | 0 | len = full_path_tos(conn->connectpath, |
214 | 0 | fname->base_name, |
215 | 0 | tmpbuf, |
216 | 0 | sizeof(tmpbuf), |
217 | 0 | &full_path, |
218 | 0 | &to_free); |
219 | 0 | if (len == -1) { |
220 | 0 | errno = ENOMEM; |
221 | 0 | return -1; |
222 | 0 | } |
223 | | |
224 | 0 | if (VALID_STAT(fname->st) && S_ISREG(fname->st.st_ex_mode)) { |
225 | | /* |
226 | | * In case of a file use the parent directory to reduce number |
227 | | * of cache entries. |
228 | | */ |
229 | 0 | bool ok; |
230 | |
|
231 | 0 | ok = parent_dirname(talloc_tos(), |
232 | 0 | full_path, |
233 | 0 | &key_path, |
234 | 0 | NULL); |
235 | 0 | TALLOC_FREE(to_free); /* We're done with full_path */ |
236 | |
|
237 | 0 | if (!ok) { |
238 | 0 | errno = ENOMEM; |
239 | 0 | return -1; |
240 | 0 | } |
241 | | |
242 | | /* |
243 | | * key_path is always a talloced object. |
244 | | */ |
245 | 0 | to_free = key_path; |
246 | 0 | } else { |
247 | | /* |
248 | | * key_path might not be a talloced object; rely on |
249 | | * to_free set from full_path_tos. |
250 | | */ |
251 | 0 | key_path = full_path; |
252 | 0 | } |
253 | | |
254 | 0 | key = data_blob_const(key_path, strlen(key_path)); |
255 | 0 | found = memcache_lookup(smbd_memcache(), |
256 | 0 | DFREE_CACHE, |
257 | 0 | key, |
258 | 0 | &value); |
259 | 0 | dfc = found ? (struct dfree_cached_info *)value.data : NULL; |
260 | |
|
261 | 0 | if (dfc && (conn->lastused - dfc->last_dfree_time < dfree_cache_time)) { |
262 | 0 | DBG_DEBUG("Returning dfree cache entry for %s\n", key_path); |
263 | 0 | *bsize = dfc->bsize; |
264 | 0 | *dfree = dfc->dfree; |
265 | 0 | *dsize = dfc->dsize; |
266 | 0 | dfree_ret = dfc->dfree_ret; |
267 | 0 | goto out; |
268 | 0 | } |
269 | | |
270 | 0 | dfree_ret = sys_disk_free(conn, fname, bsize, dfree, dsize); |
271 | |
|
272 | 0 | if (dfree_ret == (uint64_t)-1) { |
273 | | /* Don't cache bad data. */ |
274 | 0 | goto out; |
275 | 0 | } |
276 | | |
277 | 0 | DBG_DEBUG("Creating dfree cache entry for %s\n", key_path); |
278 | 0 | dfc_new.bsize = *bsize; |
279 | 0 | dfc_new.dfree = *dfree; |
280 | 0 | dfc_new.dsize = *dsize; |
281 | 0 | dfc_new.dfree_ret = dfree_ret; |
282 | 0 | dfc_new.last_dfree_time = conn->lastused; |
283 | 0 | memcache_add(smbd_memcache(), |
284 | 0 | DFREE_CACHE, |
285 | 0 | key, |
286 | 0 | data_blob_const(&dfc_new, sizeof(dfc_new))); |
287 | |
|
288 | 0 | out: |
289 | 0 | TALLOC_FREE(to_free); |
290 | 0 | return dfree_ret; |
291 | 0 | } |
292 | | |
293 | | void flush_dfree_cache(void) |
294 | 0 | { |
295 | 0 | memcache_flush(smbd_memcache(), DFREE_CACHE); |
296 | 0 | } |