ext4: make quota as first class supported feature
authorAditya Kali <adityakali@google.com>
Mon, 23 Jul 2012 00:21:31 +0000 (20:21 -0400)
committerTheodore Ts'o <tytso@mit.edu>
Mon, 23 Jul 2012 00:21:31 +0000 (20:21 -0400)
This patch adds support for quotas as a first class feature in ext4;
which is to say, the quota files are stored in hidden inodes as file
system metadata, instead of as separate files visible in the file system
directory hierarchy.

It is based on the proposal at:
https://ext4.wiki.kernel.org/index.php/Design_For_1st_Class_Quota_in_Ext4

This patch introduces a new feature - EXT4_FEATURE_RO_COMPAT_QUOTA
which, when turned on, enables quota accounting at mount time
iteself. Also, the quota inodes are stored in two additional superblock
fields.  Some changes introduced by this patch that should be pointed
out are:

1) Two new ext4-superblock fields - s_usr_quota_inum and
   s_grp_quota_inum for storing the quota inodes in use.
2) Default quota inodes are: inode#3 for tracking userquota and inode#4
   for tracking group quota. The superblock fields can be set to use
   other inodes as well.
3) If the QUOTA feature and corresponding quota inodes are set in
   superblock, the quota usage tracking is turned on at mount time. On
   'quotaon' ioctl, the quota limits enforcement is turned
   on. 'quotaoff' ioctl turns off only the limits enforcement in this
   case.
4) When QUOTA feature is in use, the quota mount options 'quota',
   'usrquota', 'grpquota' are ignored by the kernel.
5) mke2fs or tune2fs can be used to set the QUOTA feature and initialize
   quota inodes. The default reserved inodes will not be visible to user
   as regular files.
6) The quota-tools will need to be modified to support hidden quota
   files on ext4. E2fsprogs will also include support for creating and
   fixing quota files.
7) Support is only for the new V2 quota file format.

Tested-by: Jan Kara <jack@suse.cz>
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: Johann Lombardi <johann@whamcloud.com>
Signed-off-by: Aditya Kali <adityakali@google.com>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
fs/ext4/ext4.h
fs/ext4/ext4_jbd2.h
fs/ext4/super.c

index 4a49f8225d0b8a413edb0c378619589c93c41d11..1610e808ebe3c256d8cac72222a350b1af2e66d6 100644 (file)
@@ -1315,6 +1315,8 @@ static inline struct timespec ext4_current_time(struct inode *inode)
 static inline int ext4_valid_inum(struct super_block *sb, unsigned long ino)
 {
        return ino == EXT4_ROOT_INO ||
+               ino == EXT4_USR_QUOTA_INO ||
+               ino == EXT4_GRP_QUOTA_INO ||
                ino == EXT4_JOURNAL_INO ||
                ino == EXT4_RESIZE_INO ||
                (ino >= EXT4_FIRST_INO(sb) &&
@@ -1497,7 +1499,8 @@ static inline void ext4_clear_state_flags(struct ext4_inode_info *ei)
                                         EXT4_FEATURE_RO_COMPAT_BTREE_DIR |\
                                         EXT4_FEATURE_RO_COMPAT_HUGE_FILE |\
                                         EXT4_FEATURE_RO_COMPAT_BIGALLOC |\
-                                        EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)
+                                        EXT4_FEATURE_RO_COMPAT_METADATA_CSUM|\
+                                        EXT4_FEATURE_RO_COMPAT_QUOTA)
 
 /*
  * Default values for user and/or group using reserved blocks
index f440e8f1841f4e2521486bd94ae19ed83aa896ab..1393c830411657932754bcd3f3244f2119bfc3f7 100644 (file)
 #ifdef CONFIG_QUOTA
 /* Amount of blocks needed for quota update - we know that the structure was
  * allocated so we need to update only data block */
-#define EXT4_QUOTA_TRANS_BLOCKS(sb) (test_opt(sb, QUOTA) ? 1 : 0)
+#define EXT4_QUOTA_TRANS_BLOCKS(sb) ((test_opt(sb, QUOTA) ||\
+               EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_QUOTA)) ?\
+               1 : 0)
 /* Amount of blocks needed for quota insert/delete - we do some block writes
  * but inode, sb and group updates are done only once */
-#define EXT4_QUOTA_INIT_BLOCKS(sb) (test_opt(sb, QUOTA) ? (DQUOT_INIT_ALLOC*\
-               (EXT4_SINGLEDATA_TRANS_BLOCKS(sb)-3)+3+DQUOT_INIT_REWRITE) : 0)
-
-#define EXT4_QUOTA_DEL_BLOCKS(sb) (test_opt(sb, QUOTA) ? (DQUOT_DEL_ALLOC*\
-               (EXT4_SINGLEDATA_TRANS_BLOCKS(sb)-3)+3+DQUOT_DEL_REWRITE) : 0)
+#define EXT4_QUOTA_INIT_BLOCKS(sb) ((test_opt(sb, QUOTA) ||\
+               EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_QUOTA)) ?\
+               (DQUOT_INIT_ALLOC*(EXT4_SINGLEDATA_TRANS_BLOCKS(sb)-3)\
+                +3+DQUOT_INIT_REWRITE) : 0)
+
+#define EXT4_QUOTA_DEL_BLOCKS(sb) ((test_opt(sb, QUOTA) ||\
+               EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_QUOTA)) ?\
+               (DQUOT_DEL_ALLOC*(EXT4_SINGLEDATA_TRANS_BLOCKS(sb)-3)\
+                +3+DQUOT_DEL_REWRITE) : 0)
 #else
 #define EXT4_QUOTA_TRANS_BLOCKS(sb) 0
 #define EXT4_QUOTA_INIT_BLOCKS(sb) 0
index 78b7ede2efa0bf32fc2c7abcb9f0a91ef1579b41..bebf8e5bf0878afd9ad05b1b7e70263ad0dfaf70 100644 (file)
@@ -1137,12 +1137,18 @@ static int ext4_mark_dquot_dirty(struct dquot *dquot);
 static int ext4_write_info(struct super_block *sb, int type);
 static int ext4_quota_on(struct super_block *sb, int type, int format_id,
                         struct path *path);
+static int ext4_quota_on_sysfile(struct super_block *sb, int type,
+                                int format_id);
 static int ext4_quota_off(struct super_block *sb, int type);
+static int ext4_quota_off_sysfile(struct super_block *sb, int type);
 static int ext4_quota_on_mount(struct super_block *sb, int type);
 static ssize_t ext4_quota_read(struct super_block *sb, int type, char *data,
                               size_t len, loff_t off);
 static ssize_t ext4_quota_write(struct super_block *sb, int type,
                                const char *data, size_t len, loff_t off);
+static int ext4_quota_enable(struct super_block *sb, int type, int format_id,
+                            unsigned int flags);
+static int ext4_enable_quotas(struct super_block *sb);
 
 static const struct dquot_operations ext4_quota_operations = {
        .get_reserved_space = ext4_get_reserved_space,
@@ -1164,6 +1170,16 @@ static const struct quotactl_ops ext4_qctl_operations = {
        .get_dqblk      = dquot_get_dqblk,
        .set_dqblk      = dquot_set_dqblk
 };
+
+static const struct quotactl_ops ext4_qctl_sysfile_operations = {
+       .quota_on_meta  = ext4_quota_on_sysfile,
+       .quota_off      = ext4_quota_off_sysfile,
+       .quota_sync     = dquot_quota_sync,
+       .get_info       = dquot_get_dqinfo,
+       .set_info       = dquot_set_dqinfo,
+       .get_dqblk      = dquot_get_dqblk,
+       .set_dqblk      = dquot_set_dqblk
+};
 #endif
 
 static const struct super_operations ext4_sops = {
@@ -2661,6 +2677,16 @@ static int ext4_feature_set_ok(struct super_block *sb, int readonly)
                         "extents feature\n");
                return 0;
        }
+
+#ifndef CONFIG_QUOTA
+       if (EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_QUOTA) &&
+           !readonly) {
+               ext4_msg(sb, KERN_ERR,
+                        "Filesystem with quota feature cannot be mounted RDWR "
+                        "without CONFIG_QUOTA");
+               return 0;
+       }
+#endif  /* CONFIG_QUOTA */
        return 1;
 }
 
@@ -3748,6 +3774,11 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
 #ifdef CONFIG_QUOTA
        sb->s_qcop = &ext4_qctl_operations;
        sb->dq_op = &ext4_quota_operations;
+
+       if (EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_QUOTA)) {
+               /* Use qctl operations for hidden quota files. */
+               sb->s_qcop = &ext4_qctl_sysfile_operations;
+       }
 #endif
        memcpy(sb->s_uuid, es->s_uuid, sizeof(es->s_uuid));
 
@@ -3960,6 +3991,16 @@ no_journal:
        } else
                descr = "out journal";
 
+#ifdef CONFIG_QUOTA
+       /* Enable quota usage during mount. */
+       if (EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_QUOTA) &&
+           !(sb->s_flags & MS_RDONLY)) {
+               ret = ext4_enable_quotas(sb);
+               if (ret)
+                       goto failed_mount7;
+       }
+#endif  /* CONFIG_QUOTA */
+
        ext4_msg(sb, KERN_INFO, "mounted filesystem with%s. "
                 "Opts: %s%s%s", descr, sbi->s_es->s_mount_opts,
                 *sbi->s_es->s_mount_opts ? "; " : "", orig_data);
@@ -4682,16 +4723,26 @@ static int ext4_remount(struct super_block *sb, int *flags, char *data)
        if (sbi->s_journal == NULL)
                ext4_commit_super(sb, 1);
 
+       unlock_super(sb);
 #ifdef CONFIG_QUOTA
        /* Release old quota file names */
        for (i = 0; i < MAXQUOTAS; i++)
                if (old_opts.s_qf_names[i] &&
                    old_opts.s_qf_names[i] != sbi->s_qf_names[i])
                        kfree(old_opts.s_qf_names[i]);
+       if (enable_quota) {
+               if (sb_any_quota_suspended(sb))
+                       dquot_resume(sb, -1);
+               else if (EXT4_HAS_RO_COMPAT_FEATURE(sb,
+                                       EXT4_FEATURE_RO_COMPAT_QUOTA)) {
+                       err = ext4_enable_quotas(sb);
+                       if (err) {
+                               lock_super(sb);
+                               goto restore_opts;
+                       }
+               }
+       }
 #endif
-       unlock_super(sb);
-       if (enable_quota)
-               dquot_resume(sb, -1);
 
        ext4_msg(sb, KERN_INFO, "re-mounted. Opts: %s", orig_data);
        kfree(orig_data);
@@ -4904,6 +4955,74 @@ static int ext4_quota_on(struct super_block *sb, int type, int format_id,
        return dquot_quota_on(sb, type, format_id, path);
 }
 
+static int ext4_quota_enable(struct super_block *sb, int type, int format_id,
+                            unsigned int flags)
+{
+       int err;
+       struct inode *qf_inode;
+       unsigned long qf_inums[MAXQUOTAS] = {
+               le32_to_cpu(EXT4_SB(sb)->s_es->s_usr_quota_inum),
+               le32_to_cpu(EXT4_SB(sb)->s_es->s_grp_quota_inum)
+       };
+
+       BUG_ON(!EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_QUOTA));
+
+       if (!qf_inums[type])
+               return -EPERM;
+
+       qf_inode = ext4_iget(sb, qf_inums[type]);
+       if (IS_ERR(qf_inode)) {
+               ext4_error(sb, "Bad quota inode # %lu", qf_inums[type]);
+               return PTR_ERR(qf_inode);
+       }
+
+       err = dquot_enable(qf_inode, type, format_id, flags);
+       iput(qf_inode);
+
+       return err;
+}
+
+/* Enable usage tracking for all quota types. */
+static int ext4_enable_quotas(struct super_block *sb)
+{
+       int type, err = 0;
+       unsigned long qf_inums[MAXQUOTAS] = {
+               le32_to_cpu(EXT4_SB(sb)->s_es->s_usr_quota_inum),
+               le32_to_cpu(EXT4_SB(sb)->s_es->s_grp_quota_inum)
+       };
+
+       sb_dqopt(sb)->flags |= DQUOT_QUOTA_SYS_FILE;
+       for (type = 0; type < MAXQUOTAS; type++) {
+               if (qf_inums[type]) {
+                       err = ext4_quota_enable(sb, type, QFMT_VFS_V1,
+                                               DQUOT_USAGE_ENABLED);
+                       if (err) {
+                               ext4_warning(sb,
+                                       "Failed to enable quota (type=%d) "
+                                       "tracking. Please run e2fsck to fix.",
+                                       type);
+                               return err;
+                       }
+               }
+       }
+       return 0;
+}
+
+/*
+ * quota_on function that is used when QUOTA feature is set.
+ */
+static int ext4_quota_on_sysfile(struct super_block *sb, int type,
+                                int format_id)
+{
+       if (!EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_QUOTA))
+               return -EINVAL;
+
+       /*
+        * USAGE was enabled at mount time. Only need to enable LIMITS now.
+        */
+       return ext4_quota_enable(sb, type, format_id, DQUOT_LIMITS_ENABLED);
+}
+
 static int ext4_quota_off(struct super_block *sb, int type)
 {
        struct inode *inode = sb_dqopt(sb)->files[type];
@@ -4930,6 +5049,18 @@ out:
        return dquot_quota_off(sb, type);
 }
 
+/*
+ * quota_off function that is used when QUOTA feature is set.
+ */
+static int ext4_quota_off_sysfile(struct super_block *sb, int type)
+{
+       if (!EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_QUOTA))
+               return -EINVAL;
+
+       /* Disable only the limits. */
+       return dquot_disable(sb, type, DQUOT_LIMITS_ENABLED);
+}
+
 /* Read data from quotafile - avoid pagecache and such because we cannot afford
  * acquiring the locks... As quota files are never truncated and quota code
  * itself serializes the operations (and no one else should touch the files)