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