Re: [PATCH v4 2/3] FUSE: Implement atomic lookup + open

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, May 4, 2022 at 11:50 PM Vivek Goyal <vgoyal@xxxxxxxxxx> wrote:
>
> On Mon, May 02, 2022 at 03:55:20PM +0530, Dharmendra Singh wrote:
> > From: Dharmendra Singh <dsingh@xxxxxxx>
> >
> > We can optimize aggressive lookups which are triggered when
> > there is normal open for file/dir (dentry is new/negative).
> >
> > Here in this case since we are anyway going to open the file/dir
> > with USER SPACE, avoid this separate lookup call into libfuse
> > and combine it with open call into libfuse.
> >
> > This lookup + open in single call to libfuse has been named
> > as atomic open. It is expected that USER SPACE opens the file
> > and fills in the attributes, which are then used to make inode
> > stand/revalidate in the kernel cache.
> >
> > Signed-off-by: Dharmendra Singh <dsingh@xxxxxxx>
> > ---
> >  fs/fuse/dir.c             | 78 ++++++++++++++++++++++++++++++---------
> >  fs/fuse/fuse_i.h          |  3 ++
> >  fs/fuse/inode.c           |  4 +-
> >  include/uapi/linux/fuse.h |  2 +
> >  4 files changed, 69 insertions(+), 18 deletions(-)
> >
> > diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
> > index cad3322a007f..6879d3a86796 100644
> > --- a/fs/fuse/dir.c
> > +++ b/fs/fuse/dir.c
> > @@ -516,18 +516,18 @@ static int get_security_context(struct dentry *entry, umode_t mode,
> >  }
> >
> >  /*
> > - * Atomic create+open operation
> > - *
> > - * If the filesystem doesn't support this, then fall back to separate
> > - * 'mknod' + 'open' requests.
> > + * This is common function for initiating atomic operations into libfuse.
> > + * Currently being used by Atomic create(atomic lookup + create) and
> > + * Atomic open(atomic lookup + open).
> >   */
> > -static int fuse_create_open(struct inode *dir, struct dentry *entry,
> > -                         struct file *file, unsigned int flags,
> > -                         umode_t mode, uint32_t opcode)
> > +static int fuse_atomic_do_common(struct inode *dir, struct dentry *entry,
> > +                              struct dentry **alias, struct file *file,
> > +                              unsigned int flags, umode_t mode, uint32_t opcode)
>
> If this common function is really useful for atomic create + atomic open,
> I will first add a patch which adds a common function and makes FUSE_CREATE_EXT use that function. And in later patch add functionality to do atomic open.
> Just makes code more readable.

This func is commonly used by atomic create and atomic open, both.
atomic open has fuse_do_atomic_open wrapper over it. I just renamed
this func except doing it in a separate patch (just to avoid another
patch though I understand that it might be a little hard to read the
code).


>
> >  {
> >       int err;
> >       struct inode *inode;
> >       struct fuse_mount *fm = get_fuse_mount(dir);
> > +     struct fuse_conn *fc = get_fuse_conn(dir);
> >       FUSE_ARGS(args);
> >       struct fuse_forget_link *forget;
> >       struct fuse_create_in inarg;
> > @@ -539,9 +539,13 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
> >       void *security_ctx = NULL;
> >       u32 security_ctxlen;
> >       bool atomic_create = (opcode == FUSE_ATOMIC_CREATE ? true : false);
> > +     bool create_op = (opcode == FUSE_CREATE ||
> > +                       opcode == FUSE_ATOMIC_CREATE) ? true : false;
> > +     if (alias)
> > +             *alias = NULL;
> >
> >       /* Userspace expects S_IFREG in create mode */
> > -     BUG_ON((mode & S_IFMT) != S_IFREG);
> > +     BUG_ON(create_op && (mode & S_IFMT) != S_IFREG);
> >
> >       forget = fuse_alloc_forget();
> >       err = -ENOMEM;
> > @@ -553,7 +557,7 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
> >       if (!ff)
> >               goto out_put_forget_req;
> >
> > -     if (!fm->fc->dont_mask)
> > +     if (!fc->dont_mask)
> >               mode &= ~current_umask();
> >
> >       flags &= ~O_NOCTTY;
> > @@ -642,8 +646,9 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
> >                               err = PTR_ERR(res);
> >                               goto out_err;
> >                       }
> > -                     /* res is expected to be NULL since its REG file */
> > -                     WARN_ON(res);
> > +                     entry = res;
> > +                     if (alias)
> > +                             *alias = res;
> >               }
> >       }
> >       fuse_change_entry_timeout(entry, &outentry);
> > @@ -681,8 +686,8 @@ static int fuse_atomic_create(struct inode *dir, struct dentry *entry,
> >       if (fc->no_atomic_create)
> >               return -ENOSYS;
> >
> > -     err = fuse_create_open(dir, entry, file, flags, mode,
> > -                            FUSE_ATOMIC_CREATE);
> > +     err = fuse_atomic_do_common(dir, entry, NULL, file, flags, mode,
> > +                                 FUSE_ATOMIC_CREATE);
> >       /* If atomic create is not implemented then indicate in fc so that next
> >        * request falls back to normal create instead of going into libufse and
> >        * returning with -ENOSYS.
> > @@ -694,6 +699,29 @@ static int fuse_atomic_create(struct inode *dir, struct dentry *entry,
> >       return err;
> >  }
> >
> > +static int fuse_do_atomic_open(struct inode *dir, struct dentry *entry,
> > +                             struct dentry **alias, struct file *file,
> > +                             unsigned int flags, umode_t mode)
> > +{
> > +     int err;
> > +     struct fuse_conn *fc = get_fuse_conn(dir);
> > +
> > +     if (!fc->do_atomic_open)
> > +             return -ENOSYS;
> > +
> > +     err = fuse_atomic_do_common(dir, entry, alias, file, flags, mode,
> > +                                 FUSE_ATOMIC_OPEN);
> > +     /* Atomic open imply atomic trunc as well i.e truncate should be performed
> > +      * as part of atomic open call itself.
> > +      */
> > +     if (!fc->atomic_o_trunc) {
> > +             if (fc->do_atomic_open)
> > +                     fc->atomic_o_trunc = 1;
> > +     }
> > +
> > +     return err;
> > +}
> > +
> >  static int fuse_mknod(struct user_namespace *, struct inode *, struct dentry *,
> >                     umode_t, dev_t);
> >  static int fuse_atomic_open(struct inode *dir, struct dentry *entry,
> > @@ -702,12 +730,23 @@ static int fuse_atomic_open(struct inode *dir, struct dentry *entry,
> >  {
> >       int err;
> >       struct fuse_conn *fc = get_fuse_conn(dir);
> > -     struct dentry *res = NULL;
> > +     struct dentry *res = NULL, *alias = NULL;
> >       bool create = flags & O_CREAT ? true : false;
> >
> >       if (fuse_is_bad(dir))
> >               return -EIO;
> >
> > +     if (!create) {
> > +             err = fuse_do_atomic_open(dir, entry, &alias,
> > +                                       file, flags, mode);
>
> So this is the core change of behavior. As of now, if we are not doing
> create operation, we just lookup and return. But now you are doing
> open + lookup in a single operation. Interesting. I am not sure if
> it breaks anything in VFS.

Yes, here we are handling optimizing lookups for open calls. If atomic
open is implemented underlying we use it otherwise falls back to
normal open.

>
> > +             res = alias;
> > +             if (!err || err != -ENOSYS)
> > +                     goto out_dput;
> > +     }
> > +      /*
> > +       * ENOSYS fall back for open- user space does not have full
> > +       * atomic open.
> > +       */
>
> This ENOSYS stuff all the place is making these patches look very unclean
> to me. A part of me says that should we negotiate these as feature bits.
> Having said that feature bits are precious and should not be used for
> trivial purposes. Hmm..., may be we can make handle ENOSYS little better
> in general.

Actually atomic open is based upon init bits considering the 3rd patch
which optimizes lookups in d_revalidate(). I would see if I can clean
ENOSYS a bit.
>
> >       if ((!create || fc->no_atomic_create) && d_in_lookup(entry)) {
> >               res = fuse_lookup(dir, entry, 0);
> >               if (IS_ERR(res))
> > @@ -730,9 +769,14 @@ static int fuse_atomic_open(struct inode *dir, struct dentry *entry,
> >       /* Libfuse/user space has not implemented atomic create, therefore
> >        * fall back to normal create.
> >        */
> > -     if (err == -ENOSYS)
> > -             err = fuse_create_open(dir, entry, file, flags, mode,
> > -                                    FUSE_CREATE);
> > +     /* Atomic create+open operation
> > +      * If the filesystem doesn't support this, then fall back to separate
> > +      * 'mknod' + 'open' requests.
> > +      */
> > +     if (err == -ENOSYS) {
> > +             err = fuse_atomic_do_common(dir, entry, NULL, file, flags,
> > +                                         mode, FUSE_CREATE);
> > +     }
> >       if (err == -ENOSYS) {
> >               fc->no_create = 1;
> >               goto mknod;
> > diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> > index d577a591ab16..24793b82303f 100644
> > --- a/fs/fuse/fuse_i.h
> > +++ b/fs/fuse/fuse_i.h
> > @@ -669,6 +669,9 @@ struct fuse_conn {
> >       /** Is open/release not implemented by fs? */
> >       unsigned no_open:1;
> >
> > +     /** Is atomic open implemented by fs ? */
> > +     unsigned do_atomic_open : 1;
> > +
> >       /** Is atomic create not implemented by fs? */
> >       unsigned no_atomic_create:1;
> >
> > diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
> > index ee846ce371d8..5f667de69115 100644
> > --- a/fs/fuse/inode.c
> > +++ b/fs/fuse/inode.c
> > @@ -1190,6 +1190,8 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args,
> >                               fc->setxattr_ext = 1;
> >                       if (flags & FUSE_SECURITY_CTX)
> >                               fc->init_security = 1;
> > +                     if (flags & FUSE_DO_ATOMIC_OPEN)
> > +                             fc->do_atomic_open = 1;
>
> Hey you are negotiating FUSE_DO_ATOMIC_OPEN flag. If that's the case why
> do you have to deal with -ENOSYS in fuse_do_atomic_open(). You should
> be able to just check.
>
> if (!create && fc->do_atomic_open) {
>         fuse_do_atomic_open().
> }

You are right. This happens due to the reason that we have auto atomic
create check in atomic create but yes, we do not need ENOSYS here. I
will clean it.


> I have yet to check what happens if file does not exist.
>
> >               } else {
> >                       ra_pages = fc->max_read / PAGE_SIZE;
> >                       fc->no_lock = 1;
> > @@ -1235,7 +1237,7 @@ void fuse_send_init(struct fuse_mount *fm)
> >               FUSE_ABORT_ERROR | FUSE_MAX_PAGES | FUSE_CACHE_SYMLINKS |
> >               FUSE_NO_OPENDIR_SUPPORT | FUSE_EXPLICIT_INVAL_DATA |
> >               FUSE_HANDLE_KILLPRIV_V2 | FUSE_SETXATTR_EXT | FUSE_INIT_EXT |
> > -             FUSE_SECURITY_CTX;
> > +             FUSE_SECURITY_CTX | FUSE_DO_ATOMIC_OPEN;
> >  #ifdef CONFIG_FUSE_DAX
> >       if (fm->fc->dax)
> >               flags |= FUSE_MAP_ALIGNMENT;
> > diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
> > index e4b56004b148..ab91e391241a 100644
> > --- a/include/uapi/linux/fuse.h
> > +++ b/include/uapi/linux/fuse.h
> > @@ -391,6 +391,7 @@ struct fuse_file_lock {
> >  /* bits 32..63 get shifted down 32 bits into the flags2 field */
> >  #define FUSE_SECURITY_CTX    (1ULL << 32)
> >  #define FUSE_HAS_INODE_DAX   (1ULL << 33)
> > +#define FUSE_DO_ATOMIC_OPEN  (1ULL << 34)
> >
> >  /**
> >   * CUSE INIT request/reply flags
> > @@ -540,6 +541,7 @@ enum fuse_opcode {
> >       FUSE_REMOVEMAPPING      = 49,
> >       FUSE_SYNCFS             = 50,
> >       FUSE_ATOMIC_CREATE      = 51,
> > +     FUSE_ATOMIC_OPEN        = 52,
> >
> >       /* CUSE specific operations */
> >       CUSE_INIT               = 4096,
> > --
> > 2.17.1
> >
>



[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [NTFS 3]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [NTFS 3]     [Samba]     [Device Mapper]     [CEPH Development]

  Powered by Linux