summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosef Bacik <jbacik@redhat.com>2008-07-24 12:16:36 -0400
committerChris Mason <chris.mason@oracle.com>2008-09-25 11:04:05 -0400
commit33268eaf0b3db5e2bd12c0ada81a8e8f87a46d68 (patch)
tree93b6d00a73402b59c2ea7ae7434138feecefc085
parent6099afe88fe64b2f47c43a8a71c13be3a416bbf7 (diff)
downloadlinux-33268eaf0b3db5e2bd12c0ada81a8e8f87a46d68.tar.gz
linux-33268eaf0b3db5e2bd12c0ada81a8e8f87a46d68.tar.xz
Btrfs: Add ACL support
Signed-off-by: Chris Mason <chris.mason@oracle.com>
-rw-r--r--fs/btrfs/acl.c296
-rw-r--r--fs/btrfs/btrfs_inode.h3
-rw-r--r--fs/btrfs/ctree.h7
-rw-r--r--fs/btrfs/inode.c42
-rw-r--r--fs/btrfs/super.c9
-rw-r--r--fs/btrfs/xattr.c26
6 files changed, 335 insertions, 48 deletions
diff --git a/fs/btrfs/acl.c b/fs/btrfs/acl.c
index ff0fcc72848a..b192659b4f5f 100644
--- a/fs/btrfs/acl.c
+++ b/fs/btrfs/acl.c
@@ -20,76 +20,302 @@
#include <linux/string.h>
#include <linux/xattr.h>
#include <linux/posix_acl_xattr.h>
+#include <linux/posix_acl.h>
#include <linux/sched.h>
+
#include "ctree.h"
+#include "btrfs_inode.h"
#include "xattr.h"
-#ifndef is_owner_or_cap
-#define is_owner_or_cap(inode) \
- ((current->fsuid == (inode)->i_uid) || capable(CAP_FOWNER))
-#endif
+
+static void btrfs_update_cached_acl(struct inode *inode,
+ struct posix_acl **p_acl,
+ struct posix_acl *acl)
+{
+ spin_lock(&inode->i_lock);
+ if (*p_acl && *p_acl != BTRFS_ACL_NOT_CACHED)
+ posix_acl_release(*p_acl);
+ *p_acl = posix_acl_dup(acl);
+ spin_unlock(&inode->i_lock);
+}
+
+static struct posix_acl *btrfs_get_acl(struct inode *inode, int type)
+{
+ int size, name_index;
+ char *value = NULL;
+ struct posix_acl *acl = NULL, **p_acl;
+
+ switch (type) {
+ case ACL_TYPE_ACCESS:
+ name_index = BTRFS_XATTR_INDEX_POSIX_ACL_ACCESS;
+ p_acl = &BTRFS_I(inode)->i_acl;
+ break;
+ case ACL_TYPE_DEFAULT:
+ name_index = BTRFS_XATTR_INDEX_POSIX_ACL_DEFAULT;
+ p_acl = &BTRFS_I(inode)->i_default_acl;
+ break;
+ default:
+ return ERR_PTR(-EINVAL);
+ }
+
+ spin_lock(&inode->i_lock);
+ if (*p_acl != BTRFS_ACL_NOT_CACHED)
+ acl = posix_acl_dup(*p_acl);
+ spin_unlock(&inode->i_lock);
+
+ if (acl)
+ return acl;
+
+
+ size = btrfs_xattr_get(inode, name_index, "", NULL, 0);
+ if (size > 0) {
+ value = kzalloc(size, GFP_NOFS);
+ if (!value)
+ return ERR_PTR(-ENOMEM);
+ size = btrfs_xattr_get(inode, name_index, "", value, size);
+ if (size > 0) {
+ acl = posix_acl_from_xattr(value, size);
+ btrfs_update_cached_acl(inode, p_acl, acl);
+ }
+ kfree(value);
+ } else if (size == -ENOENT) {
+ acl = NULL;
+ btrfs_update_cached_acl(inode, p_acl, acl);
+ }
+
+ return acl;
+}
+
+static int btrfs_xattr_get_acl(struct inode *inode, int type,
+ void *value, size_t size)
+{
+ struct posix_acl *acl;
+ int ret = 0;
+
+ acl = btrfs_get_acl(inode, type);
+
+ if (IS_ERR(acl))
+ return PTR_ERR(acl);
+ if (acl == NULL)
+ return -ENODATA;
+ ret = posix_acl_to_xattr(acl, value, size);
+ posix_acl_release(acl);
+
+ return ret;
+}
+
+/*
+ * Needs to be called with fs_mutex held
+ */
+static int btrfs_set_acl(struct inode *inode, struct posix_acl *acl, int type)
+{
+ int ret, name_index = 0, size = 0;
+ struct posix_acl **p_acl;
+ char *value = NULL;
+ mode_t mode;
+
+ if (acl) {
+ ret = posix_acl_valid(acl);
+ if (ret < 0)
+ return ret;
+ ret = 0;
+ }
+
+ switch (type) {
+ case ACL_TYPE_ACCESS:
+ mode = inode->i_mode;
+ ret = posix_acl_equiv_mode(acl, &mode);
+ if (ret < 0)
+ return ret;
+ ret = 0;
+ inode->i_mode = mode;
+ name_index = BTRFS_XATTR_INDEX_POSIX_ACL_ACCESS;
+ p_acl = &BTRFS_I(inode)->i_acl;
+ break;
+ case ACL_TYPE_DEFAULT:
+ if (!S_ISDIR(inode->i_mode))
+ return acl ? -EINVAL : 0;
+ name_index = BTRFS_XATTR_INDEX_POSIX_ACL_DEFAULT;
+ p_acl = &BTRFS_I(inode)->i_default_acl;
+ 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;
+ }
+
+ ret = posix_acl_to_xattr(acl, value, size);
+ if (ret < 0)
+ goto out;
+ }
+
+ ret = btrfs_xattr_set(inode, name_index, "", value, size, 0);
+
+out:
+ if (value)
+ kfree(value);
+
+ if (!ret)
+ btrfs_update_cached_acl(inode, p_acl, acl);
+
+ return ret;
+}
static int btrfs_xattr_set_acl(struct inode *inode, int type,
const void *value, size_t size)
{
int ret = 0;
- struct posix_acl *acl;
+ struct posix_acl *acl = NULL;
- if (!is_owner_or_cap(inode))
- return -EPERM;
if (value) {
acl = posix_acl_from_xattr(value, size);
if (acl == NULL) {
value = NULL;
size = 0;
} else if (IS_ERR(acl)) {
- ret = PTR_ERR(acl);
- } else {
- ret = posix_acl_valid(acl);
- posix_acl_release(acl);
+ return PTR_ERR(acl);
}
- if (ret)
- return ret;
}
- return btrfs_xattr_set(inode, type, "", value, size, 0);
-}
-static int btrfs_xattr_get_acl(struct inode *inode, int type,
- void *value, size_t size)
-{
- return btrfs_xattr_get(inode, type, "", value, size);
+ ret = btrfs_set_acl(inode, acl, type);
+
+ posix_acl_release(acl);
+
+ return ret;
}
+
+
static int btrfs_xattr_acl_access_get(struct inode *inode, const char *name,
void *value, size_t size)
{
- if (*name != '\0')
- return -EINVAL;
- return btrfs_xattr_get_acl(inode, BTRFS_XATTR_INDEX_POSIX_ACL_ACCESS,
- value, size);
+ return btrfs_xattr_get_acl(inode, ACL_TYPE_ACCESS, value, size);
}
+
static int btrfs_xattr_acl_access_set(struct inode *inode, const char *name,
const void *value, size_t size, int flags)
{
- if (*name != '\0')
- return -EINVAL;
- return btrfs_xattr_set_acl(inode, BTRFS_XATTR_INDEX_POSIX_ACL_ACCESS,
- value, size);
+ return btrfs_xattr_set_acl(inode, ACL_TYPE_ACCESS, value, size);
}
+
static int btrfs_xattr_acl_default_get(struct inode *inode, const char *name,
void *value, size_t size)
{
- if (*name != '\0')
- return -EINVAL;
- return btrfs_xattr_get_acl(inode, BTRFS_XATTR_INDEX_POSIX_ACL_DEFAULT,
- value, size);
+ return btrfs_xattr_get_acl(inode, ACL_TYPE_DEFAULT, value, size);
}
+
static int btrfs_xattr_acl_default_set(struct inode *inode, const char *name,
const void *value, size_t size, int flags)
{
- if (*name != '\0')
- return -EINVAL;
- return btrfs_xattr_set_acl(inode, BTRFS_XATTR_INDEX_POSIX_ACL_DEFAULT,
- value, size);
+ return btrfs_xattr_set_acl(inode, ACL_TYPE_DEFAULT, value, size);
+}
+
+int btrfs_check_acl(struct inode *inode, int mask)
+{
+ struct posix_acl *acl;
+ int error = -EAGAIN;
+
+ acl = btrfs_get_acl(inode, ACL_TYPE_ACCESS);
+
+ if (IS_ERR(acl))
+ return PTR_ERR(acl);
+ if (acl) {
+ error = posix_acl_permission(inode, acl, mask);
+ posix_acl_release(acl);
+ }
+
+ return error;
+}
+
+/*
+ * btrfs_init_acl is already generally called under fs_mutex, so the locking
+ * stuff has been fixed to work with that. If the locking stuff changes, we
+ * need to re-evaluate the acl locking stuff.
+ */
+int btrfs_init_acl(struct inode *inode, struct inode *dir)
+{
+ struct posix_acl *acl = NULL;
+ int ret = 0;
+
+ /* this happens with subvols */
+ if (!dir)
+ return 0;
+
+ if (!S_ISLNK(inode->i_mode)) {
+ if (IS_POSIXACL(dir)) {
+ acl = btrfs_get_acl(dir, ACL_TYPE_DEFAULT);
+ if (IS_ERR(acl))
+ return PTR_ERR(acl);
+ }
+
+ if (!acl)
+ inode->i_mode &= ~current->fs->umask;
+ }
+
+ if (IS_POSIXACL(dir) && acl) {
+ struct posix_acl *clone;
+ mode_t mode;
+
+ if (S_ISDIR(inode->i_mode)) {
+ ret = btrfs_set_acl(inode, acl, ACL_TYPE_DEFAULT);
+ if (ret)
+ goto failed;
+ }
+ clone = posix_acl_clone(acl, GFP_NOFS);
+ ret = -ENOMEM;
+ if (!clone)
+ goto failed;
+
+ mode = inode->i_mode;
+ ret = posix_acl_create_masq(clone, &mode);
+ if (ret >= 0) {
+ inode->i_mode = mode;
+ if (ret > 0) {
+ /* we need an acl */
+ ret = btrfs_set_acl(inode, clone,
+ ACL_TYPE_ACCESS);
+ }
+ }
+ }
+failed:
+ posix_acl_release(acl);
+
+ return ret;
+}
+
+int btrfs_acl_chmod(struct inode *inode)
+{
+ struct posix_acl *acl, *clone;
+ int ret = 0;
+
+ if (S_ISLNK(inode->i_mode))
+ return -EOPNOTSUPP;
+
+ if (!IS_POSIXACL(inode))
+ return 0;
+
+ acl = btrfs_get_acl(inode, ACL_TYPE_ACCESS);
+ if (IS_ERR(acl) || !acl)
+ return PTR_ERR(acl);
+
+ clone = posix_acl_clone(acl, GFP_KERNEL);
+ posix_acl_release(acl);
+ if (!clone)
+ return -ENOMEM;
+
+ ret = posix_acl_chmod_masq(clone, inode->i_mode);
+ if (!ret)
+ ret = btrfs_set_acl(inode, clone, ACL_TYPE_ACCESS);
+
+ posix_acl_release(clone);
+
+ return ret;
}
+
struct xattr_handler btrfs_xattr_acl_default_handler = {
.prefix = POSIX_ACL_XATTR_DEFAULT,
.list = btrfs_xattr_generic_list,
diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h
index b7bd60e4fdd7..9f2a4ef944a7 100644
--- a/fs/btrfs/btrfs_inode.h
+++ b/fs/btrfs/btrfs_inode.h
@@ -36,6 +36,9 @@ struct btrfs_inode {
struct inode vfs_inode;
struct btrfs_ordered_inode_tree ordered_tree;
+ struct posix_acl *i_acl;
+ struct posix_acl *i_default_acl;
+
/*
* transid of the trans_handle that last modified this inode
*/
diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h
index 53d315bdd16d..f87d7263f2d7 100644
--- a/fs/btrfs/ctree.h
+++ b/fs/btrfs/ctree.h
@@ -42,6 +42,8 @@ struct btrfs_ordered_sum;
#define BTRFS_MAGIC "_B5RfS_M"
+#define BTRFS_ACL_NOT_CACHED ((void *)-1)
+
#ifdef CONFIG_LOCKDEP
# define BTRFS_MAX_LEVEL 7
#else
@@ -1694,4 +1696,9 @@ ssize_t btrfs_listxattr(struct dentry *dentry, char *buffer, size_t size);
u64 btrfs_parse_size(char *str);
int btrfs_parse_options(struct btrfs_root *root, char *options);
int btrfs_sync_fs(struct super_block *sb, int wait);
+
+/* acl.c */
+int btrfs_check_acl(struct inode *inode, int mask);
+int btrfs_init_acl(struct inode *inode, struct inode *dir);
+int btrfs_acl_chmod(struct inode *inode);
#endif
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 8d371d6fe551..2d8853543a71 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -36,6 +36,7 @@
#include <linux/bit_spinlock.h>
#include <linux/version.h>
#include <linux/xattr.h>
+#include <linux/posix_acl.h>
#include "ctree.h"
#include "disk-io.h"
#include "transaction.h"
@@ -1478,6 +1479,9 @@ static int btrfs_setattr(struct dentry *dentry, struct iattr *attr)
}
out:
err = inode_setattr(inode, attr);
+
+ if (!err && ((attr->ia_valid & ATTR_MODE)))
+ err = btrfs_acl_chmod(inode);
fail:
return err;
}
@@ -2184,6 +2188,12 @@ static int btrfs_mknod(struct inode *dir, struct dentry *dentry,
if (IS_ERR(inode))
goto out_unlock;
+ err = btrfs_init_acl(inode, dir);
+ if (err) {
+ drop_inode = 1;
+ goto out_unlock;
+ }
+
btrfs_set_trans_block_group(trans, inode);
err = btrfs_add_nondir(trans, dentry, inode, 0);
if (err)
@@ -2239,6 +2249,12 @@ static int btrfs_create(struct inode *dir, struct dentry *dentry,
if (IS_ERR(inode))
goto out_unlock;
+ err = btrfs_init_acl(inode, dir);
+ if (err) {
+ drop_inode = 1;
+ goto out_unlock;
+ }
+
btrfs_set_trans_block_group(trans, inode);
err = btrfs_add_nondir(trans, dentry, inode, 0);
if (err)
@@ -2366,6 +2382,11 @@ static int btrfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
}
drop_on_err = 1;
+
+ err = btrfs_init_acl(inode, dir);
+ if (err)
+ goto out_fail;
+
inode->i_op = &btrfs_dir_inode_operations;
inode->i_fop = &btrfs_dir_file_operations;
btrfs_set_trans_block_group(trans, inode);
@@ -3023,6 +3044,8 @@ struct inode *btrfs_alloc_inode(struct super_block *sb)
return NULL;
ei->last_trans = 0;
btrfs_ordered_inode_tree_init(&ei->ordered_tree);
+ ei->i_acl = BTRFS_ACL_NOT_CACHED;
+ ei->i_default_acl = BTRFS_ACL_NOT_CACHED;
return &ei->vfs_inode;
}
@@ -3032,6 +3055,13 @@ void btrfs_destroy_inode(struct inode *inode)
WARN_ON(!list_empty(&inode->i_dentry));
WARN_ON(inode->i_data.nrpages);
+ if (BTRFS_I(inode)->i_acl &&
+ BTRFS_I(inode)->i_acl != BTRFS_ACL_NOT_CACHED)
+ posix_acl_release(BTRFS_I(inode)->i_acl);
+ if (BTRFS_I(inode)->i_default_acl &&
+ BTRFS_I(inode)->i_default_acl != BTRFS_ACL_NOT_CACHED)
+ posix_acl_release(BTRFS_I(inode)->i_default_acl);
+
while(1) {
ordered = btrfs_lookup_first_ordered_extent(inode, (u64)-1);
if (!ordered)
@@ -3230,6 +3260,12 @@ static int btrfs_symlink(struct inode *dir, struct dentry *dentry,
if (IS_ERR(inode))
goto out_unlock;
+ err = btrfs_init_acl(inode, dir);
+ if (err) {
+ drop_inode = 1;
+ goto out_unlock;
+ }
+
btrfs_set_trans_block_group(trans, inode);
err = btrfs_add_nondir(trans, dentry, inode, 0);
if (err)
@@ -3310,7 +3346,7 @@ static int btrfs_permission(struct inode *inode, int mask,
{
if (btrfs_test_flag(inode, READONLY) && (mask & MAY_WRITE))
return -EACCES;
- return generic_permission(inode, mask, NULL);
+ return generic_permission(inode, mask, btrfs_check_acl);
}
static struct inode_operations btrfs_dir_inode_operations = {
@@ -3392,6 +3428,10 @@ static struct inode_operations btrfs_special_inode_operations = {
.getattr = btrfs_getattr,
.setattr = btrfs_setattr,
.permission = btrfs_permission,
+ .setxattr = generic_setxattr,
+ .getxattr = generic_getxattr,
+ .listxattr = btrfs_listxattr,
+ .removexattr = generic_removexattr,
};
static struct inode_operations btrfs_symlink_inode_operations = {
.readlink = generic_readlink,
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index 4cb6aac5122e..a6a418b6894b 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -68,7 +68,7 @@ static void btrfs_put_super (struct super_block * sb)
enum {
Opt_degraded, Opt_subvol, Opt_device, Opt_nodatasum, Opt_nodatacow,
Opt_max_extent, Opt_max_inline, Opt_alloc_start, Opt_nobarrier,
- Opt_ssd, Opt_thread_pool, Opt_err,
+ Opt_ssd, Opt_thread_pool, Opt_noacl, Opt_err,
};
static match_table_t tokens = {
@@ -83,7 +83,8 @@ static match_table_t tokens = {
{Opt_alloc_start, "alloc_start=%s"},
{Opt_thread_pool, "thread_pool=%d"},
{Opt_ssd, "ssd"},
- {Opt_err, NULL}
+ {Opt_noacl, "noacl"},
+ {Opt_err, NULL},
};
u64 btrfs_parse_size(char *str)
@@ -215,6 +216,9 @@ int btrfs_parse_options(struct btrfs_root *root, char *options)
info->alloc_start);
}
break;
+ case Opt_noacl:
+ root->fs_info->sb->s_flags &= ~MS_POSIXACL;
+ break;
default:
break;
}
@@ -301,6 +305,7 @@ static int btrfs_fill_super(struct super_block * sb,
sb->s_op = &btrfs_super_ops;
sb->s_xattr = btrfs_xattr_handlers;
sb->s_time_gran = 1;
+ sb->s_flags |= MS_POSIXACL;
tree_root = open_ctree(sb, fs_devices, (char *)data);
diff --git a/fs/btrfs/xattr.c b/fs/btrfs/xattr.c
index 6730b5958844..121c9550314f 100644
--- a/fs/btrfs/xattr.c
+++ b/fs/btrfs/xattr.c
@@ -26,25 +26,27 @@
#include "transaction.h"
#include "xattr.h"
#include "disk-io.h"
+
static struct xattr_handler *btrfs_xattr_handler_map[] = {
[BTRFS_XATTR_INDEX_USER] = &btrfs_xattr_user_handler,
#ifdef CONFIG_FS_POSIX_ACL
-// [BTRFS_XATTR_INDEX_POSIX_ACL_ACCESS] = &btrfs_xattr_acl_access_handler,
-// [BTRFS_XATTR_INDEX_POSIX_ACL_DEFAULT] = &btrfs_xattr_acl_default_handler,
+ [BTRFS_XATTR_INDEX_POSIX_ACL_ACCESS] = &btrfs_xattr_acl_access_handler,
+ [BTRFS_XATTR_INDEX_POSIX_ACL_DEFAULT] = &btrfs_xattr_acl_default_handler,
#endif
[BTRFS_XATTR_INDEX_TRUSTED] = &btrfs_xattr_trusted_handler,
[BTRFS_XATTR_INDEX_SECURITY] = &btrfs_xattr_security_handler,
-// [BTRFS_XATTR_INDEX_SYSTEM] = &btrfs_xattr_system_handler,
+ [BTRFS_XATTR_INDEX_SYSTEM] = &btrfs_xattr_system_handler,
};
+
struct xattr_handler *btrfs_xattr_handlers[] = {
&btrfs_xattr_user_handler,
#ifdef CONFIG_FS_POSIX_ACL
-// &btrfs_xattr_acl_access_handler,
-// &btrfs_xattr_acl_default_handler,
+ &btrfs_xattr_acl_access_handler,
+ &btrfs_xattr_acl_default_handler,
#endif
&btrfs_xattr_trusted_handler,
&btrfs_xattr_security_handler,
-// &btrfs_xattr_system_handler,
+ &btrfs_xattr_system_handler,
NULL,
};
@@ -237,10 +239,14 @@ int btrfs_xattr_set(struct inode *inode, int name_index,
mod = 1;
goto out;
}
- } else if (flags & XATTR_REPLACE) {
- /* we couldn't find the attr to replace, so error out */
- ret = -ENODATA;
- goto out;
+ } else {
+ btrfs_release_path(root, path);
+
+ if (flags & XATTR_REPLACE) {
+ /* we couldn't find the attr to replace */
+ ret = -ENODATA;
+ goto out;
+ }
}
/* ok we have to create a completely new xattr */