This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
+ the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "lib/util_file.h"
+#include "lib/util/memcache.h"
/****************************************************************************
-normalise for DOS usage
+ Normalise for DOS usage.
****************************************************************************/
-static void disk_norm(BOOL small_query, SMB_BIG_UINT *bsize,SMB_BIG_UINT *dfree,SMB_BIG_UINT *dsize)
+
+static void disk_norm(uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
{
/* check if the disk is beyond the max disk size */
- SMB_BIG_UINT maxdisksize = lp_maxdisksize();
+ uint64_t maxdisksize = lp_max_disk_size();
if (maxdisksize) {
/* convert to blocks - and don't overflow */
maxdisksize = ((maxdisksize*1024)/(*bsize))*1024;
- if (*dsize > maxdisksize) *dsize = maxdisksize;
- if (*dfree > maxdisksize) *dfree = maxdisksize-1;
+ if (*dsize > maxdisksize) {
+ *dsize = maxdisksize;
+ }
+ if (*dfree > maxdisksize) {
+ *dfree = maxdisksize - 1;
+ }
/* the -1 should stop applications getting div by 0
errors */
- }
-
- while (*dfree > WORDMAX || *dsize > WORDMAX || *bsize < 512) {
- *dfree /= 2;
- *dsize /= 2;
- *bsize *= 2;
- if(small_query) {
- /*
- * Force max to fit in 16 bit fields.
- */
- if (*bsize > (WORDMAX*512)) {
- *bsize = (WORDMAX*512);
- if (*dsize > WORDMAX)
- *dsize = WORDMAX;
- if (*dfree > WORDMAX)
- *dfree = WORDMAX;
- break;
- }
- }
}
}
/****************************************************************************
- return number of 1K blocks available on a path and total number
+ Return number of 1K blocks available on a path and total number.
****************************************************************************/
-static SMB_BIG_UINT disk_free(const char *path, BOOL small_query,
- SMB_BIG_UINT *bsize,SMB_BIG_UINT *dfree,SMB_BIG_UINT *dsize)
+static uint64_t sys_disk_free(connection_struct *conn,
+ struct smb_filename *fname,
+ uint64_t *bsize,
+ uint64_t *dfree,
+ uint64_t *dsize)
{
- int dfree_retval;
- SMB_BIG_UINT dfree_q = 0;
- SMB_BIG_UINT bsize_q = 0;
- SMB_BIG_UINT dsize_q = 0;
- char *dfree_command;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uint64_t dfree_retval;
+ uint64_t dfree_q = 0;
+ uint64_t bsize_q = 0;
+ uint64_t dsize_q = 0;
+ const char *dfree_command;
+ static bool dfree_broken = false;
+ char *path = fname->base_name;
(*dfree) = (*dsize) = 0;
(*bsize) = 512;
* If external disk calculation specified, use it.
*/
- dfree_command = lp_dfree_command();
+ dfree_command = lp_dfree_command(talloc_tos(), lp_sub, SNUM(conn));
if (dfree_command && *dfree_command) {
const char *p;
- char **lines;
- pstring syscmd;
+ char **lines = NULL;
+ char **argl = NULL;
+
+ argl = str_list_make_empty(talloc_tos());
+ str_list_add_printf(&argl, "%s", dfree_command);
+ str_list_add_printf(&argl, "%s", path);
+ if (argl == NULL) {
+ return (uint64_t)-1;
+ }
+
+ DBG_NOTICE("Running command '%s %s'\n",
+ dfree_command,
+ path);
+
+ lines = file_lines_ploadv(talloc_tos(), argl, NULL);
- slprintf(syscmd, sizeof(syscmd)-1, "%s %s", dfree_command, path);
- DEBUG (3, ("disk_free: Running command %s\n", syscmd));
+ TALLOC_FREE(argl);
- lines = file_lines_pload(syscmd, NULL);
- if (lines) {
+ if (lines != NULL) {
char *line = lines[0];
DEBUG (3, ("Read input from dfree, \"%s\"\n", line));
*bsize = STR_TO_SMB_BIG_UINT(p, NULL);
else
*bsize = 1024;
- file_lines_free(lines);
+ TALLOC_FREE(lines);
DEBUG (3, ("Parsed output of dfree, dsize=%u, dfree=%u, bsize=%u\n",
(unsigned int)*dsize, (unsigned int)*dfree, (unsigned int)*bsize));
*dsize = 2048;
if (!*dfree)
*dfree = 1024;
- } else {
- DEBUG (0, ("disk_free: sys_popen() failed for command %s. Error was : %s\n",
- syscmd, strerror(errno) ));
- sys_fsusage(path, dfree, dsize);
+
+ goto dfree_done;
}
- } else
- sys_fsusage(path, dfree, dsize);
+ DBG_ERR("file_lines_load() failed for "
+ "command '%s %s'. Error was : %s\n",
+ dfree_command, path, strerror(errno));
+ }
- if (disk_quotas(path, &bsize_q, &dfree_q, &dsize_q)) {
- (*bsize) = bsize_q;
+ if (SMB_VFS_DISK_FREE(conn, fname, bsize, dfree, dsize) ==
+ (uint64_t)-1) {
+ DBG_ERR("VFS disk_free failed. Error was : %s\n",
+ strerror(errno));
+ return (uint64_t)-1;
+ }
+
+ if (disk_quotas(conn, fname, &bsize_q, &dfree_q, &dsize_q)) {
+ uint64_t min_bsize = MIN(*bsize, bsize_q);
+
+ (*dfree) = (*dfree) * (*bsize) / min_bsize;
+ (*dsize) = (*dsize) * (*bsize) / min_bsize;
+ dfree_q = dfree_q * bsize_q / min_bsize;
+ dsize_q = dsize_q * bsize_q / min_bsize;
+
+ (*bsize) = min_bsize;
(*dfree) = MIN(*dfree,dfree_q);
(*dsize) = MIN(*dsize,dsize_q);
}
}
if ((*dsize)<1) {
- static int done;
- if (!done) {
+ if (!dfree_broken) {
DEBUG(0,("WARNING: dfree is broken on this system\n"));
- done=1;
+ dfree_broken=true;
}
*dsize = 20*1024*1024/(*bsize);
*dfree = MAX(1,*dfree);
}
- disk_norm(small_query,bsize,dfree,dsize);
+dfree_done:
+ disk_norm(bsize, dfree, dsize);
if ((*bsize) < 1024) {
dfree_retval = (*dfree)/(1024/(*bsize));
return(dfree_retval);
}
-
/****************************************************************************
-wrap it to get filenames right
+ Potentially returned cached dfree info.
+
+ Depending on the file system layout and file system features, the free space
+ information can be different for different sub directories underneath a SMB
+ share. Store the cache information in memcache using the query path as the
+ key to accommodate this.
****************************************************************************/
-SMB_BIG_UINT sys_disk_free(const char *path, BOOL small_query,
- SMB_BIG_UINT *bsize,SMB_BIG_UINT *dfree,SMB_BIG_UINT *dsize)
+
+struct dfree_cached_info {
+ time_t last_dfree_time;
+ uint64_t dfree_ret;
+ uint64_t bsize;
+ uint64_t dfree;
+ uint64_t dsize;
+};
+
+uint64_t get_dfree_info(connection_struct *conn, struct smb_filename *fname,
+ uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
+{
+ int dfree_cache_time = lp_dfree_cache_time(SNUM(conn));
+ struct dfree_cached_info *dfc = NULL;
+ struct dfree_cached_info dfc_new = { 0 };
+ uint64_t dfree_ret;
+ char tmpbuf[PATH_MAX];
+ char *full_path = NULL;
+ char *to_free = NULL;
+ char *key_path = NULL;
+ size_t len;
+ DATA_BLOB key, value;
+ bool found;
+
+ if (!dfree_cache_time) {
+ return sys_disk_free(conn, fname, bsize, dfree, dsize);
+ }
+
+ len = full_path_tos(conn->connectpath,
+ fname->base_name,
+ tmpbuf,
+ sizeof(tmpbuf),
+ &full_path,
+ &to_free);
+ if (len == -1) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (VALID_STAT(fname->st) && S_ISREG(fname->st.st_ex_mode)) {
+ /*
+ * In case of a file use the parent directory to reduce number
+ * of cache entries.
+ */
+ bool ok;
+
+ ok = parent_dirname(talloc_tos(),
+ full_path,
+ &key_path,
+ NULL);
+ TALLOC_FREE(to_free); /* We're done with full_path */
+
+ if (!ok) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ /*
+ * key_path is always a talloced object.
+ */
+ to_free = key_path;
+ } else {
+ /*
+ * key_path might not be a talloced object; rely on
+ * to_free set from full_path_tos.
+ */
+ key_path = full_path;
+ }
+
+ key = data_blob_const(key_path, strlen(key_path));
+ found = memcache_lookup(smbd_memcache(),
+ DFREE_CACHE,
+ key,
+ &value);
+ dfc = found ? (struct dfree_cached_info *)value.data : NULL;
+
+ if (dfc && (conn->lastused - dfc->last_dfree_time < dfree_cache_time)) {
+ DBG_DEBUG("Returning dfree cache entry for %s\n", key_path);
+ *bsize = dfc->bsize;
+ *dfree = dfc->dfree;
+ *dsize = dfc->dsize;
+ dfree_ret = dfc->dfree_ret;
+ goto out;
+ }
+
+ dfree_ret = sys_disk_free(conn, fname, bsize, dfree, dsize);
+
+ if (dfree_ret == (uint64_t)-1) {
+ /* Don't cache bad data. */
+ goto out;
+ }
+
+ DBG_DEBUG("Creating dfree cache entry for %s\n", key_path);
+ dfc_new.bsize = *bsize;
+ dfc_new.dfree = *dfree;
+ dfc_new.dsize = *dsize;
+ dfc_new.dfree_ret = dfree_ret;
+ dfc_new.last_dfree_time = conn->lastused;
+ memcache_add(smbd_memcache(),
+ DFREE_CACHE,
+ key,
+ data_blob_const(&dfc_new, sizeof(dfc_new)));
+
+out:
+ TALLOC_FREE(to_free);
+ return dfree_ret;
+}
+
+void flush_dfree_cache(void)
{
- return disk_free(path,small_query, bsize,dfree,dsize);
+ memcache_flush(smbd_memcache(), DFREE_CACHE);
}