FAILED: patch "[PATCH] ceph: fix writeback_count leak in write_folio_nounlock()" failed to apply to 6.6-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 6.6-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.6.y
git checkout FETCH_HEAD
git cherry-pick -x cbf59617cd715219e84c50d106a3d0e1e8ba054e
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026072947-gulp-passivism-4b27@gregkh' --subject-prefix 'PATCH 6.6.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From cbf59617cd715219e84c50d106a3d0e1e8ba054e Mon Sep 17 00:00:00 2001
From: Wentao Liang <[email protected]>
Date: Thu, 4 Jun 2026 02:19:51 +0000
Subject: [PATCH] ceph: fix writeback_count leak in write_folio_nounlock()
write_folio_nounlock() increments fsc->writeback_count to track in-flight writeback operations. On several error paths where the function returns early (folio lookup failure, snapshot context allocation failure, and writepages submission failure), the function returns without calling atomic_long_dec_return() to decrement the counter.
Each leaked increment keeps the counter above zero, which can prevent the filesystem from cleanly unmounting or suspending writes.
Add atomic_long_dec_return() calls on all error paths that currently return without decrementing the counter.
fs/ceph/addr.c +9 -0
@@ -790,6 +790,9 @@ static int write_folio_nounlock(struct folio *folio, ceph_wbc.truncate_size, true);if (IS_ERR (req)) {folio_redirty_for_writepage (wbc, folio); +if (atomic_long_dec_return (&fsc->writeback_count) < +CONGESTION_OFF_THRESH (fsc->mount_options->congestion_kb)) + fsc->write_congested = false;return PTR_ERR (req); }
@@ -809,6 +812,9 @@ static int write_folio_nounlock(struct folio *folio,folio_redirty_for_writepage (wbc, folio);folio_end_writeback (folio);ceph_osdc_put_request (req); +if (atomic_long_dec_return (&fsc->writeback_count) < +CONGESTION_OFF_THRESH (fsc->mount_options->congestion_kb)) + fsc->write_congested = false;return PTR_ERR (bounce_page); } }
@@ -847,6 +853,9 @@ static int write_folio_nounlock(struct folio *folio, ceph_vinop(inode), folio);folio_redirty_for_writepage (wbc, folio);folio_end_writeback (folio); +if (atomic_long_dec_return (&fsc->writeback_count) < +CONGESTION_OFF_THRESH (fsc->mount_options->congestion_kb)) + fsc->write_congested = false;return err; }if (err == -EBLOCKLISTED )
Sasha Levin
maintainerPATCH 1/5 ceph: Remove ceph_writepage()
From: "Matthew Wilcox (Oracle)" <[email protected]>
[ Upstream commit 19a288110435408fcf2cb274823f2a17d03caf9b ]
Ceph already has a writepages operation which is preferred over writepage in all situations except for page migration. By adding a migrate_folio operation, there will be no situations in which ->writepage should be called. filemap_migrate_folio() is an appropriate operation to use because the ceph data stored in folio->private does not contain any reference to the memory address of the folio.
Stable-dep-of: cbf59617cd71 ("ceph: fix writeback_count leak in write_folio_nounlock()")
--- fs/ceph/addr.c | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-)
fs/ceph/addr.c +1 -27
@@ -799,32 +799,6 @@ static int writepage_nounlock(struct page *page, struct writeback_control *wbc)return err; } -static int ceph_writepage (struct page *page,struct writeback_control *wbc) -{ -int err; -struct inode *inode = page->mapping->host; -BUG_ON (!inode); -ihold (inode); - -if (wbc->sync_mode ==WB_SYNC_NONE && -ceph_inode_to_fs_client (inode)->write_congested) { -redirty_page_for_writepage (wbc, page); -return AOP_WRITEPAGE_ACTIVATE ; - } - -wait_on_page_fscache (page); - - err =writepage_nounlock (page, wbc); -if (err == -ERESTARTSYS ) { -/* direct memory reclaimer was killed by SIGKILL. return 0 -* to prevent caller from setting mapping/page error */ - err =0 ; - } -unlock_page (page); -iput (inode); -return err; -} -/* * async writeback completion handler. *
@@ -1563,7 +1537,6 @@ static int ceph_write_end(struct file *file, struct address_space *mapping,const struct address_space_operations ceph_aops = { .read_folio = netfs_read_folio, .readahead = netfs_readahead, - .writepage = ceph_writepage, .writepages = ceph_writepages_start, .write_begin = ceph_write_begin, .write_end = ceph_write_end,
@@ -1571,6 +1544,7 @@ const struct address_space_operations ceph_aops = { .invalidate_folio = ceph_invalidate_folio, .release_folio = ceph_release_folio, .direct_IO = noop_direct_IO, + .migrate_folio = filemap_migrate_folio, };static void ceph_block_sigs (sigset_t *oldset)
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/5 ceph: Use a folio in ceph_page_mkwrite()
From: "Matthew Wilcox (Oracle)" <[email protected]>
[ Upstream commit 88a59bda3f3786107694a3f5fd7f9df421752c21 ]
Convert the passed page to a folio and use it
throughout ceph_page_mkwrite(). Removes the last call to page_mkwrite_check_truncate(), the last call to offset_in_thp() and one of the last calls to thp_size(). Saves a few calls to compound_head().
Stable-dep-of: cbf59617cd71 ("ceph: fix writeback_count leak in write_folio_nounlock()")
--- fs/ceph/addr.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-)
fs/ceph/addr.c +13 -13
@@ -1659,8 +1659,8 @@ static vm_fault_t ceph_page_mkwrite(struct vm_fault *vmf)struct ceph_inode_info *ci =ceph_inode (inode);struct ceph_file_info *fi = vma->vm_file->private_data;struct ceph_cap_flush *prealloc_cf; -struct page *page = vmf->page; -loff_t off =page_offset (page); +struct folio *folio =page_folio (vmf->page); +loff_t off =folio_pos (folio);loff_t size =i_size_read (inode);size_t len;int want, got, err;
@@ -1677,10 +1677,10 @@ static vm_fault_t ceph_page_mkwrite(struct vm_fault *vmf)sb_start_pagefault (inode->i_sb);ceph_block_sigs (&oldset); -if (off +thp_size (page) <= size) - len =thp_size (page); +if (off +folio_size (folio) <= size) + len =folio_size (folio);else - len =offset_in_thp (page, size); + len =offset_in_folio (folio, size);dout ("page_mkwrite %p %llx.%llx %llu~%zd getting caps i_size %llu\n" , inode,ceph_vinop (inode), off, len, size);
@@ -1697,30 +1697,30 @@ static vm_fault_t ceph_page_mkwrite(struct vm_fault *vmf)dout ("page_mkwrite %p %llu~%zd got cap refs on %s\n" , inode, off, len,ceph_cap_string (got)); -/* Update time before taking page lock */ +/* Update time before taking folio lock */ file_update_time (vma->vm_file);inode_inc_iversion_raw (inode); do { struct ceph_snap_context *snapc; - lock_page(page); + folio_lock(folio); - if (page_mkwrite_check_truncate(page, inode) < 0) { - unlock_page(page); + if (folio_mkwrite_check_truncate(folio, inode) < 0) { + folio_unlock(folio); ret = VM_FAULT_NOPAGE; break; } - snapc = ceph_find_incompatible(page); + snapc = ceph_find_incompatible(&folio->page); if (!snapc) { - /* success. we'll keep the page locked. */ - set_page_dirty(page); + /* success. we'll keep the folio locked. */ + folio_mark_dirty(folio); ret = VM_FAULT_LOCKED; break; } - unlock_page(page); + folio_unlock(folio); if (IS_ERR(snapc)) { ret = VM_FAULT_SIGBUS;
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 3/5 ceph: Convert ceph_find_incompatible() to take a folio
In reply to Sasha LevinFrom: "Matthew Wilcox (Oracle)" <[email protected]>
[ Upstream commit f9707a8b5b9d0a631e0a64eab5c3d2bb6d43758c ]
Both callers already have the folio. Pass it in and use it throughout. Removes some hidden calls to compound_head() and a reference to page->mapping.
Stable-dep-of: cbf59617cd71 ("ceph: fix writeback_count leak in write_folio_nounlock()")
--- fs/ceph/addr.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-)
fs/ceph/addr.c +16 -16
@@ -1391,23 +1391,23 @@ static int context_is_writeable_or_written(struct inode *inode,/** * ceph_find_incompatible - find an incompatible context and return it -* @page: page being dirtied +* @folio: folio being dirtied * -* We are only allowed to write into/dirty a page if the page is +* We are only allowed to write into/dirty a folio if the folio is * clean, or already dirty within the same snap context. Returns a * conflicting context if there is one, NULL if there isn't, or a * negative error code on other errors. * -* Must be called with page lock held. +* Must be called with folio lock held. */ static struct ceph_snap_context * -ceph_find_incompatible (struct page *page) +ceph_find_incompatible (struct folio *folio) { -struct inode *inode = page->mapping->host; +struct inode *inode = folio->mapping->host;struct ceph_inode_info *ci =ceph_inode (inode);if (ceph_inode_is_shutdown (inode)) { -dout (" page %p %llx:%llx is shutdown\n" , page, +dout (" folio %p %llx:%llx is shutdown\n" , folio,ceph_vinop (inode));return ERR_PTR (-ESTALE ); }
@@ -1415,29 +1415,29 @@ ceph_find_incompatible(struct page *page)for (;;) {struct ceph_snap_context *snapc, *oldest; -wait_on_page_writeback (page); +folio_wait_writeback (folio); - snapc =page_snap_context (page); + snapc =page_snap_context (&folio->page);if (!snapc || snapc == ci->i_head_snapc)break ;/* -* this page is already dirty in another (older) snap +* this folio is already dirty in another (older) snap * context! is it writeable now? */ oldest =get_oldest_context (inode,NULL ,NULL );if (snapc->seq > oldest->seq) {/* not writeable -- return it for the caller to deal with */ ceph_put_snap_context (oldest); -dout (" page %p snapc %p not current or oldest\n" , page, snapc); +dout (" folio %p snapc %p not current or oldest\n" , folio, snapc);return ceph_get_snap_context (snapc); }ceph_put_snap_context (oldest); -/* yay, writeable, do it now (without dropping page lock) */ -dout (" page %p snapc %p not current, but oldest\n" , page, snapc); -if (clear_page_dirty_for_io (page)) { -int r =writepage_nounlock (page,NULL ); +/* yay, writeable, do it now (without dropping folio lock) */ +dout (" folio %p snapc %p not current, but oldest\n" , folio, snapc); +if (folio_clear_dirty_for_io (folio)) { +int r =writepage_nounlock (&folio->page,NULL );if (r <0 )return ERR_PTR (r); }
@@ -1452,7 +1452,7 @@ static int ceph_netfs_check_write_begin(struct file *file, loff_t pos, unsignedstruct ceph_inode_info *ci =ceph_inode (inode);struct ceph_snap_context *snapc; - snapc =ceph_find_incompatible (folio_page (*foliop,0 )); + snapc =ceph_find_incompatible (*foliop);if (snapc) {int r;
@@ -1712,7 +1712,7 @@ static vm_fault_t ceph_page_mkwrite(struct vm_fault *vmf)break ; } - snapc =ceph_find_incompatible (&folio->page); + snapc =ceph_find_incompatible (folio);if (!snapc) {/* success. we'll keep the folio locked. */ folio_mark_dirty (folio);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 4/5 ceph: Convert writepage_nounlock() to write_folio_nounlock()
In reply to Sasha LevinFrom: "Matthew Wilcox (Oracle)" <[email protected]>
[ Upstream commit 62171c16da6000811e172a76a1f73d132c4697e8 ]
Remove references to page->index, page->mapping, thp_size(), page_offset() and other page APIs in favour of their more efficient folio replacements.
Stable-dep-of: cbf59617cd71 ("ceph: fix writeback_count leak in write_folio_nounlock()")
--- fs/ceph/addr.c | 63 +++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 31 deletions(-)
fs/ceph/addr.c +32 -31
@@ -652,21 +652,22 @@ static u64 get_writepages_data_length(struct inode *inode, }/* -* Write a single page, but leave the page locked. +* Write a folio, but leave it locked. * * If we get a write error, mark the mapping for error, but still adjust the -* dirty page accounting (i.e., page is no longer dirty). +* dirty page accounting (i.e., folio is no longer dirty). */ -static int writepage_nounlock (struct page *page,struct writeback_control *wbc) +static int write_folio_nounlock (struct folio *folio, +struct writeback_control *wbc) { -struct folio *folio =page_folio (page); -struct inode *inode = page->mapping->host; +struct page *page = &folio->page; +struct inode *inode = folio->mapping->host;struct ceph_inode_info *ci =ceph_inode (inode);struct ceph_fs_client *fsc =ceph_inode_to_fs_client (inode);struct ceph_snap_context *snapc, *oldest; -loff_t page_off =page_offset (page); +loff_t page_off =folio_pos (folio);int err; -loff_t len =thp_size (page); +loff_t len =folio_size (folio);loff_t wlen;struct ceph_writeback_ctl ceph_wbc;struct ceph_osd_client *osdc = &fsc->client->osdc;
@@ -674,25 +675,25 @@ static int writepage_nounlock(struct page *page, struct writeback_control *wbc)bool caching =ceph_is_cache_enabled (inode);struct page *bounce_page =NULL ; -dout ("writepage %p idx %lu\n" , page, page->index); +dout ("writepage %p idx %lu\n" , folio, folio->index);if (ceph_inode_is_shutdown (inode))return -EIO ;/* verify this is a writeable snap context */ - snapc =page_snap_context (page); + snapc =page_snap_context (&folio->page);if (!snapc) { -dout ("writepage %p page %p not dirty?\n" , inode, page); +dout ("writepage %p folio %p not dirty?\n" , inode, folio);return 0 ; } oldest =get_oldest_context (inode, &ceph_wbc, snapc);if (snapc->seq > oldest->seq) { -dout ("writepage %p page %p snapc %p not writeable - noop\n" , - inode, page, snapc); +dout ("writepage %p folio %p snapc %p not writeable - noop\n" , + inode, folio, snapc);/* we should only noop if called by kswapd */ WARN_ON (!(current->flags &PF_MEMALLOC ));ceph_put_snap_context (oldest); -redirty_page_for_writepage (wbc, page); +folio_redirty_for_writepage (wbc, folio);return 0 ; }ceph_put_snap_context (oldest);
@@ -709,8 +710,8 @@ static int writepage_nounlock(struct page *page, struct writeback_control *wbc) len = ceph_wbc.i_size - page_off; wlen =IS_ENCRYPTED (inode) ?round_up (len,CEPH_FSCRYPT_BLOCK_SIZE ) : len; -dout ("writepage %p page %p index %lu on %llu~%llu snapc %p seq %lld\n" , - inode, page, page->index, page_off, wlen, snapc, snapc->seq); +dout ("writepage %p folio %p index %lu on %llu~%llu snapc %p seq %lld\n" , + inode, folio, folio->index, page_off, wlen, snapc, snapc->seq);if (atomic_long_inc_return (&fsc->writeback_count) >CONGESTION_ON_THRESH (fsc->mount_options->congestion_kb))
@@ -722,32 +723,32 @@ static int writepage_nounlock(struct page *page, struct writeback_control *wbc) ceph_wbc.truncate_seq, ceph_wbc.truncate_size, true);if (IS_ERR (req)) { -redirty_page_for_writepage (wbc, page); +folio_redirty_for_writepage (wbc, folio);return PTR_ERR (req); }if (wlen < len) len = wlen; -set_page_writeback (page); +folio_start_writeback (folio);if (caching) -ceph_set_page_fscache (page); +ceph_set_page_fscache (&folio->page);ceph_fscache_write_to_cache (inode, page_off, len, caching);if (IS_ENCRYPTED (inode)) { - bounce_page =fscrypt_encrypt_pagecache_blocks (page, + bounce_page =fscrypt_encrypt_pagecache_blocks (&folio->page,CEPH_FSCRYPT_BLOCK_SIZE ,0 ,GFP_NOFS );if (IS_ERR (bounce_page)) { -redirty_page_for_writepage (wbc, page); -end_page_writeback (page); +folio_redirty_for_writepage (wbc, folio); +folio_end_writeback (folio);ceph_osdc_put_request (req);return PTR_ERR (bounce_page); } }/* it may be a short write due to an object boundary */ -WARN_ON_ONCE (len >thp_size (page)); +WARN_ON_ONCE (len >folio_size (folio));osd_req_op_extent_osd_data_pages (req,0 , bounce_page ? &bounce_page : &page, wlen,0 , false, false);
@@ -771,24 +772,24 @@ static int writepage_nounlock(struct page *page, struct writeback_control *wbc) wbc = &tmp_wbc;if (err == -ERESTARTSYS ) {/* killed by SIGKILL */ -dout ("writepage interrupted page %p\n" , page); -redirty_page_for_writepage (wbc, page); -end_page_writeback (page); +dout ("writepage interrupted folio %p\n" , folio); +folio_redirty_for_writepage (wbc, folio); +folio_end_writeback (folio);return err; }if (err == -EBLOCKLISTED ) fsc->blocklisted = true; -dout ("writepage setting page/mapping error %d %p\n" , - err, page); +dout ("writepage setting mapping error %d %p\n" , + err, folio);mapping_set_error (&inode->i_data, err); wbc->pages_skipped++; }else { -dout ("writepage cleaned page %p\n" , page); +dout ("writepage cleaned folio %p\n" , folio); err =0 ;/* vfs expects us to return 0 */ } - oldest =detach_page_private (page); + oldest =folio_detach_private (folio);WARN_ON_ONCE (oldest != snapc); -end_page_writeback (page); +folio_end_writeback (folio);ceph_put_wrbuffer_cap_refs (ci,1 , snapc);ceph_put_snap_context (snapc);/* page's reference */
@@ -1437,7 +1438,7 @@ ceph_find_incompatible(struct folio *folio)/* yay, writeable, do it now (without dropping folio lock) */ dout (" folio %p snapc %p not current, but oldest\n" , folio, snapc);if (folio_clear_dirty_for_io (folio)) { -int r =writepage_nounlock (&folio->page,NULL ); +int r =write_folio_nounlock (folio,NULL );if (r <0 )return ERR_PTR (r); }
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 5/5 ceph: fix writeback_count leak in write_folio_nounlock()
In reply to Sasha LevinFrom: Wentao Liang <[email protected]>
[ Upstream commit cbf59617cd715219e84c50d106a3d0e1e8ba054e ]
write_folio_nounlock() increments fsc->writeback_count to track in-flight writeback operations. On several error paths where the function returns early (folio lookup failure, snapshot context allocation failure, and writepages submission failure), the function returns without calling atomic_long_dec_return() to decrement the counter.
Each leaked increment keeps the counter above zero, which can prevent the filesystem from cleanly unmounting or suspending writes.
Add atomic_long_dec_return() calls on all error paths that currently return without decrementing the counter.
--- fs/ceph/addr.c | 9 +++++++++ 1 file changed, 9 insertions(+)
fs/ceph/addr.c +9 -0
@@ -724,6 +724,9 @@ static int write_folio_nounlock(struct folio *folio, ceph_wbc.truncate_size, true);if (IS_ERR (req)) {folio_redirty_for_writepage (wbc, folio); +if (atomic_long_dec_return (&fsc->writeback_count) < +CONGESTION_OFF_THRESH (fsc->mount_options->congestion_kb)) + fsc->write_congested = false;return PTR_ERR (req); }
@@ -743,6 +746,9 @@ static int write_folio_nounlock(struct folio *folio,folio_redirty_for_writepage (wbc, folio);folio_end_writeback (folio);ceph_osdc_put_request (req); +if (atomic_long_dec_return (&fsc->writeback_count) < +CONGESTION_OFF_THRESH (fsc->mount_options->congestion_kb)) + fsc->write_congested = false;return PTR_ERR (bounce_page); } }
@@ -775,6 +781,9 @@ static int write_folio_nounlock(struct folio *folio,dout ("writepage interrupted folio %p\n" , folio);folio_redirty_for_writepage (wbc, folio);folio_end_writeback (folio); +if (atomic_long_dec_return (&fsc->writeback_count) < +CONGESTION_OFF_THRESH (fsc->mount_options->congestion_kb)) + fsc->write_congested = false;return err; }if (err == -EBLOCKLISTED )
Signature · 1 line
2.53.0