diff --git a/fs/fuse/Kconfig b/fs/fuse/Kconfig
index 1b2f6c2c3aaf..76f09ce7e5b2 100644
--- a/fs/fuse/Kconfig
+++ b/fs/fuse/Kconfig
@@ -1,5 +1,6 @@
config FUSE_FS
tristate "FUSE (Filesystem in Userspace) support"
+ select FS_POSIX_ACL
help
With FUSE it is possible to implement a fully functional filesystem
in a userspace program.
diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile
index 448aa27ada00..60da84a86dab 100644
--- a/fs/fuse/Makefile
+++ b/fs/fuse/Makefile
@@ -5,4 +5,4 @@
obj-$(CONFIG_FUSE_FS) += fuse.o
obj-$(CONFIG_CUSE) += cuse.o
-fuse-objs := dev.o dir.o file.o inode.o control.o xattr.o
+fuse-objs := dev.o dir.o file.o inode.o control.o xattr.o acl.o
diff --git a/fs/fuse/acl.c b/fs/fuse/acl.c
new file mode 100644
index 000000000000..87220da95e91
--- /dev/null
+++ b/fs/fuse/acl.c
@@ -0,0 +1,100 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2016 Canonical Ltd. <seth.forshee@canonical.com>
+
+ This program can be distributed under the terms of the GNU GPL.
+ See the file COPYING.
+*/
+
+#include "fuse_i.h"
+
+#include <linux/posix_acl.h>
+#include <linux/posix_acl_xattr.h>
+
+struct posix_acl *fuse_get_acl(struct inode *inode, int type)
+{
+ struct fuse_conn *fc = get_fuse_conn(inode);
+ int size;
+ const char *name;
+ void *value = NULL;
+ struct posix_acl *acl;
+
+ if (!fc->posix_acl || fc->no_getxattr)
+ return NULL;
+
+ if (type == ACL_TYPE_ACCESS)
+ name = XATTR_NAME_POSIX_ACL_ACCESS;
+ else if (type == ACL_TYPE_DEFAULT)
+ name = XATTR_NAME_POSIX_ACL_DEFAULT;
+ else
+ return ERR_PTR(-EOPNOTSUPP);
+
+ value = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!value)
+ return ERR_PTR(-ENOMEM);
+ size = fuse_getxattr(inode, name, value, PAGE_SIZE);
+ if (size == -ERANGE) {
+ kfree(value);
+ size = fuse_getxattr(inode, name, NULL, 0);
+ value = kmalloc(size, GFP_KERNEL);
+ if (!value)
+ return ERR_PTR(-ENOMEM);
+ size = fuse_getxattr(inode, name, value, size);
+ }
+
+ if (size > 0)
+ acl = posix_acl_from_xattr(&init_user_ns, value, size);
+ else if ((size == 0) || (size == -ENODATA) ||
+ (size == -EOPNOTSUPP && fc->no_getxattr))
+ acl = NULL;
+ else
+ acl = ERR_PTR(size);
+
+ kfree(value);
+ return acl;
+}
+
+int fuse_set_acl(struct inode *inode, struct posix_acl *acl, int type)
+{
+ struct fuse_conn *fc = get_fuse_conn(inode);
+ const char *name;
+ int ret;
+
+ if (!fc->posix_acl || fc->no_setxattr)
+ return -EOPNOTSUPP;
+
+ if (type == ACL_TYPE_ACCESS)
+ name = XATTR_NAME_POSIX_ACL_ACCESS;
+ else if (type == ACL_TYPE_DEFAULT)
+ name = XATTR_NAME_POSIX_ACL_DEFAULT;
+ else
+ return -EINVAL;
+
+ if (acl) {
+ /*
+ * Fuse userspace is responsible for updating access
+ * permissions in the inode, if needed. fuse_setxattr
+ * invalidates the inode attributes, which will force
+ * them to be refreshed the next time they are used,
+ * and it also updates i_ctime.
+ */
+ size_t size = posix_acl_xattr_size(acl->a_count);
+ void *value = kmalloc(size, GFP_KERNEL);
+ if (!value)
+ return -ENOMEM;
+
+ ret = posix_acl_to_xattr(&init_user_ns, acl, value, size);
+ if (ret < 0) {
+ kfree(value);
+ return ret;
+ }
+
+ ret = fuse_setxattr(inode, name, value, size, 0);
+ kfree(value);
+ } else {
+ ret = fuse_removexattr(inode, name);
+ }
+ if (ret == 0)
+ set_cached_acl(inode, type, acl);
+ return ret;
+}diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 7490db141dd9..d25e00cfe72d 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -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)
{@@ -244,6 +245,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);
@@ -918,6 +920,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;@@ -1065,6 +1068,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);
}
@@ -1234,6 +1238,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);
@@ -1703,14 +1708,24 @@ 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);
+ 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);
+ ret = fuse_do_setattr(inode, attr, attr->ia_file);
else
- return fuse_do_setattr(inode, attr, NULL);
+ ret = fuse_do_setattr(inode, attr, NULL);
+
+ /*
+ * If filesystem supports acls it may have updated acl xattrs in
+ * the filesystem, so forget cached acls for the inode.
+ */
+ if (!ret && fc->posix_acl)
+ forget_all_cached_acls(inode);
+ return ret;
}
static int fuse_getattr(struct vfsmount *mnt, struct dentry *entry,@@ -1743,6 +1758,8 @@ static const struct inode_operations fuse_dir_inode_operations = {
.getxattr = generic_getxattr,
.listxattr = fuse_listxattr,
.removexattr = generic_removexattr,
+ .get_acl = fuse_get_acl,
+ .set_acl = fuse_set_acl,
};
static const struct file_operations fuse_dir_operations = {@@ -1764,6 +1781,8 @@ static const struct inode_operations fuse_common_inode_operations = {
.getxattr = generic_getxattr,
.listxattr = fuse_listxattr,
.removexattr = generic_removexattr,
+ .get_acl = fuse_get_acl,
+ .set_acl = fuse_set_acl,
};
static const struct inode_operations fuse_symlink_inode_operations = {@@ -1775,6 +1794,8 @@ static const struct inode_operations fuse_symlink_inode_operations = {
.getxattr = generic_getxattr,
.listxattr = fuse_listxattr,
.removexattr = generic_removexattr,
+ .get_acl = fuse_get_acl,
+ .set_acl = fuse_set_acl,
};
void fuse_init_common(struct inode *inode)diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 6db54d0bd81b..5d8a6bf6594d 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -23,6 +23,7 @@
#include <linux/poll.h>
#include <linux/workqueue.h>
#include <linux/kref.h>
+#include <linux/xattr.h>
/** Max number of pages that can be used in a single read request */
#define FUSE_MAX_PAGES_PER_REQ 32
@@ -624,6 +625,9 @@ struct fuse_conn {
/** Is lseek not implemented by fs? */
unsigned no_lseek:1;
+ /** Does the filesystem support posix acls? */
+ unsigned posix_acl:1;
+
/** The number of requests waiting for completion */
atomic_t num_waiting;
@@ -968,7 +972,21 @@ void fuse_set_initialized(struct fuse_conn *fc);
void fuse_unlock_inode(struct inode *inode);
void fuse_lock_inode(struct inode *inode);
+int fuse_setxattr(struct inode *inode, const char *name, const void *value,
+ size_t size, int flags);
+ssize_t fuse_getxattr(struct inode *inode, const char *name, void *value,
+ size_t size);
ssize_t fuse_listxattr(struct dentry *entry, char *list, size_t size);
+int fuse_removexattr(struct inode *inode, const char *name);
extern const struct xattr_handler *fuse_xattr_handlers[];
+extern const struct xattr_handler *fuse_acl_xattr_handlers[];
+
+struct posix_acl;
+
+extern const struct xattr_handler fuse_xattr_acl_access_handler;
+extern const struct xattr_handler fuse_xattr_acl_default_handler;
+
+struct posix_acl *fuse_get_acl(struct inode *inode, int type);
+int fuse_set_acl(struct inode *inode, struct posix_acl *acl, int type);
#endif /* _FS_FUSE_I_H */
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 1e535f31fed0..a3cfcb541243 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -20,6 +20,7 @@
#include <linux/random.h>
#include <linux/sched.h>
#include <linux/exportfs.h>
+#include <linux/posix_acl.h>
MODULE_AUTHOR("Miklos Szeredi <miklos@szeredi.hu>");
MODULE_DESCRIPTION("Filesystem in Userspace");@@ -340,6 +341,7 @@ int fuse_reverse_inval_inode(struct super_block *sb, u64 nodeid,
return -ENOENT;
fuse_invalidate_attr(inode);
+ forget_all_cached_acls(inode);
if (offset >= 0) {
pg_start = offset >> PAGE_SHIFT;
if (len <= 0)@@ -912,6 +914,11 @@ static void process_init_reply(struct fuse_conn *fc, struct fuse_req *req)
fc->parallel_dirops = 1;
if (arg->time_gran && arg->time_gran <= 1000000000)
fc->sb->s_time_gran = arg->time_gran;
+ if ((arg->flags & FUSE_POSIX_ACL) &&
+ (fc->flags & FUSE_DEFAULT_PERMISSIONS)) {
+ fc->posix_acl = 1;
+ fc->sb->s_xattr = fuse_acl_xattr_handlers;
+ }
} else {
ra_pages = fc->max_read / PAGE_SIZE;
fc->no_lock = 1;@@ -942,6 +949,8 @@ static void fuse_send_init(struct fuse_conn *fc, struct fuse_req *req)
FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO | FUSE_ASYNC_DIO |
FUSE_WRITEBACK_CACHE | FUSE_NO_OPEN_SUPPORT |
FUSE_PARALLEL_DIROPS;
+ if (fc->flags & FUSE_DEFAULT_PERMISSIONS)
+ arg->flags |= FUSE_POSIX_ACL;
req->in.h.opcode = FUSE_INIT;
req->in.numargs = 1;
req->in.args[0].size = sizeof(*arg);
diff --git a/fs/fuse/xattr.c b/fs/fuse/xattr.c
index 9929cbe8cf03..855e701f8c11 100644
--- a/fs/fuse/xattr.c
+++ b/fs/fuse/xattr.c
@@ -9,9 +9,10 @@
#include "fuse_i.h"
#include <linux/xattr.h>
+#include <linux/posix_acl_xattr.h>
-static int fuse_setxattr(struct inode *inode, const char *name,
- const void *value, size_t size, int flags)
+int fuse_setxattr(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);@@ -45,8 +46,8 @@ static int fuse_setxattr(struct inode *inode, const char *name,
return err;
}
-static ssize_t fuse_getxattr(struct inode *inode, const char *name,
- void *value, size_t size)
+ssize_t fuse_getxattr(struct inode *inode, const char *name, void *value,
+ size_t size)
{
struct fuse_conn *fc = get_fuse_conn(inode);
FUSE_ARGS(args);@@ -128,7 +129,7 @@ ssize_t fuse_listxattr(struct dentry *entry, char *list, size_t size)
return ret;
}
-static int fuse_removexattr(struct inode *inode, const char *name)
+int fuse_removexattr(struct inode *inode, const char *name)
{
struct fuse_conn *fc = get_fuse_conn(inode);
FUSE_ARGS(args);@@ -182,3 +183,10 @@ const struct xattr_handler *fuse_xattr_handlers[] = {
&fuse_xattr_handler,
NULL
};
+
+const struct xattr_handler *fuse_acl_xattr_handlers[] = {
+ &posix_acl_access_xattr_handler,
+ &posix_acl_default_xattr_handler,
+ &fuse_xattr_handler,
+ NULL
+};diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 27e17363263a..cb48ec4546ce 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -108,6 +108,7 @@
*
* 7.25
* - add FUSE_PARALLEL_DIROPS
+ * - add FUSE_POSIX_ACL
*/
#ifndef _LINUX_FUSE_H
@@ -238,6 +239,7 @@ struct fuse_file_lock {
* FUSE_WRITEBACK_CACHE: use writeback cache for buffered writes
* FUSE_NO_OPEN_SUPPORT: kernel supports zero-message opens
* FUSE_PARALLEL_DIROPS: allow parallel lookups and readdir
+ * FUSE_POSIX_ACL: filesystem supports posix acls
*/
#define FUSE_ASYNC_READ (1 << 0)
#define FUSE_POSIX_LOCKS (1 << 1)@@ -258,6 +260,7 @@ struct fuse_file_lock {
#define FUSE_WRITEBACK_CACHE (1 << 16)
#define FUSE_NO_OPEN_SUPPORT (1 << 17)
#define FUSE_PARALLEL_DIROPS (1 << 18)
+#define FUSE_POSIX_ACL (1 << 19)
/**
* CUSE INIT request/reply flags--
2.7.4