Skip to content

Commit

Permalink
vfs: Create fs_context-aware mount_bdev() replacement
Browse files Browse the repository at this point in the history
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@redhat.com>
cc: Jens Axboe <axboe@kernel.dk>
cc: linux-block@vger.kernel.org
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
  • Loading branch information
David Howells authored and Al Viro committed Jul 5, 2019
1 parent 98e78e8 commit 461a27a
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 1 deletion.
2 changes: 2 additions & 0 deletions fs/fs_context.c
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,8 @@ void put_fs_context(struct fs_context *fc)

if (fc->need_free && fc->ops && fc->ops->free)
fc->ops->free(fc);
if (fc->dev_destructor)
fc->dev_destructor(fc);

security_free_mnt_opts(&fc->security);
put_net(fc->net_ns);
Expand Down
111 changes: 110 additions & 1 deletion fs/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -1215,6 +1215,110 @@ int get_tree_single(struct fs_context *fc,
EXPORT_SYMBOL(get_tree_single);

#ifdef CONFIG_BLOCK
static void fc_bdev_destructor(struct fs_context *fc)
{
if (fc->bdev) {
blkdev_put(fc->bdev, fc->bdev_mode);
fc->bdev = NULL;
}
}

static int set_bdev_super_fc(struct super_block *s, struct fs_context *fc)
{
s->s_mode = fc->bdev_mode;
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);
}

fc->dev_destructor = fc_bdev_destructor;
fc->bdev = 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;
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);
error = -EBUSY;
goto error_sb;
}

/* Leave fc->bdev to fc_bdev_destructor() to clean up to avoid
* locking conflicts.
*/
} else {
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);
/* Leave fc->bdev to fc_bdev_destructor() to clean up */
return error;
}
EXPORT_SYMBOL(vfs_get_block_super);


static int set_bdev_super(struct super_block *s, void *data)
{
s->s_bdev = data;
Expand Down Expand Up @@ -1414,8 +1518,13 @@ int vfs_get_tree(struct fs_context *fc)
* on the superblock.
*/
error = fc->ops->get_tree(fc);
if (error < 0)
if (error < 0) {
if (fc->dev_destructor) {
fc->dev_destructor(fc);
fc->dev_destructor = NULL;
}
return error;
}

if (!fc->root) {
pr_err("Filesystem %s get_tree() didn't set fc->root\n",
Expand Down
9 changes: 9 additions & 0 deletions include/linux/fs_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ struct fs_context {
struct mutex uapi_mutex; /* Userspace access mutex */
struct file_system_type *fs_type;
void *fs_private; /* The filesystem's context */
union {
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 */
Expand All @@ -101,6 +104,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 */
Expand All @@ -109,6 +113,7 @@ struct fs_context {
enum fs_context_phase phase:8; /* The phase the context is in */
bool need_free:1; /* Need to call ops->free() */
bool global:1; /* Goes into &init_user_ns */
void (*dev_destructor)(struct fs_context *fc); /* For block or mtd */
};

struct fs_context_operations {
Expand Down Expand Up @@ -158,6 +163,10 @@ extern int get_tree_single(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;

/*
Expand Down

0 comments on commit 461a27a

Please sign in to comment.