vfs-6.16-rc2.fixes

-----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCaD1hUAAKCRCRxhvAZXjc
 ohIBAQCJQYP7bzg+A7C7K6cA+5LwC1u4aZ424B5puIZrLiEEDQEAxQli95/rTIeE
 m2DWBDl5rMrKlfmpqGvjbbJldU75swo=
 =2EhD
 -----END PGP SIGNATURE-----

Merge tag 'vfs-6.16-rc2.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs

Pull vfs fixes from Christian Brauner:

 - Fix the AT_HANDLE_CONNECTABLE option so filesystems that don't know
   how to decode a connected non-dir dentry fail the request

 - Use repr(transparent) to ensure identical layout between the C and
   Rust implementation of struct file

 - Add a missing xas_pause() into the dax code employing
   wait_entry_unlocked_exclusive()

 - Fix FOP_DONTCACHE which we disabled for v6.15.

   A folio could get redirtied and/or scheduled for writeback after the
   initial dropbehind test. Change the test accordingly to handle these
   cases so we can re-enable FOP_DONTCACHE again

* tag 'vfs-6.16-rc2.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs:
  exportfs: require ->fh_to_parent() to encode connectable file handles
  rust: file: improve safety comments
  rust: file: mark `LocalFile` as `repr(transparent)`
  fs/dax: Fix "don't skip locked entries when scanning entries"
  iomap: don't lose folio dropbehind state for overwrites
  mm/filemap: unify dropbehind flag testing and clearing
  mm/filemap: unify read/write dropbehind naming
  Revert "Disable FOP_DONTCACHE for now due to bugs"
  mm/filemap: use filemap_end_dropbehind() for read invalidation
  mm/filemap: gate dropbehind invalidate on folio !dirty && !writeback
This commit is contained in:
Linus Torvalds 2025-06-02 12:49:16 -07:00
commit fcd0bb8e99
8 changed files with 68 additions and 24 deletions

View File

@ -257,7 +257,7 @@ static void *wait_entry_unlocked_exclusive(struct xa_state *xas, void *entry)
wq = dax_entry_waitqueue(xas, entry, &ewait.key); wq = dax_entry_waitqueue(xas, entry, &ewait.key);
prepare_to_wait_exclusive(wq, &ewait.wait, prepare_to_wait_exclusive(wq, &ewait.wait,
TASK_UNINTERRUPTIBLE); TASK_UNINTERRUPTIBLE);
xas_pause(xas); xas_reset(xas);
xas_unlock_irq(xas); xas_unlock_irq(xas);
schedule(); schedule();
finish_wait(wq, &ewait.wait); finish_wait(wq, &ewait.wait);

View File

@ -1691,6 +1691,8 @@ static int iomap_add_to_ioend(struct iomap_writepage_ctx *wpc,
ioend_flags |= IOMAP_IOEND_UNWRITTEN; ioend_flags |= IOMAP_IOEND_UNWRITTEN;
if (wpc->iomap.flags & IOMAP_F_SHARED) if (wpc->iomap.flags & IOMAP_F_SHARED)
ioend_flags |= IOMAP_IOEND_SHARED; ioend_flags |= IOMAP_IOEND_SHARED;
if (folio_test_dropbehind(folio))
ioend_flags |= IOMAP_IOEND_DONTCACHE;
if (pos == wpc->iomap.offset && (wpc->iomap.flags & IOMAP_F_BOUNDARY)) if (pos == wpc->iomap.offset && (wpc->iomap.flags & IOMAP_F_BOUNDARY))
ioend_flags |= IOMAP_IOEND_BOUNDARY; ioend_flags |= IOMAP_IOEND_BOUNDARY;

View File

@ -436,6 +436,25 @@ allocate_blocks:
return 0; return 0;
} }
static bool
xfs_ioend_needs_wq_completion(
struct iomap_ioend *ioend)
{
/* Changing inode size requires a transaction. */
if (xfs_ioend_is_append(ioend))
return true;
/* Extent manipulation requires a transaction. */
if (ioend->io_flags & (IOMAP_IOEND_UNWRITTEN | IOMAP_IOEND_SHARED))
return true;
/* Page cache invalidation cannot be done in irq context. */
if (ioend->io_flags & IOMAP_IOEND_DONTCACHE)
return true;
return false;
}
static int static int
xfs_submit_ioend( xfs_submit_ioend(
struct iomap_writepage_ctx *wpc, struct iomap_writepage_ctx *wpc,
@ -460,8 +479,7 @@ xfs_submit_ioend(
memalloc_nofs_restore(nofs_flag); memalloc_nofs_restore(nofs_flag);
/* send ioends that might require a transaction to the completion wq */ /* send ioends that might require a transaction to the completion wq */
if (xfs_ioend_is_append(ioend) || if (xfs_ioend_needs_wq_completion(ioend))
(ioend->io_flags & (IOMAP_IOEND_UNWRITTEN | IOMAP_IOEND_SHARED)))
ioend->io_bio.bi_end_io = xfs_end_bio; ioend->io_bio.bi_end_io = xfs_end_bio;
if (status) if (status)

View File

@ -314,6 +314,9 @@ static inline bool exportfs_can_decode_fh(const struct export_operations *nop)
static inline bool exportfs_can_encode_fh(const struct export_operations *nop, static inline bool exportfs_can_encode_fh(const struct export_operations *nop,
int fh_flags) int fh_flags)
{ {
if (!nop)
return false;
/* /*
* If a non-decodeable file handle was requested, we only need to make * If a non-decodeable file handle was requested, we only need to make
* sure that filesystem did not opt-out of encoding fid. * sure that filesystem did not opt-out of encoding fid.
@ -321,6 +324,13 @@ static inline bool exportfs_can_encode_fh(const struct export_operations *nop,
if (fh_flags & EXPORT_FH_FID) if (fh_flags & EXPORT_FH_FID)
return exportfs_can_encode_fid(nop); return exportfs_can_encode_fid(nop);
/*
* If a connectable file handle was requested, we need to make sure that
* filesystem can also decode connected file handles.
*/
if ((fh_flags & EXPORT_FH_CONNECTABLE) && !nop->fh_to_parent)
return false;
/* /*
* If a decodeable file handle was requested, we need to make sure that * If a decodeable file handle was requested, we need to make sure that
* filesystem can also decode file handles. * filesystem can also decode file handles.

View File

@ -2207,7 +2207,7 @@ struct file_operations {
/* Supports asynchronous lock callbacks */ /* Supports asynchronous lock callbacks */
#define FOP_ASYNC_LOCK ((__force fop_flags_t)(1 << 6)) #define FOP_ASYNC_LOCK ((__force fop_flags_t)(1 << 6))
/* File system supports uncached read/write buffered IO */ /* File system supports uncached read/write buffered IO */
#define FOP_DONTCACHE 0 /* ((__force fop_flags_t)(1 << 7)) */ #define FOP_DONTCACHE ((__force fop_flags_t)(1 << 7))
/* Wrap a directory iterator that needs exclusive inode access */ /* Wrap a directory iterator that needs exclusive inode access */
int wrap_directory_iterator(struct file *, struct dir_context *, int wrap_directory_iterator(struct file *, struct dir_context *,

View File

@ -377,13 +377,16 @@ sector_t iomap_bmap(struct address_space *mapping, sector_t bno,
#define IOMAP_IOEND_BOUNDARY (1U << 2) #define IOMAP_IOEND_BOUNDARY (1U << 2)
/* is direct I/O */ /* is direct I/O */
#define IOMAP_IOEND_DIRECT (1U << 3) #define IOMAP_IOEND_DIRECT (1U << 3)
/* is DONTCACHE I/O */
#define IOMAP_IOEND_DONTCACHE (1U << 4)
/* /*
* Flags that if set on either ioend prevent the merge of two ioends. * Flags that if set on either ioend prevent the merge of two ioends.
* (IOMAP_IOEND_BOUNDARY also prevents merges, but only one-way) * (IOMAP_IOEND_BOUNDARY also prevents merges, but only one-way)
*/ */
#define IOMAP_IOEND_NOMERGE_FLAGS \ #define IOMAP_IOEND_NOMERGE_FLAGS \
(IOMAP_IOEND_SHARED | IOMAP_IOEND_UNWRITTEN | IOMAP_IOEND_DIRECT) (IOMAP_IOEND_SHARED | IOMAP_IOEND_UNWRITTEN | IOMAP_IOEND_DIRECT | \
IOMAP_IOEND_DONTCACHE)
/* /*
* Structure for writeback I/O completions. * Structure for writeback I/O completions.

View File

@ -1589,13 +1589,30 @@ int folio_wait_private_2_killable(struct folio *folio)
} }
EXPORT_SYMBOL(folio_wait_private_2_killable); EXPORT_SYMBOL(folio_wait_private_2_killable);
static void filemap_end_dropbehind(struct folio *folio)
{
struct address_space *mapping = folio->mapping;
VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio);
if (folio_test_writeback(folio) || folio_test_dirty(folio))
return;
if (!folio_test_clear_dropbehind(folio))
return;
if (mapping)
folio_unmap_invalidate(mapping, folio, 0);
}
/* /*
* If folio was marked as dropbehind, then pages should be dropped when writeback * If folio was marked as dropbehind, then pages should be dropped when writeback
* completes. Do that now. If we fail, it's likely because of a big folio - * completes. Do that now. If we fail, it's likely because of a big folio -
* just reset dropbehind for that case and latter completions should invalidate. * just reset dropbehind for that case and latter completions should invalidate.
*/ */
static void folio_end_dropbehind_write(struct folio *folio) static void filemap_end_dropbehind_write(struct folio *folio)
{ {
if (!folio_test_dropbehind(folio))
return;
/* /*
* Hitting !in_task() should not happen off RWF_DONTCACHE writeback, * Hitting !in_task() should not happen off RWF_DONTCACHE writeback,
* but can happen if normal writeback just happens to find dirty folios * but can happen if normal writeback just happens to find dirty folios
@ -1604,8 +1621,7 @@ static void folio_end_dropbehind_write(struct folio *folio)
* invalidation in that case. * invalidation in that case.
*/ */
if (in_task() && folio_trylock(folio)) { if (in_task() && folio_trylock(folio)) {
if (folio->mapping) filemap_end_dropbehind(folio);
folio_unmap_invalidate(folio->mapping, folio, 0);
folio_unlock(folio); folio_unlock(folio);
} }
} }
@ -1620,8 +1636,6 @@ static void folio_end_dropbehind_write(struct folio *folio)
*/ */
void folio_end_writeback(struct folio *folio) void folio_end_writeback(struct folio *folio)
{ {
bool folio_dropbehind = false;
VM_BUG_ON_FOLIO(!folio_test_writeback(folio), folio); VM_BUG_ON_FOLIO(!folio_test_writeback(folio), folio);
/* /*
@ -1643,14 +1657,11 @@ void folio_end_writeback(struct folio *folio)
* reused before the folio_wake_bit(). * reused before the folio_wake_bit().
*/ */
folio_get(folio); folio_get(folio);
if (!folio_test_dirty(folio))
folio_dropbehind = folio_test_clear_dropbehind(folio);
if (__folio_end_writeback(folio)) if (__folio_end_writeback(folio))
folio_wake_bit(folio, PG_writeback); folio_wake_bit(folio, PG_writeback);
acct_reclaim_writeback(folio);
if (folio_dropbehind) filemap_end_dropbehind_write(folio);
folio_end_dropbehind_write(folio); acct_reclaim_writeback(folio);
folio_put(folio); folio_put(folio);
} }
EXPORT_SYMBOL(folio_end_writeback); EXPORT_SYMBOL(folio_end_writeback);
@ -2635,16 +2646,14 @@ static inline bool pos_same_folio(loff_t pos1, loff_t pos2, struct folio *folio)
return (pos1 >> shift == pos2 >> shift); return (pos1 >> shift == pos2 >> shift);
} }
static void filemap_end_dropbehind_read(struct address_space *mapping, static void filemap_end_dropbehind_read(struct folio *folio)
struct folio *folio)
{ {
if (!folio_test_dropbehind(folio)) if (!folio_test_dropbehind(folio))
return; return;
if (folio_test_writeback(folio) || folio_test_dirty(folio)) if (folio_test_writeback(folio) || folio_test_dirty(folio))
return; return;
if (folio_trylock(folio)) { if (folio_trylock(folio)) {
if (folio_test_clear_dropbehind(folio)) filemap_end_dropbehind(folio);
folio_unmap_invalidate(mapping, folio, 0);
folio_unlock(folio); folio_unlock(folio);
} }
} }
@ -2765,7 +2774,7 @@ put_folios:
for (i = 0; i < folio_batch_count(&fbatch); i++) { for (i = 0; i < folio_batch_count(&fbatch); i++) {
struct folio *folio = fbatch.folios[i]; struct folio *folio = fbatch.folios[i];
filemap_end_dropbehind_read(mapping, folio); filemap_end_dropbehind_read(folio);
folio_put(folio); folio_put(folio);
} }
folio_batch_init(&fbatch); folio_batch_init(&fbatch);

View File

@ -219,12 +219,13 @@ unsafe impl AlwaysRefCounted for File {
/// must be on the same thread as this file. /// must be on the same thread as this file.
/// ///
/// [`assume_no_fdget_pos`]: LocalFile::assume_no_fdget_pos /// [`assume_no_fdget_pos`]: LocalFile::assume_no_fdget_pos
#[repr(transparent)]
pub struct LocalFile { pub struct LocalFile {
inner: Opaque<bindings::file>, inner: Opaque<bindings::file>,
} }
// SAFETY: The type invariants guarantee that `LocalFile` is always ref-counted. This implementation // SAFETY: The type invariants guarantee that `LocalFile` is always ref-counted. This implementation
// makes `ARef<File>` own a normal refcount. // makes `ARef<LocalFile>` own a normal refcount.
unsafe impl AlwaysRefCounted for LocalFile { unsafe impl AlwaysRefCounted for LocalFile {
#[inline] #[inline]
fn inc_ref(&self) { fn inc_ref(&self) {
@ -235,7 +236,8 @@ unsafe impl AlwaysRefCounted for LocalFile {
#[inline] #[inline]
unsafe fn dec_ref(obj: ptr::NonNull<LocalFile>) { unsafe fn dec_ref(obj: ptr::NonNull<LocalFile>) {
// SAFETY: To call this method, the caller passes us ownership of a normal refcount, so we // SAFETY: To call this method, the caller passes us ownership of a normal refcount, so we
// may drop it. The cast is okay since `File` has the same representation as `struct file`. // may drop it. The cast is okay since `LocalFile` has the same representation as
// `struct file`.
unsafe { bindings::fput(obj.cast().as_ptr()) } unsafe { bindings::fput(obj.cast().as_ptr()) }
} }
} }
@ -273,7 +275,7 @@ impl LocalFile {
#[inline] #[inline]
pub unsafe fn from_raw_file<'a>(ptr: *const bindings::file) -> &'a LocalFile { pub unsafe fn from_raw_file<'a>(ptr: *const bindings::file) -> &'a LocalFile {
// SAFETY: The caller guarantees that the pointer is not dangling and stays valid for the // SAFETY: The caller guarantees that the pointer is not dangling and stays valid for the
// duration of 'a. The cast is okay because `File` is `repr(transparent)`. // duration of `'a`. The cast is okay because `LocalFile` is `repr(transparent)`.
// //
// INVARIANT: The caller guarantees that there are no problematic `fdget_pos` calls. // INVARIANT: The caller guarantees that there are no problematic `fdget_pos` calls.
unsafe { &*ptr.cast() } unsafe { &*ptr.cast() }
@ -347,7 +349,7 @@ impl File {
#[inline] #[inline]
pub unsafe fn from_raw_file<'a>(ptr: *const bindings::file) -> &'a File { pub unsafe fn from_raw_file<'a>(ptr: *const bindings::file) -> &'a File {
// SAFETY: The caller guarantees that the pointer is not dangling and stays valid for the // SAFETY: The caller guarantees that the pointer is not dangling and stays valid for the
// duration of 'a. The cast is okay because `File` is `repr(transparent)`. // duration of `'a`. The cast is okay because `File` is `repr(transparent)`.
// //
// INVARIANT: The caller guarantees that there are no problematic `fdget_pos` calls. // INVARIANT: The caller guarantees that there are no problematic `fdget_pos` calls.
unsafe { &*ptr.cast() } unsafe { &*ptr.cast() }