From: Sasha Levin <sashal@kernel.org>
To: stable@vger.kernel.org
Cc: Jiaming Zhang <r772577952@gmail.com>,
Ryusuke Konishi <konishi.ryusuke@gmail.com>,
Viacheslav Dubeyko <slava@dubeyko.com>,
Sasha Levin <sashal@kernel.org>
Subject: [PATCH 6.6.y] nilfs2: force clear dirty state when restoring from the shadow map
Date: Tue, 10 Nov 2026 14:41:34 -0500 [thread overview]
Message-ID: <20261110194015.3594251-1-sashal@kernel.org> (raw)
In-Reply-To: <20261107052932-failed-6.6-cb6702e63e0a@kernel.org>
From: Jiaming Zhang <r772577952@gmail.com>
[ Upstream commit cb6702e63e0a16afd83875294e5ce53becdd1b1f ]
When garbage collection fails, nilfs2 rolls the DAT metadata file's page
cache back to a shadow copy taken before GC started.
nilfs_clear_dirty_pages() drops the dirty state of the folios in the DAT
cache, then nilfs_copy_back_pages() overwrites them with the saved
contents and warns if one is still dirty:
/* overwrite existing folio in the destination cache */
WARN_ON(folio_test_dirty(dfolio));
nilfs_clear_dirty_pages() used to clear the dirty state unconditionally,
which is safe here because the rollback runs with the log writer
stopped, so nothing else can dirty the cache while it runs. The same
helper is also used when writeback finds dirty folios after the
filesystem has degraded to read-only, where it does run concurrently
with the log writer, so commit ca76bb226bf4 ("nilfs2: do not force clear
folio if buffer is referenced") made nilfs_clear_folio_dirty() skip a
folio if any of its buffer heads is busy. The rollback caller shares
that helper, so its clearing step can now return with a folio still
dirty.
A DAT folio can hold a busy buffer head without anyone modifying the
folio: nilfs_mdt_read_block() submits read-ahead for the blocks
following the one it was asked for and waits only for the first, so the
read-ahead buffers are still locked when it returns. With a block size
smaller than the page size, a locked read-ahead buffer can share a folio
with a block that GC dirtied and keep the whole folio dirty past the
clearing step.
Add a force flag to nilfs_clear_dirty_pages() and
nilfs_clear_folio_dirty() that skips the busy buffer check, and set it
in the two calls from nilfs_mdt_restore_from_shadow_map(). The
read-only fallback callers keep passing false, so that commit still
protects them and the WARN_ON() is left alone.
Fixes: ca76bb226bf4 ("nilfs2: do not force clear folio if buffer is referenced")
Closes: https://lore.kernel.org/lkml/CANypQFZSYrtcshnUzOPiqatyLd-M8_OReOewQoAi_V5yY0dTtg@mail.gmail.com/
Cc: stable@vger.kernel.org
Suggested-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Jiaming Zhang <r772577952@gmail.com>
Acked-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
[ sashal: adapted nilfs_clear_folio_dirty() to nilfs_clear_dirty_page() while preserving the separate silent argument ]
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/nilfs2/inode.c | 4 ++--
fs/nilfs2/mdt.c | 6 +++---
fs/nilfs2/page.c | 51 ++++++++++++++++++++++++++++-------------------
fs/nilfs2/page.h | 5 +++--
4 files changed, 38 insertions(+), 28 deletions(-)
diff --git a/fs/nilfs2/inode.c b/fs/nilfs2/inode.c
index 410da542a3b90..ec5d3e82f52e0 100644
--- a/fs/nilfs2/inode.c
+++ b/fs/nilfs2/inode.c
@@ -162,7 +162,7 @@ static int nilfs_writepages(struct address_space *mapping,
int err = 0;
if (sb_rdonly(inode->i_sb)) {
- nilfs_clear_dirty_pages(mapping, false);
+ nilfs_clear_dirty_pages(mapping, false, false);
return -EROFS;
}
@@ -185,7 +185,7 @@ static int nilfs_writepage(struct page *page, struct writeback_control *wbc)
* have dirty pages that try to be flushed in background.
* So, here we simply discard this dirty page.
*/
- nilfs_clear_dirty_page(page, false);
+ nilfs_clear_dirty_page(page, false, false);
unlock_page(page);
return -EROFS;
}
diff --git a/fs/nilfs2/mdt.c b/fs/nilfs2/mdt.c
index 75a2ed5ee6e09..c0c1e217bd384 100644
--- a/fs/nilfs2/mdt.c
+++ b/fs/nilfs2/mdt.c
@@ -411,7 +411,7 @@ nilfs_mdt_write_page(struct page *page, struct writeback_control *wbc)
* have dirty pages that try to be flushed in background.
* So, here we simply discard this dirty page.
*/
- nilfs_clear_dirty_page(page, false);
+ nilfs_clear_dirty_page(page, false, false);
unlock_page(page);
return -EROFS;
}
@@ -634,10 +634,10 @@ void nilfs_mdt_restore_from_shadow_map(struct inode *inode)
if (mi->mi_palloc_cache)
nilfs_palloc_clear_cache(inode);
- nilfs_clear_dirty_pages(inode->i_mapping, true);
+ nilfs_clear_dirty_pages(inode->i_mapping, true, true);
nilfs_copy_back_pages(inode->i_mapping, shadow->inode->i_mapping);
- nilfs_clear_dirty_pages(ii->i_assoc_inode->i_mapping, true);
+ nilfs_clear_dirty_pages(ii->i_assoc_inode->i_mapping, true, true);
nilfs_copy_back_pages(ii->i_assoc_inode->i_mapping,
NILFS_I(shadow->inode)->i_assoc_inode->i_mapping);
diff --git a/fs/nilfs2/page.c b/fs/nilfs2/page.c
index 2dbf74936a7ed..c664cf867e9d6 100644
--- a/fs/nilfs2/page.c
+++ b/fs/nilfs2/page.c
@@ -370,8 +370,10 @@ void nilfs_copy_back_pages(struct address_space *dmap,
* nilfs_clear_dirty_pages - discard dirty pages in address space
* @mapping: address space with dirty pages for discarding
* @silent: suppress [true] or print [false] warning messages
+ * @force: whether to clear the dirty state regardless of busy buffer heads
*/
-void nilfs_clear_dirty_pages(struct address_space *mapping, bool silent)
+void nilfs_clear_dirty_pages(struct address_space *mapping, bool silent,
+ bool force)
{
struct folio_batch fbatch;
unsigned int i;
@@ -392,7 +394,7 @@ void nilfs_clear_dirty_pages(struct address_space *mapping, bool silent)
* was acquired. Skip processing in that case.
*/
if (likely(folio->mapping == mapping))
- nilfs_clear_dirty_page(&folio->page, silent);
+ nilfs_clear_dirty_page(&folio->page, silent, force);
folio_unlock(folio);
}
@@ -405,13 +407,16 @@ void nilfs_clear_dirty_pages(struct address_space *mapping, bool silent)
* nilfs_clear_dirty_page - discard dirty page
* @page: dirty page that will be discarded
* @silent: suppress [true] or print [false] warning messages
+ * @force: whether to clear the states regardless of busy buffer heads
*
* nilfs_clear_dirty_page() clears working states including dirty state for
- * the page and its buffers. If the page has buffers, clear only if it is
- * confirmed that none of the buffer heads are busy (none have valid
- * references and none are locked).
+ * the page and its buffers. If the page has buffers and force is false,
+ * clear only if it is confirmed that none of the buffer heads are busy (none
+ * have valid references and none are locked). If force is true, the states
+ * are cleared unconditionally, the caller should guarantee that the page is
+ * not being modified concurrently.
*/
-void nilfs_clear_dirty_page(struct page *page, bool silent)
+void nilfs_clear_dirty_page(struct page *page, bool silent, bool force)
{
struct inode *inode = page->mapping->host;
struct super_block *sb = inode->i_sb;
@@ -429,24 +434,28 @@ void nilfs_clear_dirty_page(struct page *page, bool silent)
BIT(BH_Async_Write) | BIT(BH_NILFS_Volatile) |
BIT(BH_NILFS_Checked) | BIT(BH_NILFS_Redirected) |
BIT(BH_Delay));
- bool busy, invalidated = false;
+
+ if (!force) {
+ bool busy, invalidated = false;
recheck_buffers:
- busy = false;
- bh = head;
- do {
- if (atomic_read(&bh->b_count) | buffer_locked(bh)) {
- busy = true;
- break;
+ busy = false;
+ bh = head;
+ do {
+ if (atomic_read(&bh->b_count) |
+ buffer_locked(bh)) {
+ busy = true;
+ break;
+ }
+ } while (bh = bh->b_this_page, bh != head);
+
+ if (busy) {
+ if (invalidated)
+ return;
+ invalidate_bh_lrus();
+ invalidated = true;
+ goto recheck_buffers;
}
- } while (bh = bh->b_this_page, bh != head);
-
- if (busy) {
- if (invalidated)
- return;
- invalidate_bh_lrus();
- invalidated = true;
- goto recheck_buffers;
}
bh = head;
diff --git a/fs/nilfs2/page.h b/fs/nilfs2/page.h
index bff6257ef83bd..bce9a1b5c8f02 100644
--- a/fs/nilfs2/page.h
+++ b/fs/nilfs2/page.h
@@ -41,8 +41,9 @@ void nilfs_folio_bug(struct folio *);
int nilfs_copy_dirty_pages(struct address_space *, struct address_space *);
void nilfs_copy_back_pages(struct address_space *, struct address_space *);
-void nilfs_clear_dirty_page(struct page *, bool);
-void nilfs_clear_dirty_pages(struct address_space *, bool);
+void nilfs_clear_dirty_page(struct page *page, bool silent, bool force);
+void nilfs_clear_dirty_pages(struct address_space *mapping, bool silent,
+ bool force);
unsigned int nilfs_page_count_clean_buffers(struct page *, unsigned int,
unsigned int);
unsigned long nilfs_find_uncommitted_extent(struct inode *inode,
--
2.53.0
prev parent reply other threads:[~2026-11-10 19:41 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-07 10:29 FAILED: patch "[PATCH] nilfs2: force clear dirty state when restoring from the shadow map" failed to apply to 6.6-stable tree Sasha Levin
2026-11-10 19:41 ` Sasha Levin [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261110194015.3594251-1-sashal@kernel.org \
--to=sashal@kernel.org \
--cc=konishi.ryusuke@gmail.com \
--cc=r772577952@gmail.com \
--cc=slava@dubeyko.com \
--cc=stable@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox