s3:vfs_gpfs: Report disk space and usage on GPFS share according to quotas
authorChristof Schmitt <christof.schmitt@us.ibm.com>
Fri, 2 Mar 2012 21:26:24 +0000 (14:26 -0700)
committerChristian Ambach <ambi@samba.org>
Thu, 22 Mar 2012 17:41:22 +0000 (18:41 +0100)
When a client requests the information about free space and space used,
adjust the reported values according to quotas in the GPFS file system:

- Retrieve quotas for the current user, current group and fileset for
  the top level of the share.
- If the soft block quota grace time has expired, report disk as full.
- If a hard block quota has been exceeded, report disk as full.
- If none of the hard block quotas been exceeded, report
  share size and free space according to the lowest limits found in
  the quotas.
- If no applicable hard block quota has been set, report the
  information from the statfs call.

This feature is disabled by default and has to be enabled by setting the
option gpfs:dfreequota.

docs-xml/manpages-3/vfs_gpfs.8.xml
source3/modules/vfs_gpfs.c

index 2107b74..398dcff 100644 (file)
                </varlistentry>
                <varlistentry>
 
+               <term>gpfs:dfreequota = [ yes | no ]</term>
+               <listitem>
+               <para>
+               Adjust reporting of the size and free space of a share
+               according to quotas. If this setting is "yes", a
+               request for size and free space will also evaluate the
+               following quotas:
+               </para>
+
+               <itemizedlist>
+               <listitem><para>The user quota of the user requesting
+                   the data.</para></listitem>
+               <listitem><para>The group quota of the primary group
+                   of the user.</para></listitem>
+               <listitem><para>The fileset quota for the fileset
+                   containing the top level directory of the share.
+               </para></listitem>
+               </itemizedlist>
+
+               <para>
+               If any of the soft or hard quota limits has been
+               reached, the free space will be reported as 0. If a
+               quota is in place, but the limits have not been
+               reached, the free space will be reported according to
+               the space left in the quota. If more than one quota
+               applies the free space will be reported as the smallest
+               space left in those quotas. The size of the share
+               will be reported according to the quota usage. If more
+               than one quota applies, the smallest size will be
+               reported for the share size according to these quotas.
+               </para>
+
+               <itemizedlist>
+               <listitem><para>
+               <command>yes</command> - include the quotas
+               when reporting the share size and free space
+               </para></listitem>
+               <listitem><para>
+               <command>no(default)</command> - do not include quotas,
+               simply report the size and free space of the file system
+               </para></listitem>
+               </itemizedlist>
+               </listitem>
+
+               </varlistentry>
+               <varlistentry>
+
                <term>nfs4:mode = [ simple | special ]</term>
                <listitem>
                <para>
index 6a9d3d5..9597999 100644 (file)
@@ -33,6 +33,7 @@
 #include "nfs4_acls.h"
 #include "vfs_gpfs.h"
 #include "system/filesys.h"
+#include "auth.h"
 
 struct gpfs_config_data {
        bool sharemodes;
@@ -42,6 +43,7 @@ struct gpfs_config_data {
        bool winattr;
        bool ftruncate;
        bool getrealfilename;
+       bool dfreequota;
 };
 
 
@@ -1385,6 +1387,9 @@ int vfs_gpfs_connect(struct vfs_handle_struct *handle, const char *service,
        config->getrealfilename = lp_parm_bool(SNUM(handle->conn), "gpfs",
                                               "getrealfilename", true);
 
+       config->dfreequota = lp_parm_bool(SNUM(handle->conn), "gpfs",
+                                         "dfreequota", false);
+
        SMB_VFS_HANDLE_SET_DATA(handle, config,
                                NULL, struct gpfs_config_data,
                                return -1);
@@ -1392,6 +1397,129 @@ int vfs_gpfs_connect(struct vfs_handle_struct *handle, const char *service,
        return 0;
 }
 
+static int vfs_gpfs_get_quotas(const char *path, uid_t uid, gid_t gid,
+                              int *fset_id,
+                              struct gpfs_quotaInfo *qi_user,
+                              struct gpfs_quotaInfo *qi_group,
+                              struct gpfs_quotaInfo *qi_fset)
+{
+       int err;
+
+       err = get_gpfs_fset_id(path, fset_id);
+       if (err) {
+               DEBUG(0, ("Get fset id failed, errno %d.\n", errno));
+               return err;
+       }
+
+       err = get_gpfs_quota(path, GPFS_USRQUOTA, uid, qi_user);
+       if (err) {
+               return err;
+       }
+
+       err = get_gpfs_quota(path, GPFS_GRPQUOTA, gid, qi_group);
+       if (err) {
+               return err;
+       }
+
+       err = get_gpfs_quota(path, GPFS_FILESETQUOTA, *fset_id, qi_fset);
+       if (err) {
+               return err;
+       }
+
+       return 0;
+}
+
+static void vfs_gpfs_disk_free_quota(struct gpfs_quotaInfo qi, time_t cur_time,
+                                    uint64_t *dfree, uint64_t *dsize)
+{
+       uint64_t usage, limit;
+
+       /*
+        * The quota reporting is done in units of 1024 byte blocks, but
+        * sys_fsusage uses units of 512 byte blocks, adjust the block number
+        * accordingly. Also filter possibly negative usage counts from gpfs.
+        */
+       usage = qi.blockUsage < 0 ? 0 : (uint64_t)qi.blockUsage * 2;
+       limit = (uint64_t)qi.blockHardLimit * 2;
+
+       /*
+        * When the grace time for the exceeded soft block quota has been
+        * exceeded, the soft block quota becomes an additional hard limit.
+        */
+       if (qi.blockGraceTime && cur_time > qi.blockGraceTime) {
+               /* report disk as full */
+               *dfree = 0;
+               *dsize = MIN(*dsize, usage);
+       }
+
+       if (!qi.blockHardLimit)
+               return;
+
+       if (usage >= limit) {
+               /* report disk as full */
+               *dfree = 0;
+               *dsize = MIN(*dsize, usage);
+
+       } else {
+               /* limit has not been reached, determine "free space" */
+               *dfree = MIN(*dfree, limit - usage);
+               *dsize = MIN(*dsize, limit);
+       }
+}
+
+static uint64_t vfs_gpfs_disk_free(vfs_handle_struct *handle, const char *path,
+                                  bool small_query, uint64_t *bsize,
+                                  uint64_t *dfree, uint64_t *dsize)
+{
+       struct security_unix_token *utok;
+       struct gpfs_quotaInfo qi_user, qi_group, qi_fset;
+       struct gpfs_config_data *config;
+       int err, fset_id;
+       time_t cur_time;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config, struct gpfs_config_data,
+                               return (uint64_t)-1);
+       if (!config->dfreequota) {
+               return SMB_VFS_NEXT_DISK_FREE(handle, path, small_query,
+                                             bsize, dfree, dsize);
+       }
+
+       err = sys_fsusage(path, dfree, dsize);
+       if (err) {
+               DEBUG (0, ("Could not get fs usage, errno %d\n", errno));
+               return SMB_VFS_NEXT_DISK_FREE(handle, path, small_query,
+                                             bsize, dfree, dsize);
+       }
+
+       /* sys_fsusage returns units of 512 bytes */
+       *bsize = 512;
+
+       DEBUG(10, ("fs dfree %llu, dsize %llu\n",
+                  (unsigned long long)*dfree, (unsigned long long)*dsize));
+
+       utok = handle->conn->session_info->unix_token;
+       err = vfs_gpfs_get_quotas(path, utok->uid, utok->gid, &fset_id,
+                                 &qi_user, &qi_group, &qi_fset);
+       if (err) {
+               return SMB_VFS_NEXT_DISK_FREE(handle, path, small_query,
+                                             bsize, dfree, dsize);
+       }
+
+       cur_time = time(NULL);
+
+       /* Adjust free space and size according to quota limits. */
+       vfs_gpfs_disk_free_quota(qi_user, cur_time, dfree, dsize);
+       vfs_gpfs_disk_free_quota(qi_group, cur_time, dfree, dsize);
+
+       /* Id 0 indicates the default quota, not an actual quota */
+       if (fset_id != 0) {
+               vfs_gpfs_disk_free_quota(qi_fset, cur_time, dfree, dsize);
+       }
+
+       disk_norm(small_query, bsize, dfree, dsize);
+       return *dfree;
+}
+
 static uint32_t vfs_gpfs_capabilities(struct vfs_handle_struct *handle,
                                      enum timestamp_set_resolution *p_ts_res)
 {
@@ -1429,6 +1557,7 @@ static int vfs_gpfs_open(struct vfs_handle_struct *handle,
 
 static struct vfs_fn_pointers vfs_gpfs_fns = {
        .connect_fn = vfs_gpfs_connect,
+       .disk_free_fn = vfs_gpfs_disk_free,
        .fs_capabilities_fn = vfs_gpfs_capabilities,
        .kernel_flock_fn = vfs_gpfs_kernel_flock,
        .linux_setlease_fn = vfs_gpfs_setlease,