Skip to content

Commit

Permalink
fuse: Add posix ACL support
Browse files Browse the repository at this point in the history
Add a new INIT flag, FUSE_POSIX_ACL, for negotiating ACL support with
userspace.  When it is set in the INIT response, ACL support will be
enabled.  ACL support also implies "default_permissions".

When ACL support is enabled, the kernel will cache and have responsibility
for enforcing ACLs.  ACL xattrs will be passed to userspace, which is
responsible for updating the ACLs in the filesystem, keeping the file mode
in sync, and inheritance of default ACLs when new filesystem nodes are
created.

Signed-off-by: Seth Forshee <[email protected]>
Signed-off-by: Miklos Szeredi <[email protected]>
  • Loading branch information
Seth Forshee authored and Miklos Szeredi committed Oct 1, 2016
1 parent 5e940c1 commit 60bcc88
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 7 deletions.
1 change: 1 addition & 0 deletions fs/fuse/Kconfig
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion fs/fuse/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
99 changes: 99 additions & 0 deletions fs/fuse/acl.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* FUSE: Filesystem in Userspace
* Copyright (C) 2016 Canonical Ltd. <[email protected]>
*
* 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 > 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 if (size == -ERANGE)
acl = ERR_PTR(-E2BIG);
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;

if (size > PAGE_SIZE)
return -E2BIG;

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);
}
forget_all_cached_acls(inode);
fuse_invalidate_attr(inode);

return ret;
}
16 changes: 16 additions & 0 deletions fs/fuse/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -1234,6 +1238,7 @@ static int fuse_direntplus_link(struct file *file,
fi->nlookup++;
spin_unlock(&fc->lock);

forget_all_cached_acls(inode);
fuse_change_attributes(inode, &o->attr,
entry_attr_timeout(o),
attr_version);
Expand Down Expand Up @@ -1748,6 +1753,13 @@ static int fuse_setattr(struct dentry *entry, struct iattr *attr)

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);

/* Directory mode changed, may need to revalidate access */
if (d_is_dir(entry) && (attr->ia_valid & ATTR_MODE))
fuse_invalidate_entry_cache(entry);
Expand Down Expand Up @@ -1785,6 +1797,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 = {
Expand All @@ -1806,6 +1820,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 = {
Expand Down
14 changes: 14 additions & 0 deletions fs/fuse/fuse_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -627,6 +628,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;

Expand Down Expand Up @@ -971,7 +975,17 @@ 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;
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 */
9 changes: 8 additions & 1 deletion fs/fuse/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 <[email protected]>");
MODULE_DESCRIPTION("Filesystem in Userspace");
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -914,6 +916,11 @@ static void process_init_reply(struct fuse_conn *fc, struct fuse_req *req)
fc->handle_killpriv = 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;
Expand Down Expand Up @@ -943,7 +950,7 @@ static void fuse_send_init(struct fuse_conn *fc, struct fuse_req *req)
FUSE_FLOCK_LOCKS | FUSE_HAS_IOCTL_DIR | FUSE_AUTO_INVAL_DATA |
FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO | FUSE_ASYNC_DIO |
FUSE_WRITEBACK_CACHE | FUSE_NO_OPEN_SUPPORT |
FUSE_PARALLEL_DIROPS | FUSE_HANDLE_KILLPRIV;
FUSE_PARALLEL_DIROPS | FUSE_HANDLE_KILLPRIV | FUSE_POSIX_ACL;
req->in.h.opcode = FUSE_INIT;
req->in.numargs = 1;
req->in.args[0].size = sizeof(*arg);
Expand Down
18 changes: 13 additions & 5 deletions fs/fuse/xattr.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -147,7 +148,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);
Expand Down Expand Up @@ -201,3 +202,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
};
3 changes: 3 additions & 0 deletions include/uapi/linux/fuse.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
*
* 7.26
* - add FUSE_HANDLE_KILLPRIV
* - add FUSE_POSIX_ACL
*/

#ifndef _LINUX_FUSE_H
Expand Down Expand Up @@ -242,6 +243,7 @@ struct fuse_file_lock {
* FUSE_NO_OPEN_SUPPORT: kernel supports zero-message opens
* FUSE_PARALLEL_DIROPS: allow parallel lookups and readdir
* FUSE_HANDLE_KILLPRIV: fs handles killing suid/sgid/cap on write/chown/trunc
* FUSE_POSIX_ACL: filesystem supports posix acls
*/
#define FUSE_ASYNC_READ (1 << 0)
#define FUSE_POSIX_LOCKS (1 << 1)
Expand All @@ -263,6 +265,7 @@ struct fuse_file_lock {
#define FUSE_NO_OPEN_SUPPORT (1 << 17)
#define FUSE_PARALLEL_DIROPS (1 << 18)
#define FUSE_HANDLE_KILLPRIV (1 << 19)
#define FUSE_POSIX_ACL (1 << 20)

/**
* CUSE INIT request/reply flags
Expand Down

0 comments on commit 60bcc88

Please sign in to comment.