|
From: Michael T. <mt...@us...> - 2014-11-07 19:59:42
|
This is a patch to add POSIX ACL support to FUSE. The reason for this change
is because the default_permissions option doesn't work as expected when the
backing filesystem supports POSIX ACLs. For example, if an object's ACL gives
a specific user explicit read access, but the traditional permissions would
not, then default_permissions would prevent that user from having read access.
This is not intended to be a final patch, mostly because I don't think I
fully understand how this affects backing filesystems which don't support
POSIX ACLs. On the other hand, these changes appear to be sufficient to add
POSIX ACL support for FUSE servers which support arbitrary xattrs.
A few things I have considered:
1. Should the cached ACLs expire? Maybe they expire when then inode's attrs
expire.
2. Should we detect if the server doesn't support ACLs, and if so, have a
fast-path to avoid retrieving them?
3. I'm not entirely clear on how umask interacts with default ACLs. I think
this is probably only a problem of the server since we send the umask and
the server can use the DONT_MASK capability to get the original mode.
4. How does this impact servers which don't use default_permissions? I think
you can only get to fuse_get_acl() and fuse_set_acl() with
default_permission, so it should be okay there. But what about the
posix_acl_chmod() in fuse_do_setattr()?
5. Should we be doing posix_acl_create() after create/mkdir/mknod? But that
assumes the server will initialize the ACL (and the way we expect them to!)
Maybe we need to initialize the new inode's ACL pointers to ACL_NOT_CACHED
so they get retrieved on the first permission check.
Signed-off-by: Michael Theall <mt...@us...>
---
fs/fuse/dir.c | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 123 insertions(+), 4 deletions(-)
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index dbab798..26a1ca2 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -13,6 +13,9 @@
#include <linux/sched.h>
#include <linux/namei.h>
#include <linux/slab.h>
+#include <linux/xattr.h>
+#include <linux/posix_acl.h>
+#include <linux/posix_acl_xattr.h>
static bool fuse_use_readdirplus(struct inode *dir, struct dir_context *ctx)
{
@@ -1797,6 +1800,9 @@ int fuse_do_setattr(struct inode *inode, struct iattr *attr,
}
clear_bit(FUSE_I_SIZE_UNSTABLE, &fi->state);
+
+ if (attr->ia_valid & ATTR_MODE)
+ return posix_acl_chmod(inode, inode->i_mode);
return 0;
error:
@@ -1832,10 +1838,9 @@ static int fuse_getattr(struct vfsmount *mnt, struct dentry *entry,
return fuse_update_attributes(inode, stat, NULL, NULL);
}
-static int fuse_setxattr(struct dentry *entry, const char *name,
+static int __fuse_setxattr(struct inode *inode, const char *name,
const void *value, size_t size, int flags)
{
- struct inode *inode = entry->d_inode;
struct fuse_conn *fc = get_fuse_conn(inode);
struct fuse_req *req;
struct fuse_setxattr_in inarg;
@@ -1874,10 +1879,15 @@ static int fuse_setxattr(struct dentry *entry, const char *name,
return err;
}
-static ssize_t fuse_getxattr(struct dentry *entry, const char *name,
+static int fuse_setxattr(struct dentry *entry, const char *name,
+ const void *value, size_t size, int flags)
+{
+ return __fuse_setxattr(entry->d_inode, name, value, size, flags);
+}
+
+static ssize_t __fuse_getxattr(struct inode *inode, const char *name,
void *value, size_t size)
{
- struct inode *inode = entry->d_inode;
struct fuse_conn *fc = get_fuse_conn(inode);
struct fuse_req *req;
struct fuse_getxattr_in inarg;
@@ -1924,6 +1934,12 @@ static ssize_t fuse_getxattr(struct dentry *entry, const char *name,
return ret;
}
+static ssize_t fuse_getxattr(struct dentry *entry, const char *name,
+ void *value, size_t size)
+{
+ return __fuse_getxattr(entry->d_inode, name, value, size);
+}
+
static ssize_t fuse_listxattr(struct dentry *entry, char *list, size_t size)
{
struct inode *inode = entry->d_inode;
@@ -2007,6 +2023,105 @@ static int fuse_removexattr(struct dentry *entry, const char *name)
return err;
}
+static struct posix_acl *fuse_get_acl(struct inode *inode, int type)
+{
+ int size;
+ const char *name;
+ char *value = NULL;
+ struct posix_acl *acl;
+
+ switch (type) {
+ case ACL_TYPE_ACCESS:
+ name = POSIX_ACL_XATTR_ACCESS;
+ break;
+ case ACL_TYPE_DEFAULT:
+ name = POSIX_ACL_XATTR_DEFAULT;
+ break;
+ default:
+ BUG();
+ }
+
+ size = __fuse_getxattr(inode, name, NULL, 0);
+ if (size > 0) {
+ value = kzalloc(size, GFP_NOFS);
+ if (!value)
+ return ERR_PTR(-ENOMEM);
+ size = __fuse_getxattr(inode, name, value, size);
+ }
+ if (size > 0) {
+ // TODO: change when userns is in place
+ acl = posix_acl_from_xattr(&init_user_ns, value, size);
+ } else if (size == -ENOENT || size == -ENODATA || size == 0) {
+ acl = NULL;
+ } else {
+ acl = ERR_PTR(size);
+ }
+ kfree(value);
+
+ if (!IS_ERR(acl))
+ set_cached_acl(inode, type, acl);
+
+ return acl;
+}
+
+static int fuse_set_acl(struct inode *inode, struct posix_acl *acl, int type)
+{
+ int ret, size = 0;
+ const char *name;
+ char *value = NULL;
+
+ if (acl) {
+ ret = posix_acl_valid(acl);
+ if (ret < 0)
+ return ret;
+ ret = 0;
+ }
+
+ switch (type) {
+ case ACL_TYPE_ACCESS:
+ name = POSIX_ACL_XATTR_ACCESS;
+ if (acl) {
+ ret = posix_acl_equiv_mode(acl, &inode->i_mode);
+ if (ret < 0)
+ return ret;
+ if (ret == 0)
+ acl = NULL;
+ }
+ ret = 0;
+ break;
+ case ACL_TYPE_DEFAULT:
+ if (!S_ISDIR(inode->i_mode))
+ return acl ? -EINVAL : 0;
+ name = POSIX_ACL_XATTR_DEFAULT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (acl) {
+ size = posix_acl_xattr_size(acl->a_count);
+ value = kmalloc(size, GFP_NOFS);
+ if (!value) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ // TODO: change when userns is in place
+ ret = posix_acl_to_xattr(&init_user_ns, acl, value, size);
+ if (ret < 0)
+ goto out;
+ }
+
+ ret = __fuse_setxattr(inode, name, value, size, 0);
+out:
+ kfree(value);
+
+ if (!ret)
+ set_cached_acl(inode, type, acl);
+
+ return ret;
+}
+
static const struct inode_operations fuse_dir_inode_operations = {
.lookup = fuse_lookup,
.mkdir = fuse_mkdir,
@@ -2025,6 +2140,8 @@ static const struct inode_operations fuse_dir_inode_operations = {
.getxattr = fuse_getxattr,
.listxattr = fuse_listxattr,
.removexattr = fuse_removexattr,
+ .get_acl = fuse_get_acl,
+ .set_acl = fuse_set_acl,
};
static const struct file_operations fuse_dir_operations = {
@@ -2046,6 +2163,8 @@ static const struct inode_operations fuse_common_inode_operations = {
.getxattr = fuse_getxattr,
.listxattr = fuse_listxattr,
.removexattr = fuse_removexattr,
+ .get_acl = fuse_get_acl,
+ .set_acl = fuse_set_acl,
};
static const struct inode_operations fuse_symlink_inode_operations = {
--
1.9.3
|