@@ -88,6 +88,10 @@ static void ext4_clear_request_list(void);
static struct inode *ext4_get_journal_inode(struct super_block *sb,
unsigned int journal_inum);
static int ext4_validate_options(struct fs_context *fc);
+static int ext4_check_quota_consistency(struct fs_context *fc,
+ struct super_block *sb);
+static void ext4_apply_quota_options(struct fs_context *fc,
+ struct super_block *sb);
/*
* Lock ordering
@@ -1979,71 +1983,6 @@ static const char deprecated_msg[] =
"Mount option \"%s\" will be removed by %s\n"
"Contact linux-ext4@vger.kernel.org if you think we should keep it.\n";
-#ifdef CONFIG_QUOTA
-static int set_qf_name(struct super_block *sb, int qtype,
- struct fs_parameter *param)
-{
- struct ext4_sb_info *sbi = EXT4_SB(sb);
- char *qname, *old_qname = get_qf_name(sb, sbi, qtype);
- int ret = -1;
-
- if (sb_any_quota_loaded(sb) && !old_qname) {
- ext4_msg(sb, KERN_ERR,
- "Cannot change journaled "
- "quota options when quota turned on");
- return -1;
- }
- if (ext4_has_feature_quota(sb)) {
- ext4_msg(sb, KERN_INFO, "Journaled quota options "
- "ignored when QUOTA feature is enabled");
- return 1;
- }
- qname = kmemdup_nul(param->string, param->size, GFP_KERNEL);
- if (!qname) {
- ext4_msg(sb, KERN_ERR,
- "Not enough memory for storing quotafile name");
- return -1;
- }
- if (old_qname) {
- if (strcmp(old_qname, qname) == 0)
- ret = 1;
- else
- ext4_msg(sb, KERN_ERR,
- "%s quota file already specified",
- QTYPE2NAME(qtype));
- goto errout;
- }
- if (strchr(qname, '/')) {
- ext4_msg(sb, KERN_ERR,
- "quotafile must be on filesystem root");
- goto errout;
- }
- rcu_assign_pointer(sbi->s_qf_names[qtype], qname);
- set_opt(sb, QUOTA);
- return 1;
-errout:
- kfree(qname);
- return ret;
-}
-
-static int clear_qf_name(struct super_block *sb, int qtype)
-{
-
- struct ext4_sb_info *sbi = EXT4_SB(sb);
- char *old_qname = get_qf_name(sb, sbi, qtype);
-
- if (sb_any_quota_loaded(sb) && old_qname) {
- ext4_msg(sb, KERN_ERR, "Cannot change journaled quota options"
- " when quota turned on");
- return -1;
- }
- rcu_assign_pointer(sbi->s_qf_names[qtype], NULL);
- synchronize_rcu();
- kfree(old_qname);
- return 1;
-}
-#endif
-
#define MOPT_SET 0x0001
#define MOPT_CLEAR 0x0002
#define MOPT_NOSUPPORT 0x0004
@@ -2247,11 +2186,70 @@ static int ext4_set_test_dummy_encryption(struct super_block *sb,
}
struct ext4_fs_context {
- unsigned long journal_devnum;
- unsigned int journal_ioprio;
- int mb_optimize_scan;
+ char *s_qf_names[EXT4_MAXQUOTAS];
+ int s_jquota_fmt; /* Format of quota to use */
+ unsigned short qname_spec;
+ unsigned long journal_devnum;
+ unsigned int journal_ioprio;
+ int mb_optimize_scan;
};
+#ifdef CONFIG_QUOTA
+/*
+ * Note the name of the specified quota file.
+ */
+static int note_qf_name(struct fs_context *fc, int qtype,
+ struct fs_parameter *param)
+{
+ struct ext4_fs_context *ctx = fc->fs_private;
+ char *qname;
+
+ if (param->size < 1) {
+ ext4_msg(NULL, KERN_ERR, "Missing quota name");
+ return -EINVAL;
+ }
+ if (strchr(param->string, '/')) {
+ ext4_msg(NULL, KERN_ERR,
+ "quotafile must be on filesystem root");
+ return -EINVAL;
+ }
+ if (ctx->s_qf_names[qtype]) {
+ if (strcmp(ctx->s_qf_names[qtype], param->string) != 0) {
+ ext4_msg(NULL, KERN_ERR,
+ "%s quota file already specified",
+ QTYPE2NAME(qtype));
+ return -EINVAL;
+ }
+ return 0;
+ }
+
+ qname = kmemdup_nul(param->string, param->size, GFP_KERNEL);
+ if (!qname) {
+ ext4_msg(NULL, KERN_ERR,
+ "Not enough memory for storing quotafile name");
+ return -ENOMEM;
+ }
+ ctx->s_qf_names[qtype] = qname;
+ ctx->qname_spec |= 1 << qtype;
+ return 0;
+}
+
+/*
+ * Clear the name of the specified quota file.
+ */
+static int unnote_qf_name(struct fs_context *fc, int qtype)
+{
+ struct ext4_fs_context *ctx = fc->fs_private;
+
+ if (ctx->s_qf_names[qtype])
+ kfree(ctx->s_qf_names[qtype]);
+
+ ctx->s_qf_names[qtype] = NULL;
+ ctx->qname_spec |= 1 << qtype;
+ return 0;
+}
+#endif
+
static int handle_mount_opt(struct fs_context *fc, struct fs_parameter *param)
{
struct ext4_fs_context *ctx = fc->fs_private;
@@ -2272,14 +2270,14 @@ static int handle_mount_opt(struct fs_context *fc, struct fs_parameter *param)
#ifdef CONFIG_QUOTA
if (token == Opt_usrjquota) {
if (!*param->string)
- return clear_qf_name(sb, USRQUOTA);
+ return unnote_qf_name(fc, USRQUOTA);
else
- return set_qf_name(sb, USRQUOTA, param);
+ return note_qf_name(fc, USRQUOTA, param);
} else if (token == Opt_grpjquota) {
if (!*param->string)
- return clear_qf_name(sb, GRPQUOTA);
+ return unnote_qf_name(fc, GRPQUOTA);
else
- return set_qf_name(sb, GRPQUOTA, param);
+ return note_qf_name(fc, GRPQUOTA, param);
}
#endif
switch (token) {
@@ -2353,11 +2351,6 @@ static int handle_mount_opt(struct fs_context *fc, struct fs_parameter *param)
}
if (m->flags & MOPT_CLEAR_ERR)
clear_opt(sb, ERRORS_MASK);
- if (token == Opt_noquota && sb_any_quota_loaded(sb)) {
- ext4_msg(NULL, KERN_ERR, "Cannot change quota "
- "options when quota turned on");
- return -EINVAL;
- }
if (m->flags & MOPT_NOSUPPORT) {
ext4_msg(NULL, KERN_ERR, "%s option not supported",
@@ -2479,19 +2472,7 @@ static int handle_mount_opt(struct fs_context *fc, struct fs_parameter *param)
}
#ifdef CONFIG_QUOTA
} else if (m->flags & MOPT_QFMT) {
- if (sb_any_quota_loaded(sb) &&
- sbi->s_jquota_fmt != m->mount_opt) {
- ext4_msg(NULL, KERN_ERR, "Cannot change journaled "
- "quota options when quota turned on");
- return -EINVAL;
- }
- if (ext4_has_feature_quota(sb)) {
- ext4_msg(NULL, KERN_INFO,
- "Quota format mount options ignored "
- "when QUOTA feature is enabled");
- return 1;
- }
- sbi->s_jquota_fmt = m->mount_opt;
+ ctx->s_jquota_fmt = m->mount_opt;
#endif
} else if (token == Opt_dax || token == Opt_dax_always ||
token == Opt_dax_inode || token == Opt_dax_never) {
@@ -2588,7 +2569,7 @@ static int handle_mount_opt(struct fs_context *fc, struct fs_parameter *param)
}
static int parse_options(char *options, struct super_block *sb,
- struct ext4_fs_context *ret_opts,
+ struct ext4_fs_context *ctx,
int is_remount)
{
struct fs_parameter param;
@@ -2600,7 +2581,7 @@ static int parse_options(char *options, struct super_block *sb,
return 1;
memset(&fc, 0, sizeof(fc));
- fc.fs_private = ret_opts;
+ fc.fs_private = ctx;
fc.s_fs_info = EXT4_SB(sb);
if (is_remount)
@@ -2642,9 +2623,100 @@ static int parse_options(char *options, struct super_block *sb,
if (ret < 0)
return 0;
+ ret = ext4_check_quota_consistency(&fc, sb);
+ if (ret < 0)
+ return 0;
+
+ if (ctx->qname_spec)
+ ext4_apply_quota_options(&fc, sb);
+
return 1;
}
+static void ext4_apply_quota_options(struct fs_context *fc,
+ struct super_block *sb)
+{
+#ifdef CONFIG_QUOTA
+ struct ext4_fs_context *ctx = fc->fs_private;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ char *qname;
+ int i;
+
+ for (i = 0; i < EXT4_MAXQUOTAS; i++) {
+ if (!(ctx->qname_spec & (1 << i)))
+ continue;
+ qname = ctx->s_qf_names[i]; /* May be NULL */
+ ctx->s_qf_names[i] = NULL;
+ kfree(sbi->s_qf_names[i]);
+ rcu_assign_pointer(sbi->s_qf_names[i], qname);
+ set_opt(sb, QUOTA);
+ }
+#endif
+}
+
+/*
+ * Check quota settings consistency.
+ */
+static int ext4_check_quota_consistency(struct fs_context *fc,
+ struct super_block *sb)
+{
+#ifdef CONFIG_QUOTA
+ struct ext4_fs_context *ctx = fc->fs_private;
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ bool quota_feature = ext4_has_feature_quota(sb);
+ bool quota_loaded = sb_any_quota_loaded(sb);
+ int i;
+
+ if (ctx->qname_spec && quota_loaded) {
+ if (quota_feature)
+ goto err_feature;
+
+ for (i = 0; i < EXT4_MAXQUOTAS; i++) {
+ if (!(ctx->qname_spec & (1 << i)))
+ continue;
+
+ if (!!sbi->s_qf_names[i] != !!ctx->s_qf_names[i])
+ goto err_jquota_change;
+
+ if (sbi->s_qf_names[i] && ctx->s_qf_names[i] &&
+ strcmp(sbi->s_qf_names[i],
+ ctx->s_qf_names[i]) != 0)
+ goto err_jquota_specified;
+ }
+ }
+
+ if (ctx->s_jquota_fmt) {
+ if (sbi->s_jquota_fmt != ctx->s_jquota_fmt && quota_loaded)
+ goto err_quota_change;
+ if (quota_feature) {
+ ext4_msg(NULL, KERN_INFO, "Quota format mount options "
+ "ignored when QUOTA feature is enabled");
+ return 0;
+ }
+ }
+ return 0;
+
+err_quota_change:
+ ext4_msg(NULL, KERN_ERR,
+ "Cannot change quota options when quota turned on");
+ return -EINVAL;
+err_jquota_change:
+ ext4_msg(NULL, KERN_ERR, "Cannot change journaled quota "
+ "options when quota turned on");
+ return -EINVAL;
+err_jquota_specified:
+ ext4_msg(NULL, KERN_ERR, "%s quota file already specified",
+ QTYPE2NAME(i));
+ return -EINVAL;
+err_feature:
+ ext4_msg(NULL, KERN_ERR, "Journaled quota options ignored "
+ "when QUOTA feature is enabled");
+ return 0;
+#else
+ return 0;
+#endif
+}
+
static int ext4_validate_options(struct fs_context *fc)
{
struct ext4_sb_info *sbi = fc->s_fs_info;
@@ -4099,7 +4171,7 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
__u64 blocks_count;
int err = 0;
ext4_group_t first_not_zeroed;
- struct ext4_fs_context parsed_opts;
+ struct ext4_fs_context parsed_opts = {0};
/* Set defaults for the variables that will be set during parsing */
parsed_opts.journal_ioprio = DEFAULT_JOURNAL_IOPRIO;