diff options
Diffstat (limited to 'fs/super.c')
| -rw-r--r-- | fs/super.c | 143 | 
1 files changed, 136 insertions, 7 deletions
diff --git a/fs/super.c b/fs/super.c index 5960578a4076..8020974b2a68 100644 --- a/fs/super.c +++ b/fs/super.c @@ -32,6 +32,7 @@  #include <linux/backing-dev.h>  #include <linux/rculist_bl.h>  #include <linux/cleancache.h> +#include <linux/fscrypt.h>  #include <linux/fsnotify.h>  #include <linux/lockdep.h>  #include <linux/user_namespace.h> @@ -257,6 +258,8 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,  	s->s_maxbytes = MAX_NON_LFS;  	s->s_op = &default_op;  	s->s_time_gran = 1000000000; +	s->s_time_min = TIME64_MIN; +	s->s_time_max = TIME64_MAX;  	s->cleancache_poolid = CLEANCACHE_NO_POOL;  	s->s_shrink.seeks = DEFAULT_SEEKS; @@ -290,6 +293,7 @@ static void __put_super(struct super_block *s)  		WARN_ON(s->s_inode_lru.node);  		WARN_ON(!list_empty(&s->s_mounts));  		security_sb_free(s); +		fscrypt_sb_free(s);  		put_user_ns(s->s_user_ns);  		kfree(s->s_subtype);  		call_rcu(&s->rcu, destroy_super_rcu); @@ -1160,9 +1164,11 @@ int vfs_get_super(struct fs_context *fc,  {  	int (*test)(struct super_block *, struct fs_context *);  	struct super_block *sb; +	int err;  	switch (keying) {  	case vfs_get_single_super: +	case vfs_get_single_reconf_super:  		test = test_single_super;  		break;  	case vfs_get_keyed_super: @@ -1180,18 +1186,29 @@ int vfs_get_super(struct fs_context *fc,  		return PTR_ERR(sb);  	if (!sb->s_root) { -		int err = fill_super(sb, fc); -		if (err) { -			deactivate_locked_super(sb); -			return err; -		} +		err = fill_super(sb, fc); +		if (err) +			goto error;  		sb->s_flags |= SB_ACTIVE; +		fc->root = dget(sb->s_root); +	} else { +		fc->root = dget(sb->s_root); +		if (keying == vfs_get_single_reconf_super) { +			err = reconfigure_super(fc); +			if (err < 0) { +				dput(fc->root); +				fc->root = NULL; +				goto error; +			} +		}  	} -	BUG_ON(fc->root); -	fc->root = dget(sb->s_root);  	return 0; + +error: +	deactivate_locked_super(sb); +	return err;  }  EXPORT_SYMBOL(vfs_get_super); @@ -1211,7 +1228,26 @@ int get_tree_single(struct fs_context *fc,  }  EXPORT_SYMBOL(get_tree_single); +int get_tree_single_reconf(struct fs_context *fc, +		  int (*fill_super)(struct super_block *sb, +				    struct fs_context *fc)) +{ +	return vfs_get_super(fc, vfs_get_single_reconf_super, fill_super); +} +EXPORT_SYMBOL(get_tree_single_reconf); + +int get_tree_keyed(struct fs_context *fc, +		  int (*fill_super)(struct super_block *sb, +				    struct fs_context *fc), +		void *key) +{ +	fc->s_fs_info = key; +	return vfs_get_super(fc, vfs_get_keyed_super, fill_super); +} +EXPORT_SYMBOL(get_tree_keyed); +  #ifdef CONFIG_BLOCK +  static int set_bdev_super(struct super_block *s, void *data)  {  	s->s_bdev = data; @@ -1221,6 +1257,99 @@ static int set_bdev_super(struct super_block *s, void *data)  	return 0;  } +static int set_bdev_super_fc(struct super_block *s, struct fs_context *fc) +{ +	return set_bdev_super(s, fc->sget_key); +} + +static int test_bdev_super_fc(struct super_block *s, struct fs_context *fc) +{ +	return s->s_bdev == fc->sget_key; +} + +/** + * get_tree_bdev - Get a superblock based on a single block device + * @fc: The filesystem context holding the parameters + * @fill_super: Helper to initialise a new superblock + */ +int get_tree_bdev(struct fs_context *fc, +		int (*fill_super)(struct super_block *, +				  struct fs_context *)) +{ +	struct block_device *bdev; +	struct super_block *s; +	fmode_t mode = FMODE_READ | FMODE_EXCL; +	int error = 0; + +	if (!(fc->sb_flags & SB_RDONLY)) +		mode |= FMODE_WRITE; + +	if (!fc->source) +		return invalf(fc, "No source specified"); + +	bdev = blkdev_get_by_path(fc->source, mode, fc->fs_type); +	if (IS_ERR(bdev)) { +		errorf(fc, "%s: Can't open blockdev", fc->source); +		return PTR_ERR(bdev); +	} + +	/* Once the superblock is inserted into the list by sget_fc(), s_umount +	 * will protect the lockfs code from trying to start a snapshot while +	 * we are mounting +	 */ +	mutex_lock(&bdev->bd_fsfreeze_mutex); +	if (bdev->bd_fsfreeze_count > 0) { +		mutex_unlock(&bdev->bd_fsfreeze_mutex); +		warnf(fc, "%pg: Can't mount, blockdev is frozen", bdev); +		return -EBUSY; +	} + +	fc->sb_flags |= SB_NOSEC; +	fc->sget_key = bdev; +	s = sget_fc(fc, test_bdev_super_fc, set_bdev_super_fc); +	mutex_unlock(&bdev->bd_fsfreeze_mutex); +	if (IS_ERR(s)) +		return PTR_ERR(s); + +	if (s->s_root) { +		/* Don't summarily change the RO/RW state. */ +		if ((fc->sb_flags ^ s->s_flags) & SB_RDONLY) { +			warnf(fc, "%pg: Can't mount, would change RO state", bdev); +			deactivate_locked_super(s); +			blkdev_put(bdev, mode); +			return -EBUSY; +		} + +		/* +		 * s_umount nests inside bd_mutex during +		 * __invalidate_device().  blkdev_put() acquires +		 * bd_mutex and can't be called under s_umount.  Drop +		 * s_umount temporarily.  This is safe as we're +		 * holding an active reference. +		 */ +		up_write(&s->s_umount); +		blkdev_put(bdev, mode); +		down_write(&s->s_umount); +	} else { +		s->s_mode = mode; +		snprintf(s->s_id, sizeof(s->s_id), "%pg", bdev); +		sb_set_blocksize(s, block_size(bdev)); +		error = fill_super(s, fc); +		if (error) { +			deactivate_locked_super(s); +			return error; +		} + +		s->s_flags |= SB_ACTIVE; +		bdev->bd_super = s; +	} + +	BUG_ON(fc->root); +	fc->root = dget(s->s_root); +	return 0; +} +EXPORT_SYMBOL(get_tree_bdev); +  static int test_bdev_super(struct super_block *s, void *data)  {  	return (void *)s->s_bdev == data;  |