431547b3c4
Add a flags argument to struct xattr_handler and pass it to all xattr handler methods. This allows using the same methods for multiple handlers, e.g. for the ACL methods which perform exactly the same action for the access and default ACLs, just using a different underlying attribute. With a little more groundwork it'll also allow sharing the methods for the regular user/trusted/secure handlers in extN, ocfs2 and jffs2 like it's already done for xfs in this patch. Also change the inode argument to the handlers to a dentry to allow using the handlers mechnism for filesystems that require it later, e.g. cifs. [with GFS2 bits updated by Steven Whitehouse <swhiteho@redhat.com>] Signed-off-by: Christoph Hellwig <hch@lst.de> Reviewed-by: James Morris <jmorris@namei.org> Acked-by: Joel Becker <joel.becker@oracle.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
438 lines
9.6 KiB
C
438 lines
9.6 KiB
C
/*
|
|
* linux/fs/ext2/acl.c
|
|
*
|
|
* Copyright (C) 2001-2003 Andreas Gruenbacher, <agruen@suse.de>
|
|
*/
|
|
|
|
#include <linux/capability.h>
|
|
#include <linux/init.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/fs.h>
|
|
#include "ext2.h"
|
|
#include "xattr.h"
|
|
#include "acl.h"
|
|
|
|
/*
|
|
* Convert from filesystem to in-memory representation.
|
|
*/
|
|
static struct posix_acl *
|
|
ext2_acl_from_disk(const void *value, size_t size)
|
|
{
|
|
const char *end = (char *)value + size;
|
|
int n, count;
|
|
struct posix_acl *acl;
|
|
|
|
if (!value)
|
|
return NULL;
|
|
if (size < sizeof(ext2_acl_header))
|
|
return ERR_PTR(-EINVAL);
|
|
if (((ext2_acl_header *)value)->a_version !=
|
|
cpu_to_le32(EXT2_ACL_VERSION))
|
|
return ERR_PTR(-EINVAL);
|
|
value = (char *)value + sizeof(ext2_acl_header);
|
|
count = ext2_acl_count(size);
|
|
if (count < 0)
|
|
return ERR_PTR(-EINVAL);
|
|
if (count == 0)
|
|
return NULL;
|
|
acl = posix_acl_alloc(count, GFP_KERNEL);
|
|
if (!acl)
|
|
return ERR_PTR(-ENOMEM);
|
|
for (n=0; n < count; n++) {
|
|
ext2_acl_entry *entry =
|
|
(ext2_acl_entry *)value;
|
|
if ((char *)value + sizeof(ext2_acl_entry_short) > end)
|
|
goto fail;
|
|
acl->a_entries[n].e_tag = le16_to_cpu(entry->e_tag);
|
|
acl->a_entries[n].e_perm = le16_to_cpu(entry->e_perm);
|
|
switch(acl->a_entries[n].e_tag) {
|
|
case ACL_USER_OBJ:
|
|
case ACL_GROUP_OBJ:
|
|
case ACL_MASK:
|
|
case ACL_OTHER:
|
|
value = (char *)value +
|
|
sizeof(ext2_acl_entry_short);
|
|
acl->a_entries[n].e_id = ACL_UNDEFINED_ID;
|
|
break;
|
|
|
|
case ACL_USER:
|
|
case ACL_GROUP:
|
|
value = (char *)value + sizeof(ext2_acl_entry);
|
|
if ((char *)value > end)
|
|
goto fail;
|
|
acl->a_entries[n].e_id =
|
|
le32_to_cpu(entry->e_id);
|
|
break;
|
|
|
|
default:
|
|
goto fail;
|
|
}
|
|
}
|
|
if (value != end)
|
|
goto fail;
|
|
return acl;
|
|
|
|
fail:
|
|
posix_acl_release(acl);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
/*
|
|
* Convert from in-memory to filesystem representation.
|
|
*/
|
|
static void *
|
|
ext2_acl_to_disk(const struct posix_acl *acl, size_t *size)
|
|
{
|
|
ext2_acl_header *ext_acl;
|
|
char *e;
|
|
size_t n;
|
|
|
|
*size = ext2_acl_size(acl->a_count);
|
|
ext_acl = kmalloc(sizeof(ext2_acl_header) + acl->a_count *
|
|
sizeof(ext2_acl_entry), GFP_KERNEL);
|
|
if (!ext_acl)
|
|
return ERR_PTR(-ENOMEM);
|
|
ext_acl->a_version = cpu_to_le32(EXT2_ACL_VERSION);
|
|
e = (char *)ext_acl + sizeof(ext2_acl_header);
|
|
for (n=0; n < acl->a_count; n++) {
|
|
ext2_acl_entry *entry = (ext2_acl_entry *)e;
|
|
entry->e_tag = cpu_to_le16(acl->a_entries[n].e_tag);
|
|
entry->e_perm = cpu_to_le16(acl->a_entries[n].e_perm);
|
|
switch(acl->a_entries[n].e_tag) {
|
|
case ACL_USER:
|
|
case ACL_GROUP:
|
|
entry->e_id =
|
|
cpu_to_le32(acl->a_entries[n].e_id);
|
|
e += sizeof(ext2_acl_entry);
|
|
break;
|
|
|
|
case ACL_USER_OBJ:
|
|
case ACL_GROUP_OBJ:
|
|
case ACL_MASK:
|
|
case ACL_OTHER:
|
|
e += sizeof(ext2_acl_entry_short);
|
|
break;
|
|
|
|
default:
|
|
goto fail;
|
|
}
|
|
}
|
|
return (char *)ext_acl;
|
|
|
|
fail:
|
|
kfree(ext_acl);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
/*
|
|
* inode->i_mutex: don't care
|
|
*/
|
|
static struct posix_acl *
|
|
ext2_get_acl(struct inode *inode, int type)
|
|
{
|
|
int name_index;
|
|
char *value = NULL;
|
|
struct posix_acl *acl;
|
|
int retval;
|
|
|
|
if (!test_opt(inode->i_sb, POSIX_ACL))
|
|
return NULL;
|
|
|
|
acl = get_cached_acl(inode, type);
|
|
if (acl != ACL_NOT_CACHED)
|
|
return acl;
|
|
|
|
switch (type) {
|
|
case ACL_TYPE_ACCESS:
|
|
name_index = EXT2_XATTR_INDEX_POSIX_ACL_ACCESS;
|
|
break;
|
|
case ACL_TYPE_DEFAULT:
|
|
name_index = EXT2_XATTR_INDEX_POSIX_ACL_DEFAULT;
|
|
break;
|
|
default:
|
|
BUG();
|
|
}
|
|
retval = ext2_xattr_get(inode, name_index, "", NULL, 0);
|
|
if (retval > 0) {
|
|
value = kmalloc(retval, GFP_KERNEL);
|
|
if (!value)
|
|
return ERR_PTR(-ENOMEM);
|
|
retval = ext2_xattr_get(inode, name_index, "", value, retval);
|
|
}
|
|
if (retval > 0)
|
|
acl = ext2_acl_from_disk(value, retval);
|
|
else if (retval == -ENODATA || retval == -ENOSYS)
|
|
acl = NULL;
|
|
else
|
|
acl = ERR_PTR(retval);
|
|
kfree(value);
|
|
|
|
if (!IS_ERR(acl))
|
|
set_cached_acl(inode, type, acl);
|
|
|
|
return acl;
|
|
}
|
|
|
|
/*
|
|
* inode->i_mutex: down
|
|
*/
|
|
static int
|
|
ext2_set_acl(struct inode *inode, int type, struct posix_acl *acl)
|
|
{
|
|
int name_index;
|
|
void *value = NULL;
|
|
size_t size = 0;
|
|
int error;
|
|
|
|
if (S_ISLNK(inode->i_mode))
|
|
return -EOPNOTSUPP;
|
|
if (!test_opt(inode->i_sb, POSIX_ACL))
|
|
return 0;
|
|
|
|
switch(type) {
|
|
case ACL_TYPE_ACCESS:
|
|
name_index = EXT2_XATTR_INDEX_POSIX_ACL_ACCESS;
|
|
if (acl) {
|
|
mode_t mode = inode->i_mode;
|
|
error = posix_acl_equiv_mode(acl, &mode);
|
|
if (error < 0)
|
|
return error;
|
|
else {
|
|
inode->i_mode = mode;
|
|
mark_inode_dirty(inode);
|
|
if (error == 0)
|
|
acl = NULL;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ACL_TYPE_DEFAULT:
|
|
name_index = EXT2_XATTR_INDEX_POSIX_ACL_DEFAULT;
|
|
if (!S_ISDIR(inode->i_mode))
|
|
return acl ? -EACCES : 0;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
if (acl) {
|
|
value = ext2_acl_to_disk(acl, &size);
|
|
if (IS_ERR(value))
|
|
return (int)PTR_ERR(value);
|
|
}
|
|
|
|
error = ext2_xattr_set(inode, name_index, "", value, size, 0);
|
|
|
|
kfree(value);
|
|
if (!error)
|
|
set_cached_acl(inode, type, acl);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
ext2_check_acl(struct inode *inode, int mask)
|
|
{
|
|
struct posix_acl *acl = ext2_get_acl(inode, ACL_TYPE_ACCESS);
|
|
|
|
if (IS_ERR(acl))
|
|
return PTR_ERR(acl);
|
|
if (acl) {
|
|
int error = posix_acl_permission(inode, acl, mask);
|
|
posix_acl_release(acl);
|
|
return error;
|
|
}
|
|
|
|
return -EAGAIN;
|
|
}
|
|
|
|
/*
|
|
* Initialize the ACLs of a new inode. Called from ext2_new_inode.
|
|
*
|
|
* dir->i_mutex: down
|
|
* inode->i_mutex: up (access to inode is still exclusive)
|
|
*/
|
|
int
|
|
ext2_init_acl(struct inode *inode, struct inode *dir)
|
|
{
|
|
struct posix_acl *acl = NULL;
|
|
int error = 0;
|
|
|
|
if (!S_ISLNK(inode->i_mode)) {
|
|
if (test_opt(dir->i_sb, POSIX_ACL)) {
|
|
acl = ext2_get_acl(dir, ACL_TYPE_DEFAULT);
|
|
if (IS_ERR(acl))
|
|
return PTR_ERR(acl);
|
|
}
|
|
if (!acl)
|
|
inode->i_mode &= ~current_umask();
|
|
}
|
|
if (test_opt(inode->i_sb, POSIX_ACL) && acl) {
|
|
struct posix_acl *clone;
|
|
mode_t mode;
|
|
|
|
if (S_ISDIR(inode->i_mode)) {
|
|
error = ext2_set_acl(inode, ACL_TYPE_DEFAULT, acl);
|
|
if (error)
|
|
goto cleanup;
|
|
}
|
|
clone = posix_acl_clone(acl, GFP_KERNEL);
|
|
error = -ENOMEM;
|
|
if (!clone)
|
|
goto cleanup;
|
|
mode = inode->i_mode;
|
|
error = posix_acl_create_masq(clone, &mode);
|
|
if (error >= 0) {
|
|
inode->i_mode = mode;
|
|
if (error > 0) {
|
|
/* This is an extended ACL */
|
|
error = ext2_set_acl(inode,
|
|
ACL_TYPE_ACCESS, clone);
|
|
}
|
|
}
|
|
posix_acl_release(clone);
|
|
}
|
|
cleanup:
|
|
posix_acl_release(acl);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Does chmod for an inode that may have an Access Control List. The
|
|
* inode->i_mode field must be updated to the desired value by the caller
|
|
* before calling this function.
|
|
* Returns 0 on success, or a negative error number.
|
|
*
|
|
* We change the ACL rather than storing some ACL entries in the file
|
|
* mode permission bits (which would be more efficient), because that
|
|
* would break once additional permissions (like ACL_APPEND, ACL_DELETE
|
|
* for directories) are added. There are no more bits available in the
|
|
* file mode.
|
|
*
|
|
* inode->i_mutex: down
|
|
*/
|
|
int
|
|
ext2_acl_chmod(struct inode *inode)
|
|
{
|
|
struct posix_acl *acl, *clone;
|
|
int error;
|
|
|
|
if (!test_opt(inode->i_sb, POSIX_ACL))
|
|
return 0;
|
|
if (S_ISLNK(inode->i_mode))
|
|
return -EOPNOTSUPP;
|
|
acl = ext2_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;
|
|
error = posix_acl_chmod_masq(clone, inode->i_mode);
|
|
if (!error)
|
|
error = ext2_set_acl(inode, ACL_TYPE_ACCESS, clone);
|
|
posix_acl_release(clone);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Extended attribut handlers
|
|
*/
|
|
static size_t
|
|
ext2_xattr_list_acl_access(struct dentry *dentry, char *list, size_t list_size,
|
|
const char *name, size_t name_len, int type)
|
|
{
|
|
const size_t size = sizeof(POSIX_ACL_XATTR_ACCESS);
|
|
|
|
if (!test_opt(dentry->d_sb, POSIX_ACL))
|
|
return 0;
|
|
if (list && size <= list_size)
|
|
memcpy(list, POSIX_ACL_XATTR_ACCESS, size);
|
|
return size;
|
|
}
|
|
|
|
static size_t
|
|
ext2_xattr_list_acl_default(struct dentry *dentry, char *list, size_t list_size,
|
|
const char *name, size_t name_len, int type)
|
|
{
|
|
const size_t size = sizeof(POSIX_ACL_XATTR_DEFAULT);
|
|
|
|
if (!test_opt(dentry->d_sb, POSIX_ACL))
|
|
return 0;
|
|
if (list && size <= list_size)
|
|
memcpy(list, POSIX_ACL_XATTR_DEFAULT, size);
|
|
return size;
|
|
}
|
|
|
|
static int
|
|
ext2_xattr_get_acl(struct dentry *dentry, const char *name, void *buffer,
|
|
size_t size, int type)
|
|
{
|
|
struct posix_acl *acl;
|
|
int error;
|
|
|
|
if (strcmp(name, "") != 0)
|
|
return -EINVAL;
|
|
if (!test_opt(dentry->d_sb, POSIX_ACL))
|
|
return -EOPNOTSUPP;
|
|
|
|
acl = ext2_get_acl(dentry->d_inode, type);
|
|
if (IS_ERR(acl))
|
|
return PTR_ERR(acl);
|
|
if (acl == NULL)
|
|
return -ENODATA;
|
|
error = posix_acl_to_xattr(acl, buffer, size);
|
|
posix_acl_release(acl);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
ext2_xattr_set_acl(struct dentry *dentry, const char *name, const void *value,
|
|
size_t size, int flags, int type)
|
|
{
|
|
struct posix_acl *acl;
|
|
int error;
|
|
|
|
if (strcmp(name, "") != 0)
|
|
return -EINVAL;
|
|
if (!test_opt(dentry->d_sb, POSIX_ACL))
|
|
return -EOPNOTSUPP;
|
|
if (!is_owner_or_cap(dentry->d_inode))
|
|
return -EPERM;
|
|
|
|
if (value) {
|
|
acl = posix_acl_from_xattr(value, size);
|
|
if (IS_ERR(acl))
|
|
return PTR_ERR(acl);
|
|
else if (acl) {
|
|
error = posix_acl_valid(acl);
|
|
if (error)
|
|
goto release_and_out;
|
|
}
|
|
} else
|
|
acl = NULL;
|
|
|
|
error = ext2_set_acl(dentry->d_inode, type, acl);
|
|
|
|
release_and_out:
|
|
posix_acl_release(acl);
|
|
return error;
|
|
}
|
|
|
|
struct xattr_handler ext2_xattr_acl_access_handler = {
|
|
.prefix = POSIX_ACL_XATTR_ACCESS,
|
|
.flags = ACL_TYPE_ACCESS,
|
|
.list = ext2_xattr_list_acl_access,
|
|
.get = ext2_xattr_get_acl,
|
|
.set = ext2_xattr_set_acl,
|
|
};
|
|
|
|
struct xattr_handler ext2_xattr_acl_default_handler = {
|
|
.prefix = POSIX_ACL_XATTR_DEFAULT,
|
|
.flags = ACL_TYPE_DEFAULT,
|
|
.list = ext2_xattr_list_acl_default,
|
|
.get = ext2_xattr_get_acl,
|
|
.set = ext2_xattr_set_acl,
|
|
};
|