Linux 6.18: replace write_cache_pages()

Linux 6.18 removed write_cache_pages() without a usable replacement.
Here we implement a minimal zpl_write_cache_pages() that find the dirty
pages within the mapping, gets them into the expected state and hands
them off to zfs_putpage(), which handles the rest.

Sponsored-by: https://despairlabs.com/sponsor/
Signed-off-by: Rob Norris <robn@despairlabs.com>
This commit is contained in:
Rob Norris 2025-09-12 09:31:35 +10:00 committed by Tony Hutter
parent 04d0f83f4e
commit 005c631499
4 changed files with 134 additions and 28 deletions

View File

@ -0,0 +1,58 @@
AC_DEFUN([ZFS_AC_KERNEL_SRC_WRITEPAGE_T], [
dnl #
dnl # 6.3 API change
dnl # The writepage_t function type now has its first argument as
dnl # struct folio* instead of struct page*
dnl #
ZFS_LINUX_TEST_SRC([writepage_t_folio], [
#include <linux/writeback.h>
static int putpage(struct folio *folio,
struct writeback_control *wbc, void *data)
{ return 0; }
writepage_t func = putpage;
],[])
])
AC_DEFUN([ZFS_AC_KERNEL_WRITEPAGE_T], [
AC_MSG_CHECKING([whether int (*writepage_t)() takes struct folio*])
ZFS_LINUX_TEST_RESULT([writepage_t_folio], [
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_WRITEPAGE_T_FOLIO, 1,
[int (*writepage_t)() takes struct folio*])
],[
AC_MSG_RESULT(no)
])
])
AC_DEFUN([ZFS_AC_KERNEL_SRC_WRITE_CACHE_PAGES], [
dnl #
dnl # 6.18 API change
dnl # write_cache_pages() has been removed.
dnl #
ZFS_LINUX_TEST_SRC([write_cache_pages], [
#include <linux/writeback.h>
], [
(void) write_cache_pages(NULL, NULL, NULL, NULL);
])
])
AC_DEFUN([ZFS_AC_KERNEL_WRITE_CACHE_PAGES], [
AC_MSG_CHECKING([whether write_cache_pages() is available])
ZFS_LINUX_TEST_RESULT([write_cache_pages], [
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_WRITE_CACHE_PAGES, 1,
[write_cache_pages() is available])
],[
AC_MSG_RESULT(no)
])
])
AC_DEFUN([ZFS_AC_KERNEL_SRC_WRITEBACK], [
ZFS_AC_KERNEL_SRC_WRITEPAGE_T
ZFS_AC_KERNEL_SRC_WRITE_CACHE_PAGES
])
AC_DEFUN([ZFS_AC_KERNEL_WRITEBACK], [
ZFS_AC_KERNEL_WRITEPAGE_T
ZFS_AC_KERNEL_WRITE_CACHE_PAGES
])

View File

@ -1,26 +0,0 @@
AC_DEFUN([ZFS_AC_KERNEL_SRC_WRITEPAGE_T], [
dnl #
dnl # 6.3 API change
dnl # The writepage_t function type now has its first argument as
dnl # struct folio* instead of struct page*
dnl #
ZFS_LINUX_TEST_SRC([writepage_t_folio], [
#include <linux/writeback.h>
static int putpage(struct folio *folio,
struct writeback_control *wbc, void *data)
{ return 0; }
writepage_t func = putpage;
],[])
])
AC_DEFUN([ZFS_AC_KERNEL_WRITEPAGE_T], [
AC_MSG_CHECKING([whether int (*writepage_t)() takes struct folio*])
ZFS_LINUX_TEST_RESULT([writepage_t_folio], [
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_WRITEPAGE_T_FOLIO, 1,
[int (*writepage_t)() takes struct folio*])
],[
AC_MSG_RESULT(no)
])
])

View File

@ -121,7 +121,7 @@ AC_DEFUN([ZFS_AC_KERNEL_TEST_SRC], [
ZFS_AC_KERNEL_SRC_IDMAP_MNT_API
ZFS_AC_KERNEL_SRC_IDMAP_NO_USERNS
ZFS_AC_KERNEL_SRC_IATTR_VFSID
ZFS_AC_KERNEL_SRC_WRITEPAGE_T
ZFS_AC_KERNEL_SRC_WRITEBACK
ZFS_AC_KERNEL_SRC_RECLAIMED
ZFS_AC_KERNEL_SRC_REGISTER_SYSCTL_TABLE
ZFS_AC_KERNEL_SRC_REGISTER_SYSCTL_SZ
@ -240,7 +240,7 @@ AC_DEFUN([ZFS_AC_KERNEL_TEST_RESULT], [
ZFS_AC_KERNEL_IDMAP_MNT_API
ZFS_AC_KERNEL_IDMAP_NO_USERNS
ZFS_AC_KERNEL_IATTR_VFSID
ZFS_AC_KERNEL_WRITEPAGE_T
ZFS_AC_KERNEL_WRITEBACK
ZFS_AC_KERNEL_RECLAIMED
ZFS_AC_KERNEL_REGISTER_SYSCTL_TABLE
ZFS_AC_KERNEL_REGISTER_SYSCTL_SZ

View File

@ -23,6 +23,7 @@
* Copyright (c) 2011, Lawrence Livermore National Security, LLC.
* Copyright (c) 2015 by Chunwei Chen. All rights reserved.
* Copyright (c) 2025, Klara, Inc.
* Copyright (c) 2025, Rob Norris <robn@despairlabs.com>
*/
@ -478,6 +479,7 @@ zpl_putpage(struct page *pp, struct writeback_control *wbc, void *data)
return (ret);
}
#ifdef HAVE_WRITE_CACHE_PAGES
#ifdef HAVE_WRITEPAGE_T_FOLIO
static int
zpl_putfolio(struct folio *pp, struct writeback_control *wbc, void *data)
@ -499,6 +501,78 @@ zpl_write_cache_pages(struct address_space *mapping,
#endif
return (result);
}
#else
static inline int
zpl_write_cache_pages(struct address_space *mapping,
struct writeback_control *wbc, void *data)
{
pgoff_t start = wbc->range_start >> PAGE_SHIFT;
pgoff_t end = wbc->range_end >> PAGE_SHIFT;
struct folio_batch fbatch;
folio_batch_init(&fbatch);
/*
* This atomically (-ish) tags all DIRTY pages in the range with
* TOWRITE, allowing users to continue dirtying or undirtying pages
* while we get on with writeback, without us treading on each other.
*/
tag_pages_for_writeback(mapping, start, end);
int err = 0;
unsigned int npages;
/*
* Grab references to the TOWRITE pages just flagged. This may not get
* all of them, so we do it in a loop until there are none left.
*/
while ((npages = filemap_get_folios_tag(mapping, &start, end,
PAGECACHE_TAG_TOWRITE, &fbatch)) != 0) {
/* Loop over each page and write it out. */
struct folio *folio;
while ((folio = folio_batch_next(&fbatch)) != NULL) {
folio_lock(folio);
/*
* If the folio has been remapped, or is no longer
* dirty, then there's nothing to do.
*/
if (folio->mapping != mapping ||
!folio_test_dirty(folio)) {
folio_unlock(folio);
continue;
}
/*
* If writeback is already in progress, wait for it to
* finish. We continue after this even if the page
* ends up clean; zfs_putpage() will skip it if no
* further work is required.
*/
while (folio_test_writeback(folio))
folio_wait_bit(folio, PG_writeback);
/*
* Write it out and collect any error. zfs_putpage()
* will clear the TOWRITE and DIRTY flags, and return
* with the page unlocked.
*/
int ferr = zpl_putpage(&folio->page, wbc, data);
if (err == 0 && ferr != 0)
err = ferr;
/* Housekeeping for the caller. */
wbc->nr_to_write -= folio_nr_pages(folio);
}
/* Release any remaining references on the batch. */
folio_batch_release(&fbatch);
}
return (err);
}
#endif
static int
zpl_writepages(struct address_space *mapping, struct writeback_control *wbc)