Commit d5e4dcc2 authored by Kent Overstreet's avatar Kent Overstreet Committed by Kent Overstreet

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: default avatarKent Overstreet <kent.overstreet@linux.dev>
parent 625104ea
...@@ -491,7 +491,6 @@ enum { ...@@ -491,7 +491,6 @@ enum {
BCH_FS_ERRORS_FIXED, BCH_FS_ERRORS_FIXED,
/* misc: */ /* misc: */
BCH_FS_BDEV_MOUNTED,
BCH_FS_FIXED_GENS, BCH_FS_FIXED_GENS,
BCH_FS_ALLOC_WRITTEN, BCH_FS_ALLOC_WRITTEN,
BCH_FS_REBUILD_REPLICAS, BCH_FS_REBUILD_REPLICAS,
......
...@@ -1300,91 +1300,36 @@ static struct bch_fs *bch2_path_to_fs(const char *path) ...@@ -1300,91 +1300,36 @@ static struct bch_fs *bch2_path_to_fs(const char *path)
return ERR_PTR(ret); return ERR_PTR(ret);
c = bch2_dev_to_fs(dev); c = bch2_dev_to_fs(dev);
return c ?: ERR_PTR(-ENOENT); if (c)
}
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); closure_put(&c->cl);
pr_err("err mounting %s: incomplete filesystem", dev_name); return c ?: ERR_PTR(-ENOENT);
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, static char **split_devs(const char *_dev_name, unsigned *nr)
struct bch_opts opts)
{ {
char *dev_name = NULL, **devs = NULL, *s; char *dev_name = NULL, **devs = NULL, *s;
struct bch_fs *c = ERR_PTR(-ENOMEM);
size_t i, nr_devs = 0; size_t i, nr_devs = 0;
dev_name = kstrdup(_dev_name, GFP_KERNEL); dev_name = kstrdup(_dev_name, GFP_KERNEL);
if (!dev_name) if (!dev_name)
goto err; return NULL;
for (s = dev_name; s; s = strchr(s + 1, ':')) for (s = dev_name; s; s = strchr(s + 1, ':'))
nr_devs++; nr_devs++;
devs = kcalloc(nr_devs, sizeof(const char *), GFP_KERNEL); devs = kcalloc(nr_devs + 1, sizeof(const char *), GFP_KERNEL);
if (!devs) if (!devs) {
goto err; kfree(dev_name);
return NULL;
}
for (i = 0, s = dev_name; for (i = 0, s = dev_name;
s; s;
(s = strchr(s, ':')) && (*s++ = '\0')) (s = strchr(s, ':')) && (*s++ = '\0'))
devs[i++] = s; devs[i++] = s;
c = __bch2_open_as_blockdevs(_dev_name, devs, nr_devs, opts); *nr = nr_devs;
err: return devs;
kfree(devs);
kfree(dev_name);
return c;
} }
static int bch2_remount(struct super_block *sb, int *flags, char *data) 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) ...@@ -1471,6 +1416,13 @@ static int bch2_show_options(struct seq_file *seq, struct dentry *root)
return 0; 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 = { static const struct super_operations bch_super_operations = {
.alloc_inode = bch2_alloc_inode, .alloc_inode = bch2_alloc_inode,
.destroy_inode = bch2_destroy_inode, .destroy_inode = bch2_destroy_inode,
...@@ -1481,24 +1433,39 @@ static const struct super_operations bch_super_operations = { ...@@ -1481,24 +1433,39 @@ static const struct super_operations bch_super_operations = {
.show_devname = bch2_show_devname, .show_devname = bch2_show_devname,
.show_options = bch2_show_options, .show_options = bch2_show_options,
.remount_fs = bch2_remount, .remount_fs = bch2_remount,
#if 0
.put_super = bch2_put_super, .put_super = bch2_put_super,
#if 0
.freeze_fs = bch2_freeze, .freeze_fs = bch2_freeze,
.unfreeze_fs = bch2_unfreeze, .unfreeze_fs = bch2_unfreeze,
#endif #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) static int bch2_set_super(struct super_block *s, void *data)
{ {
s->s_fs_info = data; s->s_fs_info = data;
return 0; 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, static struct dentry *bch2_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data) int flags, const char *dev_name, void *data)
{ {
...@@ -1507,7 +1474,9 @@ static struct dentry *bch2_mount(struct file_system_type *fs_type, ...@@ -1507,7 +1474,9 @@ static struct dentry *bch2_mount(struct file_system_type *fs_type,
struct super_block *sb; struct super_block *sb;
struct inode *vinode; struct inode *vinode;
struct bch_opts opts = bch2_opts_empty(); struct bch_opts opts = bch2_opts_empty();
unsigned i; char **devs;
struct bch_fs **devs_to_fs = NULL;
unsigned i, nr_devs;
int ret; int ret;
opt_set(opts, read_only, (flags & SB_RDONLY) != 0); opt_set(opts, read_only, (flags & SB_RDONLY) != 0);
...@@ -1516,21 +1485,41 @@ static struct dentry *bch2_mount(struct file_system_type *fs_type, ...@@ -1516,21 +1485,41 @@ static struct dentry *bch2_mount(struct file_system_type *fs_type,
if (ret) if (ret)
return ERR_PTR(ret); return ERR_PTR(ret);
c = bch2_open_as_blockdevs(dev_name, opts); devs = split_devs(dev_name, &nr_devs);
if (IS_ERR(c)) if (!devs)
return ERR_CAST(c); return ERR_PTR(-ENOMEM);
sb = sget(fs_type, bch2_test_super, bch2_set_super, flags|SB_NOSEC, c); devs_to_fs = kcalloc(nr_devs + 1, sizeof(void *), GFP_KERNEL);
if (IS_ERR(sb)) { if (!devs_to_fs) {
closure_put(&c->cl); sb = ERR_PTR(-ENOMEM);
return ERR_CAST(sb); 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]);
if (sb->s_root) { sb = sget(fs_type, bch2_test_super, bch2_noset_super,
closure_put(&c->cl); 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) {
if ((flags ^ sb->s_flags) & SB_RDONLY) { if ((flags ^ sb->s_flags) & SB_RDONLY) {
ret = -EBUSY; ret = -EBUSY;
goto err_put_super; goto err_put_super;
...@@ -1603,11 +1592,7 @@ static void bch2_kill_sb(struct super_block *sb) ...@@ -1603,11 +1592,7 @@ static void bch2_kill_sb(struct super_block *sb)
struct bch_fs *c = sb->s_fs_info; struct bch_fs *c = sb->s_fs_info;
generic_shutdown_super(sb); generic_shutdown_super(sb);
bch2_fs_free(c);
if (test_bit(BCH_FS_BDEV_MOUNTED, &c->flags))
bch2_fs_stop(c);
else
closure_put(&c->cl);
} }
static struct file_system_type bcache_fs_type = { static struct file_system_type bcache_fs_type = {
......
...@@ -465,7 +465,7 @@ int bch2_fs_read_write_early(struct bch_fs *c) ...@@ -465,7 +465,7 @@ int bch2_fs_read_write_early(struct bch_fs *c)
/* Filesystem startup/shutdown: */ /* Filesystem startup/shutdown: */
static void bch2_fs_free(struct bch_fs *c) static void __bch2_fs_free(struct bch_fs *c)
{ {
unsigned i; unsigned i;
...@@ -522,10 +522,10 @@ static void bch2_fs_release(struct kobject *kobj) ...@@ -522,10 +522,10 @@ static void bch2_fs_release(struct kobject *kobj)
{ {
struct bch_fs *c = container_of(kobj, struct bch_fs, 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; struct bch_dev *ca;
unsigned i; unsigned i;
...@@ -555,13 +555,6 @@ void bch2_fs_stop(struct bch_fs *c) ...@@ -555,13 +555,6 @@ void bch2_fs_stop(struct bch_fs *c)
kobject_put(&c->opts_dir); kobject_put(&c->opts_dir);
kobject_put(&c->internal); 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: */ /* btree prefetch might have kicked off reads in the background: */
bch2_btree_flush_all_reads(c); bch2_btree_flush_all_reads(c);
...@@ -571,16 +564,39 @@ void bch2_fs_stop(struct bch_fs *c) ...@@ -571,16 +564,39 @@ void bch2_fs_stop(struct bch_fs *c)
cancel_work_sync(&c->btree_write_error_work); cancel_work_sync(&c->btree_write_error_work);
cancel_delayed_work_sync(&c->pd_controllers_update); cancel_delayed_work_sync(&c->pd_controllers_update);
cancel_work_sync(&c->read_only_work); cancel_work_sync(&c->read_only_work);
}
for (i = 0; i < c->sb.nr_devices; i++) void bch2_fs_free(struct bch_fs *c)
if (c->devs[i]) {
bch2_dev_free(rcu_dereference_protected(c->devs[i], 1)); 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"); bch_verbose(c, "shutdown complete");
kobject_put(&c->kobj); 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) static const char *bch2_fs_online(struct bch_fs *c)
{ {
struct bch_dev *ca; struct bch_dev *ca;
......
...@@ -230,6 +230,8 @@ static inline void bch2_fs_lazy_rw(struct bch_fs *c) ...@@ -230,6 +230,8 @@ static inline void bch2_fs_lazy_rw(struct bch_fs *c)
bch2_fs_read_write_early(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 *); void bch2_fs_stop(struct bch_fs *);
int bch2_fs_start(struct bch_fs *); int bch2_fs_start(struct bch_fs *);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment