Coverage Report

Created: 2026-05-24 06:47

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