Merge branch 'work.misc' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
[cascardo/linux.git] / fs / fuse / dir.c
index b235021..a430c19 100644 (file)
@@ -13,6 +13,8 @@
 #include <linux/sched.h>
 #include <linux/namei.h>
 #include <linux/slab.h>
+#include <linux/xattr.h>
+#include <linux/posix_acl.h>
 
 static bool fuse_use_readdirplus(struct inode *dir, struct dir_context *ctx)
 {
@@ -37,47 +39,39 @@ static void fuse_advise_use_readdirplus(struct inode *dir)
        set_bit(FUSE_I_ADVISE_RDPLUS, &fi->state);
 }
 
-#if BITS_PER_LONG >= 64
+union fuse_dentry {
+       u64 time;
+       struct rcu_head rcu;
+};
+
 static inline void fuse_dentry_settime(struct dentry *entry, u64 time)
 {
-       entry->d_time = time;
+       ((union fuse_dentry *) entry->d_fsdata)->time = time;
 }
 
 static inline u64 fuse_dentry_time(struct dentry *entry)
 {
-       return entry->d_time;
-}
-#else
-/*
- * On 32 bit archs store the high 32 bits of time in d_fsdata
- */
-static void fuse_dentry_settime(struct dentry *entry, u64 time)
-{
-       entry->d_time = time;
-       entry->d_fsdata = (void *) (unsigned long) (time >> 32);
-}
-
-static u64 fuse_dentry_time(struct dentry *entry)
-{
-       return (u64) entry->d_time +
-               ((u64) (unsigned long) entry->d_fsdata << 32);
+       return ((union fuse_dentry *) entry->d_fsdata)->time;
 }
-#endif
 
 /*
  * FUSE caches dentries and attributes with separate timeout.  The
  * time in jiffies until the dentry/attributes are valid is stored in
- * dentry->d_time and fuse_inode->i_time respectively.
+ * dentry->d_fsdata and fuse_inode->i_time respectively.
  */
 
 /*
  * Calculate the time in jiffies until a dentry/attributes are valid
  */
-static u64 time_to_jiffies(unsigned long sec, unsigned long nsec)
+static u64 time_to_jiffies(u64 sec, u32 nsec)
 {
        if (sec || nsec) {
-               struct timespec ts = {sec, nsec};
-               return get_jiffies_64() + timespec_to_jiffies(&ts);
+               struct timespec64 ts = {
+                       sec,
+                       max_t(u32, nsec, NSEC_PER_SEC - 1)
+               };
+
+               return get_jiffies_64() + timespec64_to_jiffies(&ts);
        } else
                return 0;
 }
@@ -243,6 +237,7 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags)
                if (ret || (outarg.attr.mode ^ inode->i_mode) & S_IFMT)
                        goto invalid;
 
+               forget_all_cached_acls(inode);
                fuse_change_attributes(inode, &outarg.attr,
                                       entry_attr_timeout(&outarg),
                                       attr_version);
@@ -272,8 +267,23 @@ static int invalid_nodeid(u64 nodeid)
        return !nodeid || nodeid == FUSE_ROOT_ID;
 }
 
+static int fuse_dentry_init(struct dentry *dentry)
+{
+       dentry->d_fsdata = kzalloc(sizeof(union fuse_dentry), GFP_KERNEL);
+
+       return dentry->d_fsdata ? 0 : -ENOMEM;
+}
+static void fuse_dentry_release(struct dentry *dentry)
+{
+       union fuse_dentry *fd = dentry->d_fsdata;
+
+       kfree_rcu(fd, rcu);
+}
+
 const struct dentry_operations fuse_dentry_operations = {
        .d_revalidate   = fuse_dentry_revalidate,
+       .d_init         = fuse_dentry_init,
+       .d_release      = fuse_dentry_release,
 };
 
 int fuse_valid_type(int m)
@@ -634,7 +644,7 @@ static int fuse_symlink(struct inode *dir, struct dentry *entry,
        return create_new_entry(fc, &args, dir, entry, S_IFLNK);
 }
 
-static inline void fuse_update_ctime(struct inode *inode)
+void fuse_update_ctime(struct inode *inode)
 {
        if (!IS_NOCMTIME(inode)) {
                inode->i_ctime = current_fs_time(inode->i_sb);
@@ -917,6 +927,7 @@ int fuse_update_attributes(struct inode *inode, struct kstat *stat,
 
        if (time_before64(fi->i_time, get_jiffies_64())) {
                r = true;
+               forget_all_cached_acls(inode);
                err = fuse_do_getattr(inode, stat, file);
        } else {
                r = false;
@@ -1017,7 +1028,7 @@ int fuse_allow_current_process(struct fuse_conn *fc)
 {
        const struct cred *cred;
 
-       if (fc->flags & FUSE_ALLOW_OTHER)
+       if (fc->allow_other)
                return 1;
 
        cred = current_cred();
@@ -1064,6 +1075,7 @@ static int fuse_perm_getattr(struct inode *inode, int mask)
        if (mask & MAY_NOT_BLOCK)
                return -ECHILD;
 
+       forget_all_cached_acls(inode);
        return fuse_do_getattr(inode, NULL, NULL);
 }
 
@@ -1092,7 +1104,7 @@ static int fuse_permission(struct inode *inode, int mask)
        /*
         * If attributes are needed, refresh them before proceeding
         */
-       if ((fc->flags & FUSE_DEFAULT_PERMISSIONS) ||
+       if (fc->default_permissions ||
            ((mask & MAY_EXEC) && S_ISREG(inode->i_mode))) {
                struct fuse_inode *fi = get_fuse_inode(inode);
 
@@ -1105,7 +1117,7 @@ static int fuse_permission(struct inode *inode, int mask)
                }
        }
 
-       if (fc->flags & FUSE_DEFAULT_PERMISSIONS) {
+       if (fc->default_permissions) {
                err = generic_permission(inode, mask);
 
                /* If permission is denied, try to refresh file
@@ -1233,6 +1245,7 @@ retry:
                fi->nlookup++;
                spin_unlock(&fc->lock);
 
+               forget_all_cached_acls(inode);
                fuse_change_attributes(inode, &o->attr,
                                       entry_attr_timeout(o),
                                       attr_version);
@@ -1606,7 +1619,7 @@ int fuse_do_setattr(struct dentry *dentry, struct iattr *attr,
        int err;
        bool trust_local_cmtime = is_wb && S_ISREG(inode->i_mode);
 
-       if (!(fc->flags & FUSE_DEFAULT_PERMISSIONS))
+       if (!fc->default_permissions)
                attr->ia_valid |= ATTR_FORCE;
 
        err = setattr_prepare(dentry, attr);
@@ -1701,174 +1714,77 @@ error:
 }
 
 static int fuse_setattr(struct dentry *entry, struct iattr *attr)
-{
-       struct inode *inode = d_inode(entry);
-
-       if (!fuse_allow_current_process(get_fuse_conn(inode)))
-               return -EACCES;
-
-       if (attr->ia_valid & ATTR_FILE)
-               return fuse_do_setattr(entry, attr, attr->ia_file);
-       else
-               return fuse_do_setattr(entry, attr, NULL);
-}
-
-static int fuse_getattr(struct vfsmount *mnt, struct dentry *entry,
-                       struct kstat *stat)
 {
        struct inode *inode = d_inode(entry);
        struct fuse_conn *fc = get_fuse_conn(inode);
+       struct file *file = (attr->ia_valid & ATTR_FILE) ? attr->ia_file : NULL;
+       int ret;
 
-       if (!fuse_allow_current_process(fc))
+       if (!fuse_allow_current_process(get_fuse_conn(inode)))
                return -EACCES;
 
-       return fuse_update_attributes(inode, stat, NULL, NULL);
-}
-
-static int fuse_setxattr(struct dentry *unused, struct inode *inode,
-                        const char *name, const void *value,
-                        size_t size, int flags)
-{
-       struct fuse_conn *fc = get_fuse_conn(inode);
-       FUSE_ARGS(args);
-       struct fuse_setxattr_in inarg;
-       int err;
-
-       if (fc->no_setxattr)
-               return -EOPNOTSUPP;
+       if (attr->ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID)) {
+               attr->ia_valid &= ~(ATTR_KILL_SUID | ATTR_KILL_SGID |
+                                   ATTR_MODE);
 
-       memset(&inarg, 0, sizeof(inarg));
-       inarg.size = size;
-       inarg.flags = flags;
-       args.in.h.opcode = FUSE_SETXATTR;
-       args.in.h.nodeid = get_node_id(inode);
-       args.in.numargs = 3;
-       args.in.args[0].size = sizeof(inarg);
-       args.in.args[0].value = &inarg;
-       args.in.args[1].size = strlen(name) + 1;
-       args.in.args[1].value = name;
-       args.in.args[2].size = size;
-       args.in.args[2].value = value;
-       err = fuse_simple_request(fc, &args);
-       if (err == -ENOSYS) {
-               fc->no_setxattr = 1;
-               err = -EOPNOTSUPP;
-       }
-       if (!err) {
-               fuse_invalidate_attr(inode);
-               fuse_update_ctime(inode);
+               /*
+                * The only sane way to reliably kill suid/sgid is to do it in
+                * the userspace filesystem
+                *
+                * This should be done on write(), truncate() and chown().
+                */
+               if (!fc->handle_killpriv) {
+                       int kill;
+
+                       /*
+                        * ia_mode calculation may have used stale i_mode.
+                        * Refresh and recalculate.
+                        */
+                       ret = fuse_do_getattr(inode, NULL, file);
+                       if (ret)
+                               return ret;
+
+                       attr->ia_mode = inode->i_mode;
+                       kill = should_remove_suid(entry);
+                       if (kill & ATTR_KILL_SUID) {
+                               attr->ia_valid |= ATTR_MODE;
+                               attr->ia_mode &= ~S_ISUID;
+                       }
+                       if (kill & ATTR_KILL_SGID) {
+                               attr->ia_valid |= ATTR_MODE;
+                               attr->ia_mode &= ~S_ISGID;
+                       }
+               }
        }
-       return err;
-}
-
-static ssize_t fuse_getxattr(struct dentry *entry, struct inode *inode,
-                            const char *name, void *value, size_t size)
-{
-       struct fuse_conn *fc = get_fuse_conn(inode);
-       FUSE_ARGS(args);
-       struct fuse_getxattr_in inarg;
-       struct fuse_getxattr_out outarg;
-       ssize_t ret;
+       if (!attr->ia_valid)
+               return 0;
 
-       if (fc->no_getxattr)
-               return -EOPNOTSUPP;
+       ret = fuse_do_setattr(entry, attr, file);
+       if (!ret) {
+               /*
+                * If filesystem supports acls it may have updated acl xattrs in
+                * the filesystem, so forget cached acls for the inode.
+                */
+               if (fc->posix_acl)
+                       forget_all_cached_acls(inode);
 
-       memset(&inarg, 0, sizeof(inarg));
-       inarg.size = size;
-       args.in.h.opcode = FUSE_GETXATTR;
-       args.in.h.nodeid = get_node_id(inode);
-       args.in.numargs = 2;
-       args.in.args[0].size = sizeof(inarg);
-       args.in.args[0].value = &inarg;
-       args.in.args[1].size = strlen(name) + 1;
-       args.in.args[1].value = name;
-       /* This is really two different operations rolled into one */
-       args.out.numargs = 1;
-       if (size) {
-               args.out.argvar = 1;
-               args.out.args[0].size = size;
-               args.out.args[0].value = value;
-       } else {
-               args.out.args[0].size = sizeof(outarg);
-               args.out.args[0].value = &outarg;
-       }
-       ret = fuse_simple_request(fc, &args);
-       if (!ret && !size)
-               ret = outarg.size;
-       if (ret == -ENOSYS) {
-               fc->no_getxattr = 1;
-               ret = -EOPNOTSUPP;
+               /* Directory mode changed, may need to revalidate access */
+               if (d_is_dir(entry) && (attr->ia_valid & ATTR_MODE))
+                       fuse_invalidate_entry_cache(entry);
        }
        return ret;
 }
 
-static ssize_t fuse_listxattr(struct dentry *entry, char *list, size_t size)
+static int fuse_getattr(struct vfsmount *mnt, struct dentry *entry,
+                       struct kstat *stat)
 {
        struct inode *inode = d_inode(entry);
        struct fuse_conn *fc = get_fuse_conn(inode);
-       FUSE_ARGS(args);
-       struct fuse_getxattr_in inarg;
-       struct fuse_getxattr_out outarg;
-       ssize_t ret;
 
        if (!fuse_allow_current_process(fc))
                return -EACCES;
 
-       if (fc->no_listxattr)
-               return -EOPNOTSUPP;
-
-       memset(&inarg, 0, sizeof(inarg));
-       inarg.size = size;
-       args.in.h.opcode = FUSE_LISTXATTR;
-       args.in.h.nodeid = get_node_id(inode);
-       args.in.numargs = 1;
-       args.in.args[0].size = sizeof(inarg);
-       args.in.args[0].value = &inarg;
-       /* This is really two different operations rolled into one */
-       args.out.numargs = 1;
-       if (size) {
-               args.out.argvar = 1;
-               args.out.args[0].size = size;
-               args.out.args[0].value = list;
-       } else {
-               args.out.args[0].size = sizeof(outarg);
-               args.out.args[0].value = &outarg;
-       }
-       ret = fuse_simple_request(fc, &args);
-       if (!ret && !size)
-               ret = outarg.size;
-       if (ret == -ENOSYS) {
-               fc->no_listxattr = 1;
-               ret = -EOPNOTSUPP;
-       }
-       return ret;
-}
-
-static int fuse_removexattr(struct dentry *entry, const char *name)
-{
-       struct inode *inode = d_inode(entry);
-       struct fuse_conn *fc = get_fuse_conn(inode);
-       FUSE_ARGS(args);
-       int err;
-
-       if (fc->no_removexattr)
-               return -EOPNOTSUPP;
-
-       args.in.h.opcode = FUSE_REMOVEXATTR;
-       args.in.h.nodeid = get_node_id(inode);
-       args.in.numargs = 1;
-       args.in.args[0].size = strlen(name) + 1;
-       args.in.args[0].value = name;
-       err = fuse_simple_request(fc, &args);
-       if (err == -ENOSYS) {
-               fc->no_removexattr = 1;
-               err = -EOPNOTSUPP;
-       }
-       if (!err) {
-               fuse_invalidate_attr(inode);
-               fuse_update_ctime(inode);
-       }
-       return err;
+       return fuse_update_attributes(inode, stat, NULL, NULL);
 }
 
 static const struct inode_operations fuse_dir_inode_operations = {
@@ -1885,10 +1801,12 @@ static const struct inode_operations fuse_dir_inode_operations = {
        .mknod          = fuse_mknod,
        .permission     = fuse_permission,
        .getattr        = fuse_getattr,
-       .setxattr       = fuse_setxattr,
-       .getxattr       = fuse_getxattr,
+       .setxattr       = generic_setxattr,
+       .getxattr       = generic_getxattr,
        .listxattr      = fuse_listxattr,
-       .removexattr    = fuse_removexattr,
+       .removexattr    = generic_removexattr,
+       .get_acl        = fuse_get_acl,
+       .set_acl        = fuse_set_acl,
 };
 
 static const struct file_operations fuse_dir_operations = {
@@ -1906,10 +1824,12 @@ static const struct inode_operations fuse_common_inode_operations = {
        .setattr        = fuse_setattr,
        .permission     = fuse_permission,
        .getattr        = fuse_getattr,
-       .setxattr       = fuse_setxattr,
-       .getxattr       = fuse_getxattr,
+       .setxattr       = generic_setxattr,
+       .getxattr       = generic_getxattr,
        .listxattr      = fuse_listxattr,
-       .removexattr    = fuse_removexattr,
+       .removexattr    = generic_removexattr,
+       .get_acl        = fuse_get_acl,
+       .set_acl        = fuse_set_acl,
 };
 
 static const struct inode_operations fuse_symlink_inode_operations = {
@@ -1917,10 +1837,10 @@ static const struct inode_operations fuse_symlink_inode_operations = {
        .get_link       = fuse_get_link,
        .readlink       = generic_readlink,
        .getattr        = fuse_getattr,
-       .setxattr       = fuse_setxattr,
-       .getxattr       = fuse_getxattr,
+       .setxattr       = generic_setxattr,
+       .getxattr       = generic_getxattr,
        .listxattr      = fuse_listxattr,
-       .removexattr    = fuse_removexattr,
+       .removexattr    = generic_removexattr,
 };
 
 void fuse_init_common(struct inode *inode)