[PATCH 14/14] Add ngnfs_dir_unlink(), ngfs_dir_rmdir(), and debugfs commands

Valerie Aurora val at versity.com
Thu Feb 27 06:16:23 PST 2025


Allow removal of links to files and directories.

Signed-off-by: Valerie Aurora <val at versity.com>
---
 cli/debugfs.c  |  35 +++++++++++
 shared/dir.c   | 153 ++++++++++++++++++++++++++++++++++++++++++++++---
 shared/dir.h   |   2 +
 shared/inode.c |  18 ++++++
 shared/inode.h |   1 +
 5 files changed, 201 insertions(+), 8 deletions(-)

diff --git a/cli/debugfs.c b/cli/debugfs.c
index fd6e2f0..dce23a9 100644
--- a/cli/debugfs.c
+++ b/cli/debugfs.c
@@ -209,6 +209,23 @@ static void cmd_readdir(struct debugfs_context *ctx, int argc, char **argv)
 	free(buf);
 }
 
+static void cmd_rmdir(struct debugfs_context *ctx, int argc, char **argv)
+{
+	char *name;
+	int ret;
+
+	if (argc != 2) {
+		printf("usage: rmdir <pathname>\n");
+		return;
+	}
+
+	name = argv[1];
+
+	ret = ngnfs_dir_rmdir(ctx->nfi, ctx->cwd_ino, name, strlen(name));
+	if (ret < 0)
+		printf("rmdir error: "ENOF"\n", ENOA(-ret));
+}
+
 static void cmd_stat(struct debugfs_context *ctx, int argc, char **argv)
 {
 	struct ngnfs_dir_handle hdl;
@@ -273,6 +290,22 @@ static void cmd_sync(struct debugfs_context *ctx, int argc, char **argv)
 		printf("sync error: "ENOF"\n", ENOA(-ret));
 }
 
+static void cmd_unlink(struct debugfs_context *ctx, int argc, char **argv)
+{
+	char *name;
+	int ret;
+
+	if (argc != 2) {
+		printf("usage: unlink <pathname>\n");
+		return;
+	}
+
+	name = argv[1];
+
+	ret = ngnfs_dir_unlink(ctx->nfi, ctx->cwd_ino, name, strlen(name));
+	if (ret < 0)
+		printf("unlink error: "ENOF"\n", ENOA(-ret));
+}
 
 static struct command {
 	char *name;
@@ -285,8 +318,10 @@ static struct command {
 	{ "mkfs", cmd_mkfs, },
 	{ "quit", cmd_quit, },
 	{ "readdir", cmd_readdir, },
+	{ "rmdir", cmd_rmdir, },
 	{ "stat", cmd_stat, },
 	{ "sync", cmd_sync, },
+	{ "unlink", cmd_unlink, },
 };
 
 static int compar_cmd_names(const void *A, const void *B)
diff --git a/shared/dir.c b/shared/dir.c
index 5589e3f..482540b 100644
--- a/shared/dir.c
+++ b/shared/dir.c
@@ -76,6 +76,15 @@ static u64 name_hash(void *name, size_t name_len) {
 	return hash;
 }
 
+/*
+ * Flags for communicating to the btree iteration function what tests it
+ * should do before carrying out the operation.
+ */
+enum {
+	NGNFS_DIRENT_WANT_DIR = 	0x1,
+	NGNFS_DIRENT_WILL_MOVE = 	0x2,
+};
+
 /*
  * These large dirent structs can fit a full sized name so that we can
  * copy in and out any dirent as we work with them.
@@ -184,8 +193,8 @@ static inline int check_ifmt(struct ngnfs_inode *ninode, u32 ifmt, int err)
 }
 
 /*
- * Update a directory's inode to reflect creation.  We can return errors
- * if the create should fail.
+ * Update a parent directory's inode to reflect creation or deletion of
+ * an entry. We can return errors if the create should fail.
  */
 static int update_dir(struct ngnfs_txn_block *tblk, struct ngnfs_inode *dir,
 		      struct dirent_args *da, int posneg)
@@ -194,12 +203,9 @@ static int update_dir(struct ngnfs_txn_block *tblk, struct ngnfs_inode *dir,
 	int ret;
 
 	if (S_ISDIR(le32_to_cpu(dir->mode))) {
-		delta = posneg * 1;
-		if ((le32_to_cpu(dir->nlink) + delta >= NGNFS_LINK_MAX)) {
-			ret = -EMLINK;
+		ret = ngnfs_inode_update(tblk, dir, posneg);
+		if (ret < 0)
 			goto out;
-		}
-		ngnfs_tblk_assign(tblk, dir->nlink, cpu_to_le32(le32_to_cpu(dir->nlink) + delta));
 	}
 
 	/* dir i_size includes null termed names */
@@ -272,7 +278,6 @@ static int insert_dirent(struct ngnfs_fs_info *nfi, struct ngnfs_transaction *tx
 				      &last, insert_dirent_wr, da);
 }
 
-
 /*
  * Allocate a new inode and add a directory entry referencing it.
  */
@@ -656,3 +661,135 @@ int ngnfs_dir_get_handle(struct ngnfs_fs_info *nfi, u64 dir_ino, char *name, siz
 out:
 	return ret;
 }
+
+static int check_empty_dir(struct ngnfs_fs_info *nfi, struct ngnfs_transaction *txn, u64 ino,
+			   struct ngnfs_inode_txn_ref *new)
+{
+	int ret;
+
+	ret = ngnfs_inode_get(nfi, txn, NBF_READ, ino, new);
+	if (ret < 0)
+		return ret;
+
+	if (le32_to_cpu(new->ninode->nlink) != 2)
+		return -ENOTEMPTY;
+
+	return 0;
+}
+
+/*
+ * Run final checks before removing a dirent, depending on whether we
+ * want a directory or if we are deleting or moving a dirent.
+ */
+static int check_remove_dirent(struct ngnfs_dirent *dent, struct dirent_args *da)
+{
+	if (!(da->flags & NGNFS_DIRENT_WANT_DIR)) {
+		if (dent->type == NGNFS_DT_DIR)
+			return -EISDIR;
+		else
+			return 0;
+	}
+
+	if (dent->type != NGNFS_DT_DIR)
+		return -ENOTDIR;
+
+	if (da->flags & NGNFS_DIRENT_WILL_MOVE)
+		return 0;
+
+	return check_empty_dir(da->nfi, da->txn, da->ino, &da->inode);
+}
+
+static int remove_dirent_wr(struct ngnfs_btree_key *key, void *val, size_t size, void *arg,
+			    struct ngnfs_btree_op *op)
+{
+	struct ngnfs_dirent *dent = val;
+	struct dirent_args *da = arg;
+	int ret;
+
+	if (!dent)
+		return -ENOENT;
+
+	if (!names_equal(dent->name, dent->name_len, da->dent.name, da->dent.name_len))
+		return NGNFS_BTREE_ITER_CONTINUE;
+
+	/* ino and dtype needed for updating inode and parent directory */
+	da->ino = le64_to_cpu(dent->ino);
+	da->dtype = ngnfs_type_to_dtype(dent->type);
+
+	ret = check_remove_dirent(dent, da);
+	if (ret < 0)
+		return ret;
+
+	op->delete = 1;
+
+	return 0;
+}
+
+/*
+ * Remove a dirent item from a directory inode's dirent btree.
+ */
+static int remove_dirent(struct ngnfs_fs_info *nfi, struct ngnfs_transaction *txn,
+			 struct ngnfs_inode_txn_ref *dir, struct dirent_args *da)
+{
+	struct ngnfs_btree_key key;
+	struct ngnfs_btree_key last;
+
+	init_dirent_key(&key, da->hash);
+	init_dirent_key(&last, da->hash | NGNFS_DIRENT_COLL_BIT);
+
+	return ngnfs_btree_write_iter(nfi, txn, dir->tblk, &dir->ninode->dirents, &key, &last,
+				      remove_dirent_wr, da);
+}
+
+/*
+ * Unlink a directory entry. The goal is to walk the btree only once, so
+ * the check for directory/non-directory or directory emptiness is done
+ * in the btree iterator function.
+ */
+static int do_unlink(struct ngnfs_fs_info *nfi, u64 dir_ino, char *name, size_t name_len,
+		     int flags)
+{
+	struct {
+		struct ngnfs_transaction txn;
+		struct ngnfs_inode_txn_ref dir;
+		struct dirent_args da;
+		u64 ino;
+		u64 nsec;
+	} *op;
+	int ret;
+
+	op = kmalloc(sizeof(*op), GFP_NOFS);
+	if (!op) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ngnfs_txn_init(&op->txn);
+	op->nsec = ktime_to_ns(ktime_get_real());
+	init_dirent_args(nfi, &op->txn, &op->da, name, name_len, 0, 0, flags);
+
+	do {
+		ret = ngnfs_inode_get(nfi, &op->txn, NBF_WRITE, dir_ino, &op->dir)		?:
+		      check_ifmt(op->dir.ninode, S_IFDIR, -ENOTDIR)				?:
+		      remove_dirent(nfi, &op->txn, &op->dir, &op->da)				?:
+		      ngnfs_inode_get(nfi, &op->txn, NBF_WRITE, op->da.ino, &op->da.inode)	?:
+		      ngnfs_inode_update(op->da.inode.tblk, op->da.inode.ninode, -1)		?:
+		      update_dir(op->dir.tblk, op->dir.ninode, &op->da, -1);
+
+	} while (ngnfs_txn_retry(nfi, &op->txn, &ret));
+
+	ngnfs_txn_teardown(nfi, &op->txn);
+	kfree(op);
+out:
+	return ret;
+}
+
+int ngnfs_dir_unlink(struct ngnfs_fs_info *nfi, u64 dir_ino, char *name, size_t name_len)
+{
+	return do_unlink(nfi, dir_ino, name, name_len, 0);
+}
+
+int ngnfs_dir_rmdir(struct ngnfs_fs_info *nfi, u64 dir_ino, char *name, size_t name_len)
+{
+	return do_unlink(nfi, dir_ino, name, name_len, NGNFS_DIRENT_WANT_DIR);
+}
diff --git a/shared/dir.h b/shared/dir.h
index 0115411..ec72c39 100644
--- a/shared/dir.h
+++ b/shared/dir.h
@@ -8,6 +8,8 @@ int ngnfs_dir_create(struct ngnfs_fs_info *nfi, u64 dir_ino, umode_t mode, char
 		     size_t name_len);
 int ngnfs_dir_mkdir(struct ngnfs_fs_info *nfi, u64 dir_ino, umode_t mode, char *name,
 		    size_t name_len);
+int ngnfs_dir_unlink(struct ngnfs_fs_info *nfi, u64 dir_ino, char *name, size_t name_len);
+int ngnfs_dir_rmdir(struct ngnfs_fs_info *nfi, u64 dir_ino, char *name, size_t name_len);
 
 /*
  * ngnfs_dir_get_handle() returns a thing that lets userspace clients do
diff --git a/shared/inode.c b/shared/inode.c
index 03aadc7..03b4af6 100644
--- a/shared/inode.c
+++ b/shared/inode.c
@@ -51,6 +51,8 @@ int ngnfs_inode_init(struct ngnfs_inode_txn_ref *itref, u64 ino, u64 gen, u32 nl
 int ngnfs_inode_get(struct ngnfs_fs_info *nfi, struct ngnfs_transaction *txn, nbf_t nbf, u64 ino,
 		    struct ngnfs_inode_txn_ref *itref)
 {
+	BUG_ON(ino == 0);
+
 	return ngnfs_txn_get_block(nfi, txn, ino, nbf, &itref->tblk, (void **)&itref->ninode);
 }
 
@@ -96,3 +98,19 @@ int ngnfs_inode_read_copy(struct ngnfs_fs_info *nfi, u64 ino, void *buf, int siz
 out:
 	return ret;
 }
+
+/*
+ * Update an inode to reflect the addition or removal of a link to it.
+ */
+int ngnfs_inode_update(struct ngnfs_txn_block *tblk, struct ngnfs_inode *inode, int posneg)
+{
+	s32 delta;
+
+	delta = posneg * 1;
+	if ((le32_to_cpu(inode->nlink) + delta >= NGNFS_LINK_MAX))
+		return -EMLINK;
+
+	ngnfs_tblk_assign(tblk, inode->nlink, cpu_to_le32(le32_to_cpu(inode->nlink) + delta));
+
+	return 0;
+}
diff --git a/shared/inode.h b/shared/inode.h
index cafbdfb..73804f0 100644
--- a/shared/inode.h
+++ b/shared/inode.h
@@ -21,5 +21,6 @@ int ngnfs_inode_get(struct ngnfs_fs_info *nfi, struct ngnfs_transaction *txn, nb
 int ngnfs_inode_alloc(struct ngnfs_fs_info *nfi, struct ngnfs_transaction *txn, u64 *ino,
 		      struct ngnfs_inode_txn_ref *itref);
 int ngnfs_inode_read_copy(struct ngnfs_fs_info *nfi, u64 ino, void *buf, int size);
+int ngnfs_inode_update(struct ngnfs_txn_block *tblk, struct ngnfs_inode *inode, int posneg);
 
 #endif
-- 
2.48.1




More information about the ngnfs-devel mailing list