Unverified Commit b6a4ae16 authored by David Howells's avatar David Howells Committed by Christian Brauner
Browse files

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: 288ace2f ("netfs: New writeback implementation")
Closes: https://sashiko.dev/#/patchset/20260427154639.180684-1-dhowells%40redhat.com


Signed-off-by: default avatarDavid 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: default avatarChristian Brauner <brauner@kernel.org>
parent 7b4dcf1b
Loading
Loading
Loading
Loading
+25 −14
Original line number Diff line number Diff line
@@ -414,11 +414,6 @@ 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_unlock(folio);

@@ -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)

	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);