From: "Darrick J. Wong" <djwong@kernel.org>
To: Miklos Szeredi <mszeredi@redhat.com>
Cc: linux-fsdevel@vger.kernel.org,
Joanne Koong <joannelkoong@gmail.com>,
John Groves <John@groves.net>, Bernd Schubert <bernd@bsbernd.com>
Subject: Re: [PATCH v2] fuse: allow synchronous FUSE_INIT
Date: Fri, 29 Aug 2025 08:43:17 -0700 [thread overview]
Message-ID: <20250829154317.GA1587915@frogsfrogsfrogs> (raw)
In-Reply-To: <20250827110004.584582-1-mszeredi@redhat.com>
On Wed, Aug 27, 2025 at 12:59:55PM +0200, Miklos Szeredi wrote:
> FUSE_INIT has always been asynchronous with mount. That means that the
> server processed this request after the mount syscall returned.
>
> This means that FUSE_INIT can't supply the root inode's ID, hence it
> currently has a hardcoded value. There are other limitations such as not
> being able to perform getxattr during mount, which is needed by selinux.
>
> To remove these limitations allow server to process FUSE_INIT while
> initializing the in-core super block for the fuse filesystem. This can
> only be done if the server is prepared to handle this, so add
> FUSE_DEV_IOC_SYNC_INIT ioctl, which
>
> a) lets the server know whether this feature is supported, returning
> ENOTTY othewrwise.
>
> b) lets the kernel know to perform a synchronous initialization
>
> The implementation is slightly tricky, since fuse_dev/fuse_conn are set up
> only during super block creation. This is solved by setting the private
> data of the fuse device file to a special value ((struct fuse_dev *) 1) and
> waiting for this to be turned into a proper fuse_dev before commecing with
> operations on the device file.
By the way, how is libfuse supposed to use SYNC_INIT? I gather libfuse
will have to start up the background fuse workers threads to listen for
events /before/ the actual mount() call?
--D
> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
> ---
> v2:
>
> - make fuse_send_init() perform sync/async sequence based on fc->sync_init
> (Joanne)
>
> fs/fuse/cuse.c | 3 +-
> fs/fuse/dev.c | 74 +++++++++++++++++++++++++++++----------
> fs/fuse/dev_uring.c | 4 +--
> fs/fuse/fuse_dev_i.h | 13 +++++--
> fs/fuse/fuse_i.h | 5 ++-
> fs/fuse/inode.c | 50 ++++++++++++++++++++------
> include/uapi/linux/fuse.h | 1 +
> 7 files changed, 115 insertions(+), 35 deletions(-)
>
> diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c
> index b39844d75a80..28c96961e85d 100644
> --- a/fs/fuse/cuse.c
> +++ b/fs/fuse/cuse.c
> @@ -52,6 +52,7 @@
> #include <linux/user_namespace.h>
>
> #include "fuse_i.h"
> +#include "fuse_dev_i.h"
>
> #define CUSE_CONNTBL_LEN 64
>
> @@ -547,7 +548,7 @@ static int cuse_channel_open(struct inode *inode, struct file *file)
> */
> static int cuse_channel_release(struct inode *inode, struct file *file)
> {
> - struct fuse_dev *fud = file->private_data;
> + struct fuse_dev *fud = __fuse_get_dev(file);
> struct cuse_conn *cc = fc_to_cc(fud->fc);
>
> /* remove from the conntbl, no more access from this point on */
> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
> index 8ac074414897..948f45c6e0ef 100644
> --- a/fs/fuse/dev.c
> +++ b/fs/fuse/dev.c
> @@ -1530,14 +1530,34 @@ static int fuse_dev_open(struct inode *inode, struct file *file)
> return 0;
> }
>
> +struct fuse_dev *fuse_get_dev(struct file *file)
> +{
> + struct fuse_dev *fud = __fuse_get_dev(file);
> + int err;
> +
> + if (likely(fud))
> + return fud;
> +
> + err = wait_event_interruptible(fuse_dev_waitq,
> + READ_ONCE(file->private_data) != FUSE_DEV_SYNC_INIT);
> + if (err)
> + return ERR_PTR(err);
> +
> + fud = __fuse_get_dev(file);
> + if (!fud)
> + return ERR_PTR(-EPERM);
> +
> + return fud;
> +}
> +
> static ssize_t fuse_dev_read(struct kiocb *iocb, struct iov_iter *to)
> {
> struct fuse_copy_state cs;
> struct file *file = iocb->ki_filp;
> struct fuse_dev *fud = fuse_get_dev(file);
>
> - if (!fud)
> - return -EPERM;
> + if (IS_ERR(fud))
> + return PTR_ERR(fud);
>
> if (!user_backed_iter(to))
> return -EINVAL;
> @@ -1557,8 +1577,8 @@ static ssize_t fuse_dev_splice_read(struct file *in, loff_t *ppos,
> struct fuse_copy_state cs;
> struct fuse_dev *fud = fuse_get_dev(in);
>
> - if (!fud)
> - return -EPERM;
> + if (IS_ERR(fud))
> + return PTR_ERR(fud);
>
> bufs = kvmalloc_array(pipe->max_usage, sizeof(struct pipe_buffer),
> GFP_KERNEL);
> @@ -2233,8 +2253,8 @@ static ssize_t fuse_dev_write(struct kiocb *iocb, struct iov_iter *from)
> struct fuse_copy_state cs;
> struct fuse_dev *fud = fuse_get_dev(iocb->ki_filp);
>
> - if (!fud)
> - return -EPERM;
> + if (IS_ERR(fud))
> + return PTR_ERR(fud);
>
> if (!user_backed_iter(from))
> return -EINVAL;
> @@ -2258,8 +2278,8 @@ static ssize_t fuse_dev_splice_write(struct pipe_inode_info *pipe,
> ssize_t ret;
>
> fud = fuse_get_dev(out);
> - if (!fud)
> - return -EPERM;
> + if (IS_ERR(fud))
> + return PTR_ERR(fud);
>
> pipe_lock(pipe);
>
> @@ -2343,7 +2363,7 @@ static __poll_t fuse_dev_poll(struct file *file, poll_table *wait)
> struct fuse_iqueue *fiq;
> struct fuse_dev *fud = fuse_get_dev(file);
>
> - if (!fud)
> + if (IS_ERR(fud))
> return EPOLLERR;
>
> fiq = &fud->fc->iq;
> @@ -2490,7 +2510,7 @@ void fuse_wait_aborted(struct fuse_conn *fc)
>
> int fuse_dev_release(struct inode *inode, struct file *file)
> {
> - struct fuse_dev *fud = fuse_get_dev(file);
> + struct fuse_dev *fud = __fuse_get_dev(file);
>
> if (fud) {
> struct fuse_conn *fc = fud->fc;
> @@ -2521,8 +2541,8 @@ static int fuse_dev_fasync(int fd, struct file *file, int on)
> {
> struct fuse_dev *fud = fuse_get_dev(file);
>
> - if (!fud)
> - return -EPERM;
> + if (IS_ERR(fud))
> + return PTR_ERR(fud);
>
> /* No locking - fasync_helper does its own locking */
> return fasync_helper(fd, file, on, &fud->fc->iq.fasync);
> @@ -2532,7 +2552,7 @@ static int fuse_device_clone(struct fuse_conn *fc, struct file *new)
> {
> struct fuse_dev *fud;
>
> - if (new->private_data)
> + if (__fuse_get_dev(new))
> return -EINVAL;
>
> fud = fuse_dev_alloc_install(fc);
> @@ -2563,7 +2583,7 @@ static long fuse_dev_ioctl_clone(struct file *file, __u32 __user *argp)
> * uses the same ioctl handler.
> */
> if (fd_file(f)->f_op == file->f_op)
> - fud = fuse_get_dev(fd_file(f));
> + fud = __fuse_get_dev(fd_file(f));
>
> res = -EINVAL;
> if (fud) {
> @@ -2581,8 +2601,8 @@ static long fuse_dev_ioctl_backing_open(struct file *file,
> struct fuse_dev *fud = fuse_get_dev(file);
> struct fuse_backing_map map;
>
> - if (!fud)
> - return -EPERM;
> + if (IS_ERR(fud))
> + return PTR_ERR(fud);
>
> if (!IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
> return -EOPNOTSUPP;
> @@ -2598,8 +2618,8 @@ static long fuse_dev_ioctl_backing_close(struct file *file, __u32 __user *argp)
> struct fuse_dev *fud = fuse_get_dev(file);
> int backing_id;
>
> - if (!fud)
> - return -EPERM;
> + if (IS_ERR(fud))
> + return PTR_ERR(fud);
>
> if (!IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
> return -EOPNOTSUPP;
> @@ -2610,6 +2630,19 @@ static long fuse_dev_ioctl_backing_close(struct file *file, __u32 __user *argp)
> return fuse_backing_close(fud->fc, backing_id);
> }
>
> +static long fuse_dev_ioctl_sync_init(struct file *file)
> +{
> + int err = -EINVAL;
> +
> + mutex_lock(&fuse_mutex);
> + if (!__fuse_get_dev(file)) {
> + WRITE_ONCE(file->private_data, FUSE_DEV_SYNC_INIT);
> + err = 0;
> + }
> + mutex_unlock(&fuse_mutex);
> + return err;
> +}
> +
> static long fuse_dev_ioctl(struct file *file, unsigned int cmd,
> unsigned long arg)
> {
> @@ -2625,6 +2658,9 @@ static long fuse_dev_ioctl(struct file *file, unsigned int cmd,
> case FUSE_DEV_IOC_BACKING_CLOSE:
> return fuse_dev_ioctl_backing_close(file, argp);
>
> + case FUSE_DEV_IOC_SYNC_INIT:
> + return fuse_dev_ioctl_sync_init(file);
> +
> default:
> return -ENOTTY;
> }
> @@ -2633,7 +2669,7 @@ static long fuse_dev_ioctl(struct file *file, unsigned int cmd,
> #ifdef CONFIG_PROC_FS
> static void fuse_dev_show_fdinfo(struct seq_file *seq, struct file *file)
> {
> - struct fuse_dev *fud = fuse_get_dev(file);
> + struct fuse_dev *fud = __fuse_get_dev(file);
> if (!fud)
> return;
>
> diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
> index 249b210becb1..bef38ed78249 100644
> --- a/fs/fuse/dev_uring.c
> +++ b/fs/fuse/dev_uring.c
> @@ -1139,9 +1139,9 @@ int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags)
> return -EINVAL;
>
> fud = fuse_get_dev(cmd->file);
> - if (!fud) {
> + if (IS_ERR(fud)) {
> pr_info_ratelimited("No fuse device found\n");
> - return -ENOTCONN;
> + return PTR_ERR(fud);
> }
> fc = fud->fc;
>
> diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h
> index 5a9bd771a319..6e8373f97040 100644
> --- a/fs/fuse/fuse_dev_i.h
> +++ b/fs/fuse/fuse_dev_i.h
> @@ -12,6 +12,8 @@
> #define FUSE_INT_REQ_BIT (1ULL << 0)
> #define FUSE_REQ_ID_STEP (1ULL << 1)
>
> +extern struct wait_queue_head fuse_dev_waitq;
> +
> struct fuse_arg;
> struct fuse_args;
> struct fuse_pqueue;
> @@ -37,15 +39,22 @@ struct fuse_copy_state {
> } ring;
> };
>
> -static inline struct fuse_dev *fuse_get_dev(struct file *file)
> +#define FUSE_DEV_SYNC_INIT ((struct fuse_dev *) 1)
> +#define FUSE_DEV_PTR_MASK (~1UL)
> +
> +static inline struct fuse_dev *__fuse_get_dev(struct file *file)
> {
> /*
> * Lockless access is OK, because file->private data is set
> * once during mount and is valid until the file is released.
> */
> - return READ_ONCE(file->private_data);
> + struct fuse_dev *fud = READ_ONCE(file->private_data);
> +
> + return (typeof(fud)) ((unsigned long) fud & FUSE_DEV_PTR_MASK);
> }
>
> +struct fuse_dev *fuse_get_dev(struct file *file);
> +
> unsigned int fuse_req_hash(u64 unique);
> struct fuse_req *fuse_request_find(struct fuse_pqueue *fpq, u64 unique);
>
> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> index 486fa550c951..233c6111f768 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -904,6 +904,9 @@ struct fuse_conn {
> /* Is link not implemented by fs? */
> unsigned int no_link:1;
>
> + /* Is synchronous FUSE_INIT allowed? */
> + unsigned int sync_init:1;
> +
> /* Use io_uring for communication */
> unsigned int io_uring;
>
> @@ -1318,7 +1321,7 @@ struct fuse_dev *fuse_dev_alloc_install(struct fuse_conn *fc);
> struct fuse_dev *fuse_dev_alloc(void);
> void fuse_dev_install(struct fuse_dev *fud, struct fuse_conn *fc);
> void fuse_dev_free(struct fuse_dev *fud);
> -void fuse_send_init(struct fuse_mount *fm);
> +int fuse_send_init(struct fuse_mount *fm);
>
> /**
> * Fill in superblock and initialize fuse connection
> diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
> index 9d26a5bc394d..7cf47d5bcc87 100644
> --- a/fs/fuse/inode.c
> +++ b/fs/fuse/inode.c
> @@ -7,6 +7,7 @@
> */
>
> #include "fuse_i.h"
> +#include "fuse_dev_i.h"
> #include "dev_uring_i.h"
>
> #include <linux/dax.h>
> @@ -34,6 +35,7 @@ MODULE_LICENSE("GPL");
> static struct kmem_cache *fuse_inode_cachep;
> struct list_head fuse_conn_list;
> DEFINE_MUTEX(fuse_mutex);
> +DECLARE_WAIT_QUEUE_HEAD(fuse_dev_waitq);
>
> static int set_global_limit(const char *val, const struct kernel_param *kp);
>
> @@ -1466,7 +1468,7 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args,
> wake_up_all(&fc->blocked_waitq);
> }
>
> -void fuse_send_init(struct fuse_mount *fm)
> +static struct fuse_init_args *fuse_new_init(struct fuse_mount *fm)
> {
> struct fuse_init_args *ia;
> u64 flags;
> @@ -1525,10 +1527,29 @@ void fuse_send_init(struct fuse_mount *fm)
> ia->args.out_args[0].value = &ia->out;
> ia->args.force = true;
> ia->args.nocreds = true;
> - ia->args.end = process_init_reply;
>
> - if (fuse_simple_background(fm, &ia->args, GFP_KERNEL) != 0)
> - process_init_reply(fm, &ia->args, -ENOTCONN);
> + return ia;
> +}
> +
> +int fuse_send_init(struct fuse_mount *fm)
> +{
> + struct fuse_init_args *ia = fuse_new_init(fm);
> + int err;
> +
> + if (fm->fc->sync_init) {
> + err = fuse_simple_request(fm, &ia->args);
> + /* Ignore size of init reply */
> + if (err > 0)
> + err = 0;
> + } else {
> + ia->args.end = process_init_reply;
> + err = fuse_simple_background(fm, &ia->args, GFP_KERNEL);
> + if (!err)
> + return 0;
> + err = -ENOTCONN;
> + }
> + process_init_reply(fm, &ia->args, err);
> + return err;
> }
> EXPORT_SYMBOL_GPL(fuse_send_init);
>
> @@ -1867,8 +1888,12 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
>
> mutex_lock(&fuse_mutex);
> err = -EINVAL;
> - if (ctx->fudptr && *ctx->fudptr)
> - goto err_unlock;
> + if (ctx->fudptr && *ctx->fudptr) {
> + if (*ctx->fudptr == FUSE_DEV_SYNC_INIT) {
> + fc->sync_init = 1;
> + } else
> + goto err_unlock;
> + }
>
> err = fuse_ctl_add_conn(fc);
> if (err)
> @@ -1876,8 +1901,10 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
>
> list_add_tail(&fc->entry, &fuse_conn_list);
> sb->s_root = root_dentry;
> - if (ctx->fudptr)
> + if (ctx->fudptr) {
> *ctx->fudptr = fud;
> + wake_up_all(&fuse_dev_waitq);
> + }
> mutex_unlock(&fuse_mutex);
> return 0;
>
> @@ -1898,6 +1925,7 @@ EXPORT_SYMBOL_GPL(fuse_fill_super_common);
> static int fuse_fill_super(struct super_block *sb, struct fs_context *fsc)
> {
> struct fuse_fs_context *ctx = fsc->fs_private;
> + struct fuse_mount *fm;
> int err;
>
> if (!ctx->file || !ctx->rootmode_present ||
> @@ -1918,8 +1946,10 @@ static int fuse_fill_super(struct super_block *sb, struct fs_context *fsc)
> return err;
> /* file->private_data shall be visible on all CPUs after this */
> smp_mb();
> - fuse_send_init(get_fuse_mount_super(sb));
> - return 0;
> +
> + fm = get_fuse_mount_super(sb);
> +
> + return fuse_send_init(fm);
> }
>
> /*
> @@ -1980,7 +2010,7 @@ static int fuse_get_tree(struct fs_context *fsc)
> * Allow creating a fuse mount with an already initialized fuse
> * connection
> */
> - fud = READ_ONCE(ctx->file->private_data);
> + fud = __fuse_get_dev(ctx->file);
> if (ctx->file->f_op == &fuse_dev_operations && fud) {
> fsc->sget_key = fud->fc;
> sb = sget_fc(fsc, fuse_test_super, fuse_set_no_super);
> diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
> index 3942d1fda599..30bf0846547f 100644
> --- a/include/uapi/linux/fuse.h
> +++ b/include/uapi/linux/fuse.h
> @@ -1130,6 +1130,7 @@ struct fuse_backing_map {
> #define FUSE_DEV_IOC_BACKING_OPEN _IOW(FUSE_DEV_IOC_MAGIC, 1, \
> struct fuse_backing_map)
> #define FUSE_DEV_IOC_BACKING_CLOSE _IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t)
> +#define FUSE_DEV_IOC_SYNC_INIT _IO(FUSE_DEV_IOC_MAGIC, 3)
>
> struct fuse_lseek_in {
> uint64_t fh;
> --
> 2.49.0
>
>
next prev parent reply other threads:[~2025-08-29 15:43 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-08-27 10:59 [PATCH v2] fuse: allow synchronous FUSE_INIT Miklos Szeredi
2025-08-27 19:02 ` Darrick J. Wong
2025-08-27 19:51 ` Miklos Szeredi
2025-08-27 20:26 ` Darrick J. Wong
2025-08-27 22:56 ` Joanne Koong
2025-08-28 0:36 ` Darrick J. Wong
2025-08-28 13:09 ` Miklos Szeredi
2025-08-28 16:06 ` Joanne Koong
2025-08-29 15:43 ` Darrick J. Wong [this message]
2025-08-29 16:11 ` Bernd Schubert
2025-08-29 16:15 ` Miklos Szeredi
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=20250829154317.GA1587915@frogsfrogsfrogs \
--to=djwong@kernel.org \
--cc=John@groves.net \
--cc=bernd@bsbernd.com \
--cc=joannelkoong@gmail.com \
--cc=linux-fsdevel@vger.kernel.org \
--cc=mszeredi@redhat.com \
/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