netfs: Fix potential deadlock in write-through mode

Fix netfs_advance_writethrough() to always unlock the supplied folio and to
mark it dirty if it isn't yet written to the end.  Unfortunately, it can't
be marked for writeback until the folio is done with as that may cause a
deadlock against mmapped reads and writes.

Even though it has been marked dirty, premature writeback can't occur as
the caller is holding both inode->i_rwsem (which will prevent concurrent
truncation, fallocation, DIO and other writes) and ictx->wb_lock (which
will cause flushing to wait and writeback to skip or wait).

Note that this may be easier to deal with once the queuing of folios is
split from the generation of subrequests.

Fixes: 288ace2f57 ("netfs: New writeback implementation")
Closes: https://sashiko.dev/#/patchset/20260427154639.180684-1-dhowells%40redhat.com
Signed-off-by: David Howells <dhowells@redhat.com>
Link: https://patch.msgid.link/20260512123404.719402-15-dhowells@redhat.com
cc: Paulo Alcantara <pc@manguebit.org>
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
David Howells 2026-05-12 13:33:51 +01:00 committed by Christian Brauner
parent 7b4dcf1b94
commit b6a4ae1634
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2

View File

@ -414,12 +414,7 @@ static int netfs_write_folio(struct netfs_io_request *wreq,
if (streamw)
netfs_issue_write(wreq, cache);
/* Flip the page to the writeback state and unlock. If we're called
* from write-through, then the page has already been put into the wb
* state.
*/
if (wreq->origin == NETFS_WRITEBACK)
folio_start_writeback(folio);
folio_start_writeback(folio);
folio_unlock(folio);
if (fgroup == NETFS_FOLIO_COPY_TO_CACHE) {
@ -647,29 +642,41 @@ int netfs_advance_writethrough(struct netfs_io_request *wreq, struct writeback_c
struct folio *folio, size_t copied, bool to_page_end,
struct folio **writethrough_cache)
{
int ret;
_enter("R=%x ic=%zu ws=%u cp=%zu tp=%u",
wreq->debug_id, wreq->buffer.iter.count, wreq->wsize, copied, to_page_end);
if (!*writethrough_cache) {
if (folio_test_dirty(folio))
/* Sigh. mmap. */
folio_clear_dirty_for_io(folio);
/* The folio is locked. */
if (*writethrough_cache != folio) {
if (*writethrough_cache) {
/* Did the folio get moved? */
folio_put(*writethrough_cache);
*writethrough_cache = NULL;
}
/* We can make multiple writes to the folio... */
folio_start_writeback(folio);
if (wreq->len == 0)
trace_netfs_folio(folio, netfs_folio_trace_wthru);
else
trace_netfs_folio(folio, netfs_folio_trace_wthru_plus);
*writethrough_cache = folio;
folio_get(folio);
}
wreq->len += copied;
if (!to_page_end)
return 0;
if (!to_page_end) {
folio_mark_dirty(folio);
folio_unlock(folio);
return 0;
}
ret = netfs_write_folio(wreq, wbc, folio);
folio_put(*writethrough_cache);
*writethrough_cache = NULL;
return netfs_write_folio(wreq, wbc, folio);
wreq->submitted = wreq->len;
return ret;
}
/*
@ -683,8 +690,12 @@ ssize_t netfs_end_writethrough(struct netfs_io_request *wreq, struct writeback_c
_enter("R=%x", wreq->debug_id);
if (writethrough_cache)
if (writethrough_cache) {
folio_lock(writethrough_cache);
netfs_write_folio(wreq, wbc, writethrough_cache);
folio_put(writethrough_cache);
wreq->submitted = wreq->len;
}
netfs_end_issue_write(wreq);