mirror of
https://github.com/torvalds/linux.git
synced 2024-11-26 06:02:05 +00:00
bcachefs: Fix unmount path
There was a long standing race in the mount/unmount code - the VFS intends for mount/unmount synchronizatino to be handled by the list of superblocks, but we were still holding devices open after tearing down our superblock in the unmount path. Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
parent
625104ea21
commit
d5e4dcc29c
@ -491,7 +491,6 @@ enum {
|
||||
BCH_FS_ERRORS_FIXED,
|
||||
|
||||
/* misc: */
|
||||
BCH_FS_BDEV_MOUNTED,
|
||||
BCH_FS_FIXED_GENS,
|
||||
BCH_FS_ALLOC_WRITTEN,
|
||||
BCH_FS_REBUILD_REPLICAS,
|
||||
|
161
fs/bcachefs/fs.c
161
fs/bcachefs/fs.c
@ -1300,91 +1300,36 @@ static struct bch_fs *bch2_path_to_fs(const char *path)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
c = bch2_dev_to_fs(dev);
|
||||
if (c)
|
||||
closure_put(&c->cl);
|
||||
return c ?: ERR_PTR(-ENOENT);
|
||||
}
|
||||
|
||||
static struct bch_fs *__bch2_open_as_blockdevs(const char *dev_name, char * const *devs,
|
||||
unsigned nr_devs, struct bch_opts opts)
|
||||
{
|
||||
struct bch_fs *c, *c1, *c2;
|
||||
size_t i;
|
||||
|
||||
if (!nr_devs)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
c = bch2_fs_open(devs, nr_devs, opts);
|
||||
|
||||
if (IS_ERR(c) && PTR_ERR(c) == -EBUSY) {
|
||||
/*
|
||||
* Already open?
|
||||
* Look up each block device, make sure they all belong to a
|
||||
* filesystem and they all belong to the _same_ filesystem
|
||||
*/
|
||||
|
||||
c1 = bch2_path_to_fs(devs[0]);
|
||||
if (IS_ERR(c1))
|
||||
return c;
|
||||
|
||||
for (i = 1; i < nr_devs; i++) {
|
||||
c2 = bch2_path_to_fs(devs[i]);
|
||||
if (!IS_ERR(c2))
|
||||
closure_put(&c2->cl);
|
||||
|
||||
if (c1 != c2) {
|
||||
closure_put(&c1->cl);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
c = c1;
|
||||
}
|
||||
|
||||
if (IS_ERR(c))
|
||||
return c;
|
||||
|
||||
down_write(&c->state_lock);
|
||||
|
||||
if (!test_bit(BCH_FS_STARTED, &c->flags)) {
|
||||
up_write(&c->state_lock);
|
||||
closure_put(&c->cl);
|
||||
pr_err("err mounting %s: incomplete filesystem", dev_name);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
up_write(&c->state_lock);
|
||||
|
||||
set_bit(BCH_FS_BDEV_MOUNTED, &c->flags);
|
||||
return c;
|
||||
}
|
||||
|
||||
static struct bch_fs *bch2_open_as_blockdevs(const char *_dev_name,
|
||||
struct bch_opts opts)
|
||||
static char **split_devs(const char *_dev_name, unsigned *nr)
|
||||
{
|
||||
char *dev_name = NULL, **devs = NULL, *s;
|
||||
struct bch_fs *c = ERR_PTR(-ENOMEM);
|
||||
size_t i, nr_devs = 0;
|
||||
|
||||
dev_name = kstrdup(_dev_name, GFP_KERNEL);
|
||||
if (!dev_name)
|
||||
goto err;
|
||||
return NULL;
|
||||
|
||||
for (s = dev_name; s; s = strchr(s + 1, ':'))
|
||||
nr_devs++;
|
||||
|
||||
devs = kcalloc(nr_devs, sizeof(const char *), GFP_KERNEL);
|
||||
if (!devs)
|
||||
goto err;
|
||||
devs = kcalloc(nr_devs + 1, sizeof(const char *), GFP_KERNEL);
|
||||
if (!devs) {
|
||||
kfree(dev_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0, s = dev_name;
|
||||
s;
|
||||
(s = strchr(s, ':')) && (*s++ = '\0'))
|
||||
devs[i++] = s;
|
||||
|
||||
c = __bch2_open_as_blockdevs(_dev_name, devs, nr_devs, opts);
|
||||
err:
|
||||
kfree(devs);
|
||||
kfree(dev_name);
|
||||
return c;
|
||||
*nr = nr_devs;
|
||||
return devs;
|
||||
}
|
||||
|
||||
static int bch2_remount(struct super_block *sb, int *flags, char *data)
|
||||
@ -1471,6 +1416,13 @@ static int bch2_show_options(struct seq_file *seq, struct dentry *root)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bch2_put_super(struct super_block *sb)
|
||||
{
|
||||
struct bch_fs *c = sb->s_fs_info;
|
||||
|
||||
__bch2_fs_stop(c);
|
||||
}
|
||||
|
||||
static const struct super_operations bch_super_operations = {
|
||||
.alloc_inode = bch2_alloc_inode,
|
||||
.destroy_inode = bch2_destroy_inode,
|
||||
@ -1481,24 +1433,39 @@ static const struct super_operations bch_super_operations = {
|
||||
.show_devname = bch2_show_devname,
|
||||
.show_options = bch2_show_options,
|
||||
.remount_fs = bch2_remount,
|
||||
#if 0
|
||||
.put_super = bch2_put_super,
|
||||
#if 0
|
||||
.freeze_fs = bch2_freeze,
|
||||
.unfreeze_fs = bch2_unfreeze,
|
||||
#endif
|
||||
};
|
||||
|
||||
static int bch2_test_super(struct super_block *s, void *data)
|
||||
{
|
||||
return s->s_fs_info == data;
|
||||
}
|
||||
|
||||
static int bch2_set_super(struct super_block *s, void *data)
|
||||
{
|
||||
s->s_fs_info = data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bch2_noset_super(struct super_block *s, void *data)
|
||||
{
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
static int bch2_test_super(struct super_block *s, void *data)
|
||||
{
|
||||
struct bch_fs *c = s->s_fs_info;
|
||||
struct bch_fs **devs = data;
|
||||
unsigned i;
|
||||
|
||||
if (!c)
|
||||
return false;
|
||||
|
||||
for (i = 0; devs[i]; i++)
|
||||
if (c != devs[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct dentry *bch2_mount(struct file_system_type *fs_type,
|
||||
int flags, const char *dev_name, void *data)
|
||||
{
|
||||
@ -1507,7 +1474,9 @@ static struct dentry *bch2_mount(struct file_system_type *fs_type,
|
||||
struct super_block *sb;
|
||||
struct inode *vinode;
|
||||
struct bch_opts opts = bch2_opts_empty();
|
||||
unsigned i;
|
||||
char **devs;
|
||||
struct bch_fs **devs_to_fs = NULL;
|
||||
unsigned i, nr_devs;
|
||||
int ret;
|
||||
|
||||
opt_set(opts, read_only, (flags & SB_RDONLY) != 0);
|
||||
@ -1516,21 +1485,41 @@ static struct dentry *bch2_mount(struct file_system_type *fs_type,
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
c = bch2_open_as_blockdevs(dev_name, opts);
|
||||
if (IS_ERR(c))
|
||||
return ERR_CAST(c);
|
||||
devs = split_devs(dev_name, &nr_devs);
|
||||
if (!devs)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
sb = sget(fs_type, bch2_test_super, bch2_set_super, flags|SB_NOSEC, c);
|
||||
if (IS_ERR(sb)) {
|
||||
closure_put(&c->cl);
|
||||
return ERR_CAST(sb);
|
||||
devs_to_fs = kcalloc(nr_devs + 1, sizeof(void *), GFP_KERNEL);
|
||||
if (!devs_to_fs) {
|
||||
sb = ERR_PTR(-ENOMEM);
|
||||
goto got_sb;
|
||||
}
|
||||
|
||||
BUG_ON(sb->s_fs_info != c);
|
||||
for (i = 0; i < nr_devs; i++)
|
||||
devs_to_fs[i] = bch2_path_to_fs(devs[i]);
|
||||
|
||||
sb = sget(fs_type, bch2_test_super, bch2_noset_super,
|
||||
flags|SB_NOSEC, devs_to_fs);
|
||||
if (!IS_ERR(sb))
|
||||
goto got_sb;
|
||||
|
||||
c = bch2_fs_open(devs, nr_devs, opts);
|
||||
|
||||
if (!IS_ERR(c))
|
||||
sb = sget(fs_type, NULL, bch2_set_super, flags|SB_NOSEC, c);
|
||||
else
|
||||
sb = ERR_CAST(c);
|
||||
got_sb:
|
||||
kfree(devs_to_fs);
|
||||
kfree(devs[0]);
|
||||
kfree(devs);
|
||||
|
||||
if (IS_ERR(sb))
|
||||
return ERR_CAST(sb);
|
||||
|
||||
c = sb->s_fs_info;
|
||||
|
||||
if (sb->s_root) {
|
||||
closure_put(&c->cl);
|
||||
|
||||
if ((flags ^ sb->s_flags) & SB_RDONLY) {
|
||||
ret = -EBUSY;
|
||||
goto err_put_super;
|
||||
@ -1603,11 +1592,7 @@ static void bch2_kill_sb(struct super_block *sb)
|
||||
struct bch_fs *c = sb->s_fs_info;
|
||||
|
||||
generic_shutdown_super(sb);
|
||||
|
||||
if (test_bit(BCH_FS_BDEV_MOUNTED, &c->flags))
|
||||
bch2_fs_stop(c);
|
||||
else
|
||||
closure_put(&c->cl);
|
||||
bch2_fs_free(c);
|
||||
}
|
||||
|
||||
static struct file_system_type bcache_fs_type = {
|
||||
|
@ -465,7 +465,7 @@ int bch2_fs_read_write_early(struct bch_fs *c)
|
||||
|
||||
/* Filesystem startup/shutdown: */
|
||||
|
||||
static void bch2_fs_free(struct bch_fs *c)
|
||||
static void __bch2_fs_free(struct bch_fs *c)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
@ -522,10 +522,10 @@ static void bch2_fs_release(struct kobject *kobj)
|
||||
{
|
||||
struct bch_fs *c = container_of(kobj, struct bch_fs, kobj);
|
||||
|
||||
bch2_fs_free(c);
|
||||
__bch2_fs_free(c);
|
||||
}
|
||||
|
||||
void bch2_fs_stop(struct bch_fs *c)
|
||||
void __bch2_fs_stop(struct bch_fs *c)
|
||||
{
|
||||
struct bch_dev *ca;
|
||||
unsigned i;
|
||||
@ -555,13 +555,6 @@ void bch2_fs_stop(struct bch_fs *c)
|
||||
kobject_put(&c->opts_dir);
|
||||
kobject_put(&c->internal);
|
||||
|
||||
mutex_lock(&bch_fs_list_lock);
|
||||
list_del(&c->list);
|
||||
mutex_unlock(&bch_fs_list_lock);
|
||||
|
||||
closure_sync(&c->cl);
|
||||
closure_debug_destroy(&c->cl);
|
||||
|
||||
/* btree prefetch might have kicked off reads in the background: */
|
||||
bch2_btree_flush_all_reads(c);
|
||||
|
||||
@ -571,16 +564,39 @@ void bch2_fs_stop(struct bch_fs *c)
|
||||
cancel_work_sync(&c->btree_write_error_work);
|
||||
cancel_delayed_work_sync(&c->pd_controllers_update);
|
||||
cancel_work_sync(&c->read_only_work);
|
||||
}
|
||||
|
||||
for (i = 0; i < c->sb.nr_devices; i++)
|
||||
if (c->devs[i])
|
||||
bch2_dev_free(rcu_dereference_protected(c->devs[i], 1));
|
||||
void bch2_fs_free(struct bch_fs *c)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
mutex_lock(&bch_fs_list_lock);
|
||||
list_del(&c->list);
|
||||
mutex_unlock(&bch_fs_list_lock);
|
||||
|
||||
closure_sync(&c->cl);
|
||||
closure_debug_destroy(&c->cl);
|
||||
|
||||
for (i = 0; i < c->sb.nr_devices; i++) {
|
||||
struct bch_dev *ca = rcu_dereference_protected(c->devs[i], true);
|
||||
|
||||
if (ca) {
|
||||
bch2_free_super(&ca->disk_sb);
|
||||
bch2_dev_free(ca);
|
||||
}
|
||||
}
|
||||
|
||||
bch_verbose(c, "shutdown complete");
|
||||
|
||||
kobject_put(&c->kobj);
|
||||
}
|
||||
|
||||
void bch2_fs_stop(struct bch_fs *c)
|
||||
{
|
||||
__bch2_fs_stop(c);
|
||||
bch2_fs_free(c);
|
||||
}
|
||||
|
||||
static const char *bch2_fs_online(struct bch_fs *c)
|
||||
{
|
||||
struct bch_dev *ca;
|
||||
|
@ -230,6 +230,8 @@ static inline void bch2_fs_lazy_rw(struct bch_fs *c)
|
||||
bch2_fs_read_write_early(c);
|
||||
}
|
||||
|
||||
void __bch2_fs_stop(struct bch_fs *);
|
||||
void bch2_fs_free(struct bch_fs *);
|
||||
void bch2_fs_stop(struct bch_fs *);
|
||||
|
||||
int bch2_fs_start(struct bch_fs *);
|
||||
|
Loading…
Reference in New Issue
Block a user