Merge tag 'fuse-update-6.6' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi...
[sfrench/cifs-2.6.git] / fs / fuse / dir.c
index 881524b9a55aa818a2b0d37d0e6b91bb0bdfb694..d707e6987da91eaaf06eda157f8487d9488dd12b 100644 (file)
@@ -92,7 +92,7 @@ static void fuse_dentry_settime(struct dentry *dentry, u64 time)
 /*
  * Calculate the time in jiffies until a dentry/attributes are valid
  */
-static u64 time_to_jiffies(u64 sec, u32 nsec)
+u64 fuse_time_to_jiffies(u64 sec, u32 nsec)
 {
        if (sec || nsec) {
                struct timespec64 ts = {
@@ -112,17 +112,7 @@ static u64 time_to_jiffies(u64 sec, u32 nsec)
 void fuse_change_entry_timeout(struct dentry *entry, struct fuse_entry_out *o)
 {
        fuse_dentry_settime(entry,
-               time_to_jiffies(o->entry_valid, o->entry_valid_nsec));
-}
-
-static u64 attr_timeout(struct fuse_attr_out *o)
-{
-       return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);
-}
-
-u64 entry_attr_timeout(struct fuse_entry_out *o)
-{
-       return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);
+               fuse_time_to_jiffies(o->entry_valid, o->entry_valid_nsec));
 }
 
 void fuse_invalidate_attr_mask(struct inode *inode, u32 mask)
@@ -265,8 +255,8 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
                        goto invalid;
 
                forget_all_cached_acls(inode);
-               fuse_change_attributes(inode, &outarg.attr,
-                                      entry_attr_timeout(&outarg),
+               fuse_change_attributes(inode, &outarg.attr, NULL,
+                                      ATTR_TIMEOUT(&outarg),
                                       attr_version);
                fuse_change_entry_timeout(entry, &outarg);
        } else if (inode) {
@@ -360,10 +350,14 @@ int fuse_valid_type(int m)
                S_ISBLK(m) || S_ISFIFO(m) || S_ISSOCK(m);
 }
 
+static bool fuse_valid_size(u64 size)
+{
+       return size <= LLONG_MAX;
+}
+
 bool fuse_invalid_attr(struct fuse_attr *attr)
 {
-       return !fuse_valid_type(attr->mode) ||
-               attr->size > LLONG_MAX;
+       return !fuse_valid_type(attr->mode) || !fuse_valid_size(attr->size);
 }
 
 int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name,
@@ -399,7 +393,7 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
                goto out_put_forget;
 
        *inode = fuse_iget(sb, outarg->nodeid, outarg->generation,
-                          &outarg->attr, entry_attr_timeout(outarg),
+                          &outarg->attr, ATTR_TIMEOUT(outarg),
                           attr_version);
        err = -ENOMEM;
        if (!*inode) {
@@ -686,7 +680,7 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
        ff->nodeid = outentry.nodeid;
        ff->open_flags = outopen.open_flags;
        inode = fuse_iget(dir->i_sb, outentry.nodeid, outentry.generation,
-                         &outentry.attr, entry_attr_timeout(&outentry), 0);
+                         &outentry.attr, ATTR_TIMEOUT(&outentry), 0);
        if (!inode) {
                flags &= ~(O_CREAT | O_EXCL | O_TRUNC);
                fuse_sync_release(NULL, ff, flags);
@@ -755,7 +749,8 @@ static int fuse_atomic_open(struct inode *dir, struct dentry *entry,
        if (err == -ENOSYS) {
                fc->no_create = 1;
                goto mknod;
-       }
+       } else if (err == -EEXIST)
+               fuse_invalidate_entry(entry);
 out_dput:
        dput(res);
        return err;
@@ -813,7 +808,7 @@ static int create_new_entry(struct fuse_mount *fm, struct fuse_args *args,
                goto out_put_forget_req;
 
        inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation,
-                         &outarg.attr, entry_attr_timeout(&outarg), 0);
+                         &outarg.attr, ATTR_TIMEOUT(&outarg), 0);
        if (!inode) {
                fuse_queue_forget(fm->fc, forget, outarg.nodeid, 1);
                return -ENOMEM;
@@ -835,6 +830,8 @@ static int create_new_entry(struct fuse_mount *fm, struct fuse_args *args,
        return 0;
 
  out_put_forget_req:
+       if (err == -EEXIST)
+               fuse_invalidate_entry(entry);
        kfree(forget);
        return err;
 }
@@ -986,7 +983,7 @@ static int fuse_unlink(struct inode *dir, struct dentry *entry)
        if (!err) {
                fuse_dir_changed(dir);
                fuse_entry_unlinked(entry);
-       } else if (err == -EINTR)
+       } else if (err == -EINTR || err == -ENOENT)
                fuse_invalidate_entry(entry);
        return err;
 }
@@ -1009,7 +1006,7 @@ static int fuse_rmdir(struct inode *dir, struct dentry *entry)
        if (!err) {
                fuse_dir_changed(dir);
                fuse_entry_unlinked(entry);
-       } else if (err == -EINTR)
+       } else if (err == -EINTR || err == -ENOENT)
                fuse_invalidate_entry(entry);
        return err;
 }
@@ -1050,7 +1047,7 @@ static int fuse_rename_common(struct inode *olddir, struct dentry *oldent,
                /* newent will end up negative */
                if (!(flags & RENAME_EXCHANGE) && d_really_is_positive(newent))
                        fuse_entry_unlinked(newent);
-       } else if (err == -EINTR) {
+       } else if (err == -EINTR || err == -ENOENT) {
                /* If request was interrupted, DEITY only knows if the
                   rename actually took place.  If the invalidation
                   fails (e.g. some process has CWD under the renamed
@@ -1153,6 +1150,87 @@ static void fuse_fillattr(struct inode *inode, struct fuse_attr *attr,
        stat->blksize = 1 << blkbits;
 }
 
+static void fuse_statx_to_attr(struct fuse_statx *sx, struct fuse_attr *attr)
+{
+       memset(attr, 0, sizeof(*attr));
+       attr->ino = sx->ino;
+       attr->size = sx->size;
+       attr->blocks = sx->blocks;
+       attr->atime = sx->atime.tv_sec;
+       attr->mtime = sx->mtime.tv_sec;
+       attr->ctime = sx->ctime.tv_sec;
+       attr->atimensec = sx->atime.tv_nsec;
+       attr->mtimensec = sx->mtime.tv_nsec;
+       attr->ctimensec = sx->ctime.tv_nsec;
+       attr->mode = sx->mode;
+       attr->nlink = sx->nlink;
+       attr->uid = sx->uid;
+       attr->gid = sx->gid;
+       attr->rdev = new_encode_dev(MKDEV(sx->rdev_major, sx->rdev_minor));
+       attr->blksize = sx->blksize;
+}
+
+static int fuse_do_statx(struct inode *inode, struct file *file,
+                        struct kstat *stat)
+{
+       int err;
+       struct fuse_attr attr;
+       struct fuse_statx *sx;
+       struct fuse_statx_in inarg;
+       struct fuse_statx_out outarg;
+       struct fuse_mount *fm = get_fuse_mount(inode);
+       u64 attr_version = fuse_get_attr_version(fm->fc);
+       FUSE_ARGS(args);
+
+       memset(&inarg, 0, sizeof(inarg));
+       memset(&outarg, 0, sizeof(outarg));
+       /* Directories have separate file-handle space */
+       if (file && S_ISREG(inode->i_mode)) {
+               struct fuse_file *ff = file->private_data;
+
+               inarg.getattr_flags |= FUSE_GETATTR_FH;
+               inarg.fh = ff->fh;
+       }
+       /* For now leave sync hints as the default, request all stats. */
+       inarg.sx_flags = 0;
+       inarg.sx_mask = STATX_BASIC_STATS | STATX_BTIME;
+       args.opcode = FUSE_STATX;
+       args.nodeid = get_node_id(inode);
+       args.in_numargs = 1;
+       args.in_args[0].size = sizeof(inarg);
+       args.in_args[0].value = &inarg;
+       args.out_numargs = 1;
+       args.out_args[0].size = sizeof(outarg);
+       args.out_args[0].value = &outarg;
+       err = fuse_simple_request(fm, &args);
+       if (err)
+               return err;
+
+       sx = &outarg.stat;
+       if (((sx->mask & STATX_SIZE) && !fuse_valid_size(sx->size)) ||
+           ((sx->mask & STATX_TYPE) && (!fuse_valid_type(sx->mode) ||
+                                        inode_wrong_type(inode, sx->mode)))) {
+               make_bad_inode(inode);
+               return -EIO;
+       }
+
+       fuse_statx_to_attr(&outarg.stat, &attr);
+       if ((sx->mask & STATX_BASIC_STATS) == STATX_BASIC_STATS) {
+               fuse_change_attributes(inode, &attr, &outarg.stat,
+                                      ATTR_TIMEOUT(&outarg), attr_version);
+       }
+
+       if (stat) {
+               stat->result_mask = sx->mask & (STATX_BASIC_STATS | STATX_BTIME);
+               stat->btime.tv_sec = sx->btime.tv_sec;
+               stat->btime.tv_nsec = min_t(u32, sx->btime.tv_nsec, NSEC_PER_SEC - 1);
+               fuse_fillattr(inode, &attr, stat);
+               stat->result_mask |= STATX_TYPE;
+       }
+
+       return 0;
+}
+
 static int fuse_do_getattr(struct inode *inode, struct kstat *stat,
                           struct file *file)
 {
@@ -1189,8 +1267,8 @@ static int fuse_do_getattr(struct inode *inode, struct kstat *stat,
                        fuse_make_bad(inode);
                        err = -EIO;
                } else {
-                       fuse_change_attributes(inode, &outarg.attr,
-                                              attr_timeout(&outarg),
+                       fuse_change_attributes(inode, &outarg.attr, NULL,
+                                              ATTR_TIMEOUT(&outarg),
                                               attr_version);
                        if (stat)
                                fuse_fillattr(inode, &outarg.attr, stat);
@@ -1204,12 +1282,22 @@ static int fuse_update_get_attr(struct inode *inode, struct file *file,
                                unsigned int flags)
 {
        struct fuse_inode *fi = get_fuse_inode(inode);
+       struct fuse_conn *fc = get_fuse_conn(inode);
        int err = 0;
        bool sync;
        u32 inval_mask = READ_ONCE(fi->inval_mask);
        u32 cache_mask = fuse_get_cache_mask(inode);
 
-       if (flags & AT_STATX_FORCE_SYNC)
+
+       /* FUSE only supports basic stats and possibly btime */
+       request_mask &= STATX_BASIC_STATS | STATX_BTIME;
+retry:
+       if (fc->no_statx)
+               request_mask &= STATX_BASIC_STATS;
+
+       if (!request_mask)
+               sync = false;
+       else if (flags & AT_STATX_FORCE_SYNC)
                sync = true;
        else if (flags & AT_STATX_DONT_SYNC)
                sync = false;
@@ -1220,11 +1308,24 @@ static int fuse_update_get_attr(struct inode *inode, struct file *file,
 
        if (sync) {
                forget_all_cached_acls(inode);
-               err = fuse_do_getattr(inode, stat, file);
+               /* Try statx if BTIME is requested */
+               if (!fc->no_statx && (request_mask & ~STATX_BASIC_STATS)) {
+                       err = fuse_do_statx(inode, file, stat);
+                       if (err == -ENOSYS) {
+                               fc->no_statx = 1;
+                               goto retry;
+                       }
+               } else {
+                       err = fuse_do_getattr(inode, stat, file);
+               }
        } else if (stat) {
                generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat);
                stat->mode = fi->orig_i_mode;
                stat->ino = fi->orig_ino;
+               if (test_bit(FUSE_I_BTIME, &fi->state)) {
+                       stat->btime = fi->i_btime;
+                       stat->result_mask |= STATX_BTIME;
+               }
        }
 
        return err;
@@ -1861,8 +1962,8 @@ int fuse_do_setattr(struct dentry *dentry, struct iattr *attr,
                /* FIXME: clear I_DIRTY_SYNC? */
        }
 
-       fuse_change_attributes_common(inode, &outarg.attr,
-                                     attr_timeout(&outarg),
+       fuse_change_attributes_common(inode, &outarg.attr, NULL,
+                                     ATTR_TIMEOUT(&outarg),
                                      fuse_get_cache_mask(inode));
        oldsize = inode->i_size;
        /* see the comment in fuse_change_attributes() */