[PATCH 6/6] rpdfs: add read_folio, dirty_folio, write_begin, write_end
Valerie Aurora <[email protected]> Thu, 7 May 2026 15:21:53 +0200
| Newsgroups | dev.linux.lists.rpdfs-devel |
|---|---|
| Message-ID | <[email protected]> |
This commit completes basic file data read/write support, including avoidance of deadlock in read_folio. It does not include ->writepages() or readahead(). Signed-off-by: Valerie Aurora <[email protected]> --- fs/rpdfs/data.c | 245 +++++++++++++++++++++++++++++++++++++++++++++++ fs/rpdfs/inode.c | 1 + 2 files changed, 246 insertions(+) diff --git a/fs/rpdfs/data.c b/fs/rpdfs/data.c index b1ccf77ced7a..b3647e1ff42f 100644 --- a/fs/rpdfs/data.c +++ b/fs/rpdfs/data.c @@ -351,3 +351,248 @@ static int get_or_alloc_data_block(struct rpdfs_fs_info *rfi, struct rpdfs_trans ret, rpdfs_inode_ino(inode), lblk, refs ? le64_to_cpu(refs[0].bnr) : 0, *hnd_ret); return ret; } + +/* + * We have to avoid a potential deadlock between the kernel's lock on + * each folio and our cache consistency algorithm. The order of + * acquisition on a read or write or similar operation is: + * + * 1. kernel grabs folio 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 folio 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 folio lock, can't get access to block + * 2. local node attempts to service invalidate request but can't get folio lock + * + * The solution is to acquire all blocks in non-blocking mode. If that + * fails, drop the folio lock and acquire the block in blocking mode, + * then release it and return AOP_TRUNCATED_PAGE. This return code means + * "the page was truncated away beneath me, please retry." The page + * cache will restart the read_folio operation, which will likely + * succeed (as long as no other node has requested the block since then). + * + * Note that readahead() should satisfy most read requests + * asynchronously, leaving us to do any remaining synchronous requests + * in read_folio(). + */ +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; + rbaf_t rbaf = RBAF_NONBLOCK_MODE; + 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, RBAF_NONBLOCK_MODE); + if (ret == -EAGAIN) { + folio_unlock(folio); + + rpdfs_prd("could not acquire ino %llu non-blocking, ret %d, retrying", + rpdfs_inode_ino(inode), ret); + + ret = rpdfs_inode_acquire(rfi, NULL, inode, &inode_hnd, 0); + if (ret == 0) { + rpdfs_block_release(rfi, &inode_hnd); + ret = AOP_TRUNCATED_PAGE; + } + goto out; + } + if (ret < 0) + goto out_unlock; + + ret = get_or_alloc_data_block(rfi, NULL, inode, inode_hnd, lblk, rbaf, &blk_hnd); + if (ret == -EAGAIN) { + folio_unlock(folio); + + rpdfs_prd("could not acquire ino %llu lblk %llu non-blocking, ret %d, retrying", + rpdfs_inode_ino(inode), lblk, ret); + + rbaf &= ~RBAF_NONBLOCK_MODE; + ret = get_or_alloc_data_block(rfi, NULL, inode, inode_hnd, lblk, rbaf, &blk_hnd); + if (ret == 0) { + rpdfs_block_release(rfi, &blk_hnd); + ret = AOP_TRUNCATED_PAGE; + } + goto out; + } + if (ret < 0) + goto out_unlock; + + /* 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); + + rpdfs_prd("copied %4s len %lu to %4s", (char *) blk_hnd->data, len, + (char *) folio_address(folio)); + + folio_mark_uptodate(folio); +out_unlock: + folio_unlock(folio); +out: + rpdfs_block_release(rfi, &blk_hnd); + rpdfs_block_release(rfi, &inode_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); +} + + +/* + * Info to be passed from write_begin to write_end to complete the write + * within a transaction. + */ +struct rpdfs_write_cb { + struct rpdfs_block_handle *inode_hnd; + struct rpdfs_block_handle *blk_hnd; + struct rpdfs_transaction txn; +}; + +/* + * Do whatever preparation is necessary to allocate space for a + * write. In the future it might check quotas, file system error state, + * etc. + * + * Called with the inode block already acquired read/write. Returns a + * locked folio on success. + */ +static int rpdfs_write_begin(struct file *file, struct address_space *mapping, + loff_t pos, unsigned len, + struct folio **foliop, void **fsdata) +{ + struct inode *inode = mapping->host; + struct rpdfs_fs_info *rfi = RPDFS_INODE_FS(inode); + struct folio *folio = NULL; + struct rpdfs_write_cb *cb; + rbaf_t rbaf; + u64 lblk; + int ret; + unsigned offset; + + lblk = lblk_from_offset(pos); + + rpdfs_prd("ino %llu lblk %llu pos %lld len %u", rpdfs_inode_ino(inode), lblk, pos, len); + + /* allocate txn and pass to write_end for updating inode i_size/times */ + cb = kzalloc(sizeof(struct rpdfs_write_cb), GFP_NOFS); + if (!cb) { + ret = -ENOMEM; + goto out; + } + + folio = __filemap_get_folio(mapping, pos >> PAGE_SHIFT, FGP_WRITEBEGIN, + mapping_gfp_mask(mapping)); + if (IS_ERR(folio)) { + ret = PTR_ERR(folio); + goto out; + } + + offset = offset_in_folio(folio, pos); + /* XXX use pos/len/offset to figure out when it is an overwrite */ + rbaf = RBAF_WRITE; + + ret = get_or_alloc_data_block(rfi, &cb->txn, inode, cb->inode_hnd, lblk, rbaf, &cb->blk_hnd); + if (ret < 0) + goto out; + + *foliop = folio; + *fsdata = cb; + ret = len; +out: + if (ret < 0) { + if (cb) { + rpdfs_txn_finish(rfi, &cb->txn); + kfree(cb); + } + if (!IS_ERR_OR_NULL(folio)) { + folio_unlock(folio); + folio_put(folio); + } + *foliop = NULL; + } + return ret; +} + +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 = mapping->host; + struct rpdfs_fs_info *rfi = RPDFS_INODE_FS(inode); + struct rpdfs_write_cb *cb = fsdata; + loff_t old_size = inode->i_size; + bool i_size_changed = false; + unsigned offset; + + offset = offset_in_folio(folio, pos); + /* + * Copy the data from the folio into the blcok. + * + * TODO: replace with writepages. + */ + memcpy(cb->blk_hnd->data + offset, folio_address(folio) + offset, len); + + rpdfs_prd("copied %4s len %d to %4s", (char *) folio_address(folio) + offset, len, + (char *) cb->blk_hnd->data + offset); + + if (pos + copied > inode->i_size) { + i_size_write(inode, pos + copied); + i_size_changed = true; + } + + folio_mark_dirty(folio); + folio_unlock(folio); + folio_put(folio); + + if (old_size < pos) + pagecache_isize_extended(inode, old_size, pos); + + /* mark inode dirty outside of folio lock for performance reasons */ + if (i_size_changed) + mark_inode_dirty(inode); + + /* finalize changes to the inode and block */ + rpdfs_block_release(rfi, &cb->blk_hnd); + rpdfs_inode_update(rfi, inode, cb->inode_hnd); + rpdfs_block_release(rfi, &cb->inode_hnd); + + rpdfs_txn_finish(rfi, &cb->txn); + kfree(cb); + + rpdfs_prd("i_size %lld copied %d", i_size_read(inode), copied); + + return copied; +} + +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 fd0913e0e4b6..ec7f1e64ba18 100644 --- a/fs/rpdfs/inode.c +++ b/fs/rpdfs/inode.c @@ -163,6 +163,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