Merge branch 'work.xattr' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
[cascardo/linux.git] / fs / fuse / dir.c
index dbf77fe..572d124 100644 (file)
@@ -14,6 +14,7 @@
 #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)
 {
@@ -38,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;
 }
@@ -244,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);
@@ -273,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)
@@ -918,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;
@@ -1018,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();
@@ -1065,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);
 }
 
@@ -1093,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);
 
@@ -1106,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
@@ -1234,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);
@@ -1592,9 +1604,10 @@ int fuse_flush_times(struct inode *inode, struct fuse_file *ff)
  * vmtruncate() doesn't allow for this case, so do the rlimit checking
  * and the actual truncation by hand.
  */
-int fuse_do_setattr(struct inode *inode, struct iattr *attr,
+int fuse_do_setattr(struct dentry *dentry, struct iattr *attr,
                    struct file *file)
 {
+       struct inode *inode = d_inode(dentry);
        struct fuse_conn *fc = get_fuse_conn(inode);
        struct fuse_inode *fi = get_fuse_inode(inode);
        FUSE_ARGS(args);
@@ -1606,10 +1619,10 @@ int fuse_do_setattr(struct inode *inode, 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 = inode_change_ok(inode, attr);
+       err = setattr_prepare(dentry, attr);
        if (err)
                return err;
 
@@ -1703,14 +1716,63 @@ error:
 static int fuse_setattr(struct dentry *entry, struct iattr *attr)
 {
        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(get_fuse_conn(inode)))
                return -EACCES;
 
-       if (attr->ia_valid & ATTR_FILE)
-               return fuse_do_setattr(inode, attr, attr->ia_file);
-       else
-               return fuse_do_setattr(inode, attr, NULL);
+       if (attr->ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID)) {
+               attr->ia_valid &= ~(ATTR_KILL_SUID | ATTR_KILL_SGID |
+                                   ATTR_MODE);
+
+               /*
+                * 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;
+                       }
+               }
+       }
+       if (!attr->ia_valid)
+               return 0;
+
+       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);
+
+               /* 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 int fuse_getattr(struct vfsmount *mnt, struct dentry *entry,
@@ -1740,6 +1802,8 @@ static const struct inode_operations fuse_dir_inode_operations = {
        .permission     = fuse_permission,
        .getattr        = fuse_getattr,
        .listxattr      = fuse_listxattr,
+       .get_acl        = fuse_get_acl,
+       .set_acl        = fuse_set_acl,
 };
 
 static const struct file_operations fuse_dir_operations = {
@@ -1758,6 +1822,8 @@ static const struct inode_operations fuse_common_inode_operations = {
        .permission     = fuse_permission,
        .getattr        = fuse_getattr,
        .listxattr      = fuse_listxattr,
+       .get_acl        = fuse_get_acl,
+       .set_acl        = fuse_set_acl,
 };
 
 static const struct inode_operations fuse_symlink_inode_operations = {