summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKent Overstreet <kent.overstreet@gmail.com>2018-07-06 16:34:31 -0400
committerKent Overstreet <kent.overstreet@gmail.com>2018-07-16 03:57:03 -0400
commite5a033b3fe442638c13604ddd318ad7f3dfa7517 (patch)
tree94a55ea6d98fe49b9baba8ea3e42e659ed7a0075
parent7d6e154b71f88c0529b59c06e52aa536e64931e3 (diff)
bcachefs: Make rename fully atomic
the core of rename - updating the src and dst dirents - has been atomic for quite some time, but the various inode updates for i_nlink and mtime/time weren't done atomically. Now, they are. Signed-off-by: Kent Overstreet <kent.overstreet@gmail.com>
-rw-r--r--fs/bcachefs/dirent.c14
-rw-r--r--fs/bcachefs/dirent.h8
-rw-r--r--fs/bcachefs/fs.c259
3 files changed, 118 insertions, 163 deletions
diff --git a/fs/bcachefs/dirent.c b/fs/bcachefs/dirent.c
index 8cd6049361a2..d979ae0eaa17 100644
--- a/fs/bcachefs/dirent.c
+++ b/fs/bcachefs/dirent.c
@@ -212,7 +212,7 @@ static struct bpos bch2_dirent_pos(struct bch_inode_info *inode,
return POS(inode->v.i_ino, bch2_dirent_hash(&inode->ei_str_hash, name));
}
-int __bch2_dirent_rename(struct btree_trans *trans,
+int bch2_dirent_rename(struct btree_trans *trans,
struct bch_inode_info *src_dir, const struct qstr *src_name,
struct bch_inode_info *dst_dir, const struct qstr *dst_name,
enum bch_rename_mode mode)
@@ -317,18 +317,6 @@ int __bch2_dirent_rename(struct btree_trans *trans,
return 0;
}
-int bch2_dirent_rename(struct bch_fs *c,
- struct bch_inode_info *src_dir, const struct qstr *src_name,
- struct bch_inode_info *dst_dir, const struct qstr *dst_name,
- u64 *journal_seq, enum bch_rename_mode mode)
-{
- return bch2_trans_do(c, journal_seq, BTREE_INSERT_ATOMIC,
- __bch2_dirent_rename(&trans,
- src_dir, src_name,
- dst_dir, dst_name,
- mode));
-}
-
int __bch2_dirent_delete(struct btree_trans *trans, u64 dir_inum,
const struct bch_hash_info *hash_info,
const struct qstr *name)
diff --git a/fs/bcachefs/dirent.h b/fs/bcachefs/dirent.h
index 2578bd887c73..4d92ffba144e 100644
--- a/fs/bcachefs/dirent.h
+++ b/fs/bcachefs/dirent.h
@@ -40,14 +40,10 @@ enum bch_rename_mode {
BCH_RENAME_EXCHANGE,
};
-int __bch2_dirent_rename(struct btree_trans *,
- struct bch_inode_info *, const struct qstr *,
- struct bch_inode_info *, const struct qstr *,
- enum bch_rename_mode);
-int bch2_dirent_rename(struct bch_fs *,
+int bch2_dirent_rename(struct btree_trans *,
struct bch_inode_info *, const struct qstr *,
struct bch_inode_info *, const struct qstr *,
- u64 *, enum bch_rename_mode);
+ enum bch_rename_mode);
u64 bch2_dirent_lookup(struct bch_fs *, u64, const struct bch_hash_info *,
const struct qstr *);
diff --git a/fs/bcachefs/fs.c b/fs/bcachefs/fs.c
index eeabb3603a10..c255e3892e11 100644
--- a/fs/bcachefs/fs.c
+++ b/fs/bcachefs/fs.c
@@ -197,36 +197,6 @@ int __must_check bch2_write_inode(struct bch_fs *c,
return __bch2_write_inode(c, inode, NULL, NULL, 0);
}
-static int inode_mod_nlink_fn(struct bch_inode_info *inode,
- struct bch_inode_unpacked *bi, void *p)
-{
- bi->bi_nlink += (long) p;
- return 0;
-}
-
-static int bch2_mod_nlink(struct bch_fs *c, struct bch_inode_info *inode,
- int count)
-{
- int ret;
-
- mutex_lock(&inode->ei_update_lock);
- ret = __bch2_write_inode(c, inode, inode_mod_nlink_fn,
- (void *)(long) count, 0);
- mutex_unlock(&inode->ei_update_lock);
-
- return ret;
-}
-
-static int bch2_inc_nlink(struct bch_fs *c, struct bch_inode_info *inode)
-{
- return bch2_mod_nlink(c, inode, 1);
-}
-
-static int bch2_dec_nlink(struct bch_fs *c, struct bch_inode_info *inode)
-{
- return bch2_mod_nlink(c, inode, -1);
-}
-
static struct inode *bch2_vfs_inode_get(struct bch_fs *c, u64 inum)
{
struct bch_inode_unpacked inode_u;
@@ -739,142 +709,143 @@ static int bch2_mknod(struct inode *vdir, struct dentry *dentry,
return __bch2_create(to_bch_ei(vdir), dentry, mode, rdev, false);
}
-static int bch2_rename(struct bch_fs *c,
- struct bch_inode_info *old_dir,
- struct dentry *old_dentry,
- struct bch_inode_info *new_dir,
- struct dentry *new_dentry)
-{
- struct bch_inode_info *old_inode = to_bch_ei(old_dentry->d_inode);
- struct bch_inode_info *new_inode = to_bch_ei(new_dentry->d_inode);
- struct timespec now = current_time(&old_dir->v);
- int ret;
-
- lockdep_assert_held(&old_dir->v.i_rwsem);
- lockdep_assert_held(&new_dir->v.i_rwsem);
-
- if (new_inode)
- filemap_write_and_wait_range(old_inode->v.i_mapping,
- 0, LLONG_MAX);
-
- if (new_inode && S_ISDIR(old_inode->v.i_mode)) {
- lockdep_assert_held(&new_inode->v.i_rwsem);
-
- if (!S_ISDIR(new_inode->v.i_mode))
- return -ENOTDIR;
-
- if (bch2_empty_dir(c, new_inode->v.i_ino))
- return -ENOTEMPTY;
+struct rename_info {
+ u64 now;
+ struct bch_inode_info *src_dir;
+ struct bch_inode_info *dst_dir;
+ struct bch_inode_info *src_inode;
+ struct bch_inode_info *dst_inode;
+ enum bch_rename_mode mode;
+};
- ret = bch2_dirent_rename(c,
- old_dir, &old_dentry->d_name,
- new_dir, &new_dentry->d_name,
- &old_inode->ei_journal_seq, BCH_RENAME_OVERWRITE);
- if (unlikely(ret))
- return ret;
+static int inode_update_for_rename_fn(struct bch_inode_info *inode,
+ struct bch_inode_unpacked *bi,
+ void *p)
+{
+ struct rename_info *info = p;
- clear_nlink(&new_inode->v);
- bch2_dec_nlink(c, old_dir);
- } else if (new_inode) {
- lockdep_assert_held(&new_inode->v.i_rwsem);
+ if (inode == info->src_dir) {
+ bi->bi_nlink -= S_ISDIR(info->src_inode->v.i_mode);
+ bi->bi_nlink += info->dst_inode &&
+ S_ISDIR(info->dst_inode->v.i_mode) &&
+ info->mode == BCH_RENAME_EXCHANGE;
+ }
- ret = bch2_dirent_rename(c,
- old_dir, &old_dentry->d_name,
- new_dir, &new_dentry->d_name,
- &old_inode->ei_journal_seq, BCH_RENAME_OVERWRITE);
- if (unlikely(ret))
- return ret;
+ if (inode == info->dst_dir) {
+ bi->bi_nlink += S_ISDIR(info->src_inode->v.i_mode);
+ bi->bi_nlink -= info->dst_inode &&
+ S_ISDIR(info->dst_inode->v.i_mode);
+ }
- new_inode->v.i_ctime = now;
- bch2_dec_nlink(c, new_inode);
- } else if (S_ISDIR(old_inode->v.i_mode)) {
- ret = bch2_dirent_rename(c,
- old_dir, &old_dentry->d_name,
- new_dir, &new_dentry->d_name,
- &old_inode->ei_journal_seq, BCH_RENAME);
- if (unlikely(ret))
- return ret;
+ if (inode == info->dst_inode &&
+ info->mode == BCH_RENAME_OVERWRITE) {
+ BUG_ON(bi->bi_nlink &&
+ S_ISDIR(info->dst_inode->v.i_mode));
- bch2_inc_nlink(c, new_dir);
- bch2_dec_nlink(c, old_dir);
- } else {
- ret = bch2_dirent_rename(c,
- old_dir, &old_dentry->d_name,
- new_dir, &new_dentry->d_name,
- &old_inode->ei_journal_seq, BCH_RENAME);
- if (unlikely(ret))
- return ret;
+ if (bi->bi_nlink)
+ bi->bi_nlink--;
+ else
+ bi->bi_flags |= BCH_INODE_UNLINKED;
}
- old_dir->v.i_ctime = old_dir->v.i_mtime = now;
- new_dir->v.i_ctime = new_dir->v.i_mtime = now;
- mark_inode_dirty_sync(&old_dir->v);
- mark_inode_dirty_sync(&new_dir->v);
-
- old_inode->v.i_ctime = now;
- mark_inode_dirty_sync(&old_inode->v);
+ if (inode == info->src_dir ||
+ inode == info->dst_dir)
+ bi->bi_mtime = info->now;
+ bi->bi_ctime = info->now;
return 0;
}
-static int bch2_rename_exchange(struct bch_fs *c,
- struct bch_inode_info *old_dir,
- struct dentry *old_dentry,
- struct bch_inode_info *new_dir,
- struct dentry *new_dentry)
+static int bch2_rename2(struct inode *src_vdir, struct dentry *src_dentry,
+ struct inode *dst_vdir, struct dentry *dst_dentry,
+ unsigned flags)
{
- struct bch_inode_info *old_inode = to_bch_ei(old_dentry->d_inode);
- struct bch_inode_info *new_inode = to_bch_ei(new_dentry->d_inode);
- struct timespec now = current_time(&old_dir->v);
+ struct bch_fs *c = src_vdir->i_sb->s_fs_info;
+ struct rename_info i = {
+ .now = timespec_to_bch2_time(c,
+ current_time(src_vdir)),
+ .src_dir = to_bch_ei(src_vdir),
+ .dst_dir = to_bch_ei(dst_vdir),
+ .src_inode = to_bch_ei(src_dentry->d_inode),
+ .dst_inode = to_bch_ei(dst_dentry->d_inode),
+ .mode = flags & RENAME_EXCHANGE
+ ? BCH_RENAME_EXCHANGE
+ : dst_dentry->d_inode
+ ? BCH_RENAME_OVERWRITE : BCH_RENAME,
+ };
+ struct btree_trans trans;
+ struct bch_inode_unpacked dst_dir_u, src_dir_u;
+ struct bch_inode_unpacked src_inode_u, dst_inode_u;
+ u64 journal_seq = 0;
int ret;
- ret = bch2_dirent_rename(c,
- old_dir, &old_dentry->d_name,
- new_dir, &new_dentry->d_name,
- &old_inode->ei_journal_seq, BCH_RENAME_EXCHANGE);
- if (unlikely(ret))
- return ret;
+ if (flags & ~(RENAME_NOREPLACE|RENAME_EXCHANGE))
+ return -EINVAL;
- if (S_ISDIR(old_inode->v.i_mode) !=
- S_ISDIR(new_inode->v.i_mode)) {
- if (S_ISDIR(old_inode->v.i_mode)) {
- bch2_inc_nlink(c, new_dir);
- bch2_dec_nlink(c, old_dir);
- } else {
- bch2_dec_nlink(c, new_dir);
- bch2_inc_nlink(c, old_dir);
- }
- }
+ if (i.mode == BCH_RENAME_OVERWRITE) {
+ if (S_ISDIR(i.src_inode->v.i_mode) !=
+ S_ISDIR(i.dst_inode->v.i_mode))
+ return -ENOTDIR;
- old_dir->v.i_ctime = old_dir->v.i_mtime = now;
- new_dir->v.i_ctime = new_dir->v.i_mtime = now;
- mark_inode_dirty_sync(&old_dir->v);
- mark_inode_dirty_sync(&new_dir->v);
+ if (S_ISDIR(i.src_inode->v.i_mode) &&
+ bch2_empty_dir(c, i.dst_inode->v.i_ino))
+ return -ENOTEMPTY;
- old_inode->v.i_ctime = now;
- new_inode->v.i_ctime = now;
- mark_inode_dirty_sync(&old_inode->v);
- mark_inode_dirty_sync(&new_inode->v);
+ ret = filemap_write_and_wait_range(i.src_inode->v.i_mapping,
+ 0, LLONG_MAX);
+ if (ret)
+ return ret;
+ }
- return 0;
-}
+ bch2_trans_init(&trans, c);
+retry:
+ bch2_trans_begin(&trans);
+ i.now = timespec_to_bch2_time(c, current_time(src_vdir)),
+
+ ret = bch2_dirent_rename(&trans,
+ i.src_dir, &src_dentry->d_name,
+ i.dst_dir, &dst_dentry->d_name,
+ i.mode) ?:
+ bch2_write_inode_trans(&trans, i.src_dir, &src_dir_u,
+ inode_update_for_rename_fn, &i) ?:
+ (i.src_dir != i.dst_dir
+ ? bch2_write_inode_trans(&trans, i.dst_dir, &dst_dir_u,
+ inode_update_for_rename_fn, &i)
+ : 0 ) ?:
+ bch2_write_inode_trans(&trans, i.src_inode, &src_inode_u,
+ inode_update_for_rename_fn, &i) ?:
+ (i.dst_inode
+ ? bch2_write_inode_trans(&trans, i.dst_inode, &dst_inode_u,
+ inode_update_for_rename_fn, &i)
+ : 0 ) ?:
+ bch2_trans_commit(&trans, NULL, NULL,
+ &journal_seq,
+ BTREE_INSERT_ATOMIC|
+ BTREE_INSERT_NOUNLOCK);
+ if (ret == -EINTR)
+ goto retry;
+ if (unlikely(ret))
+ goto err;
-static int bch2_rename2(struct inode *old_vdir, struct dentry *old_dentry,
- struct inode *new_vdir, struct dentry *new_dentry,
- unsigned flags)
-{
- struct bch_fs *c = old_vdir->i_sb->s_fs_info;
- struct bch_inode_info *old_dir = to_bch_ei(old_vdir);
- struct bch_inode_info *new_dir = to_bch_ei(new_vdir);
+ bch2_inode_update_after_write(c, i.src_dir, &src_dir_u,
+ ATTR_MTIME|ATTR_CTIME);
+ journal_seq_copy(i.src_dir, journal_seq);
- if (flags & ~(RENAME_NOREPLACE|RENAME_EXCHANGE))
- return -EINVAL;
+ if (i.src_dir != i.dst_dir) {
+ bch2_inode_update_after_write(c, i.dst_dir, &dst_dir_u,
+ ATTR_MTIME|ATTR_CTIME);
+ journal_seq_copy(i.dst_dir, journal_seq);
+ }
- if (flags & RENAME_EXCHANGE)
- return bch2_rename_exchange(c, old_dir, old_dentry,
- new_dir, new_dentry);
+ bch2_inode_update_after_write(c, i.src_inode, &src_inode_u,
+ ATTR_CTIME);
+ if (i.dst_inode)
+ bch2_inode_update_after_write(c, i.dst_inode, &dst_inode_u,
+ ATTR_CTIME);
+err:
+ bch2_trans_exit(&trans);
- return bch2_rename(c, old_dir, old_dentry, new_dir, new_dentry);
+ return ret;
}
static int bch2_setattr_nonsize(struct bch_inode_info *inode, struct iattr *iattr)