|
@@ -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);
|
|
@@ -1605,7 +1618,7 @@ 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);
|
|
@@ -1700,174 +1713,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(inode, attr, attr->ia_file);
|
|
|
- else
|
|
|
- return fuse_do_setattr(inode, 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(inode, 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 = {
|
|
@@ -1884,10 +1800,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 = {
|
|
@@ -1905,10 +1823,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 = {
|
|
@@ -1916,10 +1836,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)
|