ctdb/docs: Include ceph rados namespace support in man page
[samba.git] / source3 / smbd / dfree.c
index f93cdf3791e2cfbbb0bdde0e139cb61bb439b751..89dc11293b52bc814cb4c56ab3a134b5f6b7bba9 100644 (file)
@@ -5,7 +5,7 @@
    
    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;
@@ -78,17 +74,28 @@ static SMB_BIG_UINT disk_free(const char *path, BOOL small_query,
         * 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));
@@ -104,7 +111,7 @@ static SMB_BIG_UINT disk_free(const char *path, BOOL small_query,
                                *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));
 
@@ -112,16 +119,30 @@ static SMB_BIG_UINT disk_free(const char *path, BOOL small_query,
                                *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);
        }
@@ -133,16 +154,16 @@ static SMB_BIG_UINT disk_free(const char *path, BOOL small_query,
        }
 
        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));
@@ -153,12 +174,123 @@ static SMB_BIG_UINT disk_free(const char *path, BOOL small_query,
        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);
 }