diff options
author | Kent Overstreet <kent.overstreet@gmail.com> | 2018-07-06 16:34:31 -0400 |
---|---|---|
committer | Kent Overstreet <kent.overstreet@gmail.com> | 2018-07-16 03:57:03 -0400 |
commit | e5a033b3fe442638c13604ddd318ad7f3dfa7517 (patch) | |
tree | 94a55ea6d98fe49b9baba8ea3e42e659ed7a0075 | |
parent | 7d6e154b71f88c0529b59c06e52aa536e64931e3 (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.c | 14 | ||||
-rw-r--r-- | fs/bcachefs/dirent.h | 8 | ||||
-rw-r--r-- | fs/bcachefs/fs.c | 259 |
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) |