[PATCH 8/8] rpdfs: add read_folio, dirty_folio, write_begin, write_end

Valerie Aurora <[email protected]> Fri, 24 Apr 2026 16:05:20 +0200
Newsgroups dev.linux.lists.rpdfs-devel
Message-ID <[email protected]>
This commit completes basic file data read/write support. Currently it
will deadlock with multiple clients, so much more work is needed.

Signed-off-by: Valerie Aurora <[email protected]>
---
 fs/rpdfs/data.c  | 190 +++++++++++++++++++++++++++++++++++++++++++++++
 fs/rpdfs/inode.c |   1 +
 2 files changed, 191 insertions(+)

diff --git a/fs/rpdfs/data.c b/fs/rpdfs/data.c
index 3a6e9560b32f..42fe5c8c3c34 100644
--- a/fs/rpdfs/data.c
+++ b/fs/rpdfs/data.c
@@ -308,3 +308,193 @@ static int get_or_alloc_file_block(struct rpdfs_fs_info *rfi, struct rpdfs_trans
 		  ret, rpdfs_inode_ino(inode), lblk, bnr, *hnd_ret);
 	return ret;
 }
+
+/*
+ * We have to avoid a potential deadlock between the kernel's lock on
+ * each page and our cache consistency algorithm. The order of
+ * acquisition on a read or write or similar operation is:
+ *
+ * 1. kernel grabs page lock, calls file system routine
+ * 2. local node attempts to get access to a block
+ *
+ * But invalidation of a page has this order:
+ *
+ * 1. remote node requests exclusive access to a block
+ * 2. local node receives cache invalidate message and tries to get page lock
+ *
+ * If the local node is trying to get access to a block while another
+ * node tries to get exclusive access, we could end up with:
+ *
+ * 1. local node holds page lock, can't get access to block
+ * 2. local node attempts to service invalidate request but can't get page lock
+ *
+ * The solution is to drop the page lock while getting access to blocks,
+ * then re-acquire the lock and handle any changes that occurred while
+ * it was unlocked. Generally this looks like returning
+ * AOP_TRUNCATED_PAGE if the page is gone.
+ *
+ * Note that readahead() should satisfy most read requests
+ * asynchronously, leaving us to do any remaining synchronous requests
+ * in read_folio().
+ */
+
+/*
+ * TODO: cannot block on block acquisition while holding the folio lock.
+ */
+static int rpdfs_read_folio(struct file *file, struct folio *folio)
+{
+	struct inode *inode = folio->mapping->host;
+	struct rpdfs_fs_info *rfi = RPDFS_INODE_FS(inode);
+	loff_t pos = folio_pos(folio);
+	size_t len = folio_size(folio);
+	struct rpdfs_block_handle *inode_hnd = NULL;
+	struct rpdfs_block_handle *blk_hnd = NULL;
+	u64 lblk;
+	int ret;
+
+	lblk = lblk_from_offset(pos);
+
+	rpdfs_prd("ino %llu lblk %llu pos %lld len %lu",
+		  rpdfs_inode_ino(inode), lblk, pos, len);
+
+	/* we turn off atime always, inode will not be written */
+	ret = rpdfs_inode_acquire(rfi, NULL, inode, &inode_hnd, 0);
+	if (ret < 0)
+		goto out;
+
+	ret = get_or_alloc_file_block(rfi, NULL, inode, inode_hnd, lblk, 0, &blk_hnd);
+	if (ret < 0)
+		goto out;
+
+	/* copy the data into the actual block */
+	if (blk_hnd)
+		memcpy(folio_address(folio), blk_hnd->data, len);
+	else
+		memset(folio_address(folio), 0, len);
+
+	folio_mark_uptodate(folio);
+out:
+	folio_unlock(folio);
+
+	rpdfs_block_release(rfi, &inode_hnd);
+	rpdfs_block_release(rfi, &blk_hnd);
+
+	rpdfs_prd("ret %d", ret);
+
+	return ret;
+}
+
+static bool rpdfs_dirty_folio(struct address_space *mapping, struct folio *folio)
+{
+	rpdfs_prd("ino %lu index %lu", mapping->host->i_ino, folio->index);
+
+	return filemap_dirty_folio(folio_mapping(folio), folio);
+}
+
+/*
+ * Do whatever preparation is necessary before the VFS copies the data
+ * from the user buffer into the folio pages. Must return a folio.
+ *
+ * Called before a write starts with the page lock held. Due to this, we
+ * can't do anything that blocks on acquisition of a block. Right now it
+ * just gets the folio. In the future it might check quotas, file system
+ * error state, etc.
+ */
+static int rpdfs_write_begin(struct file *file, struct address_space *mapping,
+			     loff_t pos, unsigned len,
+			     struct folio **foliop, void **fsdata)
+{
+	struct folio *folio;
+	int ret;
+
+	rpdfs_prd("ino %lu pos %lld len %u", mapping->host->i_ino, pos, len);
+
+	folio = __filemap_get_folio(mapping, pos >> PAGE_SHIFT, FGP_WRITEBEGIN,
+				    mapping_gfp_mask(mapping));
+	if (IS_ERR(folio)) {
+		ret = PTR_ERR(folio);
+		goto out;
+	}
+
+	*foliop = folio;
+	ret = 0;
+out:
+	return ret;
+}
+
+/*
+ * Called when the data for a write has been copied in memory, with page
+ * lock held. Here we actually do the write.
+ *
+ * To avoid deadlocking with another thread/node that has exclusive
+ * access to a block we need and is also attempting to get the page lock
+ * on this page, we first do a non-blocking lookup of the blocks we
+ * need. If that fails, we drop the page lock, do a blocking
+ * acquisition, and when that succeeds, return AOP_TRUNCATE_PAGE to
+ * request a retry.
+ */
+static int rpdfs_write_end(struct file *file, struct address_space *mapping,
+			   loff_t pos, unsigned len, unsigned copied,
+			   struct folio *folio, void *fsdata)
+{
+	struct inode *inode = folio->mapping->host;
+	struct rpdfs_fs_info *rfi = RPDFS_INODE_FS(inode);
+	struct rpdfs_inode_info *ri = RPDFS_I(inode);
+	struct rpdfs_block_handle *inode_hnd = NULL;
+	struct rpdfs_block_handle *blk_hnd = NULL;
+	struct rpdfs_transaction txn = RPDFS_INIT_TXN;
+	rbaf_t rbaf;
+	u64 lblk;
+	int ret;
+	unsigned offset = offset_in_folio(folio, pos);
+
+	rpdfs_prd("ino %llu pos %lld len %u offset %u", ri->ig.ino, pos, len, offset);
+
+	lblk = lblk_from_offset(pos);
+
+	/* XXX use pos/len to figure out when it is an overwrite */
+	rbaf = RBAF_WRITE;
+
+	ret = rpdfs_inode_acquire(rfi, &txn, inode, &inode_hnd, RBAF_WRITE);
+	if (ret < 0)
+		goto out;
+
+	ret = get_or_alloc_file_block(rfi, &txn, inode, inode_hnd, lblk, rbaf, &blk_hnd);
+	if (ret < 0)
+		goto out;
+
+	/* copy the data into the actual block */
+	memcpy(blk_hnd->data + offset, folio_address(folio) + offset, len);
+
+	rpdfs_prd("copied %c len %d to %c", *((char *) folio_address(folio) + offset), len,
+		  *((char *) blk_hnd->data + offset));
+
+	if ((pos + len) > inode->i_size)
+		i_size_write(inode, pos + len);
+
+	folio_mark_uptodate(folio);
+	folio_mark_dirty(folio);
+	folio_unlock(folio);
+
+	file_update_time(file);
+	mark_inode_dirty(inode); /* TODO: not always necessary */
+
+	/* finalize changes to the inode and block */
+	rpdfs_inode_update(rfi, inode, inode_hnd);
+	rpdfs_block_release(rfi, &inode_hnd);
+	rpdfs_block_release(rfi, &blk_hnd);
+
+	ret = len;
+out:
+	rpdfs_txn_finish(rfi, &txn);
+
+	rpdfs_prd("i_size %lld ret %d", i_size_read(inode), ret);
+	return ret;
+}
+
+const struct address_space_operations rpdfs_aops = {
+	.read_folio =		rpdfs_read_folio,
+	.dirty_folio =		rpdfs_dirty_folio,
+	.write_begin =		rpdfs_write_begin,
+	.write_end =		rpdfs_write_end,
+};
diff --git a/fs/rpdfs/inode.c b/fs/rpdfs/inode.c
index 947a8f1703a3..79265d2716c5 100644
--- a/fs/rpdfs/inode.c
+++ b/fs/rpdfs/inode.c
@@ -164,6 +164,7 @@ void rpdfs_inode_init_ops(struct inode *inode)
 	case S_IFREG:
 		inode->i_op = &rpdfs_file_iops;
 		inode->i_fop = &rpdfs_file_fops;
+		inode->i_mapping->a_ops = &rpdfs_aops;
 		break;
 	case S_IFDIR:
 		inode->i_op = &rpdfs_dir_iops;
-- 
2.49.0