summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKent Overstreet <kent.overstreet@gmail.com>2019-03-21 23:13:46 -0400
committerKent Overstreet <kent.overstreet@gmail.com>2019-03-24 19:37:45 -0400
commitffe09df1065dd1b326913b21381ed1ad35ab8ef9 (patch)
tree141db55b1bd075938db5ca05ab6ee2a4a8a18c33
parent75a2fe43f066110fb4c264afbb4a03bd73ff954c (diff)
bcachefs: Verify fs hasn't been modified before going rw
-rw-r--r--fs/bcachefs/bcachefs.h1
-rw-r--r--fs/bcachefs/fs.c1
-rw-r--r--fs/bcachefs/recovery.c15
-rw-r--r--fs/bcachefs/super-io.c79
-rw-r--r--fs/bcachefs/super-io.h2
-rw-r--r--fs/bcachefs/super.c4
-rw-r--r--fs/bcachefs/super.h1
-rw-r--r--fs/bcachefs/super_types.h1
8 files changed, 80 insertions, 24 deletions
diff --git a/fs/bcachefs/bcachefs.h b/fs/bcachefs/bcachefs.h
index c3272c397170..ac90d8aa0131 100644
--- a/fs/bcachefs/bcachefs.h
+++ b/fs/bcachefs/bcachefs.h
@@ -387,6 +387,7 @@ struct bch_dev {
char name[BDEVNAME_SIZE];
struct bch_sb_handle disk_sb;
+ struct bch_sb *sb_read_scratch;
int sb_write_error;
struct bch_devs_mask self;
diff --git a/fs/bcachefs/fs.c b/fs/bcachefs/fs.c
index e2fddb8ab39b..f05606753dd6 100644
--- a/fs/bcachefs/fs.c
+++ b/fs/bcachefs/fs.c
@@ -1596,6 +1596,7 @@ static int bch2_remount(struct super_block *sb, int *flags, char *data)
ret = bch2_fs_read_write(c);
if (ret) {
bch_err(c, "error going rw: %i", ret);
+ mutex_unlock(&c->state_lock);
return -EINVAL;
}
diff --git a/fs/bcachefs/recovery.c b/fs/bcachefs/recovery.c
index 949097978b84..4cde23b9e43d 100644
--- a/fs/bcachefs/recovery.c
+++ b/fs/bcachefs/recovery.c
@@ -106,10 +106,11 @@ static int journal_replay_entry_early(struct bch_fs *c,
}
static int verify_superblock_clean(struct bch_fs *c,
- struct bch_sb_field_clean *clean,
+ struct bch_sb_field_clean **cleanp,
struct jset *j)
{
unsigned i;
+ struct bch_sb_field_clean *clean = *cleanp;
int ret = 0;
if (!clean || !j)
@@ -119,11 +120,9 @@ static int verify_superblock_clean(struct bch_fs *c,
"superblock journal seq (%llu) doesn't match journal (%llu) after clean shutdown",
le64_to_cpu(clean->journal_seq),
le64_to_cpu(j->seq))) {
- ret = bch2_fs_mark_dirty(c);
- if (ret) {
- bch_err(c, "error going rw");
- return ret;
- }
+ kfree(clean);
+ *cleanp = NULL;
+ return 0;
}
mustfix_fsck_err_on(j->read_clock != clean->read_clock, c,
@@ -235,7 +234,7 @@ int bch2_fs_recovery(struct bch_fs *c)
BUG_ON(ret);
}
- ret = verify_superblock_clean(c, clean, j);
+ ret = verify_superblock_clean(c, &clean, j);
if (ret)
goto err;
@@ -429,7 +428,7 @@ int bch2_fs_initialize(struct bch_fs *c)
bch2_journal_set_replay_done(&c->journal);
err = "error going read write";
- ret = bch2_fs_read_write_early(c);
+ ret = __bch2_fs_read_write(c, true);
if (ret)
goto err;
diff --git a/fs/bcachefs/super-io.c b/fs/bcachefs/super-io.c
index 7b754df6d35a..9568cb46755c 100644
--- a/fs/bcachefs/super-io.c
+++ b/fs/bcachefs/super-io.c
@@ -502,6 +502,8 @@ reread:
if (bch2_crc_cmp(csum, sb->sb->csum))
return "bad checksum reading superblock";
+ sb->seq = le64_to_cpu(sb->sb->seq);
+
return NULL;
}
@@ -637,6 +639,27 @@ static void write_super_endio(struct bio *bio)
percpu_ref_put(&ca->io_ref);
}
+static void read_back_super(struct bch_fs *c, struct bch_dev *ca)
+{
+ struct bch_sb *sb = ca->disk_sb.sb;
+ struct bio *bio = ca->disk_sb.bio;
+
+ bio_reset(bio);
+ bio_set_dev(bio, ca->disk_sb.bdev);
+ bio->bi_iter.bi_sector = le64_to_cpu(sb->layout.sb_offset[0]);
+ bio->bi_iter.bi_size = 4096;
+ bio->bi_end_io = write_super_endio;
+ bio->bi_private = ca;
+ bio_set_op_attrs(bio, REQ_OP_READ, REQ_SYNC|REQ_META);
+ bch2_bio_map(bio, ca->sb_read_scratch);
+
+ this_cpu_add(ca->io_done->sectors[READ][BCH_DATA_SB],
+ bio_sectors(bio));
+
+ percpu_ref_get(&ca->io_ref);
+ closure_bio_submit(bio, &c->sb_write);
+}
+
static void write_one_super(struct bch_fs *c, struct bch_dev *ca, unsigned idx)
{
struct bch_sb *sb = ca->disk_sb.sb;
@@ -666,7 +689,7 @@ static void write_one_super(struct bch_fs *c, struct bch_dev *ca, unsigned idx)
closure_bio_submit(bio, &c->sb_write);
}
-void bch2_write_super(struct bch_fs *c)
+int bch2_write_super(struct bch_fs *c)
{
struct closure *cl = &c->sb_write;
struct bch_dev *ca;
@@ -674,6 +697,7 @@ void bch2_write_super(struct bch_fs *c)
const char *err;
struct bch_devs_mask sb_written;
bool wrote, can_mount_without_written, can_mount_with_written;
+ int ret = 0;
lockdep_assert_held(&c->sb_lock);
@@ -689,6 +713,7 @@ void bch2_write_super(struct bch_fs *c)
err = bch2_sb_validate(&ca->disk_sb);
if (err) {
bch2_fs_inconsistent(c, "sb invalid before write: %s", err);
+ ret = -1;
goto out;
}
}
@@ -702,10 +727,27 @@ void bch2_write_super(struct bch_fs *c)
ca->sb_write_error = 0;
}
+ for_each_online_member(ca, c, i)
+ read_back_super(c, ca);
+ closure_sync(cl);
+
+ for_each_online_member(ca, c, i) {
+ if (!ca->sb_write_error &&
+ ca->disk_sb.seq !=
+ le64_to_cpu(ca->sb_read_scratch->seq)) {
+ bch2_fs_fatal_error(c,
+ "Superblock modified by another process");
+ percpu_ref_put(&ca->io_ref);
+ ret = -EROFS;
+ goto out;
+ }
+ }
+
do {
wrote = false;
for_each_online_member(ca, c, i)
- if (sb < ca->disk_sb.sb->layout.nr_superblocks) {
+ if (!ca->sb_write_error &&
+ sb < ca->disk_sb.sb->layout.nr_superblocks) {
write_one_super(c, ca, sb);
wrote = true;
}
@@ -713,9 +755,12 @@ void bch2_write_super(struct bch_fs *c)
sb++;
} while (wrote);
- for_each_online_member(ca, c, i)
+ for_each_online_member(ca, c, i) {
if (ca->sb_write_error)
__clear_bit(ca->dev_idx, sb_written.d);
+ else
+ ca->disk_sb.seq = le64_to_cpu(ca->disk_sb.sb->seq);
+ }
nr_wrote = dev_mask_nr(&sb_written);
@@ -738,13 +783,15 @@ void bch2_write_super(struct bch_fs *c)
* written anything (new filesystem), we continue if we'd be able to
* mount with the devices we did successfully write to:
*/
- bch2_fs_fatal_err_on(!nr_wrote ||
- (can_mount_without_written &&
- !can_mount_with_written), c,
- "Unable to write superblock to sufficient devices");
+ if (bch2_fs_fatal_err_on(!nr_wrote ||
+ (can_mount_without_written &&
+ !can_mount_with_written), c,
+ "Unable to write superblock to sufficient devices"))
+ ret = -1;
out:
/* Make new options visible after they're persistent: */
bch2_sb_update(c);
+ return ret;
}
/* BCH_SB_FIELD_journal: */
@@ -885,16 +932,20 @@ void bch2_sb_clean_renumber(struct bch_sb_field_clean *clean, int write)
int bch2_fs_mark_dirty(struct bch_fs *c)
{
+ int ret;
+
+ /*
+ * Unconditionally write superblock, to verify it hasn't changed before
+ * we go rw:
+ */
+
mutex_lock(&c->sb_lock);
- if (BCH_SB_CLEAN(c->disk_sb.sb) ||
- (c->disk_sb.sb->compat[0] & (1ULL << BCH_COMPAT_FEAT_ALLOC_INFO))) {
- SET_BCH_SB_CLEAN(c->disk_sb.sb, false);
- c->disk_sb.sb->compat[0] &= ~(1ULL << BCH_COMPAT_FEAT_ALLOC_INFO);
- bch2_write_super(c);
- }
+ SET_BCH_SB_CLEAN(c->disk_sb.sb, false);
+ c->disk_sb.sb->compat[0] &= ~(1ULL << BCH_COMPAT_FEAT_ALLOC_INFO);
+ ret = bch2_write_super(c);
mutex_unlock(&c->sb_lock);
- return 0;
+ return ret;
}
struct jset_entry *
diff --git a/fs/bcachefs/super-io.h b/fs/bcachefs/super-io.h
index 6b72f473709c..aa91b8216082 100644
--- a/fs/bcachefs/super-io.h
+++ b/fs/bcachefs/super-io.h
@@ -88,7 +88,7 @@ int bch2_sb_realloc(struct bch_sb_handle *, unsigned);
const char *bch2_sb_validate(struct bch_sb_handle *);
int bch2_read_super(const char *, struct bch_opts *, struct bch_sb_handle *);
-void bch2_write_super(struct bch_fs *);
+int bch2_write_super(struct bch_fs *);
/* BCH_SB_FIELD_journal: */
diff --git a/fs/bcachefs/super.c b/fs/bcachefs/super.c
index fd0e67f1a901..3bcc32400987 100644
--- a/fs/bcachefs/super.c
+++ b/fs/bcachefs/super.c
@@ -397,7 +397,7 @@ static int bch2_fs_read_write_late(struct bch_fs *c)
return 0;
}
-static int __bch2_fs_read_write(struct bch_fs *c, bool early)
+int __bch2_fs_read_write(struct bch_fs *c, bool early)
{
struct bch_dev *ca;
unsigned i;
@@ -940,6 +940,7 @@ static void bch2_dev_free(struct bch_dev *ca)
free_percpu(ca->io_done);
bioset_exit(&ca->replica_set);
bch2_dev_buckets_free(ca);
+ kfree(ca->sb_read_scratch);
bch2_time_stats_exit(&ca->io_latency[WRITE]);
bch2_time_stats_exit(&ca->io_latency[READ]);
@@ -1053,6 +1054,7 @@ static struct bch_dev *__bch2_dev_alloc(struct bch_fs *c,
0, GFP_KERNEL) ||
percpu_ref_init(&ca->io_ref, bch2_dev_io_ref_complete,
PERCPU_REF_INIT_DEAD, GFP_KERNEL) ||
+ !(ca->sb_read_scratch = kmalloc(4096, GFP_KERNEL)) ||
bch2_dev_buckets_alloc(c, ca) ||
bioset_init(&ca->replica_set, 4,
offsetof(struct bch_write_bio, bio), 0) ||
diff --git a/fs/bcachefs/super.h b/fs/bcachefs/super.h
index 130b0b67a214..9bb672c44f4a 100644
--- a/fs/bcachefs/super.h
+++ b/fs/bcachefs/super.h
@@ -218,6 +218,7 @@ struct bch_dev *bch2_dev_lookup(struct bch_fs *, const char *);
bool bch2_fs_emergency_read_only(struct bch_fs *);
void bch2_fs_read_only(struct bch_fs *);
+int __bch2_fs_read_write(struct bch_fs *, bool);
int bch2_fs_read_write(struct bch_fs *);
int bch2_fs_read_write_early(struct bch_fs *);
diff --git a/fs/bcachefs/super_types.h b/fs/bcachefs/super_types.h
index ebb238aa5472..6277be42c914 100644
--- a/fs/bcachefs/super_types.h
+++ b/fs/bcachefs/super_types.h
@@ -10,6 +10,7 @@ struct bch_sb_handle {
unsigned have_layout:1;
unsigned have_bio:1;
unsigned fs_sb:1;
+ u64 seq;
};
struct bch_devs_mask {