Change the iomap ->page_prepare() handler to get and return a locked folio instead of doing that in iomap_write_begin(). This allows to recover from out-of-memory situations in ->page_prepare(), which eliminates the corresponding error handling code in iomap_write_begin(). The ->page_done() handler is now not called with a NULL folio anymore. Filesystems are expected to use the iomap_folio_prepare() helper for getting locked folios in their ->page_prepare() handlers. Signed-off-by: Andreas Gruenbacher <agruenba@xxxxxxxxxx> --- fs/gfs2/bmap.c | 16 +++++++++++++--- fs/iomap/buffered-io.c | 21 +++++++++------------ include/linux/iomap.h | 9 +++++---- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/fs/gfs2/bmap.c b/fs/gfs2/bmap.c index 11115fce68cb..cd5984d3ba50 100644 --- a/fs/gfs2/bmap.c +++ b/fs/gfs2/bmap.c @@ -959,15 +959,25 @@ static int __gfs2_iomap_get(struct inode *inode, loff_t pos, loff_t length, goto out; } -static int gfs2_iomap_page_prepare(struct inode *inode, loff_t pos, - unsigned len) +static struct folio * +gfs2_iomap_page_prepare(struct iomap_iter *iter, loff_t pos, unsigned len) { + struct inode *inode = iter->inode; unsigned int blockmask = i_blocksize(inode) - 1; struct gfs2_sbd *sdp = GFS2_SB(inode); unsigned int blocks; + struct folio *folio; + int status; blocks = ((pos & blockmask) + len + blockmask) >> inode->i_blkbits; - return gfs2_trans_begin(sdp, RES_DINODE + blocks, 0); + status = gfs2_trans_begin(sdp, RES_DINODE + blocks, 0); + if (status) + return ERR_PTR(status); + + folio = iomap_folio_prepare(iter, pos); + if (!folio) + gfs2_trans_end(sdp); + return folio; } static void gfs2_iomap_page_done(struct inode *inode, loff_t pos, diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c index f0f167ca1b2e..6b7c1a10b8ec 100644 --- a/fs/iomap/buffered-io.c +++ b/fs/iomap/buffered-io.c @@ -602,7 +602,7 @@ static void iomap_folio_done(struct iomap_iter *iter, loff_t pos, size_t ret, if (page_ops && page_ops->page_done) { page_ops->page_done(iter->inode, pos, ret, folio); - } else if (folio) { + } else { folio_unlock(folio); folio_put(folio); } @@ -635,17 +635,14 @@ static int iomap_write_begin(struct iomap_iter *iter, loff_t pos, if (!mapping_large_folio_support(iter->inode->i_mapping)) len = min_t(size_t, len, PAGE_SIZE - offset_in_page(pos)); - if (page_ops && page_ops->page_prepare) { - status = page_ops->page_prepare(iter->inode, pos, len); - if (status) - return status; - } - - folio = iomap_folio_prepare(iter, pos); - if (!folio) { - status = (iter->flags & IOMAP_NOWAIT) ? -EAGAIN : -ENOMEM; - iomap_folio_done(iter, pos, 0, NULL); - return status; + if (page_ops && page_ops->page_prepare) + folio = page_ops->page_prepare(iter, pos, len); + else + folio = iomap_folio_prepare(iter, pos); + if (IS_ERR_OR_NULL(folio)) { + if (!folio) + return (iter->flags & IOMAP_NOWAIT) ? -EAGAIN : -ENOMEM; + return PTR_ERR(folio); } /* diff --git a/include/linux/iomap.h b/include/linux/iomap.h index 0bf16ef22d81..c74ab8c53b47 100644 --- a/include/linux/iomap.h +++ b/include/linux/iomap.h @@ -13,6 +13,7 @@ struct address_space; struct fiemap_extent_info; struct inode; +struct iomap_iter; struct iomap_dio; struct iomap_writepage_ctx; struct iov_iter; @@ -131,12 +132,12 @@ static inline bool iomap_inline_data_valid(const struct iomap *iomap) * associated with them. * * When page_prepare succeeds, page_done will always be called to do any - * cleanup work necessary. In that page_done call, @folio will be NULL if the - * associated folio could not be obtained. When folio is not NULL, page_done - * is responsible for unlocking and putting the folio. + * cleanup work necessary. page_done is responsible for unlocking and putting + * @folio. */ struct iomap_page_ops { - int (*page_prepare)(struct inode *inode, loff_t pos, unsigned len); + struct folio *(*page_prepare)(struct iomap_iter *iter, loff_t pos, + unsigned len); void (*page_done)(struct inode *inode, loff_t pos, unsigned copied, struct folio *folio); -- 2.38.1