diff options
Diffstat (limited to 'fs/reiserfs/xattr_acl.c')
-rw-r--r-- | fs/reiserfs/xattr_acl.c | 571 |
1 files changed, 571 insertions, 0 deletions
diff --git a/fs/reiserfs/xattr_acl.c b/fs/reiserfs/xattr_acl.c new file mode 100644 index 000000000000..e302071903a1 --- /dev/null +++ b/fs/reiserfs/xattr_acl.c @@ -0,0 +1,571 @@ +#include <linux/fs.h> +#include <linux/posix_acl.h> +#include <linux/reiserfs_fs.h> +#include <linux/errno.h> +#include <linux/pagemap.h> +#include <linux/xattr.h> +#include <linux/xattr_acl.h> +#include <linux/reiserfs_xattr.h> +#include <linux/reiserfs_acl.h> +#include <asm/uaccess.h> + +static int reiserfs_set_acl(struct inode *inode, int type, struct posix_acl *acl); + +static int +xattr_set_acl(struct inode *inode, int type, const void *value, size_t size) +{ + struct posix_acl *acl; + int error; + + if (!reiserfs_posixacl(inode->i_sb)) + return -EOPNOTSUPP; + if ((current->fsuid != inode->i_uid) && !capable(CAP_FOWNER)) + 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 = reiserfs_set_acl (inode, type, acl); + +release_and_out: + posix_acl_release(acl); + return error; +} + + +static int +xattr_get_acl(struct inode *inode, int type, void *buffer, size_t size) +{ + struct posix_acl *acl; + int error; + + if (!reiserfs_posixacl(inode->i_sb)) + return -EOPNOTSUPP; + + acl = reiserfs_get_acl (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; +} + + +/* + * Convert from filesystem to in-memory representation. + */ +static struct posix_acl * +posix_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(reiserfs_acl_header)) + return ERR_PTR(-EINVAL); + if (((reiserfs_acl_header *)value)->a_version != + cpu_to_le32(REISERFS_ACL_VERSION)) + return ERR_PTR(-EINVAL); + value = (char *)value + sizeof(reiserfs_acl_header); + count = reiserfs_acl_count(size); + if (count < 0) + return ERR_PTR(-EINVAL); + if (count == 0) + return NULL; + acl = posix_acl_alloc(count, GFP_NOFS); + if (!acl) + return ERR_PTR(-ENOMEM); + for (n=0; n < count; n++) { + reiserfs_acl_entry *entry = + (reiserfs_acl_entry *)value; + if ((char *)value + sizeof(reiserfs_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(reiserfs_acl_entry_short); + acl->a_entries[n].e_id = ACL_UNDEFINED_ID; + break; + + case ACL_USER: + case ACL_GROUP: + value = (char *)value + sizeof(reiserfs_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 * +posix_acl_to_disk(const struct posix_acl *acl, size_t *size) +{ + reiserfs_acl_header *ext_acl; + char *e; + int n; + + *size = reiserfs_acl_size(acl->a_count); + ext_acl = (reiserfs_acl_header *)kmalloc(sizeof(reiserfs_acl_header) + + acl->a_count * sizeof(reiserfs_acl_entry), GFP_NOFS); + if (!ext_acl) + return ERR_PTR(-ENOMEM); + ext_acl->a_version = cpu_to_le32(REISERFS_ACL_VERSION); + e = (char *)ext_acl + sizeof(reiserfs_acl_header); + for (n=0; n < acl->a_count; n++) { + reiserfs_acl_entry *entry = (reiserfs_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(reiserfs_acl_entry); + break; + + case ACL_USER_OBJ: + case ACL_GROUP_OBJ: + case ACL_MASK: + case ACL_OTHER: + e += sizeof(reiserfs_acl_entry_short); + break; + + default: + goto fail; + } + } + return (char *)ext_acl; + +fail: + kfree(ext_acl); + return ERR_PTR(-EINVAL); +} + +/* + * Inode operation get_posix_acl(). + * + * inode->i_sem: down + * BKL held [before 2.5.x] + */ +struct posix_acl * +reiserfs_get_acl(struct inode *inode, int type) +{ + char *name, *value; + struct posix_acl *acl, **p_acl; + size_t size; + int retval; + struct reiserfs_inode_info *reiserfs_i = REISERFS_I(inode); + + switch (type) { + case ACL_TYPE_ACCESS: + name = XATTR_NAME_ACL_ACCESS; + p_acl = &reiserfs_i->i_acl_access; + break; + case ACL_TYPE_DEFAULT: + name = XATTR_NAME_ACL_DEFAULT; + p_acl = &reiserfs_i->i_acl_default; + break; + default: + return ERR_PTR (-EINVAL); + } + + if (IS_ERR (*p_acl)) { + if (PTR_ERR (*p_acl) == -ENODATA) + return NULL; + } else if (*p_acl != NULL) + return posix_acl_dup (*p_acl); + + size = reiserfs_xattr_get (inode, name, NULL, 0); + if ((int)size < 0) { + if (size == -ENODATA || size == -ENOSYS) { + *p_acl = ERR_PTR (-ENODATA); + return NULL; + } + return ERR_PTR (size); + } + + value = kmalloc (size, GFP_NOFS); + if (!value) + return ERR_PTR (-ENOMEM); + + retval = reiserfs_xattr_get(inode, name, value, size); + if (retval == -ENODATA || retval == -ENOSYS) { + /* This shouldn't actually happen as it should have + been caught above.. but just in case */ + acl = NULL; + *p_acl = ERR_PTR (-ENODATA); + } else if (retval < 0) { + acl = ERR_PTR(retval); + } else { + acl = posix_acl_from_disk(value, retval); + *p_acl = posix_acl_dup (acl); + } + + kfree(value); + return acl; +} + +/* + * Inode operation set_posix_acl(). + * + * inode->i_sem: down + * BKL held [before 2.5.x] + */ +static int +reiserfs_set_acl(struct inode *inode, int type, struct posix_acl *acl) +{ + char *name; + void *value = NULL; + struct posix_acl **p_acl; + size_t size; + int error; + struct reiserfs_inode_info *reiserfs_i = REISERFS_I(inode); + + if (S_ISLNK(inode->i_mode)) + return -EOPNOTSUPP; + + switch (type) { + case ACL_TYPE_ACCESS: + name = XATTR_NAME_ACL_ACCESS; + p_acl = &reiserfs_i->i_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; + if (error == 0) + acl = NULL; + } + } + break; + case ACL_TYPE_DEFAULT: + name = XATTR_NAME_ACL_DEFAULT; + p_acl = &reiserfs_i->i_acl_default; + if (!S_ISDIR (inode->i_mode)) + return acl ? -EACCES : 0; + break; + default: + return -EINVAL; + } + + if (acl) { + value = posix_acl_to_disk(acl, &size); + if (IS_ERR(value)) + return (int)PTR_ERR(value); + error = reiserfs_xattr_set(inode, name, value, size, 0); + } else { + error = reiserfs_xattr_del (inode, name); + if (error == -ENODATA) { + /* This may seem odd here, but it means that the ACL was set + * with a value representable with mode bits. If there was + * an ACL before, reiserfs_xattr_del already dirtied the inode. + */ + mark_inode_dirty (inode); + error = 0; + } + } + + if (value) + kfree(value); + + if (!error) { + /* Release the old one */ + if (!IS_ERR (*p_acl) && *p_acl) + posix_acl_release (*p_acl); + + if (acl == NULL) + *p_acl = ERR_PTR (-ENODATA); + else + *p_acl = posix_acl_dup (acl); + } + + return error; +} + +/* dir->i_sem: down, + * inode is new and not released into the wild yet */ +int +reiserfs_inherit_default_acl (struct inode *dir, struct dentry *dentry, struct inode *inode) +{ + struct posix_acl *acl; + int err = 0; + + /* ACLs only get applied to files and directories */ + if (S_ISLNK (inode->i_mode)) + return 0; + + /* ACLs can only be used on "new" objects, so if it's an old object + * there is nothing to inherit from */ + if (get_inode_sd_version (dir) == STAT_DATA_V1) + goto apply_umask; + + /* Don't apply ACLs to objects in the .reiserfs_priv tree.. This + * would be useless since permissions are ignored, and a pain because + * it introduces locking cycles */ + if (is_reiserfs_priv_object (dir)) { + reiserfs_mark_inode_private (inode); + goto apply_umask; + } + + acl = reiserfs_get_acl (dir, ACL_TYPE_DEFAULT); + if (IS_ERR (acl)) { + if (PTR_ERR (acl) == -ENODATA) + goto apply_umask; + return PTR_ERR (acl); + } + + if (acl) { + struct posix_acl *acl_copy; + mode_t mode = inode->i_mode; + int need_acl; + + /* Copy the default ACL to the default ACL of a new directory */ + if (S_ISDIR (inode->i_mode)) { + err = reiserfs_set_acl (inode, ACL_TYPE_DEFAULT, acl); + if (err) + goto cleanup; + } + + /* Now we reconcile the new ACL and the mode, + potentially modifying both */ + acl_copy = posix_acl_clone (acl, GFP_NOFS); + if (!acl_copy) { + err = -ENOMEM; + goto cleanup; + } + + + need_acl = posix_acl_create_masq (acl_copy, &mode); + if (need_acl >= 0) { + if (mode != inode->i_mode) { + inode->i_mode = mode; + } + + /* If we need an ACL.. */ + if (need_acl > 0) { + err = reiserfs_set_acl (inode, ACL_TYPE_ACCESS, acl_copy); + if (err) + goto cleanup_copy; + } + } +cleanup_copy: + posix_acl_release (acl_copy); +cleanup: + posix_acl_release (acl); + } else { +apply_umask: + /* no ACL, apply umask */ + inode->i_mode &= ~current->fs->umask; + } + + return err; +} + +/* Looks up and caches the result of the default ACL. + * We do this so that we don't need to carry the xattr_sem into + * reiserfs_new_inode if we don't need to */ +int +reiserfs_cache_default_acl (struct inode *inode) +{ + int ret = 0; + if (reiserfs_posixacl (inode->i_sb) && + !is_reiserfs_priv_object (inode)) { + struct posix_acl *acl; + reiserfs_read_lock_xattr_i (inode); + reiserfs_read_lock_xattrs (inode->i_sb); + acl = reiserfs_get_acl (inode, ACL_TYPE_DEFAULT); + reiserfs_read_unlock_xattrs (inode->i_sb); + reiserfs_read_unlock_xattr_i (inode); + ret = acl ? 1 : 0; + posix_acl_release (acl); + } + + return ret; +} + +int +reiserfs_acl_chmod (struct inode *inode) +{ + struct posix_acl *acl, *clone; + int error; + + if (S_ISLNK(inode->i_mode)) + return -EOPNOTSUPP; + + if (get_inode_sd_version (inode) == STAT_DATA_V1 || + !reiserfs_posixacl(inode->i_sb)) + { + return 0; + } + + reiserfs_read_lock_xattrs (inode->i_sb); + acl = reiserfs_get_acl(inode, ACL_TYPE_ACCESS); + reiserfs_read_unlock_xattrs (inode->i_sb); + if (!acl) + return 0; + if (IS_ERR(acl)) + return PTR_ERR(acl); + clone = posix_acl_clone(acl, GFP_NOFS); + posix_acl_release(acl); + if (!clone) + return -ENOMEM; + error = posix_acl_chmod_masq(clone, inode->i_mode); + if (!error) { + int lock = !has_xattr_dir (inode); + reiserfs_write_lock_xattr_i (inode); + if (lock) + reiserfs_write_lock_xattrs (inode->i_sb); + else + reiserfs_read_lock_xattrs (inode->i_sb); + error = reiserfs_set_acl(inode, ACL_TYPE_ACCESS, clone); + if (lock) + reiserfs_write_unlock_xattrs (inode->i_sb); + else + reiserfs_read_unlock_xattrs (inode->i_sb); + reiserfs_write_unlock_xattr_i (inode); + } + posix_acl_release(clone); + return error; +} + +static int +posix_acl_access_get(struct inode *inode, const char *name, + void *buffer, size_t size) +{ + if (strlen(name) != sizeof(XATTR_NAME_ACL_ACCESS)-1) + return -EINVAL; + return xattr_get_acl(inode, ACL_TYPE_ACCESS, buffer, size); +} + +static int +posix_acl_access_set(struct inode *inode, const char *name, + const void *value, size_t size, int flags) +{ + if (strlen(name) != sizeof(XATTR_NAME_ACL_ACCESS)-1) + return -EINVAL; + return xattr_set_acl(inode, ACL_TYPE_ACCESS, value, size); +} + +static int +posix_acl_access_del (struct inode *inode, const char *name) +{ + struct reiserfs_inode_info *reiserfs_i = REISERFS_I(inode); + struct posix_acl **acl = &reiserfs_i->i_acl_access; + if (strlen(name) != sizeof(XATTR_NAME_ACL_ACCESS)-1) + return -EINVAL; + if (!IS_ERR (*acl) && *acl) { + posix_acl_release (*acl); + *acl = ERR_PTR (-ENODATA); + } + + return 0; +} + +static int +posix_acl_access_list (struct inode *inode, const char *name, int namelen, char *out) +{ + int len = namelen; + if (!reiserfs_posixacl (inode->i_sb)) + return 0; + if (out) + memcpy (out, name, len); + + return len; +} + +struct reiserfs_xattr_handler posix_acl_access_handler = { + .prefix = XATTR_NAME_ACL_ACCESS, + .get = posix_acl_access_get, + .set = posix_acl_access_set, + .del = posix_acl_access_del, + .list = posix_acl_access_list, +}; + +static int +posix_acl_default_get (struct inode *inode, const char *name, + void *buffer, size_t size) +{ + if (strlen(name) != sizeof(XATTR_NAME_ACL_DEFAULT)-1) + return -EINVAL; + return xattr_get_acl(inode, ACL_TYPE_DEFAULT, buffer, size); +} + +static int +posix_acl_default_set(struct inode *inode, const char *name, + const void *value, size_t size, int flags) +{ + if (strlen(name) != sizeof(XATTR_NAME_ACL_DEFAULT)-1) + return -EINVAL; + return xattr_set_acl(inode, ACL_TYPE_DEFAULT, value, size); +} + +static int +posix_acl_default_del (struct inode *inode, const char *name) +{ + struct reiserfs_inode_info *reiserfs_i = REISERFS_I(inode); + struct posix_acl **acl = &reiserfs_i->i_acl_default; + if (strlen(name) != sizeof(XATTR_NAME_ACL_DEFAULT)-1) + return -EINVAL; + if (!IS_ERR (*acl) && *acl) { + posix_acl_release (*acl); + *acl = ERR_PTR (-ENODATA); + } + + return 0; +} + +static int +posix_acl_default_list (struct inode *inode, const char *name, int namelen, char *out) +{ + int len = namelen; + if (!reiserfs_posixacl (inode->i_sb)) + return 0; + if (out) + memcpy (out, name, len); + + return len; +} + +struct reiserfs_xattr_handler posix_acl_default_handler = { + .prefix = XATTR_NAME_ACL_DEFAULT, + .get = posix_acl_default_get, + .set = posix_acl_default_set, + .del = posix_acl_default_del, + .list = posix_acl_default_list, +}; |