Coverage Report

Created: 2026-02-14 07:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}