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                        acl->a_entries[n].e_id = ACL_UNDEFINED_ID;
  59                        break;
  60
  61                case ACL_USER:
  62                case ACL_GROUP:
  63                        value = (char *)value + sizeof(ext4_acl_entry);
  64                        if ((char *)value > end)
  65                                goto fail;
  66                        acl->a_entries[n].e_id =
  67                                le32_to_cpu(entry->e_id);
  68                        break;
  69
  70                default:
  71                        goto fail;
  72                }
  73        }
  74        if (value != end)
  75                goto fail;
  76        return acl;
  77
  78fail:
  79        posix_acl_release(acl);
  80        return ERR_PTR(-EINVAL);
  81}
  82
  83/*
  84 * Convert from in-memory to filesystem representation.
  85 */
  86static void *
  87ext4_acl_to_disk(const struct posix_acl *acl, size_t *size)
  88{
  89        ext4_acl_header *ext_acl;
  90        char *e;
  91        size_t n;
  92
  93        *size = ext4_acl_size(acl->a_count);
  94        ext_acl = kmalloc(sizeof(ext4_acl_header) + acl->a_count *
  95                        sizeof(ext4_acl_entry), GFP_NOFS);
  96        if (!ext_acl)
  97                return ERR_PTR(-ENOMEM);
  98        ext_acl->a_version = cpu_to_le32(EXT4_ACL_VERSION);
  99        e = (char *)ext_acl + sizeof(ext4_acl_header);
 100        for (n = 0; n < acl->a_count; n++) {
 101                ext4_acl_entry *entry = (ext4_acl_entry *)e;
 102                entry->e_tag  = cpu_to_le16(acl->a_entries[n].e_tag);
 103                entry->e_perm = cpu_to_le16(acl->a_entries[n].e_perm);
 104                switch (acl->a_entries[n].e_tag) {
 105                case ACL_USER:
 106                case ACL_GROUP:
 107                        entry->e_id = cpu_to_le32(acl->a_entries[n].e_id);
 108                        e += sizeof(ext4_acl_entry);
 109                        break;
 110
 111                case ACL_USER_OBJ:
 112                case ACL_GROUP_OBJ:
 113                case ACL_MASK:
 114                case ACL_OTHER:
 115                        e += sizeof(ext4_acl_entry_short);
 116                        break;
 117
 118                default:
 119                        goto fail;
 120                }
 121        }
 122        return (char *)ext_acl;
 123
 124fail:
 125        kfree(ext_acl);
 126        return ERR_PTR(-EINVAL);
 127}
 128
 129static inline struct posix_acl *
 130ext4_iget_acl(struct inode *inode, struct posix_acl **i_acl)
 131{
 132        struct posix_acl *acl = EXT4_ACL_NOT_CACHED;
 133
 134        spin_lock(&inode->i_lock);
 135        if (*i_acl != EXT4_ACL_NOT_CACHED)
 136                acl = posix_acl_dup(*i_acl);
 137        spin_unlock(&inode->i_lock);
 138
 139        return acl;
 140}
 141
 142static inline void
 143ext4_iset_acl(struct inode *inode, struct posix_acl **i_acl,
 144                struct posix_acl *acl)
 145{
 146        spin_lock(&inode->i_lock);
 147        if (*i_acl != EXT4_ACL_NOT_CACHED)
 148                posix_acl_release(*i_acl);
 149        *i_acl = posix_acl_dup(acl);
 150        spin_unlock(&inode->i_lock);
 151}
 152
 153/*
 154 * Inode operation get_posix_acl().
 155 *
 156 * inode->i_mutex: don't care
 157 */
 158static struct posix_acl *
 159ext4_get_acl(struct inode *inode, int type)
 160{
 161        struct ext4_inode_info *ei = EXT4_I(inode);
 162        int name_index;
 163        char *value = NULL;
 164        struct posix_acl *acl;
 165        int retval;
 166
 167        if (!test_opt(inode->i_sb, POSIX_ACL))
 168                return NULL;
 169
 170        switch (type) {
 171        case ACL_TYPE_ACCESS:
 172                acl = ext4_iget_acl(inode, &ei->i_acl);
 173                if (acl != EXT4_ACL_NOT_CACHED)
 174                        return acl;
 175                name_index = EXT4_XATTR_INDEX_POSIX_ACL_ACCESS;
 176                break;
 177
 178        case ACL_TYPE_DEFAULT:
 179                acl = ext4_iget_acl(inode, &ei->i_default_acl);
 180                if (acl != EXT4_ACL_NOT_CACHED)
 181                        return acl;
 182                name_index = EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT;
 183                break;
 184
 185        default:
 186                return ERR_PTR(-EINVAL);
 187        }
 188        retval = ext4_xattr_get(inode, name_index, "", NULL, 0);
 189        if (retval > 0) {
 190                value = kmalloc(retval, GFP_NOFS);
 191                if (!value)
 192                        return ERR_PTR(-ENOMEM);
 193                retval = ext4_xattr_get(inode, name_index, "", value, retval);
 194        }
 195        if (retval > 0)
 196                acl = ext4_acl_from_disk(value, retval);
 197        else if (retval == -ENODATA || retval == -ENOSYS)
 198                acl = NULL;
 199        else
 200                acl = ERR_PTR(retval);
 201        kfree(value);
 202
 203        if (!IS_ERR(acl)) {
 204                switch (type) {
 205                case ACL_TYPE_ACCESS:
 206                        ext4_iset_acl(inode, &ei->i_acl, acl);
 207                        break;
 208
 209                case ACL_TYPE_DEFAULT:
 210                        ext4_iset_acl(inode, &ei->i_default_acl, acl);
 211                        break;
 212                }
 213        }
 214        return acl;
 215}
 216
 217/*
 218 * Set the access or default ACL of an inode.
 219 *
 220 * inode->i_mutex: down unless called from ext4_new_inode
 221 */
 222static int
 223ext4_set_acl(handle_t *handle, struct inode *inode, int type,
 224             struct posix_acl *acl)
 225{
 226        struct ext4_inode_info *ei = EXT4_I(inode);
 227        int name_index;
 228        void *value = NULL;
 229        size_t size = 0;
 230        int error;
 231
 232        if (S_ISLNK(inode->i_mode))
 233                return -EOPNOTSUPP;
 234
 235        switch (type) {
 236        case ACL_TYPE_ACCESS:
 237                name_index = EXT4_XATTR_INDEX_POSIX_ACL_ACCESS;
 238                if (acl) {
 239                        mode_t mode = inode->i_mode;
 240                        error = posix_acl_equiv_mode(acl, &mode);
 241                        if (error < 0)
 242                                return error;
 243                        else {
 244                                inode->i_mode = mode;
 245                                ext4_mark_inode_dirty(handle, inode);
 246                                if (error == 0)
 247                                        acl = NULL;
 248                        }
 249                }
 250                break;
 251
 252        case ACL_TYPE_DEFAULT:
 253                name_index = EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT;
 254                if (!S_ISDIR(inode->i_mode))
 255                        return acl ? -EACCES : 0;
 256                break;
 257
 258        default:
 259                return -EINVAL;
 260        }
 261        if (acl) {
 262                value = ext4_acl_to_disk(acl, &size);
 263                if (IS_ERR(value))
 264                        return (int)PTR_ERR(value);
 265        }
 266
 267        error = ext4_xattr_set_handle(handle, inode, name_index, "",
 268                                      value, size, 0);
 269
 270        kfree(value);
 271        if (!error) {
 272                switch (type) {
 273                case ACL_TYPE_ACCESS:
 274                        ext4_iset_acl(inode, &ei->i_acl, acl);
 275                        break;
 276
 277                case ACL_TYPE_DEFAULT:
 278                        ext4_iset_acl(inode, &ei->i_default_acl, acl);
 279                        break;
 280                }
 281        }
 282        return error;
 283}
 284
 285static int
 286ext4_check_acl(struct inode *inode, int mask)
 287{
 288        struct posix_acl *acl = ext4_get_acl(inode, ACL_TYPE_ACCESS);
 289
 290        if (IS_ERR(acl))
 291                return PTR_ERR(acl);
 292        if (acl) {
 293                int error = posix_acl_permission(inode, acl, mask);
 294                posix_acl_release(acl);
 295                return error;
 296        }
 297
 298        return -EAGAIN;
 299}
 300
 301int
 302ext4_permission(struct inode *inode, int mask)
 303{
 304        return generic_permission(inode, mask, ext4_check_acl);
 305}
 306
 307/*
 308 * Initialize the ACLs of a new inode. Called from ext4_new_inode.
 309 *
 310 * dir->i_mutex: down
 311 * inode->i_mutex: up (access to inode is still exclusive)
 312 */
 313int
 314ext4_init_acl(handle_t *handle, struct inode *inode, struct inode *dir)
 315{
 316        struct posix_acl *acl = NULL;
 317        int error = 0;
 318
 319        if (!S_ISLNK(inode->i_mode)) {
 320                if (test_opt(dir->i_sb, POSIX_ACL)) {
 321                        acl = ext4_get_acl(dir, ACL_TYPE_DEFAULT);
 322                        if (IS_ERR(acl))
 323                                return PTR_ERR(acl);
 324                }
 325                if (!acl)
 326                        inode->i_mode &= ~current->fs->umask;
 327        }
 328        if (test_opt(inode->i_sb, POSIX_ACL) && acl) {
 329                struct posix_acl *clone;
 330                mode_t mode;
 331
 332                if (S_ISDIR(inode->i_mode)) {
 333                        error = ext4_set_acl(handle, inode,
 334                                             ACL_TYPE_DEFAULT, acl);
 335                        if (error)
 336                                goto cleanup;
 337                }
 338                clone = posix_acl_clone(acl, GFP_NOFS);
 339                error = -ENOMEM;
 340                if (!clone)
 341                        goto cleanup;
 342
 343                mode = inode->i_mode;
 344                error = posix_acl_create_masq(clone, &mode);
 345                if (error >= 0) {
 346                        inode->i_mode = mode;
 347                        if (error > 0) {
 348                                /* This is an extended ACL */
 349                                error = ext4_set_acl(handle, inode,
 350                                                     ACL_TYPE_ACCESS, clone);
 351                        }
 352                }
 353                posix_acl_release(clone);
 354        }
 355cleanup:
 356        posix_acl_release(acl);
 357        return error;
 358}
 359
 360/*
 361 * Does chmod for an inode that may have an Access Control List. The
 362 * inode->i_mode field must be updated to the desired value by the caller
 363 * before calling this function.
 364 * Returns 0 on success, or a negative error number.
 365 *
 366 * We change the ACL rather than storing some ACL entries in the file
 367 * mode permission bits (which would be more efficient), because that
 368 * would break once additional permissions (like  ACL_APPEND, ACL_DELETE
 369 * for directories) are added. There are no more bits available in the
 370 * file mode.
 371 *
 372 * inode->i_mutex: down
 373 */
 374int
 375ext4_acl_chmod(struct inode *inode)
 376{
 377        struct posix_acl *acl, *clone;
 378        int error;
 379
 380        if (S_ISLNK(inode->i_mode))
 381                return -EOPNOTSUPP;
 382        if (!test_opt(inode->i_sb, POSIX_ACL))
 383                return 0;
 384        acl = ext4_get_acl(inode, ACL_TYPE_ACCESS);
 385        if (IS_ERR(acl) || !acl)
 386                return PTR_ERR(acl);
 387        clone = posix_acl_clone(acl, GFP_KERNEL);
 388        posix_acl_release(acl);
 389        if (!clone)
 390                return -ENOMEM;
 391        error = posix_acl_chmod_masq(clone, inode->i_mode);
 392        if (!error) {
 393                handle_t *handle;
 394                int retries = 0;
 395
 396        retry:
 397                handle = ext4_journal_start(inode,
 398                                EXT4_DATA_TRANS_BLOCKS(inode->i_sb));
 399                if (IS_ERR(handle)) {
 400                        error = PTR_ERR(handle);
 401                        ext4_std_error(inode->i_sb, error);
 402                        goto out;
 403                }
 404                error = ext4_set_acl(handle, inode, ACL_TYPE_ACCESS, clone);
 405                ext4_journal_stop(handle);
 406                if (error == -ENOSPC &&
 407                    ext4_should_retry_alloc(inode->i_sb, &retries))
 408                        goto retry;
 409        }
 410out:
 411        posix_acl_release(clone);
 412        return error;
 413}
 414
 415/*
 416 * Extended attribute handlers
 417 */
 418static size_t
 419ext4_xattr_list_acl_access(struct inode *inode, char *list, size_t list_len,
 420                           const char *name, size_t name_len)
 421{
 422        const size_t size = sizeof(POSIX_ACL_XATTR_ACCESS);
 423
 424        if (!test_opt(inode->i_sb, POSIX_ACL))
 425                return 0;
 426        if (list && size <= list_len)
 427                memcpy(list, POSIX_ACL_XATTR_ACCESS, size);
 428        return size;
 429}
 430
 431static size_t
 432ext4_xattr_list_acl_default(struct inode *inode, char *list, size_t list_len,
 433                            const char *name, size_t name_len)
 434{
 435        const size_t size = sizeof(POSIX_ACL_XATTR_DEFAULT);
 436
 437        if (!test_opt(inode->i_sb, POSIX_ACL))
 438                return 0;
 439        if (list && size <= list_len)
 440                memcpy(list, POSIX_ACL_XATTR_DEFAULT, size);
 441        return size;
 442}
 443
 444static int
 445ext4_xattr_get_acl(struct inode *inode, int type, void *buffer, size_t size)
 446{
 447        struct posix_acl *acl;
 448        int error;
 449
 450        if (!test_opt(inode->i_sb, POSIX_ACL))
 451                return -EOPNOTSUPP;
 452
 453        acl = ext4_get_acl(inode, type);
 454        if (IS_ERR(acl))
 455                return PTR_ERR(acl);
 456        if (acl == NULL)
 457                return -ENODATA;
 458        error = posix_acl_to_xattr(acl, buffer, size);
 459        posix_acl_release(acl);
 460
 461        return error;
 462}
 463
 464static int
 465ext4_xattr_get_acl_access(struct inode *inode, const char *name,
 466                          void *buffer, size_t size)
 467{
 468        if (strcmp(name, "") != 0)
 469                return -EINVAL;
 470        return ext4_xattr_get_acl(inode, ACL_TYPE_ACCESS, buffer, size);
 471}
 472
 473static int
 474ext4_xattr_get_acl_default(struct inode *inode, const char *name,
 475                           void *buffer, size_t size)
 476{
 477        if (strcmp(name, "") != 0)
 478                return -EINVAL;
 479        return ext4_xattr_get_acl(inode, ACL_TYPE_DEFAULT, buffer, size);
 480}
 481
 482static int
 483ext4_xattr_set_acl(struct inode *inode, int type, const void *value,
 484                   size_t size)
 485{
 486        handle_t *handle;
 487        struct posix_acl *acl;
 488        int error, retries = 0;
 489
 490        if (!test_opt(inode->i_sb, POSIX_ACL))
 491                return -EOPNOTSUPP;
 492        if (!is_owner_or_cap(inode))
 493                return -EPERM;
 494
 495        if (value) {
 496                acl = posix_acl_from_xattr(value, size);
 497                if (IS_ERR(acl))
 498                        return PTR_ERR(acl);
 499                else if (acl) {
 500                        error = posix_acl_valid(acl);
 501                        if (error)
 502                                goto release_and_out;
 503                }
 504        } else
 505                acl = NULL;
 506
 507retry:
 508        handle = ext4_journal_start(inode, EXT4_DATA_TRANS_BLOCKS(inode->i_sb));
 509        if (IS_ERR(handle))
 510                return PTR_ERR(handle);
 511        error = ext4_set_acl(handle, inode, type, acl);
 512        ext4_journal_stop(handle);
 513        if (error == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries))
 514                goto retry;
 515
 516release_and_out:
 517        posix_acl_release(acl);
 518        return error;
 519}
 520
 521static int
 522ext4_xattr_set_acl_access(struct inode *inode, const char *name,
 523                          const void *value, size_t size, int flags)
 524{
 525        if (strcmp(name, "") != 0)
 526                return -EINVAL;
 527        return ext4_xattr_set_acl(inode, ACL_TYPE_ACCESS, value, size);
 528}
 529
 530static int
 531ext4_xattr_set_acl_default(struct inode *inode, const char *name,
 532                           const void *value, size_t size, int flags)
 533{
 534        if (strcmp(name, "") != 0)
 535                return -EINVAL;
 536        return ext4_xattr_set_acl(inode, ACL_TYPE_DEFAULT, value, size);
 537}
 538
 539struct xattr_handler ext4_xattr_acl_access_handler = {
 540        .prefix = POSIX_ACL_XATTR_ACCESS,
 541        .list   = ext4_xattr_list_acl_access,
 542        .get    = ext4_xattr_get_acl_access,
 543        .set    = ext4_xattr_set_acl_access,
 544};
 545
 546struct xattr_handler ext4_xattr_acl_default_handler = {
 547        .prefix = POSIX_ACL_XATTR_DEFAULT,
 548        .list   = ext4_xattr_list_acl_default,
 549        .get    = ext4_xattr_get_acl_default,
 550        .set    = ext4_xattr_set_acl_default,
 551};
 552
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.