From 62f5e4fa67dde8255fa18a06d5354cdca02b6fc7 Mon Sep 17 00:00:00 2001
From: Kent Overstreet <kent.overstreet@gmail.com>
Date: Fri, 4 Oct 2019 16:25:58 -0400
Subject: [PATCH] Update bcachefs sources to ce9293e9d0 bcachefs: Factor out
 fs-common.c

---
 .bcachefs_revision      |   2 +-
 cmd_migrate.c           |  50 ++--
 libbcachefs/dirent.c    |  97 ++++----
 libbcachefs/dirent.h    |  27 ++-
 libbcachefs/fs-common.c | 280 +++++++++++++++++++++++
 libbcachefs/fs-common.h |  37 +++
 libbcachefs/fs-ioctl.c  |  10 +
 libbcachefs/fs.c        | 490 +++++++++++-----------------------------
 libbcachefs/fs.h        |  13 --
 libbcachefs/fsck.c      |  74 ++----
 libbcachefs/inode.c     |  39 ++--
 libbcachefs/inode.h     |  16 +-
 libbcachefs/recovery.c  |  26 +--
 13 files changed, 599 insertions(+), 562 deletions(-)
 create mode 100644 libbcachefs/fs-common.c
 create mode 100644 libbcachefs/fs-common.h

diff --git a/.bcachefs_revision b/.bcachefs_revision
index 08e62996..2ad0374a 100644
--- a/.bcachefs_revision
+++ b/.bcachefs_revision
@@ -1 +1 @@
-cddca21efc74d10223f6e2e149dfa79eeb67fdce
+ce9293e9d063f7f1a22209f9cc2f5cb7478e886c
diff --git a/cmd_migrate.c b/cmd_migrate.c
index 7d6af443..7d15a08e 100644
--- a/cmd_migrate.c
+++ b/cmd_migrate.c
@@ -30,7 +30,7 @@
 #include "libbcachefs/btree_update.h"
 #include "libbcachefs/buckets.h"
 #include "libbcachefs/dirent.h"
-#include "libbcachefs/fs.h"
+#include "libbcachefs/fs-common.h"
 #include "libbcachefs/inode.h"
 #include "libbcachefs/io.h"
 #include "libbcachefs/replicas.h"
@@ -38,6 +38,9 @@
 #include "libbcachefs/super.h"
 #include "libbcachefs/xattr.h"
 
+/* XXX cut and pasted from fsck.c */
+#define QSTR(n) { { { .len = strlen(n) } }, .name = n }
+
 static char *dev_t_to_path(dev_t dev)
 {
 	char link[PATH_MAX], *p;
@@ -123,39 +126,21 @@ static void update_inode(struct bch_fs *c,
 	ret = bch2_btree_insert(c, BTREE_ID_INODES, &packed.inode.k_i,
 				NULL, NULL, 0);
 	if (ret)
-		die("error creating file: %s", strerror(-ret));
-}
-
-static void create_dirent(struct bch_fs *c,
-			  struct bch_inode_unpacked *parent,
-			  const char *name, u64 inum, mode_t mode)
-{
-	struct bch_hash_info parent_hash_info = bch2_hash_info_init(c, parent);
-	struct qstr qname = { { { .len = strlen(name), } }, .name = name };
-
-	int ret = bch2_dirent_create(c, parent->bi_inum, &parent_hash_info,
-				     mode_to_type(mode), &qname,
-				     inum, NULL, BCH_HASH_SET_MUST_CREATE);
-	if (ret)
-		die("error creating file: %s", strerror(-ret));
-
-	if (S_ISDIR(mode))
-		parent->bi_nlink++;
+		die("error updating inode: %s", strerror(-ret));
 }
 
 static void create_link(struct bch_fs *c,
 			struct bch_inode_unpacked *parent,
 			const char *name, u64 inum, mode_t mode)
 {
+	struct qstr qstr = QSTR(name);
 	struct bch_inode_unpacked inode;
-	int ret = bch2_inode_find_by_inum(c, inum, &inode);
+
+	int ret = bch2_trans_do(c, NULL, BTREE_INSERT_ATOMIC,
+		bch2_link_trans(&trans, parent->bi_inum,
+				inum, &inode, &qstr));
 	if (ret)
-		die("error looking up hardlink: %s", strerror(-ret));
-
-	inode.bi_nlink++;
-	update_inode(c, &inode);
-
-	create_dirent(c, parent, name, inum, mode);
+		die("error creating hardlink: %s", strerror(-ret));
 }
 
 static struct bch_inode_unpacked create_file(struct bch_fs *c,
@@ -164,18 +149,17 @@ static struct bch_inode_unpacked create_file(struct bch_fs *c,
 					     uid_t uid, gid_t gid,
 					     mode_t mode, dev_t rdev)
 {
+	struct qstr qstr = QSTR(name);
 	struct bch_inode_unpacked new_inode;
-	int ret;
 
-	bch2_inode_init(c, &new_inode, uid, gid, mode, rdev, parent);
-
-	ret = bch2_inode_create(c, &new_inode, BLOCKDEV_INODE_MAX, 0,
-				&c->unused_inode_hint);
+	int ret = bch2_trans_do(c, NULL, BTREE_INSERT_ATOMIC,
+		bch2_create_trans(&trans,
+				  parent->bi_inum, parent,
+				  &new_inode, &qstr,
+				  uid, gid, mode, rdev, NULL, NULL));
 	if (ret)
 		die("error creating file: %s", strerror(-ret));
 
-	create_dirent(c, parent, name, new_inode.bi_inum, mode);
-
 	return new_inode;
 }
 
diff --git a/libbcachefs/dirent.c b/libbcachefs/dirent.c
index 38dd9680..304128d7 100644
--- a/libbcachefs/dirent.c
+++ b/libbcachefs/dirent.c
@@ -138,10 +138,10 @@ static struct bkey_i_dirent *dirent_create_key(struct btree_trans *trans,
 	return dirent;
 }
 
-int __bch2_dirent_create(struct btree_trans *trans,
-			 u64 dir_inum, const struct bch_hash_info *hash_info,
-			 u8 type, const struct qstr *name, u64 dst_inum,
-			 int flags)
+int bch2_dirent_create(struct btree_trans *trans,
+		       u64 dir_inum, const struct bch_hash_info *hash_info,
+		       u8 type, const struct qstr *name, u64 dst_inum,
+		       int flags)
 {
 	struct bkey_i_dirent *dirent;
 	int ret;
@@ -155,16 +155,6 @@ int __bch2_dirent_create(struct btree_trans *trans,
 			     dir_inum, &dirent->k_i, flags);
 }
 
-int bch2_dirent_create(struct bch_fs *c, u64 dir_inum,
-		       const struct bch_hash_info *hash_info,
-		       u8 type, const struct qstr *name, u64 dst_inum,
-		       u64 *journal_seq, int flags)
-{
-	return bch2_trans_do(c, journal_seq, flags,
-		__bch2_dirent_create(&trans, dir_inum, hash_info,
-				     type, name, dst_inum, flags));
-}
-
 static void dirent_copy_target(struct bkey_i_dirent *dst,
 			       struct bkey_s_c_dirent src)
 {
@@ -172,23 +162,22 @@ static void dirent_copy_target(struct bkey_i_dirent *dst,
 	dst->v.d_type = src.v->d_type;
 }
 
-static struct bpos bch2_dirent_pos(struct bch_inode_info *inode,
-				   const struct qstr *name)
-{
-	return POS(inode->v.i_ino, bch2_dirent_hash(&inode->ei_str_hash, name));
-}
-
 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)
+		       u64 src_dir, struct bch_hash_info *src_hash,
+		       u64 dst_dir, struct bch_hash_info *dst_hash,
+		       const struct qstr *src_name, u64 *src_inum,
+		       const struct qstr *dst_name, u64 *dst_inum,
+		       enum bch_rename_mode mode)
 {
 	struct btree_iter *src_iter, *dst_iter;
 	struct bkey_s_c old_src, old_dst;
 	struct bkey_i_dirent *new_src = NULL, *new_dst = NULL;
-	struct bpos dst_pos = bch2_dirent_pos(dst_dir, dst_name);
+	struct bpos dst_pos =
+		POS(dst_dir, bch2_dirent_hash(dst_hash, dst_name));
 	int ret;
 
+	*src_inum = *dst_inum = 0;
+
 	/*
 	 * Lookup dst:
 	 *
@@ -198,24 +187,25 @@ int bch2_dirent_rename(struct btree_trans *trans,
 	 */
 	dst_iter = mode == BCH_RENAME
 		? bch2_hash_hole(trans, bch2_dirent_hash_desc,
-				 &dst_dir->ei_str_hash,
-				 dst_dir->v.i_ino, dst_name)
+				 dst_hash, dst_dir, dst_name)
 		: bch2_hash_lookup(trans, bch2_dirent_hash_desc,
-				   &dst_dir->ei_str_hash,
-				   dst_dir->v.i_ino, dst_name,
+				   dst_hash, dst_dir, dst_name,
 				   BTREE_ITER_INTENT);
 	if (IS_ERR(dst_iter))
 		return PTR_ERR(dst_iter);
 	old_dst = bch2_btree_iter_peek_slot(dst_iter);
 
+	if (mode != BCH_RENAME)
+		*dst_inum = le64_to_cpu(bkey_s_c_to_dirent(old_dst).v->d_inum);
+
 	/* Lookup src: */
 	src_iter = bch2_hash_lookup(trans, bch2_dirent_hash_desc,
-				    &src_dir->ei_str_hash,
-				    src_dir->v.i_ino, src_name,
+				    src_hash, src_dir, src_name,
 				    BTREE_ITER_INTENT);
 	if (IS_ERR(src_iter))
 		return PTR_ERR(src_iter);
 	old_src = bch2_btree_iter_peek_slot(src_iter);
+	*src_inum = le64_to_cpu(bkey_s_c_to_dirent(old_src).v->d_inum);
 
 	/* Create new dst key: */
 	new_dst = dirent_create_key(trans, 0, dst_name, 0);
@@ -269,8 +259,7 @@ int bch2_dirent_rename(struct btree_trans *trans,
 		} else {
 			/* Check if we need a whiteout to delete src: */
 			ret = bch2_hash_needs_whiteout(trans, bch2_dirent_hash_desc,
-						       &src_dir->ei_str_hash,
-						       src_iter);
+						       src_hash, src_iter);
 			if (ret < 0)
 				return ret;
 
@@ -284,12 +273,12 @@ int bch2_dirent_rename(struct btree_trans *trans,
 	return 0;
 }
 
-int __bch2_dirent_delete(struct btree_trans *trans, u64 dir_inum,
-			 const struct bch_hash_info *hash_info,
-			 const struct qstr *name)
+int bch2_dirent_delete_at(struct btree_trans *trans,
+			  const struct bch_hash_info *hash_info,
+			  struct btree_iter *iter)
 {
-	return bch2_hash_delete(trans, bch2_dirent_hash_desc, hash_info,
-				dir_inum, name);
+	return bch2_hash_delete_at(trans, bch2_dirent_hash_desc,
+				   hash_info, iter);
 }
 
 int bch2_dirent_delete(struct bch_fs *c, u64 dir_inum,
@@ -300,7 +289,17 @@ int bch2_dirent_delete(struct bch_fs *c, u64 dir_inum,
 	return bch2_trans_do(c, journal_seq,
 			     BTREE_INSERT_ATOMIC|
 			     BTREE_INSERT_NOFAIL,
-		__bch2_dirent_delete(&trans, dir_inum, hash_info, name));
+		bch2_hash_delete(&trans, bch2_dirent_hash_desc, hash_info,
+				 dir_inum, name));
+}
+
+struct btree_iter *
+__bch2_dirent_lookup_trans(struct btree_trans *trans, u64 dir_inum,
+			   const struct bch_hash_info *hash_info,
+			   const struct qstr *name)
+{
+	return bch2_hash_lookup(trans, bch2_dirent_hash_desc,
+				hash_info, dir_inum, name, 0);
 }
 
 u64 bch2_dirent_lookup(struct bch_fs *c, u64 dir_inum,
@@ -314,8 +313,7 @@ u64 bch2_dirent_lookup(struct bch_fs *c, u64 dir_inum,
 
 	bch2_trans_init(&trans, c, 0, 0);
 
-	iter = bch2_hash_lookup(&trans, bch2_dirent_hash_desc,
-				hash_info, dir_inum, name, 0);
+	iter = __bch2_dirent_lookup_trans(&trans, dir_inum, hash_info, name);
 	if (IS_ERR(iter)) {
 		BUG_ON(PTR_ERR(iter) == -EINTR);
 		goto out;
@@ -349,16 +347,8 @@ int bch2_empty_dir_trans(struct btree_trans *trans, u64 dir_inum)
 	return ret;
 }
 
-int bch2_empty_dir(struct bch_fs *c, u64 dir_inum)
+int bch2_readdir(struct bch_fs *c, u64 inum, struct dir_context *ctx)
 {
-	return bch2_trans_do(c, NULL, 0,
-		bch2_empty_dir_trans(&trans, dir_inum));
-}
-
-int bch2_readdir(struct bch_fs *c, struct file *file,
-		 struct dir_context *ctx)
-{
-	struct bch_inode_info *inode = file_bch_inode(file);
 	struct btree_trans trans;
 	struct btree_iter *iter;
 	struct bkey_s_c k;
@@ -366,22 +356,19 @@ int bch2_readdir(struct bch_fs *c, struct file *file,
 	unsigned len;
 	int ret;
 
-	if (!dir_emit_dots(file, ctx))
-		return 0;
-
 	bch2_trans_init(&trans, c, 0, 0);
 
 	for_each_btree_key(&trans, iter, BTREE_ID_DIRENTS,
-			   POS(inode->v.i_ino, ctx->pos), 0, k, ret) {
+			   POS(inum, ctx->pos), 0, k, ret) {
 		if (k.k->type != KEY_TYPE_dirent)
 			continue;
 
 		dirent = bkey_s_c_to_dirent(k);
 
-		if (bkey_cmp(k.k->p, POS(inode->v.i_ino, ctx->pos)) < 0)
+		if (bkey_cmp(k.k->p, POS(inum, ctx->pos)) < 0)
 			continue;
 
-		if (k.k->p.inode > inode->v.i_ino)
+		if (k.k->p.inode > inum)
 			break;
 
 		len = bch2_dirent_name_bytes(dirent);
diff --git a/libbcachefs/dirent.h b/libbcachefs/dirent.h
index bc64718a..9a57ad00 100644
--- a/libbcachefs/dirent.h
+++ b/libbcachefs/dirent.h
@@ -29,15 +29,13 @@ static inline unsigned dirent_val_u64s(unsigned len)
 			    sizeof(u64));
 }
 
-int __bch2_dirent_create(struct btree_trans *, u64,
-			 const struct bch_hash_info *, u8,
-			 const struct qstr *, u64, int);
-int bch2_dirent_create(struct bch_fs *c, u64, const struct bch_hash_info *,
-		       u8, const struct qstr *, u64, u64 *, int);
+int bch2_dirent_create(struct btree_trans *, u64,
+		       const struct bch_hash_info *, u8,
+		       const struct qstr *, u64, int);
 
-int __bch2_dirent_delete(struct btree_trans *, u64,
-			 const struct bch_hash_info *,
-			 const struct qstr *);
+int bch2_dirent_delete_at(struct btree_trans *,
+			  const struct bch_hash_info *,
+			  struct btree_iter *);
 int bch2_dirent_delete(struct bch_fs *, u64, const struct bch_hash_info *,
 		       const struct qstr *, u64 *);
 
@@ -48,15 +46,20 @@ enum bch_rename_mode {
 };
 
 int bch2_dirent_rename(struct btree_trans *,
-		       struct bch_inode_info *, const struct qstr *,
-		       struct bch_inode_info *, const struct qstr *,
+		       u64, struct bch_hash_info *,
+		       u64, struct bch_hash_info *,
+		       const struct qstr *, u64 *,
+		       const struct qstr *, u64 *,
 		       enum bch_rename_mode);
 
+struct btree_iter *
+__bch2_dirent_lookup_trans(struct btree_trans *, u64,
+			   const struct bch_hash_info *,
+			   const struct qstr *);
 u64 bch2_dirent_lookup(struct bch_fs *, u64, const struct bch_hash_info *,
 		       const struct qstr *);
 
 int bch2_empty_dir_trans(struct btree_trans *, u64);
-int bch2_empty_dir(struct bch_fs *, u64);
-int bch2_readdir(struct bch_fs *, struct file *, struct dir_context *);
+int bch2_readdir(struct bch_fs *, u64, struct dir_context *);
 
 #endif /* _BCACHEFS_DIRENT_H */
diff --git a/libbcachefs/fs-common.c b/libbcachefs/fs-common.c
new file mode 100644
index 00000000..fdd2b9b6
--- /dev/null
+++ b/libbcachefs/fs-common.c
@@ -0,0 +1,280 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "bcachefs.h"
+#include "acl.h"
+#include "btree_update.h"
+#include "dirent.h"
+#include "fs-common.h"
+#include "inode.h"
+#include "xattr.h"
+
+#include <linux/posix_acl.h>
+
+int bch2_create_trans(struct btree_trans *trans, u64 dir_inum,
+		      struct bch_inode_unpacked *dir_u,
+		      struct bch_inode_unpacked *new_inode,
+		      const struct qstr *name,
+		      uid_t uid, gid_t gid, umode_t mode, dev_t rdev,
+		      struct posix_acl *default_acl,
+		      struct posix_acl *acl)
+{
+	struct bch_fs *c = trans->c;
+	struct btree_iter *dir_iter;
+	struct bch_hash_info hash = bch2_hash_info_init(c, new_inode);
+	u64 now = bch2_current_time(trans->c);
+	int ret;
+
+	dir_iter = bch2_inode_peek(trans, dir_u, dir_inum,
+				   name ? BTREE_ITER_INTENT : 0);
+	if (IS_ERR(dir_iter))
+		return PTR_ERR(dir_iter);
+
+	bch2_inode_init_late(new_inode, now, uid, gid, mode, rdev, dir_u);
+
+	if (!name)
+		new_inode->bi_flags |= BCH_INODE_UNLINKED;
+
+	ret = bch2_inode_create(trans, new_inode,
+				BLOCKDEV_INODE_MAX, 0,
+				&c->unused_inode_hint);
+	if (ret)
+		return ret;
+
+	if (default_acl) {
+		ret = bch2_set_acl_trans(trans, new_inode, &hash,
+					 default_acl, ACL_TYPE_DEFAULT);
+		if (ret)
+			return ret;
+	}
+
+	if (acl) {
+		ret = bch2_set_acl_trans(trans, new_inode, &hash,
+					 acl, ACL_TYPE_ACCESS);
+		if (ret)
+			return ret;
+	}
+
+	if (name) {
+		struct bch_hash_info dir_hash = bch2_hash_info_init(c, dir_u);
+		dir_u->bi_mtime = dir_u->bi_ctime = now;
+
+		if (S_ISDIR(new_inode->bi_mode))
+			dir_u->bi_nlink++;
+
+		ret = bch2_inode_write(trans, dir_iter, dir_u);
+		if (ret)
+			return ret;
+
+		ret = bch2_dirent_create(trans, dir_inum, &dir_hash,
+					 mode_to_type(new_inode->bi_mode),
+					 name, new_inode->bi_inum,
+					 BCH_HASH_SET_MUST_CREATE);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+int bch2_link_trans(struct btree_trans *trans,
+		    u64 dir_inum,
+		    u64 inum, struct bch_inode_unpacked *inode_u,
+		    const struct qstr *name)
+{
+	struct btree_iter *dir_iter, *inode_iter;
+	struct bch_inode_unpacked dir_u;
+	struct bch_hash_info dir_hash;
+	u64 now = bch2_current_time(trans->c);
+
+	dir_iter = bch2_inode_peek(trans, &dir_u, dir_inum, 0);
+	if (IS_ERR(dir_iter))
+		return PTR_ERR(dir_iter);
+
+	inode_iter = bch2_inode_peek(trans, inode_u, inum, BTREE_ITER_INTENT);
+	if (IS_ERR(inode_iter))
+		return PTR_ERR(inode_iter);
+
+	dir_hash = bch2_hash_info_init(trans->c, &dir_u);
+
+	inode_u->bi_ctime = now;
+	bch2_inode_nlink_inc(inode_u);
+
+	return bch2_dirent_create(trans, dir_inum, &dir_hash,
+				  mode_to_type(inode_u->bi_mode),
+				  name, inum, BCH_HASH_SET_MUST_CREATE) ?:
+		bch2_inode_write(trans, inode_iter, inode_u);
+}
+
+int bch2_unlink_trans(struct btree_trans *trans,
+		      u64 dir_inum, struct bch_inode_unpacked *dir_u,
+		      struct bch_inode_unpacked *inode_u,
+		      const struct qstr *name)
+{
+	struct btree_iter *dir_iter, *dirent_iter, *inode_iter;
+	struct bch_hash_info dir_hash;
+	u64 inum, now = bch2_current_time(trans->c);
+	struct bkey_s_c k;
+
+	dir_iter = bch2_inode_peek(trans, dir_u, dir_inum, BTREE_ITER_INTENT);
+	if (IS_ERR(dir_iter))
+		return PTR_ERR(dir_iter);
+
+	dir_hash = bch2_hash_info_init(trans->c, dir_u);
+
+	dirent_iter = __bch2_dirent_lookup_trans(trans, dir_inum,
+						 &dir_hash, name);
+	if (IS_ERR(dirent_iter))
+		return PTR_ERR(dirent_iter);
+
+	k = bch2_btree_iter_peek_slot(dirent_iter);
+	inum = le64_to_cpu(bkey_s_c_to_dirent(k).v->d_inum);
+
+	inode_iter = bch2_inode_peek(trans, inode_u, inum, BTREE_ITER_INTENT);
+	if (IS_ERR(inode_iter))
+		return PTR_ERR(inode_iter);
+
+	dir_u->bi_mtime = dir_u->bi_ctime = inode_u->bi_ctime = now;
+	dir_u->bi_nlink -= S_ISDIR(inode_u->bi_mode);
+	bch2_inode_nlink_dec(inode_u);
+
+	return  (S_ISDIR(inode_u->bi_mode)
+		 ? bch2_empty_dir_trans(trans, inum)
+		 : 0) ?:
+		bch2_dirent_delete_at(trans, &dir_hash, dirent_iter) ?:
+		bch2_inode_write(trans, dir_iter, dir_u) ?:
+		bch2_inode_write(trans, inode_iter, inode_u);
+}
+
+bool bch2_reinherit_attrs(struct bch_inode_unpacked *dst_u,
+			  struct bch_inode_unpacked *src_u)
+{
+	u64 src, dst;
+	unsigned id;
+	bool ret = false;
+
+	for (id = 0; id < Inode_opt_nr; id++) {
+		if (dst_u->bi_fields_set & (1 << id))
+			continue;
+
+		src = bch2_inode_opt_get(src_u, id);
+		dst = bch2_inode_opt_get(dst_u, id);
+
+		if (src == dst)
+			continue;
+
+		bch2_inode_opt_set(dst_u, id, src);
+		ret = true;
+	}
+
+	return ret;
+}
+
+int bch2_rename_trans(struct btree_trans *trans,
+		      u64 src_dir, struct bch_inode_unpacked *src_dir_u,
+		      u64 dst_dir, struct bch_inode_unpacked *dst_dir_u,
+		      struct bch_inode_unpacked *src_inode_u,
+		      struct bch_inode_unpacked *dst_inode_u,
+		      const struct qstr *src_name,
+		      const struct qstr *dst_name,
+		      enum bch_rename_mode mode)
+{
+	struct btree_iter *src_dir_iter, *dst_dir_iter = NULL;
+	struct btree_iter *src_inode_iter, *dst_inode_iter = NULL;
+	struct bch_hash_info src_hash, dst_hash;
+	u64 src_inode, dst_inode, now = bch2_current_time(trans->c);
+	int ret;
+
+	src_dir_iter = bch2_inode_peek(trans, src_dir_u, src_dir,
+				       BTREE_ITER_INTENT);
+	if (IS_ERR(src_dir_iter))
+		return PTR_ERR(src_dir_iter);
+
+	src_hash = bch2_hash_info_init(trans->c, src_dir_u);
+
+	if (dst_dir != src_dir) {
+		dst_dir_iter = bch2_inode_peek(trans, dst_dir_u, dst_dir,
+					       BTREE_ITER_INTENT);
+		if (IS_ERR(dst_dir_iter))
+			return PTR_ERR(dst_dir_iter);
+
+		dst_hash = bch2_hash_info_init(trans->c, dst_dir_u);
+	} else {
+		dst_dir_u = src_dir_u;
+		dst_hash = src_hash;
+	}
+
+	ret = bch2_dirent_rename(trans,
+				 src_dir, &src_hash,
+				 dst_dir, &dst_hash,
+				 src_name, &src_inode,
+				 dst_name, &dst_inode,
+				 mode);
+	if (ret)
+		return ret;
+
+	src_inode_iter = bch2_inode_peek(trans, src_inode_u, src_inode,
+					 BTREE_ITER_INTENT);
+	if (IS_ERR(src_inode_iter))
+		return PTR_ERR(src_inode_iter);
+
+	if (dst_inode) {
+		dst_inode_iter = bch2_inode_peek(trans, dst_inode_u, dst_inode,
+						 BTREE_ITER_INTENT);
+		if (IS_ERR(dst_inode_iter))
+			return PTR_ERR(dst_inode_iter);
+	}
+
+	if (mode == BCH_RENAME_OVERWRITE) {
+		if (S_ISDIR(src_inode_u->bi_mode) !=
+		    S_ISDIR(dst_inode_u->bi_mode))
+			return -ENOTDIR;
+
+		if (S_ISDIR(dst_inode_u->bi_mode) &&
+		    bch2_empty_dir_trans(trans, dst_inode))
+			return -ENOTEMPTY;
+	}
+
+	if (bch2_reinherit_attrs(src_inode_u, dst_dir_u) &&
+	    S_ISDIR(src_inode_u->bi_mode))
+		return -EXDEV;
+
+	if (mode == BCH_RENAME_EXCHANGE &&
+	    bch2_reinherit_attrs(dst_inode_u, src_dir_u) &&
+	    S_ISDIR(dst_inode_u->bi_mode))
+		return -EXDEV;
+
+	if (S_ISDIR(src_inode_u->bi_mode)) {
+		src_dir_u->bi_nlink--;
+		dst_dir_u->bi_nlink++;
+	}
+
+	if (dst_inode && S_ISDIR(dst_inode_u->bi_mode)) {
+		dst_dir_u->bi_nlink--;
+		src_dir_u->bi_nlink += mode == BCH_RENAME_EXCHANGE;
+	}
+
+	if (mode == BCH_RENAME_OVERWRITE)
+		bch2_inode_nlink_dec(dst_inode_u);
+
+	src_dir_u->bi_mtime		= now;
+	src_dir_u->bi_ctime		= now;
+
+	if (src_dir != dst_dir) {
+		dst_dir_u->bi_mtime	= now;
+		dst_dir_u->bi_ctime	= now;
+	}
+
+	src_inode_u->bi_ctime		= now;
+
+	if (dst_inode)
+		dst_inode_u->bi_ctime	= now;
+
+	return  bch2_inode_write(trans, src_dir_iter, src_dir_u) ?:
+		(src_dir != dst_dir
+		 ? bch2_inode_write(trans, dst_dir_iter, dst_dir_u)
+		 : 0 ) ?:
+		bch2_inode_write(trans, src_inode_iter, src_inode_u) ?:
+		(dst_inode
+		 ? bch2_inode_write(trans, dst_inode_iter, dst_inode_u)
+		 : 0 );
+}
diff --git a/libbcachefs/fs-common.h b/libbcachefs/fs-common.h
new file mode 100644
index 00000000..7adcfcf9
--- /dev/null
+++ b/libbcachefs/fs-common.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _BCACHEFS_FS_COMMON_H
+#define _BCACHEFS_FS_COMMON_H
+
+struct posix_acl;
+
+int bch2_create_trans(struct btree_trans *, u64,
+		      struct bch_inode_unpacked *,
+		      struct bch_inode_unpacked *,
+		      const struct qstr *,
+		      uid_t, gid_t, umode_t, dev_t,
+		      struct posix_acl *,
+		      struct posix_acl *);
+
+int bch2_link_trans(struct btree_trans *,
+		    u64,
+		    u64, struct bch_inode_unpacked *,
+		    const struct qstr *);
+
+int bch2_unlink_trans(struct btree_trans *,
+		      u64, struct bch_inode_unpacked *,
+		      struct bch_inode_unpacked *,
+		      const struct qstr *);
+
+int bch2_rename_trans(struct btree_trans *,
+		      u64, struct bch_inode_unpacked *,
+		      u64, struct bch_inode_unpacked *,
+		      struct bch_inode_unpacked *,
+		      struct bch_inode_unpacked *,
+		      const struct qstr *,
+		      const struct qstr *,
+		      enum bch_rename_mode);
+
+bool bch2_reinherit_attrs(struct bch_inode_unpacked *,
+			  struct bch_inode_unpacked *);
+
+#endif /* _BCACHEFS_FS_COMMON_H */
diff --git a/libbcachefs/fs-ioctl.c b/libbcachefs/fs-ioctl.c
index e80576f5..031e6d93 100644
--- a/libbcachefs/fs-ioctl.c
+++ b/libbcachefs/fs-ioctl.c
@@ -5,6 +5,7 @@
 #include "chardev.h"
 #include "dirent.h"
 #include "fs.h"
+#include "fs-common.h"
 #include "fs-ioctl.h"
 #include "quota.h"
 
@@ -164,6 +165,15 @@ err:
 	return ret;
 }
 
+static int bch2_reinherit_attrs_fn(struct bch_inode_info *inode,
+				   struct bch_inode_unpacked *bi,
+				   void *p)
+{
+	struct bch_inode_info *dir = p;
+
+	return !bch2_reinherit_attrs(bi, &dir->ei_inode);
+}
+
 static int bch2_ioc_reinherit_attrs(struct bch_fs *c,
 				    struct file *file,
 				    struct bch_inode_info *src,
diff --git a/libbcachefs/fs.c b/libbcachefs/fs.c
index aa92066d..1a52a750 100644
--- a/libbcachefs/fs.c
+++ b/libbcachefs/fs.c
@@ -9,6 +9,7 @@
 #include "dirent.h"
 #include "extents.h"
 #include "fs.h"
+#include "fs-common.h"
 #include "fs-io.h"
 #include "fs-ioctl.h"
 #include "fsck.h"
@@ -98,34 +99,13 @@ void bch2_inode_update_after_write(struct bch_fs *c,
 	bch2_inode_flags_to_vfs(inode);
 }
 
-int __must_check bch2_write_inode_trans(struct btree_trans *trans,
-				struct bch_inode_info *inode,
-				struct bch_inode_unpacked *inode_u,
-				inode_set_fn set,
-				void *p)
-{
-	struct btree_iter *iter = NULL;
-	int ret = 0;
-
-	iter = bch2_inode_peek(trans, inode_u, inode->v.i_ino,
-			       BTREE_ITER_INTENT);
-	ret = PTR_ERR_OR_ZERO(iter);
-	if (ret)
-		return ret;
-
-	ret = set ? set(inode, inode_u, p) : 0;
-	if (ret)
-		return ret;
-
-	return bch2_inode_write(trans, iter, inode_u);
-}
-
 int __must_check bch2_write_inode(struct bch_fs *c,
 				  struct bch_inode_info *inode,
 				  inode_set_fn set,
 				  void *p, unsigned fields)
 {
 	struct btree_trans trans;
+	struct btree_iter *iter;
 	struct bch_inode_unpacked inode_u;
 	int ret;
 
@@ -133,7 +113,11 @@ int __must_check bch2_write_inode(struct bch_fs *c,
 retry:
 	bch2_trans_begin(&trans);
 
-	ret = bch2_write_inode_trans(&trans, inode, &inode_u, set, p) ?:
+	iter = bch2_inode_peek(&trans, &inode_u, inode->v.i_ino,
+			       BTREE_ITER_INTENT);
+	ret   = PTR_ERR_OR_ZERO(iter) ?:
+		(set ? set(inode, &inode_u, p) : 0) ?:
+		bch2_inode_write(&trans, iter, &inode_u) ?:
 		bch2_trans_commit(&trans, NULL,
 				  &inode->ei_journal_seq,
 				  BTREE_INSERT_ATOMIC|
@@ -188,32 +172,6 @@ int bch2_fs_quota_transfer(struct bch_fs *c,
 	return ret;
 }
 
-int bch2_reinherit_attrs_fn(struct bch_inode_info *inode,
-			    struct bch_inode_unpacked *bi,
-			    void *p)
-{
-	struct bch_inode_info *dir = p;
-	u64 src, dst;
-	unsigned id;
-	int ret = 1;
-
-	for (id = 0; id < Inode_opt_nr; id++) {
-		if (bi->bi_fields_set & (1 << id))
-			continue;
-
-		src = bch2_inode_opt_get(&dir->ei_inode, id);
-		dst = bch2_inode_opt_get(bi, id);
-
-		if (src == dst)
-			continue;
-
-		bch2_inode_opt_set(bi, id, src);
-		ret = 0;
-	}
-
-	return ret;
-}
-
 struct inode *bch2_vfs_inode_get(struct bch_fs *c, u64 inum)
 {
 	struct bch_inode_unpacked inode_u;
@@ -241,82 +199,37 @@ struct inode *bch2_vfs_inode_get(struct bch_fs *c, u64 inum)
 	return &inode->v;
 }
 
-static void bch2_inode_init_owner(struct bch_inode_unpacked *inode_u,
-				  const struct inode *dir, umode_t mode)
-{
-	kuid_t uid = current_fsuid();
-	kgid_t gid;
-
-	if (dir && dir->i_mode & S_ISGID) {
-		gid = dir->i_gid;
-		if (S_ISDIR(mode))
-			mode |= S_ISGID;
-	} else
-		gid = current_fsgid();
-
-	inode_u->bi_uid		= from_kuid(dir->i_sb->s_user_ns, uid);
-	inode_u->bi_gid		= from_kgid(dir->i_sb->s_user_ns, gid);
-	inode_u->bi_mode	= mode;
-}
-
-static int inode_update_for_create_fn(struct bch_inode_info *inode,
-				      struct bch_inode_unpacked *bi,
-				      void *p)
-{
-	struct bch_fs *c = inode->v.i_sb->s_fs_info;
-	struct bch_inode_unpacked *new_inode = p;
-
-	bi->bi_mtime = bi->bi_ctime = bch2_current_time(c);
-
-	if (S_ISDIR(new_inode->bi_mode))
-		bi->bi_nlink++;
-
-	return 0;
-}
-
 static struct bch_inode_info *
 __bch2_create(struct bch_inode_info *dir, struct dentry *dentry,
 	      umode_t mode, dev_t rdev, bool tmpfile)
 {
 	struct bch_fs *c = dir->v.i_sb->s_fs_info;
+	struct user_namespace *ns = dir->v.i_sb->s_user_ns;
 	struct btree_trans trans;
 	struct bch_inode_unpacked dir_u;
 	struct bch_inode_info *inode, *old;
 	struct bch_inode_unpacked inode_u;
-	struct bch_hash_info hash_info;
 	struct posix_acl *default_acl = NULL, *acl = NULL;
 	u64 journal_seq = 0;
 	int ret;
 
-	bch2_inode_init(c, &inode_u, 0, 0, 0, rdev, &dir->ei_inode);
-	bch2_inode_init_owner(&inode_u, &dir->v, mode);
-
-	hash_info = bch2_hash_info_init(c, &inode_u);
-
-	if (tmpfile)
-		inode_u.bi_flags |= BCH_INODE_UNLINKED;
-
-	ret = bch2_quota_acct(c, bch_qid(&inode_u), Q_INO, 1,
-			      KEY_TYPE_QUOTA_PREALLOC);
+	/*
+	 * preallocate acls + vfs inode before btree transaction, so that
+	 * nothing can fail after the transaction succeeds:
+	 */
+#ifdef CONFIG_BCACHEFS_POSIX_ACL
+	ret = posix_acl_create(&dir->v, &mode, &default_acl, &acl);
 	if (ret)
 		return ERR_PTR(ret);
-
-#ifdef CONFIG_BCACHEFS_POSIX_ACL
-	ret = posix_acl_create(&dir->v, &inode_u.bi_mode, &default_acl, &acl);
-	if (ret)
-		goto err;
 #endif
-
-	/*
-	 * preallocate vfs inode before btree transaction, so that nothing can
-	 * fail after the transaction succeeds:
-	 */
 	inode = to_bch_ei(new_inode(c->vfs_sb));
 	if (unlikely(!inode)) {
-		ret = -ENOMEM;
+		inode = ERR_PTR(-ENOMEM);
 		goto err;
 	}
 
+	bch2_inode_init_early(c, &inode_u);
+
 	if (!tmpfile)
 		mutex_lock(&dir->ei_update_lock);
 
@@ -324,38 +237,28 @@ __bch2_create(struct bch_inode_info *dir, struct dentry *dentry,
 retry:
 	bch2_trans_begin(&trans);
 
-	ret   = __bch2_inode_create(&trans, &inode_u,
-				    BLOCKDEV_INODE_MAX, 0,
-				    &c->unused_inode_hint) ?:
-		(default_acl
-		 ? bch2_set_acl_trans(&trans, &inode_u, &hash_info,
-				      default_acl, ACL_TYPE_DEFAULT)
-		 : 0) ?:
-		(acl
-		 ? bch2_set_acl_trans(&trans, &inode_u, &hash_info,
-				      acl, ACL_TYPE_ACCESS)
-		 : 0) ?:
-		(!tmpfile
-		 ? __bch2_dirent_create(&trans, dir->v.i_ino,
-					&dir->ei_str_hash,
-					mode_to_type(mode),
-					&dentry->d_name,
-					inode_u.bi_inum,
-					BCH_HASH_SET_MUST_CREATE)
-		: 0) ?:
-		(!tmpfile
-		 ? bch2_write_inode_trans(&trans, dir, &dir_u,
-					  inode_update_for_create_fn,
-					  &inode_u)
-		 : 0) ?:
-		bch2_trans_commit(&trans, NULL,
-				  &journal_seq,
+	ret   = bch2_create_trans(&trans, dir->v.i_ino, &dir_u, &inode_u,
+				  !tmpfile ? &dentry->d_name : NULL,
+				  from_kuid(ns, current_fsuid()),
+				  from_kgid(ns, current_fsgid()),
+				  mode, rdev,
+				  default_acl, acl) ?:
+		bch2_quota_acct(c, bch_qid(&inode_u), Q_INO, 1,
+				KEY_TYPE_QUOTA_PREALLOC);
+	if (unlikely(ret))
+		goto err_before_quota;
+
+	ret   = bch2_trans_commit(&trans, NULL, &journal_seq,
 				  BTREE_INSERT_ATOMIC|
 				  BTREE_INSERT_NOUNLOCK);
-	if (ret == -EINTR)
-		goto retry;
-	if (unlikely(ret))
+	if (unlikely(ret)) {
+		bch2_quota_acct(c, bch_qid(&inode_u), Q_INO, -1,
+				KEY_TYPE_QUOTA_WARN);
+err_before_quota:
+		if (ret == -EINTR)
+			goto retry;
 		goto err_trans;
+	}
 
 	if (!tmpfile) {
 		bch2_inode_update_after_write(c, dir, &dir_u,
@@ -382,7 +285,7 @@ retry:
 		 * We raced, another process pulled the new inode into cache
 		 * before us:
 		 */
-		old->ei_journal_seq = inode->ei_journal_seq;
+		journal_seq_copy(old, journal_seq);
 		make_bad_inode(&inode->v);
 		iput(&inode->v);
 
@@ -396,7 +299,7 @@ retry:
 	}
 
 	bch2_trans_exit(&trans);
-out:
+err:
 	posix_acl_release(default_acl);
 	posix_acl_release(acl);
 	return inode;
@@ -407,10 +310,8 @@ err_trans:
 	bch2_trans_exit(&trans);
 	make_bad_inode(&inode->v);
 	iput(&inode->v);
-err:
-	bch2_quota_acct(c, bch_qid(&inode_u), Q_INO, -1, KEY_TYPE_QUOTA_WARN);
 	inode = ERR_PTR(ret);
-	goto out;
+	goto err;
 }
 
 /* methods */
@@ -452,40 +353,23 @@ static int __bch2_link(struct bch_fs *c,
 		       struct dentry *dentry)
 {
 	struct btree_trans trans;
-	struct btree_iter *inode_iter;
 	struct bch_inode_unpacked inode_u;
 	int ret;
 
 	mutex_lock(&inode->ei_update_lock);
 	bch2_trans_init(&trans, c, 4, 1024);
-retry:
-	bch2_trans_begin(&trans);
-	ret   = __bch2_dirent_create(&trans, dir->v.i_ino,
-				     &dir->ei_str_hash,
-				     mode_to_type(inode->v.i_mode),
-				     &dentry->d_name,
-				     inode->v.i_ino,
-				     BCH_HASH_SET_MUST_CREATE);
-	if (ret)
-		goto err;
 
-	inode_iter = bch2_inode_peek(&trans, &inode_u, inode->v.i_ino,
-				     BTREE_ITER_INTENT);
-	ret = PTR_ERR_OR_ZERO(inode_iter);
-	if (ret)
-		goto err;
-
-	inode_u.bi_ctime = bch2_current_time(c);
-	bch2_inode_nlink_inc(&inode_u);
-
-	ret =   bch2_inode_write(&trans, inode_iter, &inode_u) ?:
-		bch2_trans_commit(&trans, NULL,
-				&inode->ei_journal_seq,
-				BTREE_INSERT_ATOMIC|
-				BTREE_INSERT_NOUNLOCK);
-err:
-	if (ret == -EINTR)
-		goto retry;
+	do {
+		bch2_trans_begin(&trans);
+		ret   = bch2_link_trans(&trans,
+					dir->v.i_ino,
+					inode->v.i_ino, &inode_u,
+					&dentry->d_name) ?:
+			bch2_trans_commit(&trans, NULL,
+					&inode->ei_journal_seq,
+					BTREE_INSERT_ATOMIC|
+					BTREE_INSERT_NOUNLOCK);
+	} while (ret == -EINTR);
 
 	if (likely(!ret))
 		bch2_inode_update_after_write(c, inode, &inode_u, ATTR_CTIME);
@@ -519,60 +403,36 @@ static int bch2_unlink(struct inode *vdir, struct dentry *dentry)
 	struct bch_fs *c = vdir->i_sb->s_fs_info;
 	struct bch_inode_info *dir = to_bch_ei(vdir);
 	struct bch_inode_info *inode = to_bch_ei(dentry->d_inode);
-	struct btree_iter *dir_iter, *inode_iter;
 	struct bch_inode_unpacked dir_u, inode_u;
 	struct btree_trans trans;
 	int ret;
 
 	bch2_lock_inodes(INODE_UPDATE_LOCK, dir, inode);
 	bch2_trans_init(&trans, c, 4, 1024);
-retry:
-	bch2_trans_begin(&trans);
 
-	ret   = __bch2_dirent_delete(&trans, dir->v.i_ino,
-				     &dir->ei_str_hash,
-				     &dentry->d_name);
-	if (ret)
-		goto btree_err;
+	do {
+		bch2_trans_begin(&trans);
 
-	dir_iter = bch2_inode_peek(&trans, &dir_u, dir->v.i_ino,
-				   BTREE_ITER_INTENT);
-	ret = PTR_ERR_OR_ZERO(dir_iter);
-	if (ret)
-		goto btree_err;
+		ret   = bch2_unlink_trans(&trans,
+					  dir->v.i_ino, &dir_u,
+					  &inode_u, &dentry->d_name) ?:
+			bch2_trans_commit(&trans, NULL,
+					  &dir->ei_journal_seq,
+					  BTREE_INSERT_ATOMIC|
+					  BTREE_INSERT_NOUNLOCK|
+					  BTREE_INSERT_NOFAIL);
+	} while (ret == -EINTR);
 
-	inode_iter = bch2_inode_peek(&trans, &inode_u, inode->v.i_ino,
-				     BTREE_ITER_INTENT);
-	ret = PTR_ERR_OR_ZERO(inode_iter);
-	if (ret)
-		goto btree_err;
+	if (likely(!ret)) {
+		BUG_ON(inode_u.bi_inum != inode->v.i_ino);
 
-	dir_u.bi_mtime = dir_u.bi_ctime = inode_u.bi_ctime =
-		bch2_current_time(c);
+		journal_seq_copy(inode, dir->ei_journal_seq);
+		bch2_inode_update_after_write(c, dir, &dir_u,
+					      ATTR_MTIME|ATTR_CTIME);
+		bch2_inode_update_after_write(c, inode, &inode_u,
+					      ATTR_MTIME);
+	}
 
-	dir_u.bi_nlink -= S_ISDIR(inode_u.bi_mode);
-	bch2_inode_nlink_dec(&inode_u);
-
-	ret =   bch2_inode_write(&trans, dir_iter, &dir_u) ?:
-		bch2_inode_write(&trans, inode_iter, &inode_u) ?:
-		bch2_trans_commit(&trans, NULL,
-				  &dir->ei_journal_seq,
-				  BTREE_INSERT_ATOMIC|
-				  BTREE_INSERT_NOUNLOCK|
-				  BTREE_INSERT_NOFAIL);
-btree_err:
-	if (ret == -EINTR)
-		goto retry;
-	if (ret)
-		goto err;
-
-	journal_seq_copy(inode, dir->ei_journal_seq);
-
-	bch2_inode_update_after_write(c, dir, &dir_u,
-				      ATTR_MTIME|ATTR_CTIME);
-	bch2_inode_update_after_write(c, inode, &inode_u,
-				      ATTR_MTIME);
-err:
 	bch2_trans_exit(&trans);
 	bch2_unlock_inodes(INODE_UPDATE_LOCK, dir, inode);
 
@@ -628,11 +488,6 @@ static int bch2_mkdir(struct inode *vdir, struct dentry *dentry, umode_t mode)
 
 static int bch2_rmdir(struct inode *vdir, struct dentry *dentry)
 {
-	struct bch_fs *c = vdir->i_sb->s_fs_info;
-
-	if (bch2_empty_dir(c, dentry->d_inode->i_ino))
-		return -ENOTEMPTY;
-
 	return bch2_unlink(vdir, dentry);
 }
 
@@ -649,98 +504,30 @@ static int bch2_mknod(struct inode *vdir, struct dentry *dentry,
 	return 0;
 }
 
-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;
-};
-
-static int inode_update_for_rename_fn(struct bch_inode_info *inode,
-				      struct bch_inode_unpacked *bi,
-				      void *p)
-{
-	struct rename_info *info = p;
-	int ret;
-
-	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;
-	}
-
-	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);
-	}
-
-	if (inode == info->src_inode) {
-		ret = bch2_reinherit_attrs_fn(inode, bi, info->dst_dir);
-
-		BUG_ON(!ret && S_ISDIR(info->src_inode->v.i_mode));
-	}
-
-	if (inode == info->dst_inode &&
-	    info->mode == BCH_RENAME_EXCHANGE) {
-		ret = bch2_reinherit_attrs_fn(inode, bi, info->src_dir);
-
-		BUG_ON(!ret && S_ISDIR(info->dst_inode->v.i_mode));
-	}
-
-	if (inode == info->dst_inode &&
-	    info->mode == BCH_RENAME_OVERWRITE) {
-		BUG_ON(bi->bi_nlink &&
-		       S_ISDIR(info->dst_inode->v.i_mode));
-
-		bch2_inode_nlink_dec(bi);
-	}
-
-	if (inode == info->src_dir ||
-	    inode == info->dst_dir)
-		bi->bi_mtime = info->now;
-	bi->bi_ctime = info->now;
-
-	return 0;
-}
-
 static int bch2_rename2(struct inode *src_vdir, struct dentry *src_dentry,
 			struct inode *dst_vdir, struct dentry *dst_dentry,
 			unsigned flags)
 {
 	struct bch_fs *c = src_vdir->i_sb->s_fs_info;
-	struct rename_info i = {
-		.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_info *src_dir = to_bch_ei(src_vdir);
+	struct bch_inode_info *dst_dir = to_bch_ei(dst_vdir);
+	struct bch_inode_info *src_inode = to_bch_ei(src_dentry->d_inode);
+	struct bch_inode_info *dst_inode = to_bch_ei(dst_dentry->d_inode);
 	struct bch_inode_unpacked dst_dir_u, src_dir_u;
 	struct bch_inode_unpacked src_inode_u, dst_inode_u;
+	struct btree_trans trans;
+	enum bch_rename_mode mode = flags & RENAME_EXCHANGE
+		? BCH_RENAME_EXCHANGE
+		: dst_dentry->d_inode
+		? BCH_RENAME_OVERWRITE : BCH_RENAME;
 	u64 journal_seq = 0;
 	int ret;
 
 	if (flags & ~(RENAME_NOREPLACE|RENAME_EXCHANGE))
 		return -EINVAL;
 
-	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;
-
-		if (S_ISDIR(i.src_inode->v.i_mode) &&
-		    bch2_empty_dir(c, i.dst_inode->v.i_ino))
-			return -ENOTEMPTY;
-
-		ret = filemap_write_and_wait_range(i.src_inode->v.i_mapping,
+	if (mode == BCH_RENAME_OVERWRITE) {
+		ret = filemap_write_and_wait_range(src_inode->v.i_mapping,
 						   0, LLONG_MAX);
 		if (ret)
 			return ret;
@@ -749,37 +536,24 @@ static int bch2_rename2(struct inode *src_vdir, struct dentry *src_dentry,
 	bch2_trans_init(&trans, c, 8, 2048);
 
 	bch2_lock_inodes(INODE_UPDATE_LOCK,
-			 i.src_dir,
-			 i.dst_dir,
-			 i.src_inode,
-			 i.dst_inode);
+			 src_dir,
+			 dst_dir,
+			 src_inode,
+			 dst_inode);
 
-	if (S_ISDIR(i.src_inode->v.i_mode) &&
-	    inode_attrs_changing(i.dst_dir, i.src_inode)) {
-		ret = -EXDEV;
-		goto err;
-	}
-
-	if (i.mode == BCH_RENAME_EXCHANGE &&
-	    S_ISDIR(i.dst_inode->v.i_mode) &&
-	    inode_attrs_changing(i.src_dir, i.dst_inode)) {
-		ret = -EXDEV;
-		goto err;
-	}
-
-	if (inode_attr_changing(i.dst_dir, i.src_inode, Inode_opt_project)) {
-		ret = bch2_fs_quota_transfer(c, i.src_inode,
-					     i.dst_dir->ei_qid,
+	if (inode_attr_changing(dst_dir, src_inode, Inode_opt_project)) {
+		ret = bch2_fs_quota_transfer(c, src_inode,
+					     dst_dir->ei_qid,
 					     1 << QTYP_PRJ,
 					     KEY_TYPE_QUOTA_PREALLOC);
 		if (ret)
 			goto err;
 	}
 
-	if (i.mode == BCH_RENAME_EXCHANGE &&
-	    inode_attr_changing(i.src_dir, i.dst_inode, Inode_opt_project)) {
-		ret = bch2_fs_quota_transfer(c, i.dst_inode,
-					     i.src_dir->ei_qid,
+	if (mode == BCH_RENAME_EXCHANGE &&
+	    inode_attr_changing(src_dir, dst_inode, Inode_opt_project)) {
+		ret = bch2_fs_quota_transfer(c, dst_inode,
+					     src_dir->ei_qid,
 					     1 << QTYP_PRJ,
 					     KEY_TYPE_QUOTA_PREALLOC);
 		if (ret)
@@ -788,24 +562,14 @@ static int bch2_rename2(struct inode *src_vdir, struct dentry *src_dentry,
 
 retry:
 	bch2_trans_begin(&trans);
-	i.now = bch2_current_time(c);
-
-	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 ) ?:
+	ret   = bch2_rename_trans(&trans,
+				  src_dir->v.i_ino, &src_dir_u,
+				  dst_dir->v.i_ino, &dst_dir_u,
+				  &src_inode_u,
+				  &dst_inode_u,
+				  &src_dentry->d_name,
+				  &dst_dentry->d_name,
+				  mode) ?:
 		bch2_trans_commit(&trans, NULL,
 				  &journal_seq,
 				  BTREE_INSERT_ATOMIC|
@@ -815,43 +579,47 @@ retry:
 	if (unlikely(ret))
 		goto err;
 
-	bch2_inode_update_after_write(c, i.src_dir, &src_dir_u,
-				      ATTR_MTIME|ATTR_CTIME);
-	journal_seq_copy(i.src_dir, journal_seq);
+	BUG_ON(src_inode->v.i_ino != src_inode_u.bi_inum);
+	BUG_ON(dst_inode &&
+	       dst_inode->v.i_ino != dst_inode_u.bi_inum);
 
-	if (i.src_dir != i.dst_dir) {
-		bch2_inode_update_after_write(c, i.dst_dir, &dst_dir_u,
+	bch2_inode_update_after_write(c, src_dir, &src_dir_u,
+				      ATTR_MTIME|ATTR_CTIME);
+	journal_seq_copy(src_dir, journal_seq);
+
+	if (src_dir != dst_dir) {
+		bch2_inode_update_after_write(c, dst_dir, &dst_dir_u,
 					      ATTR_MTIME|ATTR_CTIME);
-		journal_seq_copy(i.dst_dir, journal_seq);
+		journal_seq_copy(dst_dir, journal_seq);
 	}
 
-	journal_seq_copy(i.src_inode, journal_seq);
-	if (i.dst_inode)
-		journal_seq_copy(i.dst_inode, journal_seq);
-
-	bch2_inode_update_after_write(c, i.src_inode, &src_inode_u,
+	bch2_inode_update_after_write(c, src_inode, &src_inode_u,
 				      ATTR_CTIME);
-	if (i.dst_inode)
-		bch2_inode_update_after_write(c, i.dst_inode, &dst_inode_u,
+	journal_seq_copy(src_inode, journal_seq);
+
+	if (dst_inode) {
+		bch2_inode_update_after_write(c, dst_inode, &dst_inode_u,
 					      ATTR_CTIME);
+		journal_seq_copy(dst_inode, journal_seq);
+	}
 err:
 	bch2_trans_exit(&trans);
 
-	bch2_fs_quota_transfer(c, i.src_inode,
-			       bch_qid(&i.src_inode->ei_inode),
+	bch2_fs_quota_transfer(c, src_inode,
+			       bch_qid(&src_inode->ei_inode),
 			       1 << QTYP_PRJ,
 			       KEY_TYPE_QUOTA_NOCHECK);
-	if (i.dst_inode)
-		bch2_fs_quota_transfer(c, i.dst_inode,
-				       bch_qid(&i.dst_inode->ei_inode),
+	if (dst_inode)
+		bch2_fs_quota_transfer(c, dst_inode,
+				       bch_qid(&dst_inode->ei_inode),
 				       1 << QTYP_PRJ,
 				       KEY_TYPE_QUOTA_NOCHECK);
 
 	bch2_unlock_inodes(INODE_UPDATE_LOCK,
-			   i.src_dir,
-			   i.dst_dir,
-			   i.src_inode,
-			   i.dst_inode);
+			   src_dir,
+			   dst_dir,
+			   src_inode,
+			   dst_inode);
 
 	return ret;
 }
@@ -1174,9 +942,13 @@ static loff_t bch2_dir_llseek(struct file *file, loff_t offset, int whence)
 
 static int bch2_vfs_readdir(struct file *file, struct dir_context *ctx)
 {
-	struct bch_fs *c = file_inode(file)->i_sb->s_fs_info;
+	struct bch_inode_info *inode = file_bch_inode(file);
+	struct bch_fs *c = inode->v.i_sb->s_fs_info;
 
-	return bch2_readdir(c, file, ctx);
+	if (!dir_emit_dots(file, ctx))
+		return 0;
+
+	return bch2_readdir(c, inode->v.i_ino, ctx);
 }
 
 static const struct file_operations bch_file_operations = {
diff --git a/libbcachefs/fs.h b/libbcachefs/fs.h
index 6a9f7242..40605666 100644
--- a/libbcachefs/fs.h
+++ b/libbcachefs/fs.h
@@ -77,11 +77,6 @@ static inline struct bch_inode_info *file_bch_inode(struct file *file)
 	return to_bch_ei(file_inode(file));
 }
 
-static inline u8 mode_to_type(umode_t mode)
-{
-	return (mode >> 12) & 15;
-}
-
 static inline bool inode_attr_changing(struct bch_inode_info *dir,
 				struct bch_inode_info *inode,
 				enum inode_opt_id id)
@@ -136,17 +131,9 @@ void bch2_inode_update_after_write(struct bch_fs *,
 				   struct bch_inode_info *,
 				   struct bch_inode_unpacked *,
 				   unsigned);
-int __must_check bch2_write_inode_trans(struct btree_trans *,
-				struct bch_inode_info *,
-				struct bch_inode_unpacked *,
-				inode_set_fn, void *);
 int __must_check bch2_write_inode(struct bch_fs *, struct bch_inode_info *,
 				  inode_set_fn, void *, unsigned);
 
-int bch2_reinherit_attrs_fn(struct bch_inode_info *,
-			    struct bch_inode_unpacked *,
-			    void *);
-
 void bch2_vfs_exit(void);
 int bch2_vfs_init(void);
 
diff --git a/libbcachefs/fsck.c b/libbcachefs/fsck.c
index c5540536..5acf1fb6 100644
--- a/libbcachefs/fsck.c
+++ b/libbcachefs/fsck.c
@@ -4,7 +4,7 @@
 #include "btree_update.h"
 #include "dirent.h"
 #include "error.h"
-#include "fs.h"
+#include "fs-common.h"
 #include "fsck.h"
 #include "inode.h"
 #include "keylist.h"
@@ -80,9 +80,7 @@ static int reattach_inode(struct bch_fs *c,
 			  struct bch_inode_unpacked *lostfound_inode,
 			  u64 inum)
 {
-	struct bch_hash_info lostfound_hash_info =
-		bch2_hash_info_init(c, lostfound_inode);
-	struct bkey_inode_buf packed;
+	struct bch_inode_unpacked inode_u;
 	char name_buf[20];
 	struct qstr name;
 	int ret;
@@ -90,30 +88,14 @@ static int reattach_inode(struct bch_fs *c,
 	snprintf(name_buf, sizeof(name_buf), "%llu", inum);
 	name = (struct qstr) QSTR(name_buf);
 
-	lostfound_inode->bi_nlink++;
+	ret = bch2_trans_do(c, NULL,
+			    BTREE_INSERT_ATOMIC|
+			    BTREE_INSERT_LAZY_RW,
+		bch2_link_trans(&trans, lostfound_inode->bi_inum,
+				inum, &inode_u, &name));
+	if (ret)
+		bch_err(c, "error %i reattaching inode %llu", ret, inum);
 
-	bch2_inode_pack(&packed, lostfound_inode);
-
-	ret = bch2_btree_insert(c, BTREE_ID_INODES, &packed.inode.k_i,
-				NULL, NULL,
-				BTREE_INSERT_NOFAIL|
-				BTREE_INSERT_LAZY_RW);
-	if (ret) {
-		bch_err(c, "error %i reattaching inode %llu while updating lost+found",
-			ret, inum);
-		return ret;
-	}
-
-	ret = bch2_dirent_create(c, lostfound_inode->bi_inum,
-				 &lostfound_hash_info,
-				 DT_DIR, &name, inum, NULL,
-				 BTREE_INSERT_NOFAIL|
-				 BTREE_INSERT_LAZY_RW);
-	if (ret) {
-		bch_err(c, "error %i reattaching inode %llu while creating new dirent",
-			ret, inum);
-		return ret;
-	}
 	return ret;
 }
 
@@ -758,7 +740,7 @@ static int check_root(struct bch_fs *c, struct bch_inode_unpacked *root_inode)
 fsck_err:
 	return ret;
 create_root:
-	bch2_inode_init(c, root_inode, 0, 0, S_IFDIR|S_IRWXU|S_IRUGO|S_IXUGO,
+	bch2_inode_init(c, root_inode, 0, 0, S_IFDIR|0755,
 			0, NULL);
 	root_inode->bi_inum = BCACHEFS_ROOT_INO;
 
@@ -778,7 +760,6 @@ static int check_lostfound(struct bch_fs *c,
 	struct qstr lostfound = QSTR("lost+found");
 	struct bch_hash_info root_hash_info =
 		bch2_hash_info_init(c, root_inode);
-	struct bkey_inode_buf packed;
 	u64 inum;
 	int ret;
 
@@ -806,33 +787,20 @@ static int check_lostfound(struct bch_fs *c,
 fsck_err:
 	return ret;
 create_lostfound:
-	root_inode->bi_nlink++;
+	bch2_inode_init_early(c, lostfound_inode);
 
-	bch2_inode_pack(&packed, root_inode);
-
-	ret = bch2_btree_insert(c, BTREE_ID_INODES, &packed.inode.k_i,
-				NULL, NULL,
-				BTREE_INSERT_NOFAIL|
-				BTREE_INSERT_LAZY_RW);
+	ret = bch2_trans_do(c, NULL,
+			    BTREE_INSERT_ATOMIC|
+			    BTREE_INSERT_NOFAIL|
+			    BTREE_INSERT_LAZY_RW,
+		bch2_create_trans(&trans,
+				  BCACHEFS_ROOT_INO, root_inode,
+				  lostfound_inode, &lostfound,
+				  0, 0, S_IFDIR|0755, 0, NULL, NULL));
 	if (ret)
-		return ret;
+		bch_err(c, "error creating lost+found: %i", ret);
 
-	bch2_inode_init(c, lostfound_inode, 0, 0, S_IFDIR|S_IRWXU|S_IRUGO|S_IXUGO,
-			0, root_inode);
-
-	ret = bch2_inode_create(c, lostfound_inode, BLOCKDEV_INODE_MAX, 0,
-			       &c->unused_inode_hint);
-	if (ret)
-		return ret;
-
-	ret = bch2_dirent_create(c, BCACHEFS_ROOT_INO, &root_hash_info, DT_DIR,
-				 &lostfound, lostfound_inode->bi_inum, NULL,
-				 BTREE_INSERT_NOFAIL|
-				 BTREE_INSERT_LAZY_RW);
-	if (ret)
-		return ret;
-
-	return 0;
+	return ret;
 }
 
 struct inode_bitmap {
diff --git a/libbcachefs/inode.c b/libbcachefs/inode.c
index 09b2444c..e953b78f 100644
--- a/libbcachefs/inode.c
+++ b/libbcachefs/inode.c
@@ -297,11 +297,9 @@ void bch2_inode_generation_to_text(struct printbuf *out, struct bch_fs *c,
 	pr_buf(out, "generation: %u", le32_to_cpu(gen.v->bi_generation));
 }
 
-void bch2_inode_init(struct bch_fs *c, struct bch_inode_unpacked *inode_u,
-		     uid_t uid, gid_t gid, umode_t mode, dev_t rdev,
-		     struct bch_inode_unpacked *parent)
+void bch2_inode_init_early(struct bch_fs *c,
+			   struct bch_inode_unpacked *inode_u)
 {
-	s64 now = bch2_current_time(c);
 	enum bch_str_hash_type str_hash =
 		bch2_str_hash_opt_to_type(c, c->opts.str_hash);
 
@@ -311,7 +309,12 @@ void bch2_inode_init(struct bch_fs *c, struct bch_inode_unpacked *inode_u,
 	inode_u->bi_flags |= str_hash << INODE_STR_HASH_OFFSET;
 	get_random_bytes(&inode_u->bi_hash_seed,
 			 sizeof(inode_u->bi_hash_seed));
+}
 
+void bch2_inode_init_late(struct bch_inode_unpacked *inode_u, u64 now,
+			  uid_t uid, gid_t gid, umode_t mode, dev_t rdev,
+			  struct bch_inode_unpacked *parent)
+{
 	inode_u->bi_mode	= mode;
 	inode_u->bi_uid		= uid;
 	inode_u->bi_gid		= gid;
@@ -321,6 +324,12 @@ void bch2_inode_init(struct bch_fs *c, struct bch_inode_unpacked *inode_u,
 	inode_u->bi_ctime	= now;
 	inode_u->bi_otime	= now;
 
+	if (parent && parent->bi_mode & S_ISGID) {
+		inode_u->bi_gid = parent->bi_gid;
+		if (S_ISDIR(mode))
+			inode_u->bi_mode |= S_ISGID;
+	}
+
 	if (parent) {
 #define x(_name, ...)	inode_u->bi_##_name = parent->bi_##_name;
 		BCH_INODE_OPTS()
@@ -328,6 +337,15 @@ void bch2_inode_init(struct bch_fs *c, struct bch_inode_unpacked *inode_u,
 	}
 }
 
+void bch2_inode_init(struct bch_fs *c, struct bch_inode_unpacked *inode_u,
+		     uid_t uid, gid_t gid, umode_t mode, dev_t rdev,
+		     struct bch_inode_unpacked *parent)
+{
+	bch2_inode_init_early(c, inode_u);
+	bch2_inode_init_late(inode_u, bch2_current_time(c),
+			     uid, gid, mode, rdev, parent);
+}
+
 static inline u32 bkey_generation(struct bkey_s_c k)
 {
 	switch (k.k->type) {
@@ -340,9 +358,9 @@ static inline u32 bkey_generation(struct bkey_s_c k)
 	}
 }
 
-int __bch2_inode_create(struct btree_trans *trans,
-			struct bch_inode_unpacked *inode_u,
-			u64 min, u64 max, u64 *hint)
+int bch2_inode_create(struct btree_trans *trans,
+		      struct bch_inode_unpacked *inode_u,
+		      u64 min, u64 max, u64 *hint)
 {
 	struct bch_fs *c = trans->c;
 	struct bkey_inode_buf *inode_p;
@@ -408,13 +426,6 @@ out:
 	return -ENOSPC;
 }
 
-int bch2_inode_create(struct bch_fs *c, struct bch_inode_unpacked *inode_u,
-		      u64 min, u64 max, u64 *hint)
-{
-	return bch2_trans_do(c, NULL, BTREE_INSERT_ATOMIC,
-			__bch2_inode_create(&trans, inode_u, min, max, hint));
-}
-
 int bch2_inode_rm(struct bch_fs *c, u64 inode_nr)
 {
 	struct btree_trans trans;
diff --git a/libbcachefs/inode.h b/libbcachefs/inode.h
index c5626c66..b32c0a47 100644
--- a/libbcachefs/inode.h
+++ b/libbcachefs/inode.h
@@ -51,14 +51,17 @@ struct btree_iter *bch2_inode_peek(struct btree_trans *,
 int bch2_inode_write(struct btree_trans *, struct btree_iter *,
 		     struct bch_inode_unpacked *);
 
+void bch2_inode_init_early(struct bch_fs *,
+			   struct bch_inode_unpacked *);
+void bch2_inode_init_late(struct bch_inode_unpacked *, u64,
+			  uid_t, gid_t, umode_t, dev_t,
+			  struct bch_inode_unpacked *);
 void bch2_inode_init(struct bch_fs *, struct bch_inode_unpacked *,
 		     uid_t, gid_t, umode_t, dev_t,
 		     struct bch_inode_unpacked *);
 
-int __bch2_inode_create(struct btree_trans *,
-			struct bch_inode_unpacked *,
-			u64, u64, u64 *);
-int bch2_inode_create(struct bch_fs *, struct bch_inode_unpacked *,
+int bch2_inode_create(struct btree_trans *,
+		      struct bch_inode_unpacked *,
 		      u64, u64, u64 *);
 
 int bch2_inode_rm(struct bch_fs *, u64);
@@ -108,6 +111,11 @@ static inline u64 bch2_inode_opt_get(struct bch_inode_unpacked *inode,
 	}
 }
 
+static inline u8 mode_to_type(umode_t mode)
+{
+	return (mode >> 12) & 15;
+}
+
 /* i_nlink: */
 
 static inline unsigned nlink_bias(umode_t mode)
diff --git a/libbcachefs/recovery.c b/libbcachefs/recovery.c
index 2e880955..e6015bc1 100644
--- a/libbcachefs/recovery.c
+++ b/libbcachefs/recovery.c
@@ -10,6 +10,7 @@
 #include "dirent.h"
 #include "ec.h"
 #include "error.h"
+#include "fs-common.h"
 #include "fsck.h"
 #include "journal_io.h"
 #include "journal_reclaim.h"
@@ -952,7 +953,6 @@ int bch2_fs_initialize(struct bch_fs *c)
 {
 	struct bch_inode_unpacked root_inode, lostfound_inode;
 	struct bkey_inode_buf packed_inode;
-	struct bch_hash_info root_hash_info;
 	struct qstr lostfound = QSTR("lost+found");
 	const char *err = "cannot allocate memory";
 	struct bch_dev *ca;
@@ -997,7 +997,6 @@ int bch2_fs_initialize(struct bch_fs *c)
 	bch2_inode_init(c, &root_inode, 0, 0,
 			S_IFDIR|S_IRWXU|S_IRUGO|S_IXUGO, 0, NULL);
 	root_inode.bi_inum = BCACHEFS_ROOT_INO;
-	root_inode.bi_nlink++; /* lost+found */
 	bch2_inode_pack(&packed_inode, &root_inode);
 
 	err = "error creating root directory";
@@ -1007,24 +1006,15 @@ int bch2_fs_initialize(struct bch_fs *c)
 	if (ret)
 		goto err;
 
-	bch2_inode_init(c, &lostfound_inode, 0, 0,
-			S_IFDIR|S_IRWXU|S_IRUGO|S_IXUGO, 0,
-			&root_inode);
-	lostfound_inode.bi_inum = BCACHEFS_ROOT_INO + 1;
-	bch2_inode_pack(&packed_inode, &lostfound_inode);
+	bch2_inode_init_early(c, &lostfound_inode);
 
 	err = "error creating lost+found";
-	ret = bch2_btree_insert(c, BTREE_ID_INODES,
-				&packed_inode.inode.k_i,
-				NULL, NULL, 0);
-	if (ret)
-		goto err;
-
-	root_hash_info = bch2_hash_info_init(c, &root_inode);
-
-	ret = bch2_dirent_create(c, BCACHEFS_ROOT_INO, &root_hash_info, DT_DIR,
-				 &lostfound, lostfound_inode.bi_inum, NULL,
-				 BTREE_INSERT_NOFAIL);
+	ret = bch2_trans_do(c, NULL, BTREE_INSERT_ATOMIC,
+		bch2_create_trans(&trans, BCACHEFS_ROOT_INO,
+				  &root_inode, &lostfound_inode,
+				  &lostfound,
+				  0, 0, 0755, 0,
+				  NULL, NULL));
 	if (ret)
 		goto err;