ext2: Parse mount options into a dedicated structure

Instead of parsing mount options directly into the superblock (and
restoring options in case of error), parse the options into a dedicated
structure and only copy everything when we know we can safely switch
options. This will allow us to simplify locking and do option parsing
without holding sb->s_lock.

Signed-off-by: Jan Kara <jack@suse.cz>
This commit is contained in:
Jan Kara 2017-10-09 16:34:58 +02:00
parent de8cd83e91
commit 088519572c

View File

@ -479,10 +479,10 @@ static const match_table_t tokens = {
{Opt_err, NULL} {Opt_err, NULL}
}; };
static int parse_options(char *options, struct super_block *sb) static int parse_options(char *options, struct super_block *sb,
struct ext2_mount_options *opts)
{ {
char *p; char *p;
struct ext2_sb_info *sbi = EXT2_SB(sb);
substring_t args[MAX_OPT_ARGS]; substring_t args[MAX_OPT_ARGS];
int option; int option;
kuid_t uid; kuid_t uid;
@ -499,16 +499,16 @@ static int parse_options(char *options, struct super_block *sb)
token = match_token(p, tokens, args); token = match_token(p, tokens, args);
switch (token) { switch (token) {
case Opt_bsd_df: case Opt_bsd_df:
clear_opt (sbi->s_mount_opt, MINIX_DF); clear_opt (opts->s_mount_opt, MINIX_DF);
break; break;
case Opt_minix_df: case Opt_minix_df:
set_opt (sbi->s_mount_opt, MINIX_DF); set_opt (opts->s_mount_opt, MINIX_DF);
break; break;
case Opt_grpid: case Opt_grpid:
set_opt (sbi->s_mount_opt, GRPID); set_opt (opts->s_mount_opt, GRPID);
break; break;
case Opt_nogrpid: case Opt_nogrpid:
clear_opt (sbi->s_mount_opt, GRPID); clear_opt (opts->s_mount_opt, GRPID);
break; break;
case Opt_resuid: case Opt_resuid:
if (match_int(&args[0], &option)) if (match_int(&args[0], &option))
@ -519,7 +519,7 @@ static int parse_options(char *options, struct super_block *sb)
return 0; return 0;
} }
sbi->s_resuid = uid; opts->s_resuid = uid;
break; break;
case Opt_resgid: case Opt_resgid:
if (match_int(&args[0], &option)) if (match_int(&args[0], &option))
@ -529,51 +529,51 @@ static int parse_options(char *options, struct super_block *sb)
ext2_msg(sb, KERN_ERR, "Invalid gid value %d", option); ext2_msg(sb, KERN_ERR, "Invalid gid value %d", option);
return 0; return 0;
} }
sbi->s_resgid = gid; opts->s_resgid = gid;
break; break;
case Opt_sb: case Opt_sb:
/* handled by get_sb_block() instead of here */ /* handled by get_sb_block() instead of here */
/* *sb_block = match_int(&args[0]); */ /* *sb_block = match_int(&args[0]); */
break; break;
case Opt_err_panic: case Opt_err_panic:
clear_opt (sbi->s_mount_opt, ERRORS_CONT); clear_opt (opts->s_mount_opt, ERRORS_CONT);
clear_opt (sbi->s_mount_opt, ERRORS_RO); clear_opt (opts->s_mount_opt, ERRORS_RO);
set_opt (sbi->s_mount_opt, ERRORS_PANIC); set_opt (opts->s_mount_opt, ERRORS_PANIC);
break; break;
case Opt_err_ro: case Opt_err_ro:
clear_opt (sbi->s_mount_opt, ERRORS_CONT); clear_opt (opts->s_mount_opt, ERRORS_CONT);
clear_opt (sbi->s_mount_opt, ERRORS_PANIC); clear_opt (opts->s_mount_opt, ERRORS_PANIC);
set_opt (sbi->s_mount_opt, ERRORS_RO); set_opt (opts->s_mount_opt, ERRORS_RO);
break; break;
case Opt_err_cont: case Opt_err_cont:
clear_opt (sbi->s_mount_opt, ERRORS_RO); clear_opt (opts->s_mount_opt, ERRORS_RO);
clear_opt (sbi->s_mount_opt, ERRORS_PANIC); clear_opt (opts->s_mount_opt, ERRORS_PANIC);
set_opt (sbi->s_mount_opt, ERRORS_CONT); set_opt (opts->s_mount_opt, ERRORS_CONT);
break; break;
case Opt_nouid32: case Opt_nouid32:
set_opt (sbi->s_mount_opt, NO_UID32); set_opt (opts->s_mount_opt, NO_UID32);
break; break;
case Opt_nocheck: case Opt_nocheck:
clear_opt (sbi->s_mount_opt, CHECK); clear_opt (opts->s_mount_opt, CHECK);
break; break;
case Opt_debug: case Opt_debug:
set_opt (sbi->s_mount_opt, DEBUG); set_opt (opts->s_mount_opt, DEBUG);
break; break;
case Opt_oldalloc: case Opt_oldalloc:
set_opt (sbi->s_mount_opt, OLDALLOC); set_opt (opts->s_mount_opt, OLDALLOC);
break; break;
case Opt_orlov: case Opt_orlov:
clear_opt (sbi->s_mount_opt, OLDALLOC); clear_opt (opts->s_mount_opt, OLDALLOC);
break; break;
case Opt_nobh: case Opt_nobh:
set_opt (sbi->s_mount_opt, NOBH); set_opt (opts->s_mount_opt, NOBH);
break; break;
#ifdef CONFIG_EXT2_FS_XATTR #ifdef CONFIG_EXT2_FS_XATTR
case Opt_user_xattr: case Opt_user_xattr:
set_opt (sbi->s_mount_opt, XATTR_USER); set_opt (opts->s_mount_opt, XATTR_USER);
break; break;
case Opt_nouser_xattr: case Opt_nouser_xattr:
clear_opt (sbi->s_mount_opt, XATTR_USER); clear_opt (opts->s_mount_opt, XATTR_USER);
break; break;
#else #else
case Opt_user_xattr: case Opt_user_xattr:
@ -584,10 +584,10 @@ static int parse_options(char *options, struct super_block *sb)
#endif #endif
#ifdef CONFIG_EXT2_FS_POSIX_ACL #ifdef CONFIG_EXT2_FS_POSIX_ACL
case Opt_acl: case Opt_acl:
set_opt(sbi->s_mount_opt, POSIX_ACL); set_opt(opts->s_mount_opt, POSIX_ACL);
break; break;
case Opt_noacl: case Opt_noacl:
clear_opt(sbi->s_mount_opt, POSIX_ACL); clear_opt(opts->s_mount_opt, POSIX_ACL);
break; break;
#else #else
case Opt_acl: case Opt_acl:
@ -598,13 +598,13 @@ static int parse_options(char *options, struct super_block *sb)
#endif #endif
case Opt_xip: case Opt_xip:
ext2_msg(sb, KERN_INFO, "use dax instead of xip"); ext2_msg(sb, KERN_INFO, "use dax instead of xip");
set_opt(sbi->s_mount_opt, XIP); set_opt(opts->s_mount_opt, XIP);
/* Fall through */ /* Fall through */
case Opt_dax: case Opt_dax:
#ifdef CONFIG_FS_DAX #ifdef CONFIG_FS_DAX
ext2_msg(sb, KERN_WARNING, ext2_msg(sb, KERN_WARNING,
"DAX enabled. Warning: EXPERIMENTAL, use at your own risk"); "DAX enabled. Warning: EXPERIMENTAL, use at your own risk");
set_opt(sbi->s_mount_opt, DAX); set_opt(opts->s_mount_opt, DAX);
#else #else
ext2_msg(sb, KERN_INFO, "dax option not supported"); ext2_msg(sb, KERN_INFO, "dax option not supported");
#endif #endif
@ -613,11 +613,11 @@ static int parse_options(char *options, struct super_block *sb)
#if defined(CONFIG_QUOTA) #if defined(CONFIG_QUOTA)
case Opt_quota: case Opt_quota:
case Opt_usrquota: case Opt_usrquota:
set_opt(sbi->s_mount_opt, USRQUOTA); set_opt(opts->s_mount_opt, USRQUOTA);
break; break;
case Opt_grpquota: case Opt_grpquota:
set_opt(sbi->s_mount_opt, GRPQUOTA); set_opt(opts->s_mount_opt, GRPQUOTA);
break; break;
#else #else
case Opt_quota: case Opt_quota:
@ -629,11 +629,11 @@ static int parse_options(char *options, struct super_block *sb)
#endif #endif
case Opt_reservation: case Opt_reservation:
set_opt(sbi->s_mount_opt, RESERVATION); set_opt(opts->s_mount_opt, RESERVATION);
ext2_msg(sb, KERN_INFO, "reservations ON"); ext2_msg(sb, KERN_INFO, "reservations ON");
break; break;
case Opt_noreservation: case Opt_noreservation:
clear_opt(sbi->s_mount_opt, RESERVATION); clear_opt(opts->s_mount_opt, RESERVATION);
ext2_msg(sb, KERN_INFO, "reservations OFF"); ext2_msg(sb, KERN_INFO, "reservations OFF");
break; break;
case Opt_ignore: case Opt_ignore:
@ -830,6 +830,7 @@ static int ext2_fill_super(struct super_block *sb, void *data, int silent)
int i, j; int i, j;
__le32 features; __le32 features;
int err; int err;
struct ext2_mount_options opts;
err = -ENOMEM; err = -ENOMEM;
sbi = kzalloc(sizeof(*sbi), GFP_KERNEL); sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);
@ -890,35 +891,39 @@ static int ext2_fill_super(struct super_block *sb, void *data, int silent)
/* Set defaults before we parse the mount options */ /* Set defaults before we parse the mount options */
def_mount_opts = le32_to_cpu(es->s_default_mount_opts); def_mount_opts = le32_to_cpu(es->s_default_mount_opts);
if (def_mount_opts & EXT2_DEFM_DEBUG) if (def_mount_opts & EXT2_DEFM_DEBUG)
set_opt(sbi->s_mount_opt, DEBUG); set_opt(opts.s_mount_opt, DEBUG);
if (def_mount_opts & EXT2_DEFM_BSDGROUPS) if (def_mount_opts & EXT2_DEFM_BSDGROUPS)
set_opt(sbi->s_mount_opt, GRPID); set_opt(opts.s_mount_opt, GRPID);
if (def_mount_opts & EXT2_DEFM_UID16) if (def_mount_opts & EXT2_DEFM_UID16)
set_opt(sbi->s_mount_opt, NO_UID32); set_opt(opts.s_mount_opt, NO_UID32);
#ifdef CONFIG_EXT2_FS_XATTR #ifdef CONFIG_EXT2_FS_XATTR
if (def_mount_opts & EXT2_DEFM_XATTR_USER) if (def_mount_opts & EXT2_DEFM_XATTR_USER)
set_opt(sbi->s_mount_opt, XATTR_USER); set_opt(opts.s_mount_opt, XATTR_USER);
#endif #endif
#ifdef CONFIG_EXT2_FS_POSIX_ACL #ifdef CONFIG_EXT2_FS_POSIX_ACL
if (def_mount_opts & EXT2_DEFM_ACL) if (def_mount_opts & EXT2_DEFM_ACL)
set_opt(sbi->s_mount_opt, POSIX_ACL); set_opt(opts.s_mount_opt, POSIX_ACL);
#endif #endif
if (le16_to_cpu(sbi->s_es->s_errors) == EXT2_ERRORS_PANIC) if (le16_to_cpu(sbi->s_es->s_errors) == EXT2_ERRORS_PANIC)
set_opt(sbi->s_mount_opt, ERRORS_PANIC); set_opt(opts.s_mount_opt, ERRORS_PANIC);
else if (le16_to_cpu(sbi->s_es->s_errors) == EXT2_ERRORS_CONTINUE) else if (le16_to_cpu(sbi->s_es->s_errors) == EXT2_ERRORS_CONTINUE)
set_opt(sbi->s_mount_opt, ERRORS_CONT); set_opt(opts.s_mount_opt, ERRORS_CONT);
else else
set_opt(sbi->s_mount_opt, ERRORS_RO); set_opt(opts.s_mount_opt, ERRORS_RO);
sbi->s_resuid = make_kuid(&init_user_ns, le16_to_cpu(es->s_def_resuid)); opts.s_resuid = make_kuid(&init_user_ns, le16_to_cpu(es->s_def_resuid));
sbi->s_resgid = make_kgid(&init_user_ns, le16_to_cpu(es->s_def_resgid)); opts.s_resgid = make_kgid(&init_user_ns, le16_to_cpu(es->s_def_resgid));
set_opt(sbi->s_mount_opt, RESERVATION); set_opt(opts.s_mount_opt, RESERVATION);
if (!parse_options((char *) data, sb)) if (!parse_options((char *) data, sb, &opts))
goto failed_mount; goto failed_mount;
sbi->s_mount_opt = opts.s_mount_opt;
sbi->s_resuid = opts.s_resuid;
sbi->s_resgid = opts.s_resgid;
sb->s_flags = (sb->s_flags & ~MS_POSIXACL) | sb->s_flags = (sb->s_flags & ~MS_POSIXACL) |
((EXT2_SB(sb)->s_mount_opt & EXT2_MOUNT_POSIX_ACL) ? ((EXT2_SB(sb)->s_mount_opt & EXT2_MOUNT_POSIX_ACL) ?
MS_POSIXACL : 0); MS_POSIXACL : 0);
@ -1312,46 +1317,36 @@ static int ext2_remount (struct super_block * sb, int * flags, char * data)
{ {
struct ext2_sb_info * sbi = EXT2_SB(sb); struct ext2_sb_info * sbi = EXT2_SB(sb);
struct ext2_super_block * es; struct ext2_super_block * es;
struct ext2_mount_options old_opts; struct ext2_mount_options new_opts;
unsigned long old_sb_flags;
int err; int err;
sync_filesystem(sb); sync_filesystem(sb);
spin_lock(&sbi->s_lock); spin_lock(&sbi->s_lock);
/* Store the old options */ new_opts.s_mount_opt = sbi->s_mount_opt;
old_sb_flags = sb->s_flags; new_opts.s_resuid = sbi->s_resuid;
old_opts.s_mount_opt = sbi->s_mount_opt; new_opts.s_resgid = sbi->s_resgid;
old_opts.s_resuid = sbi->s_resuid;
old_opts.s_resgid = sbi->s_resgid;
/* /*
* Allow the "check" option to be passed as a remount option. * Allow the "check" option to be passed as a remount option.
*/ */
if (!parse_options(data, sb)) { if (!parse_options(data, sb, &new_opts)) {
err = -EINVAL; spin_unlock(&sbi->s_lock);
goto restore_opts; return -EINVAL;
} }
sb->s_flags = (sb->s_flags & ~MS_POSIXACL) |
((sbi->s_mount_opt & EXT2_MOUNT_POSIX_ACL) ? MS_POSIXACL : 0);
es = sbi->s_es; es = sbi->s_es;
if ((sbi->s_mount_opt ^ old_opts.s_mount_opt) & EXT2_MOUNT_DAX) { if ((sbi->s_mount_opt ^ new_opts.s_mount_opt) & EXT2_MOUNT_DAX) {
ext2_msg(sb, KERN_WARNING, "warning: refusing change of " ext2_msg(sb, KERN_WARNING, "warning: refusing change of "
"dax flag with busy inodes while remounting"); "dax flag with busy inodes while remounting");
sbi->s_mount_opt ^= EXT2_MOUNT_DAX; new_opts.s_mount_opt ^= EXT2_MOUNT_DAX;
}
if ((bool)(*flags & MS_RDONLY) == sb_rdonly(sb)) {
spin_unlock(&sbi->s_lock);
return 0;
} }
if ((bool)(*flags & MS_RDONLY) == sb_rdonly(sb))
goto out_set;
if (*flags & MS_RDONLY) { if (*flags & MS_RDONLY) {
if (le16_to_cpu(es->s_state) & EXT2_VALID_FS || if (le16_to_cpu(es->s_state) & EXT2_VALID_FS ||
!(sbi->s_mount_state & EXT2_VALID_FS)) { !(sbi->s_mount_state & EXT2_VALID_FS))
spin_unlock(&sbi->s_lock); goto out_set;
return 0;
}
/* /*
* OK, we are remounting a valid rw partition rdonly, so set * OK, we are remounting a valid rw partition rdonly, so set
@ -1362,22 +1357,20 @@ static int ext2_remount (struct super_block * sb, int * flags, char * data)
spin_unlock(&sbi->s_lock); spin_unlock(&sbi->s_lock);
err = dquot_suspend(sb, -1); err = dquot_suspend(sb, -1);
if (err < 0) { if (err < 0)
spin_lock(&sbi->s_lock); return err;
goto restore_opts;
}
ext2_sync_super(sb, es, 1); ext2_sync_super(sb, es, 1);
} else { } else {
__le32 ret = EXT2_HAS_RO_COMPAT_FEATURE(sb, __le32 ret = EXT2_HAS_RO_COMPAT_FEATURE(sb,
~EXT2_FEATURE_RO_COMPAT_SUPP); ~EXT2_FEATURE_RO_COMPAT_SUPP);
if (ret) { if (ret) {
spin_unlock(&sbi->s_lock);
ext2_msg(sb, KERN_WARNING, ext2_msg(sb, KERN_WARNING,
"warning: couldn't remount RDWR because of " "warning: couldn't remount RDWR because of "
"unsupported optional features (%x).", "unsupported optional features (%x).",
le32_to_cpu(ret)); le32_to_cpu(ret));
err = -EROFS; return -EROFS;
goto restore_opts;
} }
/* /*
* Mounting a RDONLY partition read-write, so reread and * Mounting a RDONLY partition read-write, so reread and
@ -1394,14 +1387,16 @@ static int ext2_remount (struct super_block * sb, int * flags, char * data)
dquot_resume(sb, -1); dquot_resume(sb, -1);
} }
return 0; spin_lock(&sbi->s_lock);
restore_opts: out_set:
sbi->s_mount_opt = old_opts.s_mount_opt; sbi->s_mount_opt = new_opts.s_mount_opt;
sbi->s_resuid = old_opts.s_resuid; sbi->s_resuid = new_opts.s_resuid;
sbi->s_resgid = old_opts.s_resgid; sbi->s_resgid = new_opts.s_resgid;
sb->s_flags = old_sb_flags; sb->s_flags = (sb->s_flags & ~MS_POSIXACL) |
((sbi->s_mount_opt & EXT2_MOUNT_POSIX_ACL) ? MS_POSIXACL : 0);
spin_unlock(&sbi->s_lock); spin_unlock(&sbi->s_lock);
return err;
return 0;
} }
static int ext2_statfs (struct dentry * dentry, struct kstatfs * buf) static int ext2_statfs (struct dentry * dentry, struct kstatfs * buf)