linux/fs/ext4/acl.c
<<
>>
Prefs
   1/*
   2 * linux/fs/ext4/acl.c
   3 *
   4 * Copyright (C) 2001-2003 Andreas Gruenbacher, <agruen@suse.de>
   5 */
   6
   7#include <linux/init.h>
   8#include <linux/sched.h>
   9#include <linux/slab.h>
  10#include <linux/capability.h>
  11#include <linux/fs.h>
  12#include "ext4_jbd2.h"
  13#include "ext4.h"
  14#include "xattr.h"
  15#include "acl.h"
  16
  17/*
  18 * Convert from filesystem to in-memory representation.
  19 */
  20static struct posix_acl *
  21ext4_acl_from_disk(const void *value, size_t size)
  22{
  23        const char *end = (char *)value + size;
  24        int n, count;
  25        struct posix_acl *acl;
  26
  27        if (!value)
  28                return NULL;
  29        if (size < sizeof(ext4_acl_header))
  30                 return ERR_PTR(-EINVAL);
  31        if (((ext4_acl_header *)value)->a_version !=
  32            cpu_to_le32(EXT4_ACL_VERSION))
  33                return ERR_PTR(-EINVAL);
  34        value = (char *)value + sizeof(ext4_acl_header);
  35        count = ext4_acl_count(size);
  36        if (count < 0)
  37                return ERR_PTR(-EINVAL);
  38        if (count == 0)
  39                return NULL;
  40        acl = posix_acl_alloc(count, GFP_NOFS);
  41        if (!acl)
  42                return ERR_PTR(-ENOMEM);
  43        for (n = 0; n < count; n++) {
  44                ext4_acl_entry *entry =
  45                        (ext4_acl_entry *)value;
  46                if ((char *)value + sizeof(ext4_acl_entry_short) > end)
  47                        goto fail;
  48                acl->a_entries[n].e_tag  = le16_to_cpu(entry->e_tag);
  49                acl->a_entries[n].e_perm = le16_to_cpu(entry->e_perm);
  50
  51                switch (acl->a_entries[n].e_tag) {
  52                case ACL_USER_OBJ:
  53                case ACL_GROUP_OBJ:
  54                case ACL_MASK:
  55                case ACL_OTHER:
  56                        value = (char *)value +
  57                                sizeof(ext4_acl_entry_short);
  58                        break;
  59
  60                case ACL_USER:
  61                        value = (char *)value + sizeof(ext4_acl_entry);
  62                        if ((char *)value > end)
  63                                goto fail;
  64                        acl->a_entries[n].e_uid =
  65                                make_kuid(&init_user_ns,
  66                                          le32_to_cpu(entry->e_id));
  67                        break;
  68                case ACL_GROUP:
  69                        value = (char *)value + sizeof(ext4_acl_entry);
  70                        if ((char *)value > end)
  71                                goto fail;
  72                        acl->a_entries[n].e_gid =
  73                                make_kgid(&init_user_ns,
  74                                          le32_to_cpu(entry->e_id));
  75                        break;
  76
  77                default:
  78                        goto fail;
  79                }
  80        }
  81        if (value != end)
  82                goto fail;
  83        return acl;
  84
  85fail:
  86        posix_acl_release(acl);
  87        return ERR_PTR(-EINVAL);
  88}
  89
  90/*
  91 * Convert from in-memory to filesystem representation.
  92 */
  93static void *
  94ext4_acl_to_disk(const struct posix_acl *acl, size_t *size)
  95{
  96        ext4_acl_header *ext_acl;
  97        char *e;
  98        size_t n;
  99
 100        *size = ext4_acl_size(acl->a_count);
 101        ext_acl = kmalloc(sizeof(ext4_acl_header) + acl->a_count *
 102                        sizeof(ext4_acl_entry), GFP_NOFS);
 103        if (!ext_acl)
 104                return ERR_PTR(-ENOMEM);
 105        ext_acl->a_version = cpu_to_le32(EXT4_ACL_VERSION);
 106        e = (char *)ext_acl + sizeof(ext4_acl_header);
 107        for (n = 0; n < acl->a_count; n++) {
 108                const struct posix_acl_entry *acl_e = &acl->a_entries[n];
 109                ext4_acl_entry *entry = (ext4_acl_entry *)e;
 110                entry->e_tag  = cpu_to_le16(acl_e->e_tag);
 111                entry->e_perm = cpu_to_le16(acl_e->e_perm);
 112                switch (acl_e->e_tag) {
 113                case ACL_USER:
 114                        entry->e_id = cpu_to_le32(
 115                                from_kuid(&init_user_ns, acl_e->e_uid));
 116                        e += sizeof(ext4_acl_entry);
 117                        break;
 118                case ACL_GROUP:
 119                        entry->e_id = cpu_to_le32(
 120                                from_kgid(&init_user_ns, acl_e->e_gid));
 121                        e += sizeof(ext4_acl_entry);
 122                        break;
 123
 124                case ACL_USER_OBJ:
 125                case ACL_GROUP_OBJ:
 126                case ACL_MASK:
 127                case ACL_OTHER:
 128                        e += sizeof(ext4_acl_entry_short);
 129                        break;
 130
 131                default:
 132                        goto fail;
 133                }
 134        }
 135        return (char *)ext_acl;
 136
 137fail:
 138        kfree(ext_acl);
 139        return ERR_PTR(-EINVAL);
 140}
 141
 142/*
 143 * Inode operation get_posix_acl().
 144 *
 145 * inode->i_mutex: don't care
 146 */
 147struct posix_acl *
 148ext4_get_acl(struct inode *inode, int type)
 149{
 150        int name_index;
 151        char *value = NULL;
 152        struct posix_acl *acl;
 153        int retval;
 154
 155        if (!test_opt(inode->i_sb, POSIX_ACL))
 156                return NULL;
 157
 158        acl = get_cached_acl(inode, type);
 159        if (acl != ACL_NOT_CACHED)
 160                return acl;
 161
 162        switch (type) {
 163        case ACL_TYPE_ACCESS:
 164                name_index = EXT4_XATTR_INDEX_POSIX_ACL_ACCESS;
 165                break;
 166        case ACL_TYPE_DEFAULT:
 167                name_index = EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT;
 168                break;
 169        default:
 170                BUG();
 171        }
 172        retval = ext4_xattr_get(inode, name_index, "", NULL, 0);
 173        if (retval > 0) {
 174                value = kmalloc(retval, GFP_NOFS);
 175                if (!value)
 176                        return ERR_PTR(-ENOMEM);
 177                retval = ext4_xattr_get(inode, name_index, "", value, retval);
 178        }
 179        if (retval > 0)
 180                acl = ext4_acl_from_disk(value, retval);
 181        else if (retval == -ENODATA || retval == -ENOSYS)
 182                acl = NULL;
 183        else
 184                acl = ERR_PTR(retval);
 185        kfree(value);
 186
 187        if (!IS_ERR(acl))
 188                set_cached_acl(inode, type, acl);
 189
 190        return acl;
 191}
 192
 193/*
 194 * Set the access or default ACL of an inode.
 195 *
 196 * inode->i_mutex: down unless called from ext4_new_inode
 197 */
 198static int
 199ext4_set_acl(handle_t *handle, struct inode *inode, int type,
 200             struct posix_acl *acl)
 201{
 202        int name_index;
 203        void *value = NULL;
 204        size_t size = 0;
 205        int error;
 206
 207        if (S_ISLNK(inode->i_mode))
 208                return -EOPNOTSUPP;
 209
 210        switch (type) {
 211        case ACL_TYPE_ACCESS:
 212                name_index = EXT4_XATTR_INDEX_POSIX_ACL_ACCESS;
 213                if (acl) {
 214                        error = posix_acl_equiv_mode(acl, &inode->i_mode);
 215                        if (error < 0)
 216                                return error;
 217                        else {
 218                                inode->i_ctime = ext4_current_time(inode);
 219                                ext4_mark_inode_dirty(handle, inode);
 220                                if (error == 0)
 221                                        acl = NULL;
 222                        }
 223                }
 224                break;
 225
 226        case ACL_TYPE_DEFAULT:
 227                name_index = EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT;
 228                if (!S_ISDIR(inode->i_mode))
 229                        return acl ? -EACCES : 0;
 230                break;
 231
 232        default:
 233                return -EINVAL;
 234        }
 235        if (acl) {
 236                value = ext4_acl_to_disk(acl, &size);
 237                if (IS_ERR(value))
 238                        return (int)PTR_ERR(value);
 239        }
 240
 241        error = ext4_xattr_set_handle(handle, inode, name_index, "",
 242                                      value, size, 0);
 243
 244        kfree(value);
 245        if (!error)
 246                set_cached_acl(inode, type, acl);
 247
 248        return error;
 249}
 250
 251/*
 252 * Initialize the ACLs of a new inode. Called from ext4_new_inode.
 253 *
 254 * dir->i_mutex: down
 255 * inode->i_mutex: up (access to inode is still exclusive)
 256 */
 257int
 258ext4_init_acl(handle_t *handle, struct inode *inode, struct inode *dir)
 259{
 260        struct posix_acl *acl = NULL;
 261        int error = 0;
 262
 263        if (!S_ISLNK(inode->i_mode)) {
 264                if (test_opt(dir->i_sb, POSIX_ACL)) {
 265                        acl = ext4_get_acl(dir, ACL_TYPE_DEFAULT);
 266                        if (IS_ERR(acl))
 267                                return PTR_ERR(acl);
 268                }
 269                if (!acl)
 270                        inode->i_mode &= ~current_umask();
 271        }
 272        if (test_opt(inode->i_sb, POSIX_ACL) && acl) {
 273                if (S_ISDIR(inode->i_mode)) {
 274                        error = ext4_set_acl(handle, inode,
 275                                             ACL_TYPE_DEFAULT, acl);
 276                        if (error)
 277                                goto cleanup;
 278                }
 279                error = posix_acl_create(&acl, GFP_NOFS, &inode->i_mode);
 280                if (error < 0)
 281                        return error;
 282
 283                if (error > 0) {
 284                        /* This is an extended ACL */
 285                        error = ext4_set_acl(handle, inode, ACL_TYPE_ACCESS, acl);
 286                }
 287        }
 288cleanup:
 289        posix_acl_release(acl);
 290        return error;
 291}
 292
 293/*
 294 * Does chmod for an inode that may have an Access Control List. The
 295 * inode->i_mode field must be updated to the desired value by the caller
 296 * before calling this function.
 297 * Returns 0 on success, or a negative error number.
 298 *
 299 * We change the ACL rather than storing some ACL entries in the file
 300 * mode permission bits (which would be more efficient), because that
 301 * would break once additional permissions (like  ACL_APPEND, ACL_DELETE
 302 * for directories) are added. There are no more bits available in the
 303 * file mode.
 304 *
 305 * inode->i_mutex: down
 306 */
 307int
 308ext4_acl_chmod(struct inode *inode)
 309{
 310        struct posix_acl *acl;
 311        handle_t *handle;
 312        int retries = 0;
 313        int error;
 314
 315
 316        if (S_ISLNK(inode->i_mode))
 317                return -EOPNOTSUPP;
 318        if (!test_opt(inode->i_sb, POSIX_ACL))
 319                return 0;
 320        acl = ext4_get_acl(inode, ACL_TYPE_ACCESS);
 321        if (IS_ERR(acl) || !acl)
 322                return PTR_ERR(acl);
 323        error = posix_acl_chmod(&acl, GFP_KERNEL, inode->i_mode);
 324        if (error)
 325                return error;
 326retry:
 327        handle = ext4_journal_start(inode, EXT4_HT_XATTR,
 328                                    ext4_jbd2_credits_xattr(inode));
 329        if (IS_ERR(handle)) {
 330                error = PTR_ERR(handle);
 331                ext4_std_error(inode->i_sb, error);
 332                goto out;
 333        }
 334        error = ext4_set_acl(handle, inode, ACL_TYPE_ACCESS, acl);
 335        ext4_journal_stop(handle);
 336        if (error == -ENOSPC &&
 337            ext4_should_retry_alloc(inode->i_sb, &retries))
 338                goto retry;
 339out:
 340        posix_acl_release(acl);
 341        return error;
 342}
 343
 344/*
 345 * Extended attribute handlers
 346 */
 347static size_t
 348ext4_xattr_list_acl_access(struct dentry *dentry, char *list, size_t list_len,
 349                           const char *name, size_t name_len, int type)
 350{
 351        const size_t size = sizeof(POSIX_ACL_XATTR_ACCESS);
 352
 353        if (!test_opt(dentry->d_sb, POSIX_ACL))
 354                return 0;
 355        if (list && size <= list_len)
 356                memcpy(list, POSIX_ACL_XATTR_ACCESS, size);
 357        return size;
 358}
 359
 360static size_t
 361ext4_xattr_list_acl_default(struct dentry *dentry, char *list, size_t list_len,
 362                            const char *name, size_t name_len, int type)
 363{
 364        const size_t size = sizeof(POSIX_ACL_XATTR_DEFAULT);
 365
 366        if (!test_opt(dentry->d_sb, POSIX_ACL))
 367                return 0;
 368        if (list && size <= list_len)
 369                memcpy(list, POSIX_ACL_XATTR_DEFAULT, size);
 370        return size;
 371}
 372
 373static int
 374ext4_xattr_get_acl(struct dentry *dentry, const char *name, void *buffer,
 375                   size_t size, int type)
 376{
 377        struct posix_acl *acl;
 378        int error;
 379
 380        if (strcmp(name, "") != 0)
 381                return -EINVAL;
 382        if (!test_opt(dentry->d_sb, POSIX_ACL))
 383                return -EOPNOTSUPP;
 384
 385        acl = ext4_get_acl(dentry->d_inode, type);
 386        if (IS_ERR(acl))
 387                return PTR_ERR(acl);
 388        if (acl == NULL)
 389                return -ENODATA;
 390        error = posix_acl_to_xattr(&init_user_ns, acl, buffer, size);
 391        posix_acl_release(acl);
 392
 393        return error;
 394}
 395
 396static int
 397ext4_xattr_set_acl(struct dentry *dentry, const char *name, const void *value,
 398                   size_t size, int flags, int type)
 399{
 400        struct inode *inode = dentry->d_inode;
 401        handle_t *handle;
 402        struct posix_acl *acl;
 403        int error, retries = 0;
 404
 405        if (strcmp(name, "") != 0)
 406                return -EINVAL;
 407        if (!test_opt(inode->i_sb, POSIX_ACL))
 408                return -EOPNOTSUPP;
 409        if (!inode_owner_or_capable(inode))
 410                return -EPERM;
 411
 412        if (value) {
 413                acl = posix_acl_from_xattr(&init_user_ns, value, size);
 414                if (IS_ERR(acl))
 415                        return PTR_ERR(acl);
 416                else if (acl) {
 417                        error = posix_acl_valid(acl);
 418                        if (error)
 419                                goto release_and_out;
 420                }
 421        } else
 422                acl = NULL;
 423
 424retry:
 425        handle = ext4_journal_start(inode, EXT4_HT_XATTR,
 426                                    ext4_jbd2_credits_xattr(inode));
 427        if (IS_ERR(handle)) {
 428                error = PTR_ERR(handle);
 429                goto release_and_out;
 430        }
 431        error = ext4_set_acl(handle, inode, type, acl);
 432        ext4_journal_stop(handle);
 433        if (error == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries))
 434                goto retry;
 435
 436release_and_out:
 437        posix_acl_release(acl);
 438        return error;
 439}
 440
 441const struct xattr_handler ext4_xattr_acl_access_handler = {
 442        .prefix = POSIX_ACL_XATTR_ACCESS,
 443        .flags  = ACL_TYPE_ACCESS,
 444        .list   = ext4_xattr_list_acl_access,
 445        .get    = ext4_xattr_get_acl,
 446        .set    = ext4_xattr_set_acl,
 447};
 448
 449const struct xattr_handler ext4_xattr_acl_default_handler = {
 450        .prefix = POSIX_ACL_XATTR_DEFAULT,
 451        .flags  = ACL_TYPE_DEFAULT,
 452        .list   = ext4_xattr_list_acl_default,
 453        .get    = ext4_xattr_get_acl,
 454        .set    = ext4_xattr_set_acl,
 455};
 456