Release 4.10 fs/ext2/xattr.c
/*
* linux/fs/ext2/xattr.c
*
* Copyright (C) 2001-2003 Andreas Gruenbacher <agruen@suse.de>
*
* Fix by Harrison Xing <harrison@mountainviewdata.com>.
* Extended attributes for symlinks and special files added per
* suggestion of Luka Renko <luka.renko@hermes.si>.
* xattr consolidation Copyright (c) 2004 James Morris <jmorris@redhat.com>,
* Red Hat Inc.
*
*/
/*
* Extended attributes are stored on disk blocks allocated outside of
* any inode. The i_file_acl field is then made to point to this allocated
* block. If all extended attributes of an inode are identical, these
* inodes may share the same extended attribute block. Such situations
* are automatically detected by keeping a cache of recent attribute block
* numbers and hashes over the block's contents in memory.
*
*
* Extended attribute block layout:
*
* +------------------+
* | header |
* | entry 1 | |
* | entry 2 | | growing downwards
* | entry 3 | v
* | four null bytes |
* | . . . |
* | value 1 | ^
* | value 3 | | growing upwards
* | value 2 | |
* +------------------+
*
* The block header is followed by multiple entry descriptors. These entry
* descriptors are variable in size, and aligned to EXT2_XATTR_PAD
* byte boundaries. The entry descriptors are sorted by attribute name,
* so that two extended attribute blocks can be compared efficiently.
*
* Attribute values are aligned to the end of the block, stored in
* no specific order. They are also padded to EXT2_XATTR_PAD byte
* boundaries. No additional gaps are left between them.
*
* Locking strategy
* ----------------
* EXT2_I(inode)->i_file_acl is protected by EXT2_I(inode)->xattr_sem.
* EA blocks are only changed if they are exclusive to an inode, so
* holding xattr_sem also means that nothing but the EA block's reference
* count will change. Multiple writers to an EA block are synchronized
* by the bh lock. No more than a single bh lock is held at any time
* to avoid deadlocks.
*/
#include <linux/buffer_head.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/mbcache.h>
#include <linux/quotaops.h>
#include <linux/rwsem.h>
#include <linux/security.h>
#include "ext2.h"
#include "xattr.h"
#include "acl.h"
#define HDR(bh) ((struct ext2_xattr_header *)((bh)->b_data))
#define ENTRY(ptr) ((struct ext2_xattr_entry *)(ptr))
#define FIRST_ENTRY(bh) ENTRY(HDR(bh)+1)
#define IS_LAST_ENTRY(entry) (*(__u32 *)(entry) == 0)
#ifdef EXT2_XATTR_DEBUG
# define ea_idebug(inode, f...) do { \
printk(KERN_DEBUG "inode %s:%ld: ", \
inode->i_sb->s_id, inode->i_ino); \
printk(f); \
printk("\n"); \
} while (0)
# define ea_bdebug(bh, f...) do { \
printk(KERN_DEBUG "block %pg:%lu: ", \
bh->b_bdev, (unsigned long) bh->b_blocknr); \
printk(f); \
printk("\n"); \
} while (0)
#else
# define ea_idebug(f...)
# define ea_bdebug(f...)
#endif
static int ext2_xattr_set2(struct inode *, struct buffer_head *,
struct ext2_xattr_header *);
static int ext2_xattr_cache_insert(struct mb_cache *, struct buffer_head *);
static struct buffer_head *ext2_xattr_cache_find(struct inode *,
struct ext2_xattr_header *);
static void ext2_xattr_rehash(struct ext2_xattr_header *,
struct ext2_xattr_entry *);
static const struct xattr_handler *ext2_xattr_handler_map[] = {
[EXT2_XATTR_INDEX_USER] = &ext2_xattr_user_handler,
#ifdef CONFIG_EXT2_FS_POSIX_ACL
[EXT2_XATTR_INDEX_POSIX_ACL_ACCESS] = &posix_acl_access_xattr_handler,
[EXT2_XATTR_INDEX_POSIX_ACL_DEFAULT] = &posix_acl_default_xattr_handler,
#endif
[EXT2_XATTR_INDEX_TRUSTED] = &ext2_xattr_trusted_handler,
#ifdef CONFIG_EXT2_FS_SECURITY
[EXT2_XATTR_INDEX_SECURITY] = &ext2_xattr_security_handler,
#endif
};
const struct xattr_handler *ext2_xattr_handlers[] = {
&ext2_xattr_user_handler,
&ext2_xattr_trusted_handler,
#ifdef CONFIG_EXT2_FS_POSIX_ACL
&posix_acl_access_xattr_handler,
&posix_acl_default_xattr_handler,
#endif
#ifdef CONFIG_EXT2_FS_SECURITY
&ext2_xattr_security_handler,
#endif
NULL
};
static inline const struct xattr_handler *
ext2_xattr_handler(int name_index)
{
const struct xattr_handler *handler = NULL;
if (name_index > 0 && name_index < ARRAY_SIZE(ext2_xattr_handler_map))
handler = ext2_xattr_handler_map[name_index];
return handler;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
theodore tso | theodore tso | 33 | 75.00% | 1 | 20.00% |
andreas gruenbacher | andreas gruenbacher | 5 | 11.36% | 1 | 20.00% |
james morris | james morris | 3 | 6.82% | 1 | 20.00% |
stephen hemminger | stephen hemminger | 2 | 4.55% | 1 | 20.00% |
andrew morton | andrew morton | 1 | 2.27% | 1 | 20.00% |
| Total | 44 | 100.00% | 5 | 100.00% |
/*
* ext2_xattr_get()
*
* Copy an extended attribute into the buffer
* provided, or compute the buffer size required.
* Buffer is NULL to compute the size of the buffer required.
*
* Returns a negative error number on failure, or the number of bytes
* used / required on success.
*/
int
ext2_xattr_get(struct inode *inode, int name_index, const char *name,
void *buffer, size_t buffer_size)
{
struct buffer_head *bh = NULL;
struct ext2_xattr_entry *entry;
size_t name_len, size;
char *end;
int error;
struct mb_cache *ext2_mb_cache = EXT2_SB(inode->i_sb)->s_mb_cache;
ea_idebug(inode, "name=%d.%s, buffer=%p, buffer_size=%ld",
name_index, name, buffer, (long)buffer_size);
if (name == NULL)
return -EINVAL;
name_len = strlen(name);
if (name_len > 255)
return -ERANGE;
down_read(&EXT2_I(inode)->xattr_sem);
error = -ENODATA;
if (!EXT2_I(inode)->i_file_acl)
goto cleanup;
ea_idebug(inode, "reading block %d", EXT2_I(inode)->i_file_acl);
bh = sb_bread(inode->i_sb, EXT2_I(inode)->i_file_acl);
error = -EIO;
if (!bh)
goto cleanup;
ea_bdebug(bh, "b_count=%d, refcount=%d",
atomic_read(&(bh->b_count)), le32_to_cpu(HDR(bh)->h_refcount));
end = bh->b_data + bh->b_size;
if (HDR(bh)->h_magic != cpu_to_le32(EXT2_XATTR_MAGIC) ||
HDR(bh)->h_blocks != cpu_to_le32(1)) {
bad_block: ext2_error(inode->i_sb, "ext2_xattr_get",
"inode %ld: bad block %d", inode->i_ino,
EXT2_I(inode)->i_file_acl);
error = -EIO;
goto cleanup;
}
/* find named attribute */
entry = FIRST_ENTRY(bh);
while (!IS_LAST_ENTRY(entry)) {
struct ext2_xattr_entry *next =
EXT2_XATTR_NEXT(entry);
if ((char *)next >= end)
goto bad_block;
if (name_index == entry->e_name_index &&
name_len == entry->e_name_len &&
memcmp(name, entry->e_name, name_len) == 0)
goto found;
entry = next;
}
if (ext2_xattr_cache_insert(ext2_mb_cache, bh))
ea_idebug(inode, "cache insert failed");
error = -ENODATA;
goto cleanup;
found:
/* check the buffer size */
if (entry->e_value_block != 0)
goto bad_block;
size = le32_to_cpu(entry->e_value_size);
if (size > inode->i_sb->s_blocksize ||
le16_to_cpu(entry->e_value_offs) + size > inode->i_sb->s_blocksize)
goto bad_block;
if (ext2_xattr_cache_insert(ext2_mb_cache, bh))
ea_idebug(inode, "cache insert failed");
if (buffer) {
error = -ERANGE;
if (size > buffer_size)
goto cleanup;
/* return value of attribute */
memcpy(buffer, bh->b_data + le16_to_cpu(entry->e_value_offs),
size);
}
error = size;
cleanup:
brelse(bh);
up_read(&EXT2_I(inode)->xattr_sem);
return error;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
theodore tso | theodore tso | 431 | 85.01% | 1 | 16.67% |
andrew morton | andrew morton | 41 | 8.09% | 2 | 33.33% |
jan kara | jan kara | 18 | 3.55% | 2 | 33.33% |
wang sheng-hui | wang sheng-hui | 17 | 3.35% | 1 | 16.67% |
| Total | 507 | 100.00% | 6 | 100.00% |
/*
* ext2_xattr_list()
*
* Copy a list of attribute names into the buffer
* provided, or compute the buffer size required.
* Buffer is NULL to compute the size of the buffer required.
*
* Returns a negative error number on failure, or the number of bytes
* used / required on success.
*/
static int
ext2_xattr_list(struct dentry *dentry, char *buffer, size_t buffer_size)
{
struct inode *inode = d_inode(dentry);
struct buffer_head *bh = NULL;
struct ext2_xattr_entry *entry;
char *end;
size_t rest = buffer_size;
int error;
struct mb_cache *ext2_mb_cache = EXT2_SB(inode->i_sb)->s_mb_cache;
ea_idebug(inode, "buffer=%p, buffer_size=%ld",
buffer, (long)buffer_size);
down_read(&EXT2_I(inode)->xattr_sem);
error = 0;
if (!EXT2_I(inode)->i_file_acl)
goto cleanup;
ea_idebug(inode, "reading block %d", EXT2_I(inode)->i_file_acl);
bh = sb_bread(inode->i_sb, EXT2_I(inode)->i_file_acl);
error = -EIO;
if (!bh)
goto cleanup;
ea_bdebug(bh, "b_count=%d, refcount=%d",
atomic_read(&(bh->b_count)), le32_to_cpu(HDR(bh)->h_refcount));
end = bh->b_data + bh->b_size;
if (HDR(bh)->h_magic != cpu_to_le32(EXT2_XATTR_MAGIC) ||
HDR(bh)->h_blocks != cpu_to_le32(1)) {
bad_block: ext2_error(inode->i_sb, "ext2_xattr_list",
"inode %ld: bad block %d", inode->i_ino,
EXT2_I(inode)->i_file_acl);
error = -EIO;
goto cleanup;
}
/* check the on-disk data structure */
entry = FIRST_ENTRY(bh);
while (!IS_LAST_ENTRY(entry)) {
struct ext2_xattr_entry *next = EXT2_XATTR_NEXT(entry);
if ((char *)next >= end)
goto bad_block;
entry = next;
}
if (ext2_xattr_cache_insert(ext2_mb_cache, bh))
ea_idebug(inode, "cache insert failed");
/* list the attribute names */
for (entry = FIRST_ENTRY(bh); !IS_LAST_ENTRY(entry);
entry = EXT2_XATTR_NEXT(entry)) {
const struct xattr_handler *handler =
ext2_xattr_handler(entry->e_name_index);
if (handler && (!handler->list || handler->list(dentry))) {
const char *prefix = handler->prefix ?: handler->name;
size_t prefix_len = strlen(prefix);
size_t size = prefix_len + entry->e_name_len + 1;
if (buffer) {
if (size > rest) {
error = -ERANGE;
goto cleanup;
}
memcpy(buffer, prefix, prefix_len);
buffer += prefix_len;
memcpy(buffer, entry->e_name, entry->e_name_len);
buffer += entry->e_name_len;
*buffer++ = 0;
}
rest -= size;
}
}
error = buffer_size - rest; /* total size */
cleanup:
brelse(bh);
up_read(&EXT2_I(inode)->xattr_sem);
return error;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
theodore tso | theodore tso | 261 | 53.05% | 1 | 8.33% |
andreas gruenbacher | andreas gruenbacher | 134 | 27.24% | 3 | 25.00% |
andrew morton | andrew morton | 37 | 7.52% | 1 | 8.33% |
james morris | james morris | 30 | 6.10% | 1 | 8.33% |
jan kara | jan kara | 16 | 3.25% | 2 | 16.67% |
christoph hellwig | christoph hellwig | 9 | 1.83% | 1 | 8.33% |
david howells | david howells | 3 | 0.61% | 1 | 8.33% |
adrian bunk | adrian bunk | 1 | 0.20% | 1 | 8.33% |
stephen hemminger | stephen hemminger | 1 | 0.20% | 1 | 8.33% |
| Total | 492 | 100.00% | 12 | 100.00% |
/*
* Inode operation listxattr()
*
* d_inode(dentry)->i_mutex: don't care
*/
ssize_t
ext2_listxattr(struct dentry *dentry, char *buffer, size_t size)
{
return ext2_xattr_list(dentry, buffer, size);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
adrian bunk | adrian bunk | 27 | 100.00% | 1 | 100.00% |
| Total | 27 | 100.00% | 1 | 100.00% |
/*
* If the EXT2_FEATURE_COMPAT_EXT_ATTR feature of this file system is
* not set, set it.
*/
static void ext2_xattr_update_super_block(struct super_block *sb)
{
if (EXT2_HAS_COMPAT_FEATURE(sb, EXT2_FEATURE_COMPAT_EXT_ATTR))
return;
spin_lock(&EXT2_SB(sb)->s_lock);
EXT2_SET_COMPAT_FEATURE(sb, EXT2_FEATURE_COMPAT_EXT_ATTR);
spin_unlock(&EXT2_SB(sb)->s_lock);
mark_buffer_dirty(EXT2_SB(sb)->s_sbh);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
theodore tso | theodore tso | 36 | 60.00% | 1 | 33.33% |
jan blunck | jan blunck | 22 | 36.67% | 1 | 33.33% |
andreas gruenbacher | andreas gruenbacher | 2 | 3.33% | 1 | 33.33% |
| Total | 60 | 100.00% | 3 | 100.00% |
/*
* ext2_xattr_set()
*
* Create, replace or remove an extended attribute for this inode. Value
* is NULL to remove an existing extended attribute, and non-NULL to
* either replace an existing extended attribute, or create a new extended
* attribute. The flags XATTR_REPLACE and XATTR_CREATE
* specify that an extended attribute must exist and must not exist
* previous to the call, respectively.
*
* Returns 0, or a negative error number on failure.
*/
int
ext2_xattr_set(struct inode *inode, int name_index, const char *name,
const void *value, size_t value_len, int flags)
{
struct super_block *sb = inode->i_sb;
struct buffer_head *bh = NULL;
struct ext2_xattr_header *header = NULL;
struct ext2_xattr_entry *here, *last;
size_t name_len, free, min_offs = sb->s_blocksize;
int not_found = 1, error;
char *end;
/*
* header -- Points either into bh, or to a temporarily
* allocated buffer.
* here -- The named entry found, or the place for inserting, within
* the block pointed to by header.
* last -- Points right after the last named entry within the block
* pointed to by header.
* min_offs -- The offset of the first value (values are aligned
* towards the end of the block).
* end -- Points right after the block pointed to by header.
*/
ea_idebug(inode, "name=%d.%s, value=%p, value_len=%ld",
name_index, name, value, (long)value_len);
if (value == NULL)
value_len = 0;
if (name == NULL)
return -EINVAL;
name_len = strlen(name);
if (name_len > 255 || value_len > sb->s_blocksize)
return -ERANGE;
down_write(&EXT2_I(inode)->xattr_sem);
if (EXT2_I(inode)->i_file_acl) {
/* The inode already has an extended attribute block. */
bh = sb_bread(sb, EXT2_I(inode)->i_file_acl);
error = -EIO;
if (!bh)
goto cleanup;
ea_bdebug(bh, "b_count=%d, refcount=%d",
atomic_read(&(bh->b_count)),
le32_to_cpu(HDR(bh)->h_refcount));
header = HDR(bh);
end = bh->b_data + bh->b_size;
if (header->h_magic != cpu_to_le32(EXT2_XATTR_MAGIC) ||
header->h_blocks != cpu_to_le32(1)) {
bad_block: ext2_error(sb, "ext2_xattr_set",
"inode %ld: bad block %d", inode->i_ino,
EXT2_I(inode)->i_file_acl);
error = -EIO;
goto cleanup;
}
/* Find the named attribute. */
here = FIRST_ENTRY(bh);
while (!IS_LAST_ENTRY(here)) {
struct ext2_xattr_entry *next = EXT2_XATTR_NEXT(here);
if ((char *)next >= end)
goto bad_block;
if (!here->e_value_block && here->e_value_size) {
size_t offs = le16_to_cpu(here->e_value_offs);
if (offs < min_offs)
min_offs = offs;
}
not_found = name_index - here->e_name_index;
if (!not_found)
not_found = name_len - here->e_name_len;
if (!not_found)
not_found = memcmp(name, here->e_name,name_len);
if (not_found <= 0)
break;
here = next;
}
last = here;
/* We still need to compute min_offs and last. */
while (!IS_LAST_ENTRY(last)) {
struct ext2_xattr_entry *next = EXT2_XATTR_NEXT(last);
if ((char *)next >= end)
goto bad_block;
if (!last->e_value_block && last->e_value_size) {
size_t offs = le16_to_cpu(last->e_value_offs);
if (offs < min_offs)
min_offs = offs;
}
last = next;
}
/* Check whether we have enough space left. */
free = min_offs - ((char*)last - (char*)header) - sizeof(__u32);
} else {
/* We will use a new extended attribute block. */
free = sb->s_blocksize -
sizeof(struct ext2_xattr_header) - sizeof(__u32);
here = last = NULL; /* avoid gcc uninitialized warning. */
}
if (not_found) {
/* Request to remove a nonexistent attribute? */
error = -ENODATA;
if (flags & XATTR_REPLACE)
goto cleanup;
error = 0;
if (value == NULL)
goto cleanup;
} else {
/* Request to create an existing attribute? */
error = -EEXIST;
if (flags & XATTR_CREATE)
goto cleanup;
if (!here->e_value_block && here->e_value_size) {
size_t size = le32_to_cpu(here->e_value_size);
if (le16_to_cpu(here->e_value_offs) + size >
sb->s_blocksize || size > sb->s_blocksize)
goto bad_block;
free += EXT2_XATTR_SIZE(size);
}
free += EXT2_XATTR_LEN(name_len);
}
error = -ENOSPC;
if (free < EXT2_XATTR_LEN(name_len) + EXT2_XATTR_SIZE(value_len))
goto cleanup;
/* Here we know that we can set the new attribute. */
if (header) {
/* assert(header == HDR(bh)); */
lock_buffer(bh);
if (header->h_refcount == cpu_to_le32(1)) {
__u32 hash = le32_to_cpu(header->h_hash);
ea_bdebug(bh, "modifying in-place");
/*
* This must happen under buffer lock for
* ext2_xattr_set2() to reliably detect modified block
*/
mb_cache_entry_delete_block(EXT2_SB(sb)->s_mb_cache,
hash, bh->b_blocknr);
/* keep the buffer locked while modifying it. */
} else {
int offset;
unlock_buffer(bh);
ea_bdebug(bh, "cloning");
header = kmalloc(bh->b_size, GFP_KERNEL);
error = -ENOMEM;
if (header == NULL)
goto cleanup;
memcpy(header, HDR(bh), bh->b_size);
header->h_refcount = cpu_to_le32(1);
offset = (char *)here - bh->b_data;
here = ENTRY((char *)header + offset);
offset = (char *)last - bh->b_data;
last = ENTRY((char *)header + offset);
}
} else {
/* Allocate a buffer where we construct the new block. */
header = kzalloc(sb->s_blocksize, GFP_KERNEL);
error = -ENOMEM;
if (header == NULL)
goto cleanup;
end = (char *)header + sb->s_blocksize;
header->h_magic = cpu_to_le32(EXT2_XATTR_MAGIC);
header->h_blocks = header->h_refcount = cpu_to_le32(1);
last = here = ENTRY(header+1);
}
/* Iff we are modifying the block in-place, bh is locked here. */
if (not_found) {
/* Insert the new name. */
size_t size = EXT2_XATTR_LEN(name_len);
size_t rest = (char *)last - (char *)here;
memmove((char *)here + size, here, rest);
memset(here, 0, size);
here->e_name_index = name_index;
here->e_name_len = name_len;
memcpy(here->e_name, name, name_len);
} else {
if (!here->e_value_block && here->e_value_size) {
char *first_val = (char *)header + min_offs;
size_t offs = le16_to_cpu(here->e_value_offs);
char *val = (char *)header + offs;
size_t size = EXT2_XATTR_SIZE(
le32_to_cpu(here->e_value_size));
if (size == EXT2_XATTR_SIZE(value_len)) {
/* The old and the new value have the same
size. Just replace. */
here->e_value_size = cpu_to_le32(value_len);
memset(val + size - EXT2_XATTR_PAD, 0,
EXT2_XATTR_PAD); /* Clear pad bytes. */
memcpy(val, value, value_len);
goto skip_replace;
}
/* Remove the old value. */
memmove(first_val + size, first_val, val - first_val);
memset(first_val, 0, size);
here->e_value_offs = 0;
min_offs += size;
/* Adjust all value offsets. */
last = ENTRY(header+1);
while (!IS_LAST_ENTRY(last)) {
size_t o = le16_to_cpu(last->e_value_offs);
if (!last->e_value_block && o < offs)
last->e_value_offs =
cpu_to_le16(o + size);
last = EXT2_XATTR_NEXT(last);
}
}
if (value == NULL) {
/* Remove the old name. */
size_t size = EXT2_XATTR_LEN(name_len);
last = ENTRY((char *)last - size);
memmove(here, (char*)here + size,
(char*)last - (char*)here);
memset(last, 0, size);
}
}
if (value != NULL) {
/* Insert the new value. */
here->e_value_size = cpu_to_le32(value_len);
if (value_len) {
size_t size = EXT2_XATTR_SIZE(value_len);
char *val = (char *)header + min_offs - size;
here->e_value_offs =
cpu_to_le16((char *)val - (char *)header);
memset(val + size - EXT2_XATTR_PAD, 0,
EXT2_XATTR_PAD); /* Clear the pad bytes. */
memcpy(val, value, value_len);
}
}
skip_replace:
if (IS_LAST_ENTRY(ENTRY(header+1))) {
/* This block is now empty. */
if (bh && header == HDR