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

Valerie Aurora val at versity.com
Thu Jun 12 13:11:06 PDT 2025


Allow removal of links to files and directories. Also simplify calling
convention for update_dir() and add ngnfs_inode_update() to change
link counts for the target of a directory entry.

Signed-off-by: Valerie Aurora <val at versity.com>
---
 cli/debugfs.c  |  35 +++++++++
 shared/dir.c   | 199 +++++++++++++++++++++++++++++++++++++++++++------
 shared/dir.h   |   4 +
 shared/inode.c |  30 ++++++++
 shared/inode.h |   1 +
 5 files changed, 248 insertions(+), 21 deletions(-)

diff --git a/cli/debugfs.c b/cli/debugfs.c
index 82c087d..8519df3 100644
--- a/cli/debugfs.c
+++ b/cli/debugfs.c
@@ -222,6 +222,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_ig, name, strlen(name));
+	if (ret < 0)
+		print_err("rmdir", ret);
+}
+
 static void cmd_stat(struct debugfs_context *ctx, int argc, char **argv)
 {
 	struct ngnfs_inode_ino_gen ig = ctx->cwd_ig;
@@ -287,6 +304,22 @@ static void cmd_sync(struct debugfs_context *ctx, int argc, char **argv)
 		print_err("sync", 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_ig, name, strlen(name));
+	if (ret < 0)
+		print_err("unlink", ret);
+}
 
 static struct command {
 	char *name;
@@ -299,8 +332,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 3460bf4..305dfaf 100644
--- a/shared/dir.c
+++ b/shared/dir.c
@@ -1,22 +1,14 @@
 /* SPDX-License-Identifier: GPL-2.0 */
 
 #include "shared/lk/align.h"
-#include "shared/lk/bitops.h"
 #include "shared/lk/build_bug.h"
 #include "shared/lk/bug.h"
 #include "shared/lk/byteorder.h"
-#include "shared/lk/container_of.h"
-#include "shared/lk/err.h"
-#include "shared/lk/errno.h"
 #include "shared/lk/fs_types.h"
 #include "shared/lk/kernel.h"
 #include "shared/lk/ktime.h"
-#include "shared/lk/limits.h"
-#include "shared/lk/math.h"
-#include "shared/lk/minmax.h"
 #include "shared/lk/stat.h"
 #include "shared/lk/stddef.h"
-#include "shared/lk/string.h"
 #include "shared/lk/types.h"
 #include "shared/lk/xxhash.h"
 
@@ -189,32 +181,53 @@ 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. May fail if the resulting nlink is invalid.
  */
 static int update_dir(struct ngnfs_txn_block *tblk, struct ngnfs_inode *dir,
-		      struct dirent_args *da, int posneg)
+		      struct ngnfs_dirent *dent, s32 nlink_delta)
 {
-	s32 delta;
+	s32 dent_bytes;
+	s32 posneg;
 	int ret;
 
-	if (da->dent.pers_dtype == NGNFS_DT_DIR) {
-		delta = posneg * 1;
-		if ((le32_to_cpu(dir->nlink) + delta >= NGNFS_LINK_MAX)) {
-			ret = -EMLINK;
+	if (dent->pers_dtype == NGNFS_DT_DIR) {
+		ret = ngnfs_inode_update(tblk, dir, nlink_delta);
+		if (ret < 0)
 			goto out;
-		}
-		ngnfs_tblk_assign(tblk, dir->nlink, cpu_to_le32(le32_to_cpu(dir->nlink) + delta));
 	}
 
+	posneg = nlink_delta >= 0 ? 1 : -1;
 	/* dir i_size includes null termed names */
-	delta = posneg * ((s32)da->dent.name_len + 1);
-	ngnfs_tblk_assign(tblk, dir->size, cpu_to_le64(le64_to_cpu(dir->size) + delta));
+	dent_bytes = posneg * ((s32) dent->name_len + 1);
+	ngnfs_tblk_assign(tblk, dir->size, cpu_to_le64(le64_to_cpu(dir->size) + dent_bytes));
 	ret = 0;
 out:
 	return ret;
 }
 
+/*
+ * Lookup an inode and update its link count.
+ */
+static int update_inode_nlink(struct ngnfs_fs_info *nfi, struct ngnfs_transaction *txn,
+			      struct ngnfs_ino_gen *ino_gen, s32 nlink_delta)
+{
+	struct ngnfs_inode_ino_gen ig;
+	struct ngnfs_inode_txn_ref ino;
+	int ret;
+
+	ig.ino = le64_to_cpu(ino_gen->ino);
+	ig.gen = le64_to_cpu(ino_gen->gen);
+
+	ret = ngnfs_inode_get(nfi, txn, NBF_WRITE, &ig, &ino);
+	if (ret < 0)
+		goto out;
+
+	ret = ngnfs_inode_update(ino.tblk, ino.ninode, nlink_delta);
+out:
+	return ret;
+}
+
 /*
  * Insert a new dirent by iterating over the existing dirent items which
  * collide with the caller's hashed name value.  If we see a matching
@@ -345,7 +358,7 @@ static int do_create(struct ngnfs_fs_info *nfi, struct ngnfs_inode_ino_gen *dir,
 				       &op->parent_ig)						?:
 		      update_dirent_args(&op->da, &op->ig)					?:
 		      insert_dirent(nfi, &op->txn, &op->dir, &op->da)				?:
-		      update_dir(op->dir.tblk, op->dir.ninode, &op->da, 1);
+		      update_dir(op->dir.tblk, op->dir.ninode, &op->da.dent, 1);
 
 	} while (ngnfs_txn_retry(nfi, &op->txn, &ret));
 
@@ -583,3 +596,147 @@ int ngnfs_dir_lookup(struct ngnfs_fs_info *nfi, struct ngnfs_inode_ino_gen *dir_
 out:
 	return ret;
 }
+
+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;
+
+	if (!dent)
+		return -ENOENT;
+
+	if (!names_equal(dent->name, dent->name_len, da->dent.name, da->dent.name_len))
+		return NGNFS_BTREE_ITER_CONTINUE;
+
+	da->found = 1;
+	memcpy(&da->dent, dent, size);
+
+	op->op = BOP_DELETE;
+
+	return 0;
+}
+
+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;
+	int ret;
+
+	init_dirent_key(&key, da->hash);
+	init_dirent_key(&last, da->hash | NGNFS_DIRENT_COLL_BIT);
+
+	ret = ngnfs_btree_write_iter(nfi, txn, dir->tblk, &dir->ninode->dirents, &key, &last,
+				     remove_dirent_wr, da);
+	if (ret < 0)
+		goto out;
+
+	if (!da->found)
+		ret = -ENOENT;
+out:
+	return ret;
+}
+
+static int check_empty_dir(struct ngnfs_fs_info *nfi, struct ngnfs_transaction *txn,
+			   struct ngnfs_ino_gen *ino_gen)
+{
+	struct ngnfs_inode_txn_ref dir;
+	struct ngnfs_inode_ino_gen ig;
+	int ret;
+
+	ig.ino = le64_to_cpu(ino_gen->ino);
+	ig.gen = le64_to_cpu(ino_gen->gen);
+
+	ret = ngnfs_inode_get(nfi, txn, NBF_READ, &ig, &dir);
+	if (ret < 0)
+		return ret;
+
+	if (le32_to_cpu(dir.ninode->nlink) != 2)
+		return -ENOTEMPTY;
+
+	return 0;
+}
+
+static int check_remove_dirent(struct ngnfs_fs_info *nfi, struct ngnfs_transaction *txn,
+			       struct ngnfs_inode_txn_ref *dir, struct dirent_args *da,
+			       int want_non_dir)
+{
+	int ret;
+
+	if (want_non_dir) {
+		if (da->dent.pers_dtype == NGNFS_DT_DIR)
+			ret = -EISDIR;
+		else
+			ret = 0;
+		goto out;
+	}
+
+	if (da->dent.pers_dtype != NGNFS_DT_DIR) {
+		ret = -ENOTDIR;
+		goto out;
+	}
+
+	ret = check_empty_dir(nfi, txn, &da->dent.ig);
+out:
+	return ret;
+}
+
+static int do_unlink(struct ngnfs_fs_info *nfi, struct ngnfs_inode_ino_gen *dir_ig, char *name,
+		     size_t name_len, int want_non_dir)
+{
+	struct {
+		struct ngnfs_transaction txn;
+		struct ngnfs_inode_txn_ref dir;
+		struct ngnfs_inode_ino_gen ig;
+		u64 nsec;
+		u64 ino;
+		struct dirent_args da;
+	} *op;
+	int ret;
+
+	if (name_len > NGNFS_NAME_MAX) {
+		ret = -ENAMETOOLONG;
+		goto out;
+	}
+
+	op = kmalloc(sizeof(*op), GFP_NOFS);
+	if (!op) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ngnfs_txn_init(&op->txn);
+	init_dirent_args(&op->da, name, name_len, 0);
+
+	do {
+		op->nsec = ktime_to_ns(ktime_get_real());
+		reset_dirent_args(&op->da);
+
+		ret = ngnfs_inode_get(nfi, &op->txn, NBF_WRITE, dir_ig, &op->dir)		?:
+		      check_ifmt(op->dir.ninode, S_IFDIR, -ENOTDIR)				?:
+		      remove_dirent(nfi, &op->txn, &op->dir, &op->da)				?:
+		      check_remove_dirent(nfi, &op->txn, &op->dir, &op->da, want_non_dir)	?:
+		      update_dir(op->dir.tblk, op->dir.ninode, &op->da.dent, -1)		?:
+		      update_inode_nlink(nfi, &op->txn, &op->da.dent.ig, -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, struct ngnfs_inode_ino_gen *dir, char *name,
+		     size_t name_len)
+{
+	return do_unlink(nfi, dir, name, name_len, 1);
+}
+
+int ngnfs_dir_rmdir(struct ngnfs_fs_info *nfi, struct ngnfs_inode_ino_gen *dir, char *name,
+		    size_t name_len)
+{
+	return do_unlink(nfi, dir, name, name_len, 0);
+}
diff --git a/shared/dir.h b/shared/dir.h
index 366d28e..263b419 100644
--- a/shared/dir.h
+++ b/shared/dir.h
@@ -9,6 +9,10 @@ int ngnfs_dir_create(struct ngnfs_fs_info *nfi, struct ngnfs_inode_ino_gen *dir,
 		     char *name, size_t name_len);
 int ngnfs_dir_mkdir(struct ngnfs_fs_info *nfi, struct ngnfs_inode_ino_gen *dir, umode_t mode,
 		    char *name, size_t name_len);
+int ngnfs_dir_unlink(struct ngnfs_fs_info *nfi, struct ngnfs_inode_ino_gen *dir, char *name,
+		     size_t name_len);
+int ngnfs_dir_rmdir(struct ngnfs_fs_info *nfi, struct ngnfs_inode_ino_gen *dir, char *name,
+		    size_t name_len);
 
 /*
  * Readdir fills the buffer with entries.  The start of the buffer must
diff --git a/shared/inode.c b/shared/inode.c
index 1c722a5..a6cf2fc 100644
--- a/shared/inode.c
+++ b/shared/inode.c
@@ -67,6 +67,9 @@ int ngnfs_inode_init(struct ngnfs_inode_txn_ref *itref, struct ngnfs_inode_ino_g
 int ngnfs_inode_get(struct ngnfs_fs_info *nfi, struct ngnfs_transaction *txn, nbf_t nbf,
 		    struct ngnfs_inode_ino_gen *ig, struct ngnfs_inode_txn_ref *itref)
 {
+	if (ig->ino == 0) /* probably a buggy caller but could be corruption */
+		return -EUCLEAN;
+
 	return ngnfs_txn_get_block(nfi, txn, ig->ino, nbf, &itref->tblk, (void **)&itref->ninode);
 }
 
@@ -116,3 +119,30 @@ int ngnfs_inode_read_copy(struct ngnfs_fs_info *nfi, struct ngnfs_inode_ino_gen
 out:
 	return ret;
 }
+
+/*
+ * Update an inode to reflect the addition or removal of one or more
+ * links to it.
+ */
+int ngnfs_inode_update(struct ngnfs_txn_block *tblk, struct ngnfs_inode *inode, s32 delta)
+{
+	s32 nlink = le32_to_cpu(inode->nlink);
+
+	if ((delta > 0) && (nlink > NGNFS_LINK_MAX - delta))
+		return -EMLINK;
+
+	/* nlink < 0 is a data corruption bug */
+	if ((delta < 0) && (nlink + delta < 0))
+		return -EUCLEAN;
+
+	/*
+	 * If this is the removal of the last external link to a dir, remove the "."
+	 * self-link too.
+	 */
+	if ((nlink == 2 && delta == -1) && ((le32_to_cpu(inode->mode) & S_IFMT) == S_IFDIR))
+		delta = -2;
+
+	ngnfs_tblk_assign(tblk, inode->nlink, cpu_to_le32(nlink + delta));
+
+	return 0;
+}
diff --git a/shared/inode.h b/shared/inode.h
index a2f91ba..76d4a5f 100644
--- a/shared/inode.h
+++ b/shared/inode.h
@@ -41,5 +41,6 @@ int ngnfs_inode_alloc(struct ngnfs_fs_info *nfi, struct ngnfs_transaction *txn,
 		      struct ngnfs_inode_ino_gen *ig, struct ngnfs_inode_txn_ref *itref);
 int ngnfs_inode_read_copy(struct ngnfs_fs_info *nfi, struct ngnfs_inode_ino_gen *ig,
 			  void *buf, int size);
+int ngnfs_inode_update(struct ngnfs_txn_block *tblk, struct ngnfs_inode *inode, s32 delta);
 
 #endif
-- 
2.49.0




More information about the ngnfs-devel mailing list