* 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.
+ *
*/
/*
*
* +------------------+
* | header |
- * ¦ entry 1 | |
+ * | entry 1 | |
* | entry 2 | | growing downwards
* | entry 3 | v
* | four null bytes |
#include <linux/mbcache.h>
#include <linux/quotaops.h>
#include <linux/rwsem.h>
+#include <linux/vs_dlimit.h>
#include "ext2.h"
#include "xattr.h"
#include "acl.h"
-/* These symbols may be needed by a module. */
-EXPORT_SYMBOL(ext2_xattr_register);
-EXPORT_SYMBOL(ext2_xattr_unregister);
-EXPORT_SYMBOL(ext2_xattr_get);
-EXPORT_SYMBOL(ext2_xattr_list);
-EXPORT_SYMBOL(ext2_xattr_set);
-
#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)
static int ext2_xattr_cache_insert(struct buffer_head *);
static struct buffer_head *ext2_xattr_cache_find(struct inode *,
struct ext2_xattr_header *);
-static void ext2_xattr_cache_remove(struct buffer_head *);
static void ext2_xattr_rehash(struct ext2_xattr_header *,
struct ext2_xattr_entry *);
static struct mb_cache *ext2_xattr_cache;
-static struct ext2_xattr_handler *ext2_xattr_handlers[EXT2_XATTR_INDEX_MAX];
-static rwlock_t ext2_handler_lock = RW_LOCK_UNLOCKED;
-
-int
-ext2_xattr_register(int name_index, struct ext2_xattr_handler *handler)
-{
- int error = -EINVAL;
-
- if (name_index > 0 && name_index <= EXT2_XATTR_INDEX_MAX) {
- write_lock(&ext2_handler_lock);
- if (!ext2_xattr_handlers[name_index-1]) {
- ext2_xattr_handlers[name_index-1] = handler;
- error = 0;
- }
- write_unlock(&ext2_handler_lock);
- }
- return error;
-}
-void
-ext2_xattr_unregister(int name_index, struct ext2_xattr_handler *handler)
-{
- if (name_index > 0 || name_index <= EXT2_XATTR_INDEX_MAX) {
- write_lock(&ext2_handler_lock);
- ext2_xattr_handlers[name_index-1] = NULL;
- write_unlock(&ext2_handler_lock);
- }
-}
-
-static inline const char *
-strcmp_prefix(const char *a, const char *a_prefix)
-{
- while (*a_prefix && *a == *a_prefix) {
- a++;
- a_prefix++;
- }
- return *a_prefix ? NULL : a;
-}
+static 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] = &ext2_xattr_acl_access_handler,
+ [EXT2_XATTR_INDEX_POSIX_ACL_DEFAULT] = &ext2_xattr_acl_default_handler,
+#endif
+ [EXT2_XATTR_INDEX_TRUSTED] = &ext2_xattr_trusted_handler,
+#ifdef CONFIG_EXT2_FS_SECURITY
+ [EXT2_XATTR_INDEX_SECURITY] = &ext2_xattr_security_handler,
+#endif
+};
-/*
- * Decode the extended attribute name, and translate it into
- * the name_index and name suffix.
- */
-static struct ext2_xattr_handler *
-ext2_xattr_resolve_name(const char **name)
-{
- struct ext2_xattr_handler *handler = NULL;
- int i;
-
- if (!*name)
- return NULL;
- read_lock(&ext2_handler_lock);
- for (i=0; i<EXT2_XATTR_INDEX_MAX; i++) {
- if (ext2_xattr_handlers[i]) {
- const char *n = strcmp_prefix(*name,
- ext2_xattr_handlers[i]->prefix);
- if (n) {
- handler = ext2_xattr_handlers[i];
- *name = n;
- break;
- }
- }
- }
- read_unlock(&ext2_handler_lock);
- return handler;
-}
+struct xattr_handler *ext2_xattr_handlers[] = {
+ &ext2_xattr_user_handler,
+ &ext2_xattr_trusted_handler,
+#ifdef CONFIG_EXT2_FS_POSIX_ACL
+ &ext2_xattr_acl_access_handler,
+ &ext2_xattr_acl_default_handler,
+#endif
+#ifdef CONFIG_EXT2_FS_SECURITY
+ &ext2_xattr_security_handler,
+#endif
+ NULL
+};
-static inline struct ext2_xattr_handler *
+static inline struct xattr_handler *
ext2_xattr_handler(int name_index)
{
- struct ext2_xattr_handler *handler = NULL;
- if (name_index > 0 && name_index <= EXT2_XATTR_INDEX_MAX) {
- read_lock(&ext2_handler_lock);
- handler = ext2_xattr_handlers[name_index-1];
- read_unlock(&ext2_handler_lock);
- }
- return handler;
-}
-
-/*
- * Inode operation getxattr()
- *
- * dentry->d_inode->i_sem: don't care
- */
-ssize_t
-ext2_getxattr(struct dentry *dentry, const char *name,
- void *buffer, size_t size)
-{
- struct ext2_xattr_handler *handler;
- struct inode *inode = dentry->d_inode;
-
- handler = ext2_xattr_resolve_name(&name);
- if (!handler)
- return -EOPNOTSUPP;
- return handler->get(inode, name, buffer, size);
-}
-
-/*
- * Inode operation listxattr()
- *
- * dentry->d_inode->i_sem: don't care
- */
-ssize_t
-ext2_listxattr(struct dentry *dentry, char *buffer, size_t size)
-{
- return ext2_xattr_list(dentry->d_inode, buffer, size);
-}
-
-/*
- * Inode operation setxattr()
- *
- * dentry->d_inode->i_sem: down
- */
-int
-ext2_setxattr(struct dentry *dentry, const char *name,
- const void *value, size_t size, int flags)
-{
- struct ext2_xattr_handler *handler;
- struct inode *inode = dentry->d_inode;
-
- if (size == 0)
- value = ""; /* empty EA, do not remove */
- handler = ext2_xattr_resolve_name(&name);
- if (!handler)
- return -EOPNOTSUPP;
- return handler->set(inode, name, value, size, flags);
-}
+ struct xattr_handler *handler = NULL;
-/*
- * Inode operation removexattr()
- *
- * dentry->d_inode->i_sem: down
- */
-int
-ext2_removexattr(struct dentry *dentry, const char *name)
-{
- struct ext2_xattr_handler *handler;
- struct inode *inode = dentry->d_inode;
-
- handler = ext2_xattr_resolve_name(&name);
- if (!handler)
- return -EOPNOTSUPP;
- return handler->set(inode, name, NULL, 0, XATTR_REPLACE);
+ if (name_index > 0 && name_index < ARRAY_SIZE(ext2_xattr_handler_map))
+ handler = ext2_xattr_handler_map[name_index];
+ return handler;
}
/*
* Returns a negative error number on failure, or the number of bytes
* used / required on success.
*/
-int
+static int
ext2_xattr_list(struct inode *inode, char *buffer, size_t buffer_size)
{
struct buffer_head *bh = NULL;
struct ext2_xattr_entry *entry;
- size_t size = 0;
- char *buf, *end;
+ char *end;
+ size_t rest = buffer_size;
int error;
ea_idebug(inode, "buffer=%p, buffer_size=%ld",
error = -EIO;
goto cleanup;
}
- /* compute the size required for the list of attribute names */
- for (entry = FIRST_ENTRY(bh); !IS_LAST_ENTRY(entry);
- entry = EXT2_XATTR_NEXT(entry)) {
- struct ext2_xattr_handler *handler;
- struct ext2_xattr_entry *next =
- EXT2_XATTR_NEXT(entry);
+
+ /* 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;
-
- handler = ext2_xattr_handler(entry->e_name_index);
- if (handler)
- size += handler->list(NULL, inode, entry->e_name,
- entry->e_name_len);
+ entry = next;
}
-
if (ext2_xattr_cache_insert(bh))
ea_idebug(inode, "cache insert failed");
- if (!buffer) {
- error = size;
- goto cleanup;
- } else {
- error = -ERANGE;
- if (size > buffer_size)
- goto cleanup;
- }
/* list the attribute names */
- buf = buffer;
for (entry = FIRST_ENTRY(bh); !IS_LAST_ENTRY(entry);
entry = EXT2_XATTR_NEXT(entry)) {
- struct ext2_xattr_handler *handler;
-
- handler = ext2_xattr_handler(entry->e_name_index);
- if (handler)
- buf += handler->list(buf, inode, entry->e_name,
- entry->e_name_len);
+ struct xattr_handler *handler =
+ ext2_xattr_handler(entry->e_name_index);
+
+ if (handler) {
+ size_t size = handler->list(inode, buffer, rest,
+ entry->e_name,
+ entry->e_name_len);
+ if (buffer) {
+ if (size > rest) {
+ error = -ERANGE;
+ goto cleanup;
+ }
+ buffer += size;
+ }
+ rest -= size;
+ }
}
- error = size;
+ error = buffer_size - rest; /* total size */
cleanup:
brelse(bh);
return error;
}
+/*
+ * Inode operation listxattr()
+ *
+ * dentry->d_inode->i_mutex: don't care
+ */
+ssize_t
+ext2_listxattr(struct dentry *dentry, char *buffer, size_t size)
+{
+ return ext2_xattr_list(dentry->d_inode, buffer, size);
+}
+
/*
* If the EXT2_FEATURE_COMPAT_EXT_ATTR feature of this file system is
* not set, set it.
if (EXT2_HAS_COMPAT_FEATURE(sb, EXT2_FEATURE_COMPAT_EXT_ATTR))
return;
- lock_super(sb);
- EXT2_SB(sb)->s_es->s_feature_compat |=
- cpu_to_le32(EXT2_FEATURE_COMPAT_EXT_ATTR);
+ EXT2_SET_COMPAT_FEATURE(sb, EXT2_FEATURE_COMPAT_EXT_ATTR);
sb->s_dirt = 1;
mark_buffer_dirty(EXT2_SB(sb)->s_sbh);
- unlock_super(sb);
}
/*
ea_idebug(inode, "name=%d.%s, value=%p, value_len=%ld",
name_index, name, value, (long)value_len);
- if (IS_RDONLY(inode))
- return -EROFS;
- if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
- return -EPERM;
if (value == NULL)
value_len = 0;
if (name == NULL)
/* Here we know that we can set the new attribute. */
if (header) {
+ struct mb_cache_entry *ce;
+
/* assert(header == HDR(bh)); */
+ ce = mb_cache_entry_get(ext2_xattr_cache, bh->b_bdev,
+ bh->b_blocknr);
lock_buffer(bh);
if (header->h_refcount == cpu_to_le32(1)) {
ea_bdebug(bh, "modifying in-place");
- ext2_xattr_cache_remove(bh);
+ if (ce)
+ mb_cache_entry_free(ce);
/* keep the buffer locked while modifying it. */
} else {
int offset;
+ if (ce)
+ mb_cache_entry_release(ce);
unlock_buffer(bh);
ea_bdebug(bh, "cloning");
header = kmalloc(bh->b_size, GFP_KERNEL);
}
} else {
/* Allocate a buffer where we construct the new block. */
- header = kmalloc(sb->s_blocksize, GFP_KERNEL);
+ header = kzalloc(sb->s_blocksize, GFP_KERNEL);
error = -ENOMEM;
if (header == NULL)
goto cleanup;
- memset(header, 0, sb->s_blocksize);
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);
the inode. */
ea_bdebug(new_bh, "reusing block");
+ error = -ENOSPC;
+ if (DLIMIT_ALLOC_BLOCK(inode, 1))
+ goto cleanup;
error = -EDQUOT;
if (DQUOT_ALLOC_BLOCK(inode, 1)) {
+ DLIMIT_FREE_BLOCK(inode, 1);
unlock_buffer(new_bh);
goto cleanup;
}
s_first_data_block) +
EXT2_I(inode)->i_block_group *
EXT2_BLOCKS_PER_GROUP(sb);
- int block = ext2_new_block(inode, goal, 0, 0, &error);
+ int block = ext2_new_block(inode, goal,
+ NULL, NULL, &error);
if (error)
goto cleanup;
ea_idebug(inode, "creating block %d", block);
/* Update the inode. */
EXT2_I(inode)->i_file_acl = new_bh ? new_bh->b_blocknr : 0;
- inode->i_ctime = CURRENT_TIME;
+ inode->i_ctime = CURRENT_TIME_SEC;
if (IS_SYNC(inode)) {
error = ext2_sync_inode (inode);
- if (error)
+ /* In case sync failed due to ENOSPC the inode was actually
+ * written (only some dirty data were not) so we just proceed
+ * as if nothing happened and cleanup the unused block */
+ if (error && error != -ENOSPC) {
+ if (new_bh && new_bh != old_bh)
+ DQUOT_FREE_BLOCK(inode, 1);
goto cleanup;
+ }
} else
mark_inode_dirty(inode);
error = 0;
if (old_bh && old_bh != new_bh) {
+ struct mb_cache_entry *ce;
+
/*
* If there was an old block and we are no longer using it,
* release the old block.
*/
+ ce = mb_cache_entry_get(ext2_xattr_cache, old_bh->b_bdev,
+ old_bh->b_blocknr);
lock_buffer(old_bh);
if (HDR(old_bh)->h_refcount == cpu_to_le32(1)) {
/* Free the old block. */
+ if (ce)
+ mb_cache_entry_free(ce);
ea_bdebug(old_bh, "freeing");
ext2_free_blocks(inode, old_bh->b_blocknr, 1);
/* We let our caller release old_bh, so we
/* Decrement the refcount only. */
HDR(old_bh)->h_refcount = cpu_to_le32(
le32_to_cpu(HDR(old_bh)->h_refcount) - 1);
+ if (ce)
+ mb_cache_entry_release(ce);
+ DLIMIT_FREE_BLOCK(inode, 1);
DQUOT_FREE_BLOCK(inode, 1);
mark_buffer_dirty(old_bh);
ea_bdebug(old_bh, "refcount now=%d",
ext2_xattr_delete_inode(struct inode *inode)
{
struct buffer_head *bh = NULL;
+ struct mb_cache_entry *ce;
down_write(&EXT2_I(inode)->xattr_sem);
if (!EXT2_I(inode)->i_file_acl)
EXT2_I(inode)->i_file_acl);
goto cleanup;
}
+ ce = mb_cache_entry_get(ext2_xattr_cache, bh->b_bdev, bh->b_blocknr);
lock_buffer(bh);
if (HDR(bh)->h_refcount == cpu_to_le32(1)) {
- ext2_xattr_cache_remove(bh);
+ if (ce)
+ mb_cache_entry_free(ce);
ext2_free_blocks(inode, EXT2_I(inode)->i_file_acl, 1);
get_bh(bh);
bforget(bh);
+ unlock_buffer(bh);
} else {
HDR(bh)->h_refcount = cpu_to_le32(
le32_to_cpu(HDR(bh)->h_refcount) - 1);
+ if (ce)
+ mb_cache_entry_release(ce);
+ ea_bdebug(bh, "refcount now=%d",
+ le32_to_cpu(HDR(bh)->h_refcount));
+ unlock_buffer(bh);
mark_buffer_dirty(bh);
if (IS_SYNC(inode))
sync_dirty_buffer(bh);
+ DLIMIT_FREE_BLOCK(inode, 1);
DQUOT_FREE_BLOCK(inode, 1);
}
- ea_bdebug(bh, "refcount now=%d", le32_to_cpu(HDR(bh)->h_refcount) - 1);
- unlock_buffer(bh);
EXT2_I(inode)->i_file_acl = 0;
cleanup:
void
ext2_xattr_put_super(struct super_block *sb)
{
- mb_cache_shrink(ext2_xattr_cache, sb->s_bdev);
+ mb_cache_shrink(sb->s_bdev);
}
if (IS_LAST_ENTRY(entry2))
return 1;
if (entry1->e_hash != entry2->e_hash ||
+ entry1->e_name_index != entry2->e_name_index ||
entry1->e_name_len != entry2->e_name_len ||
entry1->e_value_size != entry2->e_value_size ||
memcmp(entry1->e_name, entry2->e_name, entry1->e_name_len))
if (!header->h_hash)
return NULL; /* never share */
ea_idebug(inode, "looking for cached blocks [%x]", (int)hash);
+again:
ce = mb_cache_entry_find_first(ext2_xattr_cache, 0,
inode->i_sb->s_bdev, hash);
while (ce) {
- struct buffer_head *bh = sb_bread(inode->i_sb, ce->e_block);
+ struct buffer_head *bh;
+
+ if (IS_ERR(ce)) {
+ if (PTR_ERR(ce) == -EAGAIN)
+ goto again;
+ break;
+ }
+ bh = sb_bread(inode->i_sb, ce->e_block);
if (!bh) {
ext2_error(inode->i_sb, "ext2_xattr_cache_find",
"inode %ld: block %ld read error",
return NULL;
}
-/*
- * ext2_xattr_cache_remove()
- *
- * Remove the cache entry of a block from the cache. Called when a
- * block becomes invalid.
- */
-static void
-ext2_xattr_cache_remove(struct buffer_head *bh)
-{
- struct mb_cache_entry *ce;
-
- ce = mb_cache_entry_get(ext2_xattr_cache, bh->b_bdev, bh->b_blocknr);
- if (ce) {
- ea_bdebug(bh, "removing (%d cache entries remaining)",
- atomic_read(&ext2_xattr_cache->c_entry_count)-1);
- mb_cache_entry_free(ce);
- } else
- ea_bdebug(bh, "no cache entry");
-}
-
#define NAME_HASH_SHIFT 5
#define VALUE_HASH_SHIFT 16
}
if (entry->e_value_block == 0 && entry->e_value_size != 0) {
- __u32 *value = (__u32 *)((char *)header +
+ __le32 *value = (__le32 *)((char *)header +
le16_to_cpu(entry->e_value_offs));
for (n = (le32_to_cpu(entry->e_value_size) +
EXT2_XATTR_ROUND) >> EXT2_XATTR_PAD_BITS; n; n--) {
int __init
init_ext2_xattr(void)
{
- int err;
-
- err = ext2_xattr_register(EXT2_XATTR_INDEX_USER,
- &ext2_xattr_user_handler);
- if (err)
- return err;
- err = ext2_xattr_register(EXT2_XATTR_INDEX_TRUSTED,
- &ext2_xattr_trusted_handler);
- if (err)
- goto out;
-#ifdef CONFIG_EXT2_FS_SECURITY
- err = ext2_xattr_register(EXT2_XATTR_INDEX_SECURITY,
- &ext2_xattr_security_handler);
- if (err)
- goto out1;
-#endif
-#ifdef CONFIG_EXT2_FS_POSIX_ACL
- err = init_ext2_acl();
- if (err)
- goto out2;
-#endif
ext2_xattr_cache = mb_cache_create("ext2_xattr", NULL,
sizeof(struct mb_cache_entry) +
- sizeof(struct mb_cache_entry_index), 1, 6);
- if (!ext2_xattr_cache) {
- err = -ENOMEM;
- goto out3;
- }
+ sizeof(((struct mb_cache_entry *) 0)->e_indexes[0]), 1, 6);
+ if (!ext2_xattr_cache)
+ return -ENOMEM;
return 0;
-out3:
-#ifdef CONFIG_EXT2_FS_POSIX_ACL
- exit_ext2_acl();
-out2:
-#endif
-#ifdef CONFIG_EXT2_FS_SECURITY
- ext2_xattr_unregister(EXT2_XATTR_INDEX_SECURITY,
- &ext2_xattr_security_handler);
-out1:
-#endif
- ext2_xattr_unregister(EXT2_XATTR_INDEX_TRUSTED,
- &ext2_xattr_trusted_handler);
-out:
- ext2_xattr_unregister(EXT2_XATTR_INDEX_USER,
- &ext2_xattr_user_handler);
- return err;
}
void
exit_ext2_xattr(void)
{
mb_cache_destroy(ext2_xattr_cache);
-#ifdef CONFIG_EXT2_FS_POSIX_ACL
- exit_ext2_acl();
-#endif
-#ifdef CONFIG_EXT2_FS_SECURITY
- ext2_xattr_unregister(EXT2_XATTR_INDEX_SECURITY,
- &ext2_xattr_security_handler);
-#endif
- ext2_xattr_unregister(EXT2_XATTR_INDEX_TRUSTED,
- &ext2_xattr_trusted_handler);
- ext2_xattr_unregister(EXT2_XATTR_INDEX_USER,
- &ext2_xattr_user_handler);
}