Create a function, vfs_get_block_super(), that is fs_context-aware and a replacement for mount_bdev(). It caches the block device pointer and file open mode in the fs_context struct so that this information can be passed into sget_fc()'s test and set functions. Signed-off-by: David Howells <dhowells@xxxxxxxxxx> --- fs/fs_context.c | 2 + fs/super.c | 106 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/fs_context.h | 6 ++ 3 files changed, 114 insertions(+) diff --git a/fs/fs_context.c b/fs/fs_context.c index 87e3546b9a52..ea027762c0b2 100644 --- a/fs/fs_context.c +++ b/fs/fs_context.c @@ -425,6 +425,8 @@ void put_fs_context(struct fs_context *fc) if (fc->need_free && fc->ops && fc->ops->free) fc->ops->free(fc); + if (fc->bdev) + blkdev_put(fc->bdev, fc->bdev_mode); security_free_mnt_opts(&fc->security); put_net(fc->net_ns); diff --git a/fs/super.c b/fs/super.c index f27ee08fb26f..85851adb0f19 100644 --- a/fs/super.c +++ b/fs/super.c @@ -1211,6 +1211,112 @@ int vfs_get_super(struct fs_context *fc, EXPORT_SYMBOL(vfs_get_super); #ifdef CONFIG_BLOCK +static int set_bdev_super_fc(struct super_block *s, struct fs_context *fc) +{ + s->s_bdev = fc->bdev; + s->s_dev = s->s_bdev->bd_dev; + s->s_bdi = bdi_get(s->s_bdev->bd_bdi); + fc->bdev = NULL; + return 0; +} + +static int test_bdev_super_fc(struct super_block *s, struct fs_context *fc) +{ + return s->s_bdev == fc->bdev; +} + +/** + * vfs_get_block_super - Get a superblock based on a single block device + * @fc: The filesystem context holding the parameters + * @keying: How to distinguish superblocks + * @fill_super: Helper to initialise a new superblock + */ +int vfs_get_block_super(struct fs_context *fc, + int (*fill_super)(struct super_block *, + struct fs_context *)) +{ + struct block_device *bdev; + struct super_block *s; + int error = 0; + + fc->bdev_mode = FMODE_READ | FMODE_EXCL; + if (!(fc->sb_flags & SB_RDONLY)) + fc->bdev_mode |= FMODE_WRITE; + + if (!fc->source) + return invalf(fc, "No source specified"); + + bdev = blkdev_get_by_path(fc->source, fc->bdev_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); + error = -EBUSY; + goto error_bdev; + } + + fc->bdev = bdev; + fc->sb_flags |= SB_NOSEC; + s = sget_fc(fc, test_bdev_super_fc, set_bdev_super_fc); + mutex_unlock(&bdev->bd_fsfreeze_mutex); + if (IS_ERR(s)) { + error = PTR_ERR(s); + goto error_bdev; + } + + 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); + error = -EBUSY; + goto error_sb; + } + + /* 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, fc->bdev_mode); + down_write(&s->s_umount); + } else { + s->s_mode = fc->bdev_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) + goto error_sb; + + s->s_flags |= SB_ACTIVE; + bdev->bd_super = s; + } + + BUG_ON(fc->root); + fc->root = dget(s->s_root); + return 0; + +error_sb: + deactivate_locked_super(s); +error_bdev: + if (fc->bdev) { + blkdev_put(fc->bdev, fc->bdev_mode); + fc->bdev = NULL; + } + return error; +} +EXPORT_SYMBOL(vfs_get_block_super); + + static int set_bdev_super(struct super_block *s, void *data) { s->s_bdev = data; diff --git a/include/linux/fs_context.h b/include/linux/fs_context.h index a779022d06f5..cb49b92f02af 100644 --- a/include/linux/fs_context.h +++ b/include/linux/fs_context.h @@ -76,6 +76,7 @@ struct fs_context { const struct fs_context_operations *ops; struct file_system_type *fs_type; void *fs_private; /* The filesystem's context */ + struct block_device *bdev; /* The backing blockdev (if applicable) */ struct dentry *root; /* The root and superblock */ struct user_namespace *user_ns; /* The user namespace for this mount */ struct net *net_ns; /* The network namespace for this mount */ @@ -84,6 +85,7 @@ struct fs_context { const char *subtype; /* The subtype to set on the superblock */ void *security; /* Linux S&M options */ void *s_fs_info; /* Proposed s_fs_info */ + fmode_t bdev_mode; /* File open mode for bdev */ unsigned int sb_flags; /* Proposed superblock flags (SB_*) */ unsigned int sb_flags_mask; /* Superblock flags that were changed */ unsigned int s_iflags; /* OR'd with sb->s_iflags */ @@ -141,6 +143,10 @@ extern int vfs_get_super(struct fs_context *fc, int (*fill_super)(struct super_block *sb, struct fs_context *fc)); +extern int vfs_get_block_super(struct fs_context *fc, + int (*fill_super)(struct super_block *sb, + struct fs_context *fc)); + extern const struct file_operations fscontext_fops; #ifdef CONFIG_PRINTK