Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse
Pull fuse updates from Miklos Szeredi: "This adds POSIX ACL permission checking to the fuse kernel module. In addition there are minor bug fixes as well as cleanups" * 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse: fuse: limit xattr returned size fuse: remove duplicate cs->offset assignment fuse: don't use fuse_ioctl_copy_user() helper fuse_ioctl_copy_user(): don't open-code copy_page_{to,from}_iter() fuse: get rid of fc->flags fuse: use timespec64 fuse: don't use ->d_time fuse: Add posix ACL support fuse: handle killpriv in userspace fs fuse: fix killing s[ug]id in setattr fuse: invalidate dir dentry after chmod fuse: Use generic xattr ops fuse: listxattr: verify xattr list
This commit is contained in:
commit
edadd0e5a7
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
fuse-objs := dev.o dir.o file.o inode.o control.o xattr.o acl.o
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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 > 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;
|
||||
}
|
|
@ -767,7 +767,6 @@ static int fuse_copy_fill(struct fuse_copy_state *cs)
|
|||
cs->len = err;
|
||||
cs->offset = off;
|
||||
cs->pg = page;
|
||||
cs->offset = off;
|
||||
iov_iter_advance(cs->iter, err);
|
||||
}
|
||||
|
||||
|
|
294
fs/fuse/dir.c
294
fs/fuse/dir.c
|
@ -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;
|
||||
return ((union fuse_dentry *) entry->d_fsdata)->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);
|
||||
}
|
||||
#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 @@ 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);
|
||||
|
@ -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);
|
||||
|
@ -1702,14 +1715,63 @@ int fuse_do_setattr(struct inode *inode, struct iattr *attr,
|
|||
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(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);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int fuse_getattr(struct vfsmount *mnt, struct dentry *entry,
|
||||
|
@ -1724,152 +1786,6 @@ static int fuse_getattr(struct vfsmount *mnt, struct dentry *entry,
|
|||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
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 (fc->no_getxattr)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
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;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t fuse_listxattr(struct dentry *entry, char *list, size_t size)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
static const struct inode_operations fuse_dir_inode_operations = {
|
||||
.lookup = fuse_lookup,
|
||||
.mkdir = fuse_mkdir,
|
||||
|
@ -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)
|
||||
|
|
|
@ -2326,49 +2326,6 @@ static loff_t fuse_file_llseek(struct file *file, loff_t offset, int whence)
|
|||
return retval;
|
||||
}
|
||||
|
||||
static int fuse_ioctl_copy_user(struct page **pages, struct iovec *iov,
|
||||
unsigned int nr_segs, size_t bytes, bool to_user)
|
||||
{
|
||||
struct iov_iter ii;
|
||||
int page_idx = 0;
|
||||
|
||||
if (!bytes)
|
||||
return 0;
|
||||
|
||||
iov_iter_init(&ii, to_user ? READ : WRITE, iov, nr_segs, bytes);
|
||||
|
||||
while (iov_iter_count(&ii)) {
|
||||
struct page *page = pages[page_idx++];
|
||||
size_t todo = min_t(size_t, PAGE_SIZE, iov_iter_count(&ii));
|
||||
void *kaddr;
|
||||
|
||||
kaddr = kmap(page);
|
||||
|
||||
while (todo) {
|
||||
char __user *uaddr = ii.iov->iov_base + ii.iov_offset;
|
||||
size_t iov_len = ii.iov->iov_len - ii.iov_offset;
|
||||
size_t copy = min(todo, iov_len);
|
||||
size_t left;
|
||||
|
||||
if (!to_user)
|
||||
left = copy_from_user(kaddr, uaddr, copy);
|
||||
else
|
||||
left = copy_to_user(uaddr, kaddr, copy);
|
||||
|
||||
if (unlikely(left))
|
||||
return -EFAULT;
|
||||
|
||||
iov_iter_advance(&ii, copy);
|
||||
todo -= copy;
|
||||
kaddr += copy;
|
||||
}
|
||||
|
||||
kunmap(page);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* CUSE servers compiled on 32bit broke on 64bit kernels because the
|
||||
* ABI was defined to be 'struct iovec' which is different on 32bit
|
||||
|
@ -2520,8 +2477,9 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
|
|||
struct iovec *iov_page = NULL;
|
||||
struct iovec *in_iov = NULL, *out_iov = NULL;
|
||||
unsigned int in_iovs = 0, out_iovs = 0, num_pages = 0, max_pages;
|
||||
size_t in_size, out_size, transferred;
|
||||
int err;
|
||||
size_t in_size, out_size, transferred, c;
|
||||
int err, i;
|
||||
struct iov_iter ii;
|
||||
|
||||
#if BITS_PER_LONG == 32
|
||||
inarg.flags |= FUSE_IOCTL_32BIT;
|
||||
|
@ -2603,11 +2561,14 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
|
|||
req->in.args[1].size = in_size;
|
||||
req->in.argpages = 1;
|
||||
|
||||
err = fuse_ioctl_copy_user(pages, in_iov, in_iovs, in_size,
|
||||
false);
|
||||
if (err)
|
||||
err = -EFAULT;
|
||||
iov_iter_init(&ii, WRITE, in_iov, in_iovs, in_size);
|
||||
for (i = 0; iov_iter_count(&ii) && !WARN_ON(i >= num_pages); i++) {
|
||||
c = copy_page_from_iter(pages[i], 0, PAGE_SIZE, &ii);
|
||||
if (c != PAGE_SIZE && iov_iter_count(&ii))
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
req->out.numargs = 2;
|
||||
req->out.args[0].size = sizeof(outarg);
|
||||
|
@ -2672,7 +2633,14 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
|
|||
if (transferred > inarg.out_size)
|
||||
goto out;
|
||||
|
||||
err = fuse_ioctl_copy_user(pages, out_iov, out_iovs, transferred, true);
|
||||
err = -EFAULT;
|
||||
iov_iter_init(&ii, READ, out_iov, out_iovs, transferred);
|
||||
for (i = 0; iov_iter_count(&ii) && !WARN_ON(i >= num_pages); i++) {
|
||||
c = copy_page_to_iter(pages[i], 0, PAGE_SIZE, &ii);
|
||||
if (c != PAGE_SIZE && iov_iter_count(&ii))
|
||||
goto out;
|
||||
}
|
||||
err = 0;
|
||||
out:
|
||||
if (req)
|
||||
fuse_put_request(fc, req);
|
||||
|
|
|
@ -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
|
||||
|
@ -36,15 +37,6 @@
|
|||
/** Number of dentries for each connection in the control filesystem */
|
||||
#define FUSE_CTL_NUM_DENTRIES 5
|
||||
|
||||
/** If the FUSE_DEFAULT_PERMISSIONS flag is given, the filesystem
|
||||
module will check permissions based on the file mode. Otherwise no
|
||||
permission checking is done in the kernel */
|
||||
#define FUSE_DEFAULT_PERMISSIONS (1 << 0)
|
||||
|
||||
/** If the FUSE_ALLOW_OTHER flag is given, then not only the user
|
||||
doing the mount will be allowed to access the filesystem */
|
||||
#define FUSE_ALLOW_OTHER (1 << 1)
|
||||
|
||||
/** Number of page pointers embedded in fuse_req */
|
||||
#define FUSE_REQ_INLINE_PAGES 1
|
||||
|
||||
|
@ -469,9 +461,6 @@ struct fuse_conn {
|
|||
/** The group id for this mount */
|
||||
kgid_t group_id;
|
||||
|
||||
/** The fuse mount flags for this mount */
|
||||
unsigned flags;
|
||||
|
||||
/** Maximum read size */
|
||||
unsigned max_read;
|
||||
|
||||
|
@ -547,6 +536,9 @@ struct fuse_conn {
|
|||
/** allow parallel lookups and readdir (default is serialized) */
|
||||
unsigned parallel_dirops:1;
|
||||
|
||||
/** handle fs handles killing suid/sgid/cap on write/chown/trunc */
|
||||
unsigned handle_killpriv:1;
|
||||
|
||||
/*
|
||||
* The following bitfields are only for optimization purposes
|
||||
* and hence races in setting them will not cause malfunction
|
||||
|
@ -624,6 +616,15 @@ struct fuse_conn {
|
|||
/** Is lseek not implemented by fs? */
|
||||
unsigned no_lseek:1;
|
||||
|
||||
/** Does the filesystem support posix acls? */
|
||||
unsigned posix_acl:1;
|
||||
|
||||
/** Check permissions based on the file mode or not? */
|
||||
unsigned default_permissions:1;
|
||||
|
||||
/** Allow other than the mounter user to access the filesystem ? */
|
||||
unsigned allow_other:1;
|
||||
|
||||
/** The number of requests waiting for completion */
|
||||
atomic_t num_waiting;
|
||||
|
||||
|
@ -902,6 +903,8 @@ int fuse_allow_current_process(struct fuse_conn *fc);
|
|||
|
||||
u64 fuse_lock_owner_id(struct fuse_conn *fc, fl_owner_t id);
|
||||
|
||||
void fuse_update_ctime(struct inode *inode);
|
||||
|
||||
int fuse_update_attributes(struct inode *inode, struct kstat *stat,
|
||||
struct file *file, bool *refreshed);
|
||||
|
||||
|
@ -966,4 +969,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 */
|
||||
|
|
|
@ -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");
|
||||
|
@ -66,7 +67,8 @@ struct fuse_mount_data {
|
|||
unsigned rootmode_present:1;
|
||||
unsigned user_id_present:1;
|
||||
unsigned group_id_present:1;
|
||||
unsigned flags;
|
||||
unsigned default_permissions:1;
|
||||
unsigned allow_other:1;
|
||||
unsigned max_read;
|
||||
unsigned blksize;
|
||||
};
|
||||
|
@ -192,7 +194,7 @@ void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr,
|
|||
* check in may_delete().
|
||||
*/
|
||||
fi->orig_i_mode = inode->i_mode;
|
||||
if (!(fc->flags & FUSE_DEFAULT_PERMISSIONS))
|
||||
if (!fc->default_permissions)
|
||||
inode->i_mode &= ~S_ISVTX;
|
||||
|
||||
fi->orig_ino = attr->ino;
|
||||
|
@ -340,6 +342,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)
|
||||
|
@ -532,11 +535,11 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d, int is_bdev)
|
|||
break;
|
||||
|
||||
case OPT_DEFAULT_PERMISSIONS:
|
||||
d->flags |= FUSE_DEFAULT_PERMISSIONS;
|
||||
d->default_permissions = 1;
|
||||
break;
|
||||
|
||||
case OPT_ALLOW_OTHER:
|
||||
d->flags |= FUSE_ALLOW_OTHER;
|
||||
d->allow_other = 1;
|
||||
break;
|
||||
|
||||
case OPT_MAX_READ:
|
||||
|
@ -570,9 +573,9 @@ static int fuse_show_options(struct seq_file *m, struct dentry *root)
|
|||
|
||||
seq_printf(m, ",user_id=%u", from_kuid_munged(&init_user_ns, fc->user_id));
|
||||
seq_printf(m, ",group_id=%u", from_kgid_munged(&init_user_ns, fc->group_id));
|
||||
if (fc->flags & FUSE_DEFAULT_PERMISSIONS)
|
||||
if (fc->default_permissions)
|
||||
seq_puts(m, ",default_permissions");
|
||||
if (fc->flags & FUSE_ALLOW_OTHER)
|
||||
if (fc->allow_other)
|
||||
seq_puts(m, ",allow_other");
|
||||
if (fc->max_read != ~0)
|
||||
seq_printf(m, ",max_read=%u", fc->max_read);
|
||||
|
@ -910,8 +913,15 @@ static void process_init_reply(struct fuse_conn *fc, struct fuse_req *req)
|
|||
fc->writeback_cache = 1;
|
||||
if (arg->flags & FUSE_PARALLEL_DIROPS)
|
||||
fc->parallel_dirops = 1;
|
||||
if (arg->flags & FUSE_HANDLE_KILLPRIV)
|
||||
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->default_permissions = 1;
|
||||
fc->posix_acl = 1;
|
||||
fc->sb->s_xattr = fuse_acl_xattr_handlers;
|
||||
}
|
||||
} else {
|
||||
ra_pages = fc->max_read / PAGE_SIZE;
|
||||
fc->no_lock = 1;
|
||||
|
@ -941,7 +951,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_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);
|
||||
|
@ -1071,6 +1081,7 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent)
|
|||
}
|
||||
sb->s_magic = FUSE_SUPER_MAGIC;
|
||||
sb->s_op = &fuse_super_operations;
|
||||
sb->s_xattr = fuse_xattr_handlers;
|
||||
sb->s_maxbytes = MAX_LFS_FILESIZE;
|
||||
sb->s_time_gran = 1;
|
||||
sb->s_export_op = &fuse_export_operations;
|
||||
|
@ -1109,7 +1120,8 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent)
|
|||
fc->dont_mask = 1;
|
||||
sb->s_flags |= MS_POSIXACL;
|
||||
|
||||
fc->flags = d.flags;
|
||||
fc->default_permissions = d.default_permissions;
|
||||
fc->allow_other = d.allow_other;
|
||||
fc->user_id = d.user_id;
|
||||
fc->group_id = d.group_id;
|
||||
fc->max_read = max_t(unsigned, 4096, d.max_read);
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* FUSE: Filesystem in Userspace
|
||||
* Copyright (C) 2001-2016 Miklos Szeredi <miklos@szeredi.hu>
|
||||
*
|
||||
* This program can be distributed under the terms of the GNU GPL.
|
||||
* See the file COPYING.
|
||||
*/
|
||||
|
||||
#include "fuse_i.h"
|
||||
|
||||
#include <linux/xattr.h>
|
||||
#include <linux/posix_acl_xattr.h>
|
||||
|
||||
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);
|
||||
struct fuse_setxattr_in inarg;
|
||||
int err;
|
||||
|
||||
if (fc->no_setxattr)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
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);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
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);
|
||||
struct fuse_getxattr_in inarg;
|
||||
struct fuse_getxattr_out outarg;
|
||||
ssize_t ret;
|
||||
|
||||
if (fc->no_getxattr)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
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 = min_t(ssize_t, outarg.size, XATTR_SIZE_MAX);
|
||||
if (ret == -ENOSYS) {
|
||||
fc->no_getxattr = 1;
|
||||
ret = -EOPNOTSUPP;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int fuse_verify_xattr_list(char *list, size_t size)
|
||||
{
|
||||
size_t origsize = size;
|
||||
|
||||
while (size) {
|
||||
size_t thislen = strnlen(list, size);
|
||||
|
||||
if (!thislen || thislen == size)
|
||||
return -EIO;
|
||||
|
||||
size -= thislen + 1;
|
||||
list += thislen + 1;
|
||||
}
|
||||
|
||||
return origsize;
|
||||
}
|
||||
|
||||
ssize_t fuse_listxattr(struct dentry *entry, char *list, size_t size)
|
||||
{
|
||||
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 = min_t(ssize_t, outarg.size, XATTR_LIST_MAX);
|
||||
if (ret > 0 && size)
|
||||
ret = fuse_verify_xattr_list(list, ret);
|
||||
if (ret == -ENOSYS) {
|
||||
fc->no_listxattr = 1;
|
||||
ret = -EOPNOTSUPP;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int fuse_removexattr(struct inode *inode, const char *name)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
static int fuse_xattr_get(const struct xattr_handler *handler,
|
||||
struct dentry *dentry, struct inode *inode,
|
||||
const char *name, void *value, size_t size)
|
||||
{
|
||||
return fuse_getxattr(inode, name, value, size);
|
||||
}
|
||||
|
||||
static int fuse_xattr_set(const struct xattr_handler *handler,
|
||||
struct dentry *dentry, struct inode *inode,
|
||||
const char *name, const void *value, size_t size,
|
||||
int flags)
|
||||
{
|
||||
if (!value)
|
||||
return fuse_removexattr(inode, name);
|
||||
|
||||
return fuse_setxattr(inode, name, value, size, flags);
|
||||
}
|
||||
|
||||
static const struct xattr_handler fuse_xattr_handler = {
|
||||
.prefix = "",
|
||||
.get = fuse_xattr_get,
|
||||
.set = fuse_xattr_set,
|
||||
};
|
||||
|
||||
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
|
||||
};
|
|
@ -108,6 +108,10 @@
|
|||
*
|
||||
* 7.25
|
||||
* - add FUSE_PARALLEL_DIROPS
|
||||
*
|
||||
* 7.26
|
||||
* - add FUSE_HANDLE_KILLPRIV
|
||||
* - add FUSE_POSIX_ACL
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_FUSE_H
|
||||
|
@ -143,7 +147,7 @@
|
|||
#define FUSE_KERNEL_VERSION 7
|
||||
|
||||
/** Minor version number of this interface */
|
||||
#define FUSE_KERNEL_MINOR_VERSION 25
|
||||
#define FUSE_KERNEL_MINOR_VERSION 26
|
||||
|
||||
/** The node ID of the root inode */
|
||||
#define FUSE_ROOT_ID 1
|
||||
|
@ -238,6 +242,8 @@ 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_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)
|
||||
|
@ -258,6 +264,8 @@ 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_HANDLE_KILLPRIV (1 << 19)
|
||||
#define FUSE_POSIX_ACL (1 << 20)
|
||||
|
||||
/**
|
||||
* CUSE INIT request/reply flags
|
||||
|
|
Loading…
Reference in New Issue