/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 bool handle_dfree_command(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 | const char *dfree_command = NULL; |
63 | 0 | char *path = fname->base_name; |
64 | 0 | char **lines = NULL; |
65 | 0 | char **argl = NULL; |
66 | 0 | char *line = NULL; |
67 | 0 | int ret; |
68 | |
|
69 | 0 | dfree_command = lp_dfree_command(talloc_tos(), lp_sub, SNUM(conn)); |
70 | 0 | if (!dfree_command || !*dfree_command) { |
71 | 0 | return false; |
72 | 0 | } |
73 | | |
74 | 0 | argl = str_list_make_empty(talloc_tos()); |
75 | 0 | str_list_add_printf(&argl, "%s", dfree_command); |
76 | 0 | str_list_add_printf(&argl, "%s", path); |
77 | 0 | if (argl == NULL) { |
78 | 0 | return false; |
79 | 0 | } |
80 | | |
81 | 0 | DBG_NOTICE("Running command '%s %s'\n", |
82 | 0 | dfree_command, |
83 | 0 | path); |
84 | |
|
85 | 0 | lines = file_lines_ploadv(talloc_tos(), argl, NULL); |
86 | |
|
87 | 0 | TALLOC_FREE(argl); |
88 | |
|
89 | 0 | if ((lines == NULL) || (lines[0] == NULL)) { |
90 | 0 | DBG_ERR("file_lines_ploadv() failed for " |
91 | 0 | "command '%s %s'. Error was : %s\n", |
92 | 0 | dfree_command, path, strerror(errno)); |
93 | 0 | TALLOC_FREE(lines); |
94 | 0 | return false; |
95 | 0 | } |
96 | | |
97 | 0 | line = lines[0]; |
98 | |
|
99 | 0 | DBG_NOTICE("Read input from dfree, \"%s\"\n", line); |
100 | |
|
101 | 0 | ret = sscanf( |
102 | 0 | line, "%" SCNu64 " %" SCNu64 " %" SCNu64, dsize, dfree, bsize); |
103 | |
|
104 | 0 | TALLOC_FREE(lines); |
105 | |
|
106 | 0 | if (ret < 3) { |
107 | 0 | *bsize = 1024; |
108 | 0 | } |
109 | 0 | if (ret < 2) { |
110 | 0 | *dfree = 1024; |
111 | 0 | } |
112 | 0 | if (ret < 1) { |
113 | 0 | *dsize = 2048; |
114 | 0 | } |
115 | |
|
116 | 0 | DBG_NOTICE("Parsed output of dfree, ret=%d, dsize=%" PRIu64 ", " |
117 | 0 | "dfree=%" PRIu64 ", bsize=%" PRIu64 "\n", |
118 | 0 | ret, |
119 | 0 | *dsize, |
120 | 0 | *dfree, |
121 | 0 | *bsize); |
122 | |
|
123 | 0 | return true; |
124 | 0 | } |
125 | | |
126 | | static uint64_t sys_disk_free(connection_struct *conn, |
127 | | struct smb_filename *fname, |
128 | | uint64_t *bsize, |
129 | | uint64_t *dfree, |
130 | | uint64_t *dsize) |
131 | 0 | { |
132 | 0 | uint64_t dfree_retval; |
133 | 0 | uint64_t dfree_q = 0; |
134 | 0 | uint64_t bsize_q = 0; |
135 | 0 | uint64_t dsize_q = 0; |
136 | 0 | static bool dfree_broken = false; |
137 | 0 | bool ok; |
138 | |
|
139 | 0 | (*dfree) = (*dsize) = 0; |
140 | 0 | (*bsize) = 512; |
141 | | |
142 | | /* |
143 | | * If external disk calculation specified, use it. |
144 | | */ |
145 | 0 | ok = handle_dfree_command(conn, fname, bsize, dfree, dsize); |
146 | 0 | if (ok) { |
147 | 0 | goto dfree_done; |
148 | 0 | } |
149 | | |
150 | 0 | if (SMB_VFS_DISK_FREE(conn, fname, bsize, dfree, dsize) == |
151 | 0 | (uint64_t)-1) { |
152 | 0 | DBG_ERR("VFS disk_free failed. Error was : %s\n", |
153 | 0 | strerror(errno)); |
154 | 0 | return (uint64_t)-1; |
155 | 0 | } |
156 | | |
157 | 0 | if (disk_quotas(conn, fname, &bsize_q, &dfree_q, &dsize_q)) { |
158 | 0 | uint64_t min_bsize = MIN(*bsize, bsize_q); |
159 | |
|
160 | 0 | (*dfree) = (*dfree) * (*bsize) / min_bsize; |
161 | 0 | (*dsize) = (*dsize) * (*bsize) / min_bsize; |
162 | 0 | dfree_q = dfree_q * bsize_q / min_bsize; |
163 | 0 | dsize_q = dsize_q * bsize_q / min_bsize; |
164 | |
|
165 | 0 | (*bsize) = min_bsize; |
166 | 0 | (*dfree) = MIN(*dfree,dfree_q); |
167 | 0 | (*dsize) = MIN(*dsize,dsize_q); |
168 | 0 | } |
169 | | |
170 | | /* FIXME : Any reason for this assumption ? */ |
171 | 0 | if (*bsize < 256) { |
172 | 0 | DBG_INFO("Warning: bsize == %"PRIu64" < 256 . " |
173 | 0 | "Changing to assumed correct bsize = 512\n", |
174 | 0 | *bsize); |
175 | 0 | *bsize = 512; |
176 | 0 | } |
177 | |
|
178 | 0 | if ((*dsize)<1) { |
179 | 0 | if (!dfree_broken) { |
180 | 0 | DEBUG(0,("WARNING: dfree is broken on this system\n")); |
181 | 0 | dfree_broken=true; |
182 | 0 | } |
183 | 0 | *dsize = 20*1024*1024/(*bsize); |
184 | 0 | *dfree = MAX(1,*dfree); |
185 | 0 | } |
186 | |
|
187 | 0 | dfree_done: |
188 | 0 | disk_norm(bsize, dfree, dsize); |
189 | |
|
190 | 0 | if ((*bsize) < 1024) { |
191 | 0 | dfree_retval = (*dfree)/(1024/(*bsize)); |
192 | 0 | } else { |
193 | 0 | dfree_retval = ((*bsize)/1024)*(*dfree); |
194 | 0 | } |
195 | |
|
196 | 0 | return(dfree_retval); |
197 | 0 | } |
198 | | |
199 | | /**************************************************************************** |
200 | | Potentially returned cached dfree info. |
201 | | |
202 | | Depending on the file system layout and file system features, the free space |
203 | | information can be different for different sub directories underneath a SMB |
204 | | share. Store the cache information in memcache using the query path as the |
205 | | key to accommodate this. |
206 | | ****************************************************************************/ |
207 | | |
208 | | struct dfree_cached_info { |
209 | | time_t last_dfree_time; |
210 | | uint64_t dfree_ret; |
211 | | uint64_t bsize; |
212 | | uint64_t dfree; |
213 | | uint64_t dsize; |
214 | | }; |
215 | | |
216 | | uint64_t get_dfree_info(connection_struct *conn, struct smb_filename *fname, |
217 | | uint64_t *bsize, uint64_t *dfree, uint64_t *dsize) |
218 | 0 | { |
219 | 0 | int dfree_cache_time = lp_dfree_cache_time(SNUM(conn)); |
220 | 0 | struct dfree_cached_info *dfc = NULL; |
221 | 0 | struct dfree_cached_info dfc_new = { 0 }; |
222 | 0 | uint64_t dfree_ret; |
223 | 0 | char tmpbuf[PATH_MAX]; |
224 | 0 | char *full_path = NULL; |
225 | 0 | char *to_free = NULL; |
226 | 0 | char *key_path = NULL; |
227 | 0 | size_t len; |
228 | 0 | DATA_BLOB key, value; |
229 | 0 | bool found; |
230 | |
|
231 | 0 | if (!dfree_cache_time) { |
232 | 0 | return sys_disk_free(conn, fname, bsize, dfree, dsize); |
233 | 0 | } |
234 | | |
235 | 0 | len = full_path_tos(conn->connectpath, |
236 | 0 | fname->base_name, |
237 | 0 | tmpbuf, |
238 | 0 | sizeof(tmpbuf), |
239 | 0 | &full_path, |
240 | 0 | &to_free); |
241 | 0 | if (len == -1) { |
242 | 0 | errno = ENOMEM; |
243 | 0 | return -1; |
244 | 0 | } |
245 | | |
246 | 0 | if (VALID_STAT(fname->st) && S_ISREG(fname->st.st_ex_mode)) { |
247 | | /* |
248 | | * In case of a file use the parent directory to reduce number |
249 | | * of cache entries. |
250 | | */ |
251 | 0 | bool ok; |
252 | |
|
253 | 0 | ok = parent_dirname(talloc_tos(), |
254 | 0 | full_path, |
255 | 0 | &key_path, |
256 | 0 | NULL); |
257 | 0 | TALLOC_FREE(to_free); /* We're done with full_path */ |
258 | |
|
259 | 0 | if (!ok) { |
260 | 0 | errno = ENOMEM; |
261 | 0 | return -1; |
262 | 0 | } |
263 | | |
264 | | /* |
265 | | * key_path is always a talloced object. |
266 | | */ |
267 | 0 | to_free = key_path; |
268 | 0 | } else { |
269 | | /* |
270 | | * key_path might not be a talloced object; rely on |
271 | | * to_free set from full_path_tos. |
272 | | */ |
273 | 0 | key_path = full_path; |
274 | 0 | } |
275 | | |
276 | 0 | key = data_blob_const(key_path, strlen(key_path)); |
277 | 0 | found = memcache_lookup(smbd_memcache(), |
278 | 0 | DFREE_CACHE, |
279 | 0 | key, |
280 | 0 | &value); |
281 | 0 | dfc = found ? (struct dfree_cached_info *)value.data : NULL; |
282 | |
|
283 | 0 | if (dfc && (conn->lastused - dfc->last_dfree_time < dfree_cache_time)) { |
284 | 0 | DBG_DEBUG("Returning dfree cache entry for %s\n", key_path); |
285 | 0 | *bsize = dfc->bsize; |
286 | 0 | *dfree = dfc->dfree; |
287 | 0 | *dsize = dfc->dsize; |
288 | 0 | dfree_ret = dfc->dfree_ret; |
289 | 0 | goto out; |
290 | 0 | } |
291 | | |
292 | 0 | dfree_ret = sys_disk_free(conn, fname, bsize, dfree, dsize); |
293 | |
|
294 | 0 | if (dfree_ret == (uint64_t)-1) { |
295 | | /* Don't cache bad data. */ |
296 | 0 | goto out; |
297 | 0 | } |
298 | | |
299 | 0 | DBG_DEBUG("Creating dfree cache entry for %s\n", key_path); |
300 | 0 | dfc_new.bsize = *bsize; |
301 | 0 | dfc_new.dfree = *dfree; |
302 | 0 | dfc_new.dsize = *dsize; |
303 | 0 | dfc_new.dfree_ret = dfree_ret; |
304 | 0 | dfc_new.last_dfree_time = conn->lastused; |
305 | 0 | memcache_add(smbd_memcache(), |
306 | 0 | DFREE_CACHE, |
307 | 0 | key, |
308 | 0 | data_blob_const(&dfc_new, sizeof(dfc_new))); |
309 | |
|
310 | 0 | out: |
311 | 0 | TALLOC_FREE(to_free); |
312 | 0 | return dfree_ret; |
313 | 0 | } |
314 | | |
315 | | void flush_dfree_cache(void) |
316 | 0 | { |
317 | 0 | memcache_flush(smbd_memcache(), DFREE_CACHE); |
318 | 0 | } |