[PATCH 3/3] rpdfs: Add rpdfs_unlink and rpdfs_rmdir

Valerie Aurora <[email protected]> Mon, 9 Mar 2026 22:04:03 +0100
Newsgroups dev.linux.lists.rpdfs-devel
Message-ID <[email protected]>
Add unlink and rmdir, including checking for an empty directory and
splitting out error checking into a proper prepare_unlink() function.

Signed-off-by: Valerie Aurora <[email protected]>
---
 fs/rpdfs/dir.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 100 insertions(+)

diff --git a/fs/rpdfs/dir.c b/fs/rpdfs/dir.c
index 0de00704a603..7d51c133cba0 100644
--- a/fs/rpdfs/dir.c
+++ b/fs/rpdfs/dir.c
@@ -503,6 +503,48 @@ static int create_and_instantiate_new(struct mnt_idmap *idmap, struct inode *dir
 	return ret;
 }
 
+/*
+ * The VFS did a bunch of checks before calling our unlink
+ * implementation, but some other node could have made changes between
+ * then and now. The prepare functions only succeed if the inodes have
+ * not been freed or reused, the directory entry to be removed is still
+ * there, and there is no detected corruption (which is distinct from
+ * changes made by other nodes).
+ */
+static int prepare_unlink(struct inode *dir, struct inode *inode, struct dentry *dentry)
+{
+	int ret;
+
+	/* normal failures due to other nodes making changes */
+	if (S_ISDIR(inode->i_mode)) {
+		if (!is_dir_empty(inode)) {
+			ret = -ENOTEMPTY;
+			goto out;
+		}
+	}
+
+	/* consistency/corruption checks */
+	if (S_ISDIR(inode->i_mode)) {
+		if (inode->i_nlink != 2) {
+			pr_warn("empty dir ino %lu has bad n_link %d",
+				inode->i_ino, inode->i_nlink);
+			ret = -EUCLEAN;
+			goto out;
+		}
+	} else {
+		if (inode->i_nlink < 1) {
+			pr_warn("attempting to unlink ino %lu but n_link %d is already < 1",
+				inode->i_ino, inode->i_nlink);
+			ret = -EUCLEAN;
+			goto out;
+		}
+	}
+
+	ret = 0;
+out:
+	return ret;
+}
+
 static int rpdfs_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry,
 			umode_t mode, bool excl)
 {
@@ -515,6 +557,62 @@ static struct dentry *rpdfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
 	return ERR_PTR(create_and_instantiate_new(idmap, dir, dentry, S_IFDIR | mode));
 }
 
+static int rpdfs_unlink(struct inode *dir, struct dentry *dentry)
+{
+	struct rpdfs_fs_info *rfi = RPDFS_INODE_FS(dir);
+	struct inode *inode = d_inode(dentry);
+	struct key_dent *kd = NULL;
+	DECLARE_RPDFS_TXN(txn);
+	int ret;
+
+	kd = alloc_key_dent(dentry, inode);
+	if (!kd) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	/* prepare all the blocks for the txn */
+	do {
+		ret = rpdfs_inode_txn_prepare(rfi, &txn, dir, RBAF_WRITE) ?:
+		      rpdfs_inode_txn_prepare(rfi, &txn, inode, RBAF_WRITE) ?:
+		      prepare_remove_entry(rfi, &txn, dir, kd) ?:
+		      prepare_unlink(dir, inode, dentry);
+	} while (rpdfs_txn_retry(rfi, &txn, &ret));
+	if (ret < 0)
+		goto out;
+
+	/* apply changes to block structures */
+	apply_remove_entry(rfi, &txn, dir, kd);
+
+	/* update link count and metadata change time */
+	drop_nlink(inode);
+	if (S_ISDIR(inode->i_mode)) {
+		drop_nlink(dir);
+		drop_nlink(inode);
+	}
+	inode_set_ctime_current(inode);
+
+	/* update parent dir size and data/metadata times */
+	update_dir_size(dir, -(dentry->d_name.len + 1));
+	inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
+
+	/* update block storage of vfs inodes */
+	rpdfs_inode_txn_update(rfi, &txn, dir);
+	rpdfs_inode_txn_update(rfi, &txn, inode);
+
+	ret = 0;
+out:
+	rpdfs_txn_reset(rfi, &txn);
+	kfree(kd);
+
+	return ret;
+}
+
+static int rpdfs_rmdir(struct inode *dir, struct dentry *dentry)
+{
+	return rpdfs_unlink(dir, dentry);
+}
+
 /*
  * The vfs has verified the cached directories.  Our inode refresh will
  * fail if the inodes have been reused, so we don't have to test if
@@ -714,6 +812,8 @@ const struct inode_operations rpdfs_dir_iops = {
 	.mkdir		= rpdfs_mkdir,
 	.rename		= rpdfs_rename,
 	.setattr	= rpdfs_setattr,
+	.unlink 	= rpdfs_unlink,
+	.rmdir 		= rpdfs_rmdir,
 };
 
 const struct file_operations rpdfs_dir_fops = {
-- 
2.49.0