linux-fsdevel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Joanne Koong <joannelkoong@gmail.com>
To: Miklos Szeredi <mszeredi@redhat.com>
Cc: linux-fsdevel@vger.kernel.org,
	"Darrick J. Wong" <djwong@kernel.org>,
	 John Groves <John@groves.net>,
	Bernd Schubert <bernd@bsbernd.com>
Subject: Re: [PATCH v2] fuse: allow synchronous FUSE_INIT
Date: Wed, 27 Aug 2025 15:56:49 -0700	[thread overview]
Message-ID: <CAJnrk1b8FZC82oeWuynWk5oqiRe+04frUv-4w9=jg319KvUz0A@mail.gmail.com> (raw)
In-Reply-To: <20250827110004.584582-1-mszeredi@redhat.com>

On Wed, Aug 27, 2025 at 4:00 AM Miklos Szeredi <mszeredi@redhat.com> 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.
>
> 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/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);

I wonder if we should make the semantics the same for synchronous and
non-synchronous inits here, i.e. doing a wait for
"(READ_ONCE(file->private_data) != FUSE_DEV_SYNC_INIT) &&
READ_ONCE(file->private_data) != NULL", so that from the libfuse point
of view, the flow can be unified between the two, eg
i) send sync_init ioctl call if doing a synchronous init
ii) kick off thread to read requests
iii) do mount call
otherwise for async inits, the mount call needs to happen first.

> +       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);

Does this (and below in fuse_dev_splice_write()) need to be
fuse_get_dev()? afaict, fuse_dev_write() only starts getting used
after fud has already been initialized. i see why it's needed for
fuse_dev_read() since otherwise the server doesn't know when it can
start calling fuse_dev_read(), but for fuse_dev_write(), it seems like
that only gets used after fud is already initialized.

>
> -       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);
>
> @@ -2581,8 +2601,8 @@ static long fuse_dev_ioctl_backing_open(struct file *file,
>         struct fuse_dev *fud = fuse_get_dev(file);

Should this be __fuse_get_dev()?

>         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);

Same question here.

>         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);

Does this still need a WRITE_ONCE if it's accessed within the scope of
the mutex? My understanding (maybe wrong) is that a mutex implicitly
serves as also a memory barrier. If not, then we probably also need a
WRITE_ONCE() around the *ctx->fudptr assignment in
fuse_fill_super_common()?


Thanks,
Joanne

> +               err = 0;
> +       }
> +       mutex_unlock(&fuse_mutex);
> +       return 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;
>

  parent reply	other threads:[~2025-08-27 22:57 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 [this message]
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
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='CAJnrk1b8FZC82oeWuynWk5oqiRe+04frUv-4w9=jg319KvUz0A@mail.gmail.com' \
    --to=joannelkoong@gmail.com \
    --cc=John@groves.net \
    --cc=bernd@bsbernd.com \
    --cc=djwong@kernel.org \
    --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;
as well as URLs for NNTP newsgroup(s).