Commit 6edc20078ad0 for kernel

commit 6edc20078ad0b05ab2dc2693965d373628d65f80
Merge: 9e7e66334583 7d87a5a284bb
Author: Linus Torvalds <torvalds@linux-foundation.org>
Date:   Thu Jun 18 08:50:52 2026 -0700

    Merge tag 'fuse-update-7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse

    Pull fuse updates from Miklos Szeredi:

     - Fix lots of bugs, most from the late 6.x era, but some going back
       to 2.6.x

     - Add subsystems (io-uring, passthrough) and respective maintainers
       (Bernd, Joanne and Amir)

     - Separate transport and fs layers (Miklos)

     - Don't block on cat /dev/fuse (Joanne)

     - Perform some refactoring in fuse-uring (Joanne)

     - Don't use bounce-buffer for READDIR reply in virtio-fs (Matthew Ochs)

     - Clean up documentation (Randy)

     - Improve tracing (Amir)

     - Extend page cache invalidation after DIO (Cheng Ding)

     - Invalidate readdir cache on epoch change (Jun Wu)

     - Misc cleanups

    * tag 'fuse-update-7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse: (81 commits)
      fuse-uring: clear ent->fuse_req in commit_fetch error path
      fuse-uring: use named constants for io-uring iovec indices
      fuse-uring: refactor setting up copy state for payload copying
      fuse-uring: use enum types for header copying
      fuse-uring: refactor io-uring header copying from ring
      fuse-uring: refactor io-uring header copying to ring
      fuse-uring: separate next request fetching from sending logic
      fuse: invalidate readdir cache on epoch bump
      virtio-fs: avoid double-free on failed queue setup
      fuse: invalidate page cache after DIO and async DIO writes
      fuse: set ff->flock only on success
      fuse: clean up interrupt reading
      fuse: remove stray newline in fuse_dev_do_read()
      fuse: use READ_ONCE in fuse_chan_num_background()
      fuse: dax: Move long delayed work on system_dfl_long_wq
      fuse: add fuse_request_sent tracepoint
      fuse: Add SPDX ID lines to some files
      fuse: use QSTR() instead of QSTR_INIT() in fuse_get_dentry
      fuse: convert page array allocation to kcalloc()
      fuse: use current creds for backing files
      ...

diff --cc fs/fuse/notify.c
index 000000000000,f93d6fa05bf7..29578104ae6c
mode 000000,100644..100644
--- a/fs/fuse/notify.c
+++ b/fs/fuse/notify.c
@@@ -1,0 -1,434 +1,444 @@@
+ // SPDX-License-Identifier: GPL-2.0-only
+
+ #include "dev.h"
+ #include "fuse_i.h"
+ #include <linux/pagemap.h>
+
+ static int fuse_notify_poll(struct fuse_conn *fc, unsigned int size,
+ 			    struct fuse_copy_state *cs)
+ {
+ 	struct fuse_notify_poll_wakeup_out outarg;
+ 	int err;
+
+ 	if (size != sizeof(outarg))
+ 		return -EINVAL;
+
+ 	err = fuse_copy_one(cs, &outarg, sizeof(outarg));
+ 	if (err)
+ 		return err;
+
+ 	fuse_copy_finish(cs);
+ 	return fuse_notify_poll_wakeup(fc, &outarg);
+ }
+
+ static int fuse_notify_inval_inode(struct fuse_conn *fc, unsigned int size,
+ 				   struct fuse_copy_state *cs)
+ {
+ 	struct fuse_notify_inval_inode_out outarg;
+ 	int err;
+
+ 	if (size != sizeof(outarg))
+ 		return -EINVAL;
+
+ 	err = fuse_copy_one(cs, &outarg, sizeof(outarg));
+ 	if (err)
+ 		return err;
+ 	fuse_copy_finish(cs);
+
+ 	down_read(&fc->killsb);
+ 	err = fuse_reverse_inval_inode(fc, outarg.ino,
+ 				       outarg.off, outarg.len);
+ 	up_read(&fc->killsb);
+ 	return err;
+ }
+
+ static int fuse_notify_inval_entry(struct fuse_conn *fc, unsigned int size,
+ 				   struct fuse_copy_state *cs)
+ {
+ 	struct fuse_notify_inval_entry_out outarg;
+ 	int err;
+ 	char *buf;
+ 	struct qstr name;
+
+ 	if (size < sizeof(outarg))
+ 		return -EINVAL;
+
+ 	err = fuse_copy_one(cs, &outarg, sizeof(outarg));
+ 	if (err)
+ 		return err;
+
+ 	if (outarg.namelen > fc->name_max)
+ 		return -ENAMETOOLONG;
+
+ 	err = -EINVAL;
+ 	if (size != sizeof(outarg) + outarg.namelen + 1)
+ 		return -EINVAL;
+
+ 	buf = kzalloc(outarg.namelen + 1, GFP_KERNEL);
+ 	if (!buf)
+ 		return -ENOMEM;
+
+ 	name.name = buf;
+ 	name.len = outarg.namelen;
+ 	err = fuse_copy_one(cs, buf, outarg.namelen + 1);
+ 	if (err)
+ 		goto err;
+ 	fuse_copy_finish(cs);
+ 	buf[outarg.namelen] = 0;
+
+ 	down_read(&fc->killsb);
+ 	err = fuse_reverse_inval_entry(fc, outarg.parent, 0, &name, outarg.flags);
+ 	up_read(&fc->killsb);
+ err:
+ 	kfree(buf);
+ 	return err;
+ }
+
+ static int fuse_notify_delete(struct fuse_conn *fc, unsigned int size,
+ 			      struct fuse_copy_state *cs)
+ {
+ 	struct fuse_notify_delete_out outarg;
+ 	int err;
+ 	char *buf;
+ 	struct qstr name;
+
+ 	if (size < sizeof(outarg))
+ 		return -EINVAL;
+
+ 	err = fuse_copy_one(cs, &outarg, sizeof(outarg));
+ 	if (err)
+ 		return err;
+
+ 	if (outarg.namelen > fc->name_max)
+ 		return -ENAMETOOLONG;
+
+ 	if (size != sizeof(outarg) + outarg.namelen + 1)
+ 		return -EINVAL;
+
+ 	buf = kzalloc(outarg.namelen + 1, GFP_KERNEL);
+ 	if (!buf)
+ 		return -ENOMEM;
+
+ 	name.name = buf;
+ 	name.len = outarg.namelen;
+ 	err = fuse_copy_one(cs, buf, outarg.namelen + 1);
+ 	if (err)
+ 		goto err;
+ 	fuse_copy_finish(cs);
+ 	buf[outarg.namelen] = 0;
+
+ 	down_read(&fc->killsb);
+ 	err = fuse_reverse_inval_entry(fc, outarg.parent, outarg.child, &name, 0);
+ 	up_read(&fc->killsb);
+ err:
+ 	kfree(buf);
+ 	return err;
+ }
+
+ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size,
+ 			     struct fuse_copy_state *cs)
+ {
+ 	struct fuse_notify_store_out outarg;
+ 	struct inode *inode;
+ 	struct address_space *mapping;
+ 	u64 nodeid;
+ 	int err;
+ 	unsigned int num;
+ 	loff_t file_size;
+ 	loff_t pos;
+ 	loff_t end;
+
+ 	if (size < sizeof(outarg))
+ 		return -EINVAL;
+
+ 	err = fuse_copy_one(cs, &outarg, sizeof(outarg));
+ 	if (err)
+ 		return err;
+
+ 	if (size - sizeof(outarg) != outarg.size)
+ 		return -EINVAL;
+
+ 	if (outarg.offset >= MAX_LFS_FILESIZE)
+ 		return -EINVAL;
+
+ 	nodeid = outarg.nodeid;
+ 	pos = outarg.offset;
+ 	num = min(outarg.size, MAX_LFS_FILESIZE - pos);
+
+ 	down_read(&fc->killsb);
+
+ 	err = -ENOENT;
+ 	inode = fuse_ilookup(fc, nodeid,  NULL);
+ 	if (!inode)
+ 		goto out_up_killsb;
++	if (!S_ISREG(inode->i_mode)) {
++		err = -EINVAL;
++		goto out_iput;
++	}
+
+ 	mapping = inode->i_mapping;
+ 	file_size = i_size_read(inode);
+ 	end = pos + num;
+ 	if (end > file_size) {
+ 		file_size = end;
+ 		fuse_write_update_attr(inode, file_size, num);
+ 	}
+
+ 	while (num) {
+ 		struct folio *folio;
+ 		unsigned int folio_offset;
+ 		unsigned int nr_bytes;
+ 		pgoff_t index = pos >> PAGE_SHIFT;
+
+ 		folio = filemap_grab_folio(mapping, index);
+ 		err = PTR_ERR(folio);
+ 		if (IS_ERR(folio))
+ 			goto out_iput;
+
+ 		folio_offset = offset_in_folio(folio, pos);
+ 		nr_bytes = min(num, folio_size(folio) - folio_offset);
+
+ 		err = fuse_copy_folio(cs, &folio, folio_offset, nr_bytes, 0);
+ 		if (!folio_test_uptodate(folio) && !err && folio_offset == 0 &&
+ 		    (nr_bytes == folio_size(folio) || file_size == end)) {
+ 			folio_zero_segment(folio, nr_bytes, folio_size(folio));
+ 			folio_mark_uptodate(folio);
+ 		}
+ 		folio_unlock(folio);
+ 		folio_put(folio);
+
+ 		if (err)
+ 			goto out_iput;
+
+ 		pos += nr_bytes;
+ 		num -= nr_bytes;
+ 	}
+
+ 	err = 0;
+
+ out_iput:
+ 	iput(inode);
+ out_up_killsb:
+ 	up_read(&fc->killsb);
+ 	return err;
+ }
+
+ struct fuse_retrieve_args {
+ 	struct fuse_args_pages ap;
+ 	struct fuse_notify_retrieve_in inarg;
+ };
+
+ static void fuse_retrieve_end(struct fuse_args *args, int error)
+ {
+ 	struct fuse_retrieve_args *ra =
+ 		container_of(args, typeof(*ra), ap.args);
+
+ 	release_pages(ra->ap.folios, ra->ap.num_folios);
+ 	kfree(ra);
+ }
+
+ static int fuse_retrieve(struct fuse_mount *fm, struct inode *inode,
+ 			 struct fuse_notify_retrieve_out *outarg)
+ {
+ 	int err;
+ 	struct address_space *mapping = inode->i_mapping;
+ 	loff_t file_size;
+ 	unsigned int num;
+ 	unsigned int offset;
+ 	size_t total_len = 0;
+ 	unsigned int num_pages;
+ 	struct fuse_conn *fc = fm->fc;
+ 	struct fuse_retrieve_args *ra;
+ 	size_t args_size = sizeof(*ra);
+ 	struct fuse_args_pages *ap;
+ 	struct fuse_args *args;
+ 	loff_t pos = outarg->offset;
+
+ 	offset = offset_in_page(pos);
+ 	file_size = i_size_read(inode);
+
+ 	num = min(outarg->size, fc->max_write);
+ 	if (pos > file_size)
+ 		num = 0;
+ 	else if (num > file_size - pos)
+ 		num = file_size - pos;
+
+ 	num_pages = DIV_ROUND_UP(num + offset, PAGE_SIZE);
+ 	num_pages = min(num_pages, fc->max_pages);
+ 	num = min(num, num_pages << PAGE_SHIFT);
+
+ 	args_size += num_pages * (sizeof(ap->folios[0]) + sizeof(ap->descs[0]));
+
+ 	ra = kzalloc(args_size, GFP_KERNEL);
+ 	if (!ra)
+ 		return -ENOMEM;
+
+ 	ap = &ra->ap;
+ 	ap->folios = (void *) (ra + 1);
+ 	ap->descs = (void *) (ap->folios + num_pages);
+
+ 	args = &ap->args;
+ 	args->nodeid = outarg->nodeid;
+ 	args->opcode = FUSE_NOTIFY_REPLY;
+ 	args->in_numargs = 3;
+ 	args->in_pages = true;
+ 	args->end = fuse_retrieve_end;
+
+ 	while (num && ap->num_folios < num_pages) {
+ 		struct folio *folio;
+ 		unsigned int folio_offset;
+ 		unsigned int nr_bytes;
+ 		pgoff_t index = pos >> PAGE_SHIFT;
+
+ 		folio = filemap_get_folio(mapping, index);
+ 		if (IS_ERR(folio))
+ 			break;
++		if (!folio_test_uptodate(folio)) {
++			folio_put(folio);
++			break;
++		}
+
+ 		folio_offset = offset_in_folio(folio, pos);
+ 		nr_bytes = min(folio_size(folio) - folio_offset, num);
+
+ 		ap->folios[ap->num_folios] = folio;
+ 		ap->descs[ap->num_folios].offset = folio_offset;
+ 		ap->descs[ap->num_folios].length = nr_bytes;
+ 		ap->num_folios++;
+
+ 		pos += nr_bytes;
+ 		num -= nr_bytes;
+ 		total_len += nr_bytes;
+ 	}
+ 	ra->inarg.offset = outarg->offset;
+ 	ra->inarg.size = total_len;
+ 	fuse_set_zero_arg0(args);
+ 	args->in_args[1].size = sizeof(ra->inarg);
+ 	args->in_args[1].value = &ra->inarg;
+ 	args->in_args[2].size = total_len;
+
+ 	err = fuse_simple_notify_reply(fm, args, outarg->notify_unique);
+ 	if (err)
+ 		fuse_retrieve_end(args, err);
+
+ 	return err;
+ }
+
+ static int fuse_notify_retrieve(struct fuse_conn *fc, unsigned int size,
+ 				struct fuse_copy_state *cs)
+ {
+ 	struct fuse_notify_retrieve_out outarg;
+ 	struct fuse_mount *fm;
+ 	struct inode *inode;
+ 	u64 nodeid;
+ 	int err;
+
+ 	if (size != sizeof(outarg))
+ 		return -EINVAL;
+
+ 	err = fuse_copy_one(cs, &outarg, sizeof(outarg));
+ 	if (err)
+ 		return err;
+
+ 	fuse_copy_finish(cs);
+
+ 	if (outarg.offset >= MAX_LFS_FILESIZE)
+ 		return -EINVAL;
+
+ 	down_read(&fc->killsb);
+ 	err = -ENOENT;
+ 	nodeid = outarg.nodeid;
+
+ 	inode = fuse_ilookup(fc, nodeid, &fm);
+ 	if (inode) {
 -		err = fuse_retrieve(fm, inode, &outarg);
++		err = -EINVAL;
++		if (S_ISREG(inode->i_mode))
++			err = fuse_retrieve(fm, inode, &outarg);
+ 		iput(inode);
+ 	}
+ 	up_read(&fc->killsb);
+
+ 	return err;
+ }
+
+ static int fuse_notify_resend(struct fuse_conn *fc)
+ {
+ 	fuse_chan_resend(fc->chan);
+ 	return 0;
+ }
+
+ /*
+  * Increments the fuse connection epoch.  This will cause dentries and
+  * readdir caches from previous epochs to be invalidated.  Additionally,
+  * if inval_wq is set, a work queue is scheduled to trigger the invalidation.
+  */
+ static int fuse_notify_inc_epoch(struct fuse_conn *fc)
+ {
+ 	atomic_inc(&fc->epoch);
+ 	if (inval_wq)
+ 		schedule_work(&fc->epoch_work);
+
+ 	return 0;
+ }
+
+ static int fuse_notify_prune(struct fuse_conn *fc, unsigned int size,
+ 			     struct fuse_copy_state *cs)
+ {
+ 	struct fuse_notify_prune_out outarg;
+ 	const unsigned int batch = 512;
+ 	u64 *nodeids __free(kfree) = kmalloc(sizeof(u64) * batch, GFP_KERNEL);
+ 	unsigned int num, i;
+ 	int err;
+
+ 	if (!nodeids)
+ 		return -ENOMEM;
+
+ 	if (size < sizeof(outarg))
+ 		return -EINVAL;
+
+ 	err = fuse_copy_one(cs, &outarg, sizeof(outarg));
+ 	if (err)
+ 		return err;
+
+ 	if (size - sizeof(outarg) != array_size(outarg.count, sizeof(u64)))
+ 		return -EINVAL;
+
+ 	for (; outarg.count; outarg.count -= num) {
+ 		num = min(batch, outarg.count);
+ 		err = fuse_copy_one(cs, nodeids, num * sizeof(u64));
+ 		if (err)
+ 			return err;
+
+ 		scoped_guard(rwsem_read, &fc->killsb) {
+ 			for (i = 0; i < num; i++)
+ 				fuse_try_prune_one_inode(fc, nodeids[i]);
+ 		}
+ 	}
+ 	return 0;
+ }
+
+ int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
+ 		unsigned int size, struct fuse_copy_state *cs)
+ {
+ 	switch (code) {
+ 	case FUSE_NOTIFY_POLL:
+ 		return fuse_notify_poll(fc, size, cs);
+
+ 	case FUSE_NOTIFY_INVAL_INODE:
+ 		return fuse_notify_inval_inode(fc, size, cs);
+
+ 	case FUSE_NOTIFY_INVAL_ENTRY:
+ 		return fuse_notify_inval_entry(fc, size, cs);
+
+ 	case FUSE_NOTIFY_STORE:
+ 		return fuse_notify_store(fc, size, cs);
+
+ 	case FUSE_NOTIFY_RETRIEVE:
+ 		return fuse_notify_retrieve(fc, size, cs);
+
+ 	case FUSE_NOTIFY_DELETE:
+ 		return fuse_notify_delete(fc, size, cs);
+
+ 	case FUSE_NOTIFY_RESEND:
+ 		return fuse_notify_resend(fc);
+
+ 	case FUSE_NOTIFY_INC_EPOCH:
+ 		return fuse_notify_inc_epoch(fc);
+
+ 	case FUSE_NOTIFY_PRUNE:
+ 		return fuse_notify_prune(fc, size, cs);
+
+ 	default:
+ 		return -EINVAL;
+ 	}
+ }