ref: 278e8d43d2bd28063cb0a0d30fec9ef3d9685e3d
parent: 45a1867fb54cf994a51d897ee7eb83f31d72f3fc
author: Kaho Ng <[email protected]>
date: Thu Jun 9 18:27:04 EDT 2016
ext4_xattr: rework the EA submodule
--- a/include/ext4_xattr.h
+++ b/include/ext4_xattr.h
@@ -44,106 +44,85 @@
#include "ext4_config.h"
#include "ext4_types.h"
#include "ext4_inode.h"
-#include "misc/tree.h"
-#include "misc/queue.h"
-struct ext4_xattr_item {
- /* This attribute should be stored in inode body */
- bool in_inode;
- bool is_data;
-
+struct ext4_xattr_info {
uint8_t name_index;
- char *name;
+ const char *name;
size_t name_len;
- void *data;
- size_t data_size;
+ const void *value;
+ size_t value_len;
+};
- RB_ENTRY(ext4_xattr_item) node;
+struct ext4_xattr_list_entry {
+ uint8_t name_index;
+ char *name;
+ size_t name_len;
+ struct ext4_xattr_list_entry *next;
};
-struct ext4_xattr_ref {
- bool block_loaded;
- struct ext4_block block;
- struct ext4_inode_ref *inode_ref;
- bool dirty;
- size_t ea_size;
- size_t block_size_rem;
- size_t inode_size_rem;
- struct ext4_fs *fs;
+struct ext4_xattr_search {
+ /* The first entry in the buffer */
+ struct ext4_xattr_entry *first;
- void *iter_arg;
- struct ext4_xattr_item *iter_from;
+ /* The address of the buffer */
+ void *base;
- RB_HEAD(ext4_xattr_tree,
- ext4_xattr_item) root;
+ /* The first inaccessible address */
+ void *end;
+
+ /* The current entry pointer */
+ struct ext4_xattr_entry *here;
+
+ /* Entry not found */
+ bool not_found;
};
-#define EXT4_XATTR_PAD_BITS 2
-#define EXT4_XATTR_PAD (1<<EXT4_XATTR_PAD_BITS)
-#define EXT4_XATTR_ROUND (EXT4_XATTR_PAD-1)
-#define EXT4_XATTR_LEN(name_len) \
- (((name_len) + EXT4_XATTR_ROUND + \
- sizeof(struct ext4_xattr_entry)) & ~EXT4_XATTR_ROUND)
-#define EXT4_XATTR_NEXT(entry) \
- ((struct ext4_xattr_entry *)( \
- (char *)(entry) + EXT4_XATTR_LEN((entry)->e_name_len)))
-#define EXT4_XATTR_SIZE(size) \
- (((size) + EXT4_XATTR_ROUND) & ~EXT4_XATTR_ROUND)
-#define EXT4_XATTR_NAME(entry) \
- ((char *)((entry) + 1))
+#define EXT4_XATTR_PAD_BITS 2
+#define EXT4_XATTR_PAD (1 << EXT4_XATTR_PAD_BITS)
+#define EXT4_XATTR_ROUND (EXT4_XATTR_PAD - 1)
+#define EXT4_XATTR_LEN(name_len) \
+ (((name_len) + EXT4_XATTR_ROUND + sizeof(struct ext4_xattr_entry)) & \
+ ~EXT4_XATTR_ROUND)
+#define EXT4_XATTR_NEXT(entry) \
+ ((struct ext4_xattr_entry *)((char *)(entry) + \
+ EXT4_XATTR_LEN((entry)->e_name_len)))
+#define EXT4_XATTR_SIZE(size) (((size) + EXT4_XATTR_ROUND) & ~EXT4_XATTR_ROUND)
+#define EXT4_XATTR_NAME(entry) ((char *)((entry) + 1))
-#define EXT4_XATTR_IHDR(sb, raw_inode) \
- ((struct ext4_xattr_ibody_header *) \
- ((char *)raw_inode + \
- EXT4_GOOD_OLD_INODE_SIZE + \
- ext4_inode_get_extra_isize(sb, raw_inode)))
-#define EXT4_XATTR_IFIRST(hdr) \
- ((struct ext4_xattr_entry *)((hdr)+1))
+#define EXT4_XATTR_IHDR(sb, raw_inode) \
+ ((struct ext4_xattr_ibody_header *)((char *)raw_inode + \
+ EXT4_GOOD_OLD_INODE_SIZE + \
+ ext4_inode_get_extra_isize( \
+ sb, raw_inode)))
+#define EXT4_XATTR_IFIRST(hdr) ((struct ext4_xattr_entry *)((hdr) + 1))
-#define EXT4_XATTR_BHDR(block) \
- ((struct ext4_xattr_header *)((block)->data))
-#define EXT4_XATTR_ENTRY(ptr) \
- ((struct ext4_xattr_entry *)(ptr))
-#define EXT4_XATTR_BFIRST(block) \
- EXT4_XATTR_ENTRY(EXT4_XATTR_BHDR(block)+1)
-#define EXT4_XATTR_IS_LAST_ENTRY(entry) \
- (*(uint32_t *)(entry) == 0)
+#define EXT4_XATTR_BHDR(block) ((struct ext4_xattr_header *)((block)->data))
+#define EXT4_XATTR_ENTRY(ptr) ((struct ext4_xattr_entry *)(ptr))
+#define EXT4_XATTR_BFIRST(block) EXT4_XATTR_ENTRY(EXT4_XATTR_BHDR(block) + 1)
+#define EXT4_XATTR_IS_LAST_ENTRY(entry) (*(uint32_t *)(entry) == 0)
#define EXT4_ZERO_XATTR_VALUE ((void *)-1)
+const char *ext4_extract_xattr_name(const char *full_name, size_t full_name_len,
+ uint8_t *name_index, size_t *name_len,
+ bool *found);
-#define EXT4_XATTR_ITERATE_CONT 0
-#define EXT4_XATTR_ITERATE_STOP 1
-#define EXT4_XATTR_ITERATE_PAUSE 2
+const char *ext4_get_xattr_name_prefix(uint8_t name_index,
+ size_t *ret_prefix_len);
-int ext4_fs_get_xattr_ref(struct ext4_fs *fs, struct ext4_inode_ref *inode_ref,
- struct ext4_xattr_ref *ref);
+int ext4_xattr_list(struct ext4_inode_ref *inode_ref,
+ struct ext4_xattr_list_entry *list, size_t *list_len);
-void ext4_fs_put_xattr_ref(struct ext4_xattr_ref *ref);
+int ext4_xattr_get(struct ext4_inode_ref *inode_ref, uint8_t name_index,
+ const char *name, size_t name_len, void *buf, size_t buf_len,
+ size_t *data_len);
-int ext4_fs_set_xattr(struct ext4_xattr_ref *ref, uint8_t name_index,
- const char *name, size_t name_len, const void *data,
- size_t data_size, bool replace);
+int ext4_xattr_remove(struct ext4_inode_ref *inode_ref, uint8_t name_index,
+ const char *name, size_t name_len);
-int ext4_fs_remove_xattr(struct ext4_xattr_ref *ref, uint8_t name_index,
- const char *name, size_t name_len);
-
-int ext4_fs_get_xattr(struct ext4_xattr_ref *ref, uint8_t name_index,
- const char *name, size_t name_len, void *buf,
- size_t buf_size, size_t *data_size);
-
-void ext4_fs_xattr_iterate(struct ext4_xattr_ref *ref,
- int (*iter)(struct ext4_xattr_ref *ref,
- struct ext4_xattr_item *item));
-
-void ext4_fs_xattr_iterate_reset(struct ext4_xattr_ref *ref);
-
-const char *ext4_extract_xattr_name(const char *full_name, size_t full_name_len,
- uint8_t *name_index, size_t *name_len,
- bool *found);
-
-const char *ext4_get_xattr_name_prefix(uint8_t name_index,
- size_t *ret_prefix_len);
+int ext4_xattr_set(struct ext4_inode_ref *inode_ref, uint8_t name_index,
+ const char *name, size_t name_len, const void *value,
+ size_t value_len);
#ifdef __cplusplus
}
--- a/src/ext4.c
+++ b/src/ext4.c
@@ -2423,7 +2423,6 @@
uint8_t name_index;
const char *dissected_name = NULL;
size_t dissected_len = 0;
- struct ext4_xattr_ref xattr_ref;
struct ext4_inode_ref inode_ref;
struct ext4_mountpoint *mp = ext4_get_mount(path);
if (!mp)
@@ -2451,16 +2450,9 @@
if (r != EOK)
goto Finish;
- r = ext4_fs_get_xattr_ref(&mp->fs, &inode_ref, &xattr_ref);
- if (r != EOK) {
- ext4_fs_put_inode_ref(&inode_ref);
- goto Finish;
- }
+ r = ext4_xattr_set(&inode_ref, name_index, dissected_name,
+ dissected_len, data, data_size);
- r = ext4_fs_set_xattr(&xattr_ref, name_index, dissected_name,
- dissected_len, data, data_size, replace);
-
- ext4_fs_put_xattr_ref(&xattr_ref);
ext4_fs_put_inode_ref(&inode_ref);
Finish:
if (r != EOK)
@@ -2482,7 +2474,6 @@
uint8_t name_index;
const char *dissected_name = NULL;
size_t dissected_len = 0;
- struct ext4_xattr_ref xattr_ref;
struct ext4_inode_ref inode_ref;
struct ext4_mountpoint *mp = ext4_get_mount(path);
if (!mp)
@@ -2505,16 +2496,9 @@
if (r != EOK)
goto Finish;
- r = ext4_fs_get_xattr_ref(&mp->fs, &inode_ref, &xattr_ref);
- if (r != EOK) {
- ext4_fs_put_inode_ref(&inode_ref);
- goto Finish;
- }
-
- r = ext4_fs_get_xattr(&xattr_ref, name_index, dissected_name,
+ r = ext4_xattr_get(&inode_ref, name_index, dissected_name,
dissected_len, buf, buf_size, data_size);
- ext4_fs_put_xattr_ref(&xattr_ref);
ext4_fs_put_inode_ref(&inode_ref);
Finish:
EXT4_MP_UNLOCK(mp);
@@ -2521,60 +2505,19 @@
return r;
}
-struct ext4_listxattr_iterator {
- char *list;
- char *list_ptr;
- size_t size;
- size_t ret_size;
- bool list_too_small;
- bool get_required_size;
-};
-
-static int ext4_iterate_ea_list(struct ext4_xattr_ref *ref,
- struct ext4_xattr_item *item)
-{
- struct ext4_listxattr_iterator *lxi;
- lxi = ref->iter_arg;
- if (!lxi->get_required_size) {
- size_t plen;
- const char *prefix;
- prefix = ext4_get_xattr_name_prefix(item->name_index, &plen);
- if (lxi->ret_size + plen + item->name_len + 1 > lxi->size) {
- lxi->list_too_small = 1;
- return EXT4_XATTR_ITERATE_STOP;
- }
- if (prefix) {
- memcpy(lxi->list_ptr, prefix, plen);
- lxi->list_ptr += plen;
- lxi->ret_size += plen;
- }
- memcpy(lxi->list_ptr, item->name, item->name_len);
- lxi->list_ptr[item->name_len] = 0;
- lxi->list_ptr += item->name_len + 1;
- }
- lxi->ret_size += item->name_len + 1;
- return EXT4_XATTR_ITERATE_CONT;
-}
-
int ext4_listxattr(const char *path, char *list, size_t size, size_t *ret_size)
{
int r = EOK;
ext4_file f;
uint32_t inode;
- struct ext4_xattr_ref xattr_ref;
+ size_t list_len, list_size = 0;
struct ext4_inode_ref inode_ref;
- struct ext4_listxattr_iterator lxi;
+ struct ext4_xattr_list_entry *xattr_list = NULL,
+ *entry = NULL;
struct ext4_mountpoint *mp = ext4_get_mount(path);
if (!mp)
return ENOENT;
- lxi.list = list;
- lxi.list_ptr = list;
- lxi.size = size;
- lxi.ret_size = 0;
- lxi.list_too_small = false;
- lxi.get_required_size = (!size) ? true : false;
-
EXT4_MP_LOCK(mp);
r = ext4_generic_open2(&f, path, O_RDWR, EXT4_DE_UNKNOWN, NULL, NULL);
if (r != EOK)
@@ -2586,26 +2529,57 @@
if (r != EOK)
goto Finish;
- r = ext4_fs_get_xattr_ref(&mp->fs, &inode_ref, &xattr_ref);
- if (r != EOK) {
- ext4_fs_put_inode_ref(&inode_ref);
- goto Finish;
- }
+ r = ext4_xattr_list(&inode_ref, NULL, &list_len);
+ if (r == EOK && list_len) {
+ xattr_list = malloc(list_len);
+ if (!xattr_list) {
+ ext4_fs_put_inode_ref(&inode_ref);
+ r = ENOMEM;
+ goto Finish;
+ }
+ entry = xattr_list;
+ r = ext4_xattr_list(&inode_ref, entry, &list_len);
+ if (r != EOK) {
+ ext4_fs_put_inode_ref(&inode_ref);
+ goto Finish;
+ }
- xattr_ref.iter_arg = &lxi;
- ext4_fs_xattr_iterate(&xattr_ref, ext4_iterate_ea_list);
- if (lxi.list_too_small)
- r = ERANGE;
+ for (;entry;entry = entry->next) {
+ size_t prefix_len;
+ const char *prefix =
+ ext4_get_xattr_name_prefix(entry->name_index,
+ &prefix_len);
+ if (size) {
+ if (prefix_len + entry->name_len + 1 > size) {
+ ext4_fs_put_inode_ref(&inode_ref);
+ r = ERANGE;
+ goto Finish;
+ }
+ }
- if (r == EOK) {
+ if (list && size) {
+ memcpy(list, prefix, prefix_len);
+ list += prefix_len;
+ memcpy(list, entry->name,
+ entry->name_len);
+ list[entry->name_len] = 0;
+ list += entry->name_len + 1;
+
+ size -= prefix_len + entry->name_len + 1;
+ }
+
+ list_size += prefix_len + entry->name_len + 1;
+ }
if (ret_size)
- *ret_size = lxi.ret_size;
+ *ret_size = list_size;
}
- ext4_fs_put_xattr_ref(&xattr_ref);
ext4_fs_put_inode_ref(&inode_ref);
Finish:
EXT4_MP_UNLOCK(mp);
+ if (xattr_list)
+ free(xattr_list);
+
return r;
}
@@ -2619,7 +2593,6 @@
uint8_t name_index;
const char *dissected_name = NULL;
size_t dissected_len = 0;
- struct ext4_xattr_ref xattr_ref;
struct ext4_inode_ref inode_ref;
struct ext4_mountpoint *mp = ext4_get_mount(path);
if (!mp)
@@ -2647,16 +2620,9 @@
if (r != EOK)
goto Finish;
- r = ext4_fs_get_xattr_ref(&mp->fs, &inode_ref, &xattr_ref);
- if (r != EOK) {
- ext4_fs_put_inode_ref(&inode_ref);
- goto Finish;
- }
+ r = ext4_xattr_remove(&inode_ref, name_index, dissected_name,
+ dissected_len);
- r = ext4_fs_remove_xattr(&xattr_ref, name_index, dissected_name,
- dissected_len);
-
- ext4_fs_put_xattr_ref(&xattr_ref);
ext4_fs_put_inode_ref(&inode_ref);
Finish:
if (r != EOK)
--- a/src/ext4_xattr.c
+++ b/src/ext4_xattr.c
@@ -35,23 +35,23 @@
*/
#include "ext4_config.h"
-#include "ext4_types.h"
-#include "ext4_misc.h"
-#include "ext4_errno.h"
#include "ext4_debug.h"
+#include "ext4_errno.h"
+#include "ext4_misc.h"
+#include "ext4_types.h"
-#include "ext4_fs.h"
-#include "ext4_trans.h"
-#include "ext4_xattr.h"
+#include "ext4_balloc.h"
+#include "ext4_block_group.h"
#include "ext4_blockdev.h"
-#include "ext4_super.h"
#include "ext4_crc32.h"
-#include "ext4_block_group.h"
-#include "ext4_balloc.h"
+#include "ext4_fs.h"
#include "ext4_inode.h"
+#include "ext4_super.h"
+#include "ext4_trans.h"
+#include "ext4_xattr.h"
-#include <string.h>
#include <stdlib.h>
+#include <string.h>
/**
* @file ext4_xattr.c
@@ -117,10 +117,9 @@
}
#if CONFIG_META_CSUM_ENABLE
-static uint32_t
-ext4_xattr_block_checksum(struct ext4_inode_ref *inode_ref,
- ext4_fsblk_t blocknr,
- struct ext4_xattr_header *header)
+static uint32_t ext4_xattr_block_checksum(struct ext4_inode_ref *inode_ref,
+ ext4_fsblk_t blocknr,
+ struct ext4_xattr_header *header)
{
uint32_t checksum = 0;
uint64_t le64_blocknr = blocknr;
@@ -133,15 +132,15 @@
orig_checksum = header->h_checksum;
header->h_checksum = 0;
/* First calculate crc32 checksum against fs uuid */
- checksum = ext4_crc32c(EXT4_CRC32_INIT, sb->uuid,
- sizeof(sb->uuid));
+ checksum =
+ ext4_crc32c(EXT4_CRC32_INIT, sb->uuid, sizeof(sb->uuid));
/* Then calculate crc32 checksum block number */
- checksum = ext4_crc32c(checksum, &le64_blocknr,
- sizeof(le64_blocknr));
- /* Finally calculate crc32 checksum against
+ checksum =
+ ext4_crc32c(checksum, &le64_blocknr, sizeof(le64_blocknr));
+ /* Finally calculate crc32 checksum against
* the entire xattr block */
- checksum = ext4_crc32c(checksum, header,
- ext4_sb_get_block_size(sb));
+ checksum =
+ ext4_crc32c(checksum, header, ext4_sb_get_block_size(sb));
header->h_checksum = orig_checksum;
}
return checksum;
@@ -150,10 +149,9 @@
#define ext4_xattr_block_checksum(...) 0
#endif
-static void
-ext4_xattr_set_block_checksum(struct ext4_inode_ref *inode_ref,
- ext4_fsblk_t blocknr __unused,
- struct ext4_xattr_header *header)
+static void ext4_xattr_set_block_checksum(struct ext4_inode_ref *inode_ref,
+ ext4_fsblk_t blocknr __unused,
+ struct ext4_xattr_header *header)
{
struct ext4_sblock *sb = &inode_ref->fs->sb;
if (!ext4_sb_feature_ro_com(sb, EXT4_FRO_COM_METADATA_CSUM))
@@ -160,518 +158,598 @@
return;
header->h_checksum =
- ext4_xattr_block_checksum(inode_ref, blocknr, header);
+ ext4_xattr_block_checksum(inode_ref, blocknr, header);
}
-static int ext4_xattr_item_cmp(struct ext4_xattr_item *a,
- struct ext4_xattr_item *b)
-{
- int result;
- if (a->is_data && !b->is_data)
- return -1;
-
- if (!a->is_data && b->is_data)
- return 1;
+struct xattr_prefix {
+ const char *prefix;
+ uint8_t name_index;
+};
- result = a->name_index - b->name_index;
- if (result)
- return result;
+static const struct xattr_prefix prefix_tbl[] = {
+ {"user.", EXT4_XATTR_INDEX_USER},
+ {"system.posix_acl_access", EXT4_XATTR_INDEX_POSIX_ACL_ACCESS},
+ {"system.posix_acl_default", EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT},
+ {"trusted.", EXT4_XATTR_INDEX_TRUSTED},
+ {"security.", EXT4_XATTR_INDEX_SECURITY},
+ {"system.", EXT4_XATTR_INDEX_SYSTEM},
+ {"system.richacl", EXT4_XATTR_INDEX_RICHACL},
+ {NULL, 0},
+};
- result = a->name_len - b->name_len;
- if (result)
- return result;
+const char *ext4_extract_xattr_name(const char *full_name, size_t full_name_len,
+ uint8_t *name_index, size_t *name_len,
+ bool *found)
+{
+ int i;
+ ext4_assert(name_index);
+ ext4_assert(found);
- return memcmp(a->name, b->name, a->name_len);
-}
+ *found = false;
-RB_GENERATE_INTERNAL(ext4_xattr_tree, ext4_xattr_item, node,
- ext4_xattr_item_cmp, static inline)
+ if (!full_name_len) {
+ if (name_len)
+ *name_len = 0;
-static struct ext4_xattr_item *
-ext4_xattr_item_alloc(uint8_t name_index, const char *name, size_t name_len)
-{
- struct ext4_xattr_item *item;
- item = malloc(sizeof(struct ext4_xattr_item) + name_len);
- if (!item)
return NULL;
+ }
- item->name_index = name_index;
- item->name = (char *)(item + 1);
- item->name_len = name_len;
- item->data = NULL;
- item->data_size = 0;
- item->in_inode = false;
+ for (i = 0; prefix_tbl[i].prefix; i++) {
+ size_t prefix_len = strlen(prefix_tbl[i].prefix);
+ if (full_name_len >= prefix_len &&
+ !memcmp(full_name, prefix_tbl[i].prefix, prefix_len)) {
+ bool require_name =
+ prefix_tbl[i].prefix[prefix_len - 1] == '.';
+ *name_index = prefix_tbl[i].name_index;
+ if (name_len)
+ *name_len = full_name_len - prefix_len;
- memset(&item->node, 0, sizeof(item->node));
- memcpy(item->name, name, name_len);
+ if (!(full_name_len - prefix_len) && require_name)
+ return NULL;
- if (name_index == EXT4_XATTR_INDEX_SYSTEM &&
- name_len == 4 &&
- !memcmp(name, "data", 4))
- item->is_data = true;
- else
- item->is_data = false;
+ *found = true;
+ if (require_name)
+ return full_name + prefix_len;
- return item;
-}
+ return NULL;
+ }
+ }
+ if (name_len)
+ *name_len = 0;
-static int ext4_xattr_item_alloc_data(struct ext4_xattr_item *item,
- const void *orig_data, size_t data_size)
-{
- void *data = NULL;
- ext4_assert(!item->data);
- data = malloc(data_size);
- if (!data)
- return ENOMEM;
-
- if (orig_data)
- memcpy(data, orig_data, data_size);
-
- item->data = data;
- item->data_size = data_size;
- return EOK;
+ return NULL;
}
-static void ext4_xattr_item_free_data(struct ext4_xattr_item *item)
+const char *ext4_get_xattr_name_prefix(uint8_t name_index,
+ size_t *ret_prefix_len)
{
- ext4_assert(item->data);
- free(item->data);
- item->data = NULL;
- item->data_size = 0;
-}
+ int i;
-static int ext4_xattr_item_resize_data(struct ext4_xattr_item *item,
- size_t new_data_size)
-{
- if (new_data_size != item->data_size) {
- void *new_data;
- new_data = realloc(item->data, new_data_size);
- if (!new_data)
- return ENOMEM;
+ for (i = 0; prefix_tbl[i].prefix; i++) {
+ size_t prefix_len = strlen(prefix_tbl[i].prefix);
+ if (prefix_tbl[i].name_index == name_index) {
+ if (ret_prefix_len)
+ *ret_prefix_len = prefix_len;
- item->data = new_data;
- item->data_size = new_data_size;
+ return prefix_tbl[i].prefix;
+ }
}
- return EOK;
-}
+ if (ret_prefix_len)
+ *ret_prefix_len = 0;
-static void ext4_xattr_item_free(struct ext4_xattr_item *item)
-{
- if (item->data)
- ext4_xattr_item_free_data(item);
-
- free(item);
+ return NULL;
}
-static void *ext4_xattr_entry_data(struct ext4_xattr_ref *xattr_ref,
- struct ext4_xattr_entry *entry,
- bool in_inode)
+static const char ext4_xattr_empty_value;
+
+/**
+ * @brief Insert/Remove/Modify the given entry
+ *
+ * @param i The information of the given EA entry
+ * @param s Search context block
+ * @param dry_run Do not modify the content of the buffer
+ *
+ * @return Return EOK when finished, ENOSPC when there is no enough space
+ */
+static int ext4_xattr_set_entry(struct ext4_xattr_info *i,
+ struct ext4_xattr_search *s, bool dry_run)
{
- char *ret;
- if (in_inode) {
- struct ext4_xattr_ibody_header *header;
- struct ext4_xattr_entry *first_entry;
- int16_t inode_size =
- ext4_get16(&xattr_ref->fs->sb, inode_size);
- header = EXT4_XATTR_IHDR(&xattr_ref->fs->sb,
- xattr_ref->inode_ref->inode);
- first_entry = EXT4_XATTR_IFIRST(header);
+ struct ext4_xattr_entry *last;
+ size_t free, min_offs = (char *)s->end - (char *)s->base,
+ name_len = i->name_len;
- ret = ((char *)first_entry + to_le16(entry->e_value_offs));
- if (ret + EXT4_XATTR_SIZE(to_le32(entry->e_value_size)) -
- (char *)xattr_ref->inode_ref->inode > inode_size)
- ret = NULL;
+ /*
+ * If the entry is going to be removed but not found, return 0 to
+ * indicate success.
+ */
+ if (!i->value && s->not_found)
+ return EOK;
- return ret;
+ /* Compute min_offs and last. */
+ last = s->first;
+ for (; !EXT4_XATTR_IS_LAST_ENTRY(last); last = EXT4_XATTR_NEXT(last)) {
+ if (last->e_value_size) {
+ size_t offs = to_le16(last->e_value_offs);
+ if (offs < min_offs)
+ min_offs = offs;
+ }
+ }
+ /* Calculate free space in the block. */
+ free = min_offs - ((char *)last - (char *)s->base) - sizeof(uint32_t);
+ if (!s->not_found)
+ free += EXT4_XATTR_SIZE(s->here->e_value_size) +
+ EXT4_XATTR_LEN(s->here->e_name_len);
+
+ if (i->value) {
+ /* See whether there is enough space to hold new entry */
+ if (free <
+ EXT4_XATTR_SIZE(i->value_len) + EXT4_XATTR_LEN(name_len))
+ return ENOSPC;
}
- int32_t block_size = ext4_sb_get_block_size(&xattr_ref->fs->sb);
- ret = ((char *)xattr_ref->block.data + to_le16(entry->e_value_offs));
- if (ret + EXT4_XATTR_SIZE(to_le32(entry->e_value_size)) -
- (char *)xattr_ref->block.data > block_size)
- ret = NULL;
- return ret;
-}
-static int ext4_xattr_block_fetch(struct ext4_xattr_ref *xattr_ref)
-{
- int ret = EOK;
- size_t size_rem;
- void *data;
- struct ext4_xattr_entry *entry = NULL;
+ /* Return EOK now if we do not intend to modify the content. */
+ if (dry_run)
+ return EOK;
- ext4_assert(xattr_ref->block.data);
- entry = EXT4_XATTR_BFIRST(&xattr_ref->block);
+ /* First remove the old entry's data part */
+ if (!s->not_found) {
+ size_t value_offs = to_le16(s->here->e_value_offs);
+ void *value = (char *)s->base + value_offs;
+ void *first_value = (char *)s->base + min_offs;
+ size_t value_size =
+ EXT4_XATTR_SIZE(to_le32(s->here->e_value_size));
- size_rem = ext4_sb_get_block_size(&xattr_ref->fs->sb);
- for (; size_rem > 0 && !EXT4_XATTR_IS_LAST_ENTRY(entry);
- entry = EXT4_XATTR_NEXT(entry),
- size_rem -= EXT4_XATTR_LEN(entry->e_name_len)) {
- struct ext4_xattr_item *item;
- char *e_name = EXT4_XATTR_NAME(entry);
+ if (value_offs) {
+ /* Remove the data part. */
+ memmove((char *)first_value + value_size, first_value,
+ (char *)value - (char *)first_value);
- data = ext4_xattr_entry_data(xattr_ref, entry, false);
- if (!data) {
- ret = EIO;
- goto Finish;
+ /* Zero the gap created */
+ memset(first_value, 0, value_size);
+
+ /*
+ * Calculate the new min_offs after removal of the old
+ * entry's data part
+ */
+ min_offs += value_size;
}
- item = ext4_xattr_item_alloc(entry->e_name_index, e_name,
- (size_t)entry->e_name_len);
- if (!item) {
- ret = ENOMEM;
- goto Finish;
+ /*
+ * Adjust the value offset of entries which has value offset
+ * prior to the s->here. The offset of these entries won't be
+ * shifted if the size of the entry we removed is zero.
+ */
+ for (last = s->first; !EXT4_XATTR_IS_LAST_ENTRY(last);
+ last = EXT4_XATTR_NEXT(last)) {
+ size_t offs = to_le16(last->e_value_offs);
+
+ /* For zero-value-length entry, offs will be zero. */
+ if (offs < value_offs)
+ last->e_value_offs = to_le16(offs + value_size);
}
- if (ext4_xattr_item_alloc_data(
- item, data, to_le32(entry->e_value_size)) != EOK) {
- ext4_xattr_item_free(item);
- ret = ENOMEM;
- goto Finish;
- }
- RB_INSERT(ext4_xattr_tree, &xattr_ref->root, item);
- xattr_ref->block_size_rem -=
- EXT4_XATTR_SIZE(item->data_size) +
- EXT4_XATTR_LEN(item->name_len);
- xattr_ref->ea_size += EXT4_XATTR_SIZE(item->data_size) +
- EXT4_XATTR_LEN(item->name_len);
}
-Finish:
- return ret;
-}
+ /* If caller wants us to insert... */
+ if (i->value) {
+ size_t value_offs;
+ if (i->value_len)
+ value_offs = min_offs - EXT4_XATTR_SIZE(i->value_len);
+ else
+ value_offs = 0;
-static int ext4_xattr_inode_fetch(struct ext4_xattr_ref *xattr_ref)
-{
- void *data;
- size_t size_rem;
- int ret = EOK;
- struct ext4_xattr_ibody_header *header = NULL;
- struct ext4_xattr_entry *entry = NULL;
- uint16_t inode_size = ext4_get16(&xattr_ref->fs->sb, inode_size);
- uint16_t extra_isize = ext4_inode_get_extra_isize(&xattr_ref->fs->sb,
- xattr_ref->inode_ref->inode);
+ if (!s->not_found) {
+ struct ext4_xattr_entry *here = s->here;
- header = EXT4_XATTR_IHDR(&xattr_ref->fs->sb,
- xattr_ref->inode_ref->inode);
- entry = EXT4_XATTR_IFIRST(header);
+ /* Reuse the current entry we have got */
+ here->e_value_offs = to_le16(value_offs);
+ here->e_value_size = to_le32(i->value_len);
+ } else {
+ /* Insert a new entry */
+ last->e_name_len = (uint8_t)name_len;
+ last->e_name_index = i->name_index;
+ last->e_value_offs = to_le16(value_offs);
+ last->e_value_block = 0;
+ last->e_value_size = to_le32(i->value_len);
+ memcpy(EXT4_XATTR_NAME(last), i->name, name_len);
- size_rem = inode_size - EXT4_GOOD_OLD_INODE_SIZE -
- extra_isize;
- for (; size_rem > 0 && !EXT4_XATTR_IS_LAST_ENTRY(entry);
- entry = EXT4_XATTR_NEXT(entry),
- size_rem -= EXT4_XATTR_LEN(entry->e_name_len)) {
- struct ext4_xattr_item *item;
- char *e_name = EXT4_XATTR_NAME(entry);
+ /* Set valid last entry indicator */
+ *(uint32_t *)EXT4_XATTR_NEXT(last) = 0;
- data = ext4_xattr_entry_data(xattr_ref, entry, true);
- if (!data) {
- ret = EIO;
- goto Finish;
+ s->here = last;
}
- item = ext4_xattr_item_alloc(entry->e_name_index, e_name,
- (size_t)entry->e_name_len);
- if (!item) {
- ret = ENOMEM;
- goto Finish;
+ /* Insert the value's part */
+ if (value_offs) {
+ memcpy((char *)s->base + value_offs, i->value,
+ i->value_len);
+
+ /* Clear the padding bytes if there is */
+ if (EXT4_XATTR_SIZE(i->value_len) != i->value_len)
+ memset((char *)s->base + value_offs +
+ i->value_len,
+ 0, EXT4_XATTR_SIZE(i->value_len) -
+ i->value_len);
}
- if (ext4_xattr_item_alloc_data(
- item, data, to_le32(entry->e_value_size)) != EOK) {
- ext4_xattr_item_free(item);
- ret = ENOMEM;
- goto Finish;
- }
- item->in_inode = true;
- RB_INSERT(ext4_xattr_tree, &xattr_ref->root, item);
- xattr_ref->inode_size_rem -=
- EXT4_XATTR_SIZE(item->data_size) +
- EXT4_XATTR_LEN(item->name_len);
- xattr_ref->ea_size += EXT4_XATTR_SIZE(item->data_size) +
- EXT4_XATTR_LEN(item->name_len);
+ } else {
+ size_t shift_offs;
+
+ /* Remove the whole entry */
+ shift_offs = (char *)EXT4_XATTR_NEXT(s->here) - (char *)s->here;
+ memmove(s->here, EXT4_XATTR_NEXT(s->here),
+ (char *)last + sizeof(uint32_t) -
+ (char *)EXT4_XATTR_NEXT(s->here));
+
+ /* Zero the gap created */
+ memset((char *)last - shift_offs + sizeof(uint32_t), 0,
+ shift_offs);
+
+ s->here = NULL;
}
-Finish:
- return ret;
+ return EOK;
}
-static size_t ext4_xattr_inode_space(struct ext4_xattr_ref *xattr_ref)
+static inline bool ext4_xattr_is_empty(struct ext4_xattr_search *s)
{
- uint16_t inode_size = ext4_get16(&xattr_ref->fs->sb, inode_size);
- uint16_t extra_isize = ext4_inode_get_extra_isize(&xattr_ref->fs->sb,
- xattr_ref->inode_ref->inode);
- uint16_t size_rem = inode_size - EXT4_GOOD_OLD_INODE_SIZE -
- extra_isize;
- return size_rem;
-}
+ if (!EXT4_XATTR_IS_LAST_ENTRY(s->first))
+ return false;
-static size_t ext4_xattr_block_space(struct ext4_xattr_ref *xattr_ref)
-{
- return ext4_sb_get_block_size(&xattr_ref->fs->sb);
+ return true;
}
-static int ext4_xattr_fetch(struct ext4_xattr_ref *xattr_ref)
+/**
+ * @brief Find the entry according to given information
+ *
+ * @param i The information of the EA entry to be found,
+ * including name_index, name and the length of name
+ * @param s Search context block
+ */
+static void ext4_xattr_find_entry(struct ext4_xattr_info *i,
+ struct ext4_xattr_search *s)
{
- int ret = EOK;
- uint16_t inode_size = ext4_get16(&xattr_ref->fs->sb, inode_size);
- if (inode_size > EXT4_GOOD_OLD_INODE_SIZE) {
- ret = ext4_xattr_inode_fetch(xattr_ref);
- if (ret != EOK)
- return ret;
- }
+ struct ext4_xattr_entry *entry = NULL;
- if (xattr_ref->block_loaded)
- ret = ext4_xattr_block_fetch(xattr_ref);
+ s->not_found = true;
+ s->here = NULL;
- xattr_ref->dirty = false;
- return ret;
+ /*
+ * Find the wanted EA entry by simply comparing the namespace,
+ * name and the length of name.
+ */
+ for (entry = s->first; !EXT4_XATTR_IS_LAST_ENTRY(entry);
+ entry = EXT4_XATTR_NEXT(entry)) {
+ size_t name_len = entry->e_name_len;
+ const char *name = EXT4_XATTR_NAME(entry);
+ if (name_len == i->name_len &&
+ entry->e_name_index == i->name_index &&
+ !memcmp(name, i->name, name_len)) {
+ s->here = entry;
+ s->not_found = false;
+ i->value_len = to_le32(entry->e_value_size);
+ if (i->value_len)
+ i->value = (char *)s->base +
+ to_le16(entry->e_value_offs);
+ else
+ i->value = NULL;
+
+ return;
+ }
+ }
}
-static struct ext4_xattr_item *
-ext4_xattr_lookup_item(struct ext4_xattr_ref *xattr_ref, uint8_t name_index,
- const char *name, size_t name_len)
+/**
+ * @brief Check whether the xattr block's content is valid
+ *
+ * @param inode_ref Inode reference
+ * @param block The block buffer to be validated
+ *
+ * @return true if @block is valid, false otherwise.
+ */
+static bool ext4_xattr_is_block_valid(struct ext4_inode_ref *inode_ref,
+ struct ext4_block *block)
{
- struct ext4_xattr_item tmp = {
- .name_index = name_index,
- .name = (char *)name, /*RB_FIND - won't touch this string*/
- .name_len = name_len,
- };
- if (name_index == EXT4_XATTR_INDEX_SYSTEM &&
- name_len == 4 &&
- !memcmp(name, "data", 4))
- tmp.is_data = true;
- return RB_FIND(ext4_xattr_tree, &xattr_ref->root, &tmp);
+ void *base = block->data,
+ *end = block->data + ext4_sb_get_block_size(&inode_ref->fs->sb);
+ size_t min_offs = (char *)end - (char *)base;
+ struct ext4_xattr_header *header = EXT4_XATTR_BHDR(block);
+ struct ext4_xattr_entry *entry = EXT4_XATTR_BFIRST(block);
+
+ /*
+ * Check whether the magic number in the header is correct.
+ */
+ if (header->h_magic != to_le32(EXT4_XATTR_MAGIC))
+ return false;
+
+ /*
+ * The in-kernel filesystem driver only supports 1 block currently.
+ */
+ if (header->h_blocks != to_le32(1))
+ return false;
+
+ /*
+ * Check if those entries are maliciously corrupted to inflict harm
+ * upon us.
+ */
+ for (; !EXT4_XATTR_IS_LAST_ENTRY(entry);
+ entry = EXT4_XATTR_NEXT(entry)) {
+ if (!to_le32(entry->e_value_size) &&
+ to_le16(entry->e_value_offs))
+ return false;
+
+ if ((char *)base + to_le16(entry->e_value_offs) +
+ to_le32(entry->e_value_size) >
+ (char *)end)
+ return false;
+
+ /*
+ * The name length field should also be correct,
+ * also there should be an 4-byte zero entry at the
+ * end.
+ */
+ if ((char *)EXT4_XATTR_NEXT(entry) + sizeof(uint32_t) >
+ (char *)end)
+ return false;
+
+ if (to_le32(entry->e_value_size)) {
+ size_t offs = to_le16(entry->e_value_offs);
+ if (offs < min_offs)
+ min_offs = offs;
+ }
+ }
+ /*
+ * Entry field and data field do not override each other.
+ */
+ if ((char *)base + min_offs < (char *)entry + sizeof(uint32_t))
+ return false;
+
+ return true;
}
-static struct ext4_xattr_item *
-ext4_xattr_insert_item(struct ext4_xattr_ref *xattr_ref, uint8_t name_index,
- const char *name, size_t name_len, const void *data,
- size_t data_size,
- int *err)
+/**
+ * @brief Check whether the inode buffer's content is valid
+ *
+ * @param inode_ref Inode reference
+ *
+ * @return true if the inode buffer is valid, false otherwise.
+ */
+static bool ext4_xattr_is_ibody_valid(struct ext4_inode_ref *inode_ref)
{
- struct ext4_xattr_item *item;
- item = ext4_xattr_item_alloc(name_index, name, name_len);
- if (!item) {
- if (err)
- *err = ENOMEM;
+ size_t min_offs;
+ void *base, *end;
+ struct ext4_fs *fs = inode_ref->fs;
+ struct ext4_xattr_ibody_header *iheader;
+ struct ext4_xattr_entry *entry;
+ size_t inode_size = ext4_get16(&fs->sb, inode_size);
- return NULL;
- }
+ iheader = EXT4_XATTR_IHDR(&fs->sb, inode_ref->inode);
+ entry = EXT4_XATTR_IFIRST(iheader);
+ base = iheader;
+ end = (char *)inode_ref->inode + inode_size;
+ min_offs = (char *)end - (char *)base;
- item->in_inode = true;
- if (xattr_ref->inode_size_rem <
- EXT4_XATTR_SIZE(data_size) +
- EXT4_XATTR_LEN(item->name_len)) {
- if (xattr_ref->block_size_rem <
- EXT4_XATTR_SIZE(data_size) +
- EXT4_XATTR_LEN(item->name_len)) {
- if (err)
- *err = ENOSPC;
+ /*
+ * Check whether the magic number in the header is correct.
+ */
+ if (iheader->h_magic != to_le32(EXT4_XATTR_MAGIC))
+ return false;
- return NULL;
- }
+ /*
+ * Check if those entries are maliciously corrupted to inflict harm
+ * upon us.
+ */
+ for (; !EXT4_XATTR_IS_LAST_ENTRY(entry);
+ entry = EXT4_XATTR_NEXT(entry)) {
+ if (!to_le32(entry->e_value_size) &&
+ to_le16(entry->e_value_offs))
+ return false;
- item->in_inode = false;
- }
- if (ext4_xattr_item_alloc_data(item, data, data_size) != EOK) {
- ext4_xattr_item_free(item);
- if (err)
- *err = ENOMEM;
+ if ((char *)base + to_le16(entry->e_value_offs) +
+ to_le32(entry->e_value_size) >
+ (char *)end)
+ return false;
- return NULL;
+ /*
+ * The name length field should also be correct,
+ * also there should be an 4-byte zero entry at the
+ * end.
+ */
+ if ((char *)EXT4_XATTR_NEXT(entry) + sizeof(uint32_t) >
+ (char *)end)
+ return false;
+
+ if (to_le32(entry->e_value_size)) {
+ size_t offs = to_le16(entry->e_value_offs);
+ if (offs < min_offs)
+ min_offs = offs;
+ }
}
- RB_INSERT(ext4_xattr_tree, &xattr_ref->root, item);
- xattr_ref->ea_size +=
- EXT4_XATTR_SIZE(item->data_size) + EXT4_XATTR_LEN(item->name_len);
- if (item->in_inode) {
- xattr_ref->inode_size_rem -=
- EXT4_XATTR_SIZE(item->data_size) +
- EXT4_XATTR_LEN(item->name_len);
- } else {
- xattr_ref->block_size_rem -=
- EXT4_XATTR_SIZE(item->data_size) +
- EXT4_XATTR_LEN(item->name_len);
- }
- xattr_ref->dirty = true;
- if (err)
- *err = EOK;
+ /*
+ * Entry field and data field do not override each other.
+ */
+ if ((char *)base + min_offs < (char *)entry + sizeof(uint32_t))
+ return false;
- return item;
+ return true;
}
-static int ext4_xattr_remove_item(struct ext4_xattr_ref *xattr_ref,
- uint8_t name_index, const char *name,
- size_t name_len)
+/**
+ * @brief An EA entry finder for inode buffer
+ */
+struct ext4_xattr_finder {
+ /**
+ * @brief The information of the EA entry to be find
+ */
+ struct ext4_xattr_info i;
+
+ /**
+ * @brief Search context block of the current search
+ */
+ struct ext4_xattr_search s;
+
+ /**
+ * @brief Inode reference to the corresponding inode
+ */
+ struct ext4_inode_ref *inode_ref;
+};
+
+static void ext4_xattr_ibody_initialize(struct ext4_inode_ref *inode_ref)
{
- int ret = ENOENT;
- struct ext4_xattr_item *item =
- ext4_xattr_lookup_item(xattr_ref, name_index, name, name_len);
- if (item) {
- if (item == xattr_ref->iter_from)
- xattr_ref->iter_from =
- RB_NEXT(ext4_xattr_tree, &xattr_ref->root, item);
+ struct ext4_xattr_ibody_header *header;
+ struct ext4_fs *fs = inode_ref->fs;
+ size_t extra_isize =
+ ext4_inode_get_extra_isize(&fs->sb, inode_ref->inode);
+ size_t inode_size = ext4_get16(&fs->sb, inode_size);
+ if (!extra_isize)
+ return;
- xattr_ref->ea_size -= EXT4_XATTR_SIZE(item->data_size) +
- EXT4_XATTR_LEN(item->name_len);
+ header = EXT4_XATTR_IHDR(&fs->sb, inode_ref->inode);
+ memset(header, 0, inode_size - EXT4_GOOD_OLD_INODE_SIZE - extra_isize);
+ header->h_magic = to_le32(EXT4_XATTR_MAGIC);
+ inode_ref->dirty = true;
+}
- if (item->in_inode) {
- xattr_ref->inode_size_rem +=
- EXT4_XATTR_SIZE(item->data_size) +
- EXT4_XATTR_LEN(item->name_len);
- } else {
- xattr_ref->block_size_rem +=
- EXT4_XATTR_SIZE(item->data_size) +
- EXT4_XATTR_LEN(item->name_len);
- }
+/**
+ * @brief Initialize a given xattr block
+ *
+ * @param inode_ref Inode reference
+ * @param block xattr block buffer
+ */
+static void ext4_xattr_block_initialize(struct ext4_inode_ref *inode_ref,
+ struct ext4_block *block)
+{
+ struct ext4_xattr_header *header;
+ struct ext4_fs *fs = inode_ref->fs;
- RB_REMOVE(ext4_xattr_tree, &xattr_ref->root, item);
- ext4_xattr_item_free(item);
- xattr_ref->dirty = true;
- ret = EOK;
- }
- return ret;
+ memset(block->data, 0, ext4_sb_get_block_size(&fs->sb));
+
+ header = EXT4_XATTR_BHDR(block);
+ header->h_magic = to_le32(EXT4_XATTR_MAGIC);
+ header->h_refcount = to_le32(1);
+ header->h_blocks = to_le32(1);
+
+ ext4_trans_set_block_dirty(block->buf);
}
-static int ext4_xattr_resize_item(struct ext4_xattr_ref *xattr_ref,
- struct ext4_xattr_item *item,
- size_t new_data_size)
+static void ext4_xattr_block_init_search(struct ext4_inode_ref *inode_ref,
+ struct ext4_xattr_search *s,
+ struct ext4_block *block)
{
+ s->base = block->data;
+ s->end = block->data + ext4_sb_get_block_size(&inode_ref->fs->sb);
+ s->first = EXT4_XATTR_BFIRST(block);
+ s->here = NULL;
+ s->not_found = true;
+}
+
+/**
+ * @brief Find an EA entry inside a xattr block
+ *
+ * @param inode_ref Inode reference
+ * @param finder The caller-provided finder block with
+ * information filled
+ * @param block The block buffer to be looked into
+ *
+ * @return Return EOK no matter the entry is found or not.
+ * If the IO operation or the buffer validation failed,
+ * return other value.
+ */
+static int ext4_xattr_block_find_entry(struct ext4_inode_ref *inode_ref,
+ struct ext4_xattr_finder *finder,
+ struct ext4_block *block)
+{
int ret = EOK;
- bool to_inode = false, to_block = false;
- size_t old_data_size = item->data_size;
- size_t orig_room_size = item->in_inode ?
- xattr_ref->inode_size_rem :
- xattr_ref->block_size_rem;
- /*
- * Check if we can hold this entry in both in-inode and
- * on-block form.
- *
- * More complicated case: we do not allow entries stucking in
- * the middle between in-inode space and on-block space, so
- * the entry has to stay in either inode space or block space.
- */
- if (item->in_inode) {
- if (xattr_ref->inode_size_rem +
- EXT4_XATTR_SIZE(old_data_size) <
- EXT4_XATTR_SIZE(new_data_size)) {
- if (xattr_ref->block_size_rem <
- EXT4_XATTR_SIZE(new_data_size) +
- EXT4_XATTR_LEN(item->name_len))
- return ENOSPC;
+ /* Initialize the caller-given finder */
+ finder->inode_ref = inode_ref;
+ memset(&finder->s, 0, sizeof(finder->s));
- to_block = true;
- }
- } else {
- if (xattr_ref->block_size_rem +
- EXT4_XATTR_SIZE(old_data_size) <
- EXT4_XATTR_SIZE(new_data_size)) {
- if (xattr_ref->inode_size_rem <
- EXT4_XATTR_SIZE(new_data_size) +
- EXT4_XATTR_LEN(item->name_len))
- return ENOSPC;
-
- to_inode = true;
- }
- }
- ret = ext4_xattr_item_resize_data(item, new_data_size);
if (ret != EOK)
return ret;
- xattr_ref->ea_size =
- xattr_ref->ea_size -
- EXT4_XATTR_SIZE(old_data_size) +
- EXT4_XATTR_SIZE(new_data_size);
+ /* Check the validity of the buffer */
+ if (!ext4_xattr_is_block_valid(inode_ref, block))
+ return EIO;
- /*
- * This entry may originally lie in inode space or block space,
- * and it is going to be transferred to another place.
- */
- if (to_block) {
- xattr_ref->inode_size_rem +=
- EXT4_XATTR_SIZE(old_data_size) +
- EXT4_XATTR_LEN(item->name_len);
- xattr_ref->block_size_rem -=
- EXT4_XATTR_SIZE(new_data_size) +
- EXT4_XATTR_LEN(item->name_len);
- item->in_inode = false;
- } else if (to_inode) {
- xattr_ref->block_size_rem +=
- EXT4_XATTR_SIZE(old_data_size) +
- EXT4_XATTR_LEN(item->name_len);
- xattr_ref->inode_size_rem -=
- EXT4_XATTR_SIZE(new_data_size) +
- EXT4_XATTR_LEN(item->name_len);
- item->in_inode = true;
- } else {
- /*
- * No need to transfer as there is enough space for the entry
- * to stay in inode space or block space it used to be.
- */
- orig_room_size +=
- EXT4_XATTR_SIZE(old_data_size);
- orig_room_size -=
- EXT4_XATTR_SIZE(new_data_size);
- if (item->in_inode)
- xattr_ref->inode_size_rem = orig_room_size;
- else
- xattr_ref->block_size_rem = orig_room_size;
-
- }
- xattr_ref->dirty = true;
- return ret;
+ ext4_xattr_block_init_search(inode_ref, &finder->s, block);
+ ext4_xattr_find_entry(&finder->i, &finder->s);
+ return EOK;
}
-static void ext4_xattr_purge_items(struct ext4_xattr_ref *xattr_ref)
+/**
+ * @brief Find an EA entry inside an inode's extra space
+ *
+ * @param inode_ref Inode reference
+ * @param finder The caller-provided finder block with
+ * information filled
+ *
+ * @return Return EOK no matter the entry is found or not.
+ * If the IO operation or the buffer validation failed,
+ * return other value.
+ */
+static int ext4_xattr_ibody_find_entry(struct ext4_inode_ref *inode_ref,
+ struct ext4_xattr_finder *finder)
{
- struct ext4_xattr_item *item, *save_item;
- RB_FOREACH_SAFE(item, ext4_xattr_tree, &xattr_ref->root, save_item) {
- RB_REMOVE(ext4_xattr_tree, &xattr_ref->root, item);
- ext4_xattr_item_free(item);
+ struct ext4_fs *fs = inode_ref->fs;
+ struct ext4_xattr_ibody_header *iheader;
+ size_t extra_isize =
+ ext4_inode_get_extra_isize(&fs->sb, inode_ref->inode);
+ size_t inode_size = ext4_get16(&fs->sb, inode_size);
+
+ /* Initialize the caller-given finder */
+ finder->inode_ref = inode_ref;
+ memset(&finder->s, 0, sizeof(finder->s));
+
+ /*
+ * If there is no extra inode space
+ * set ext4_xattr_ibody_finder::s::not_found to true and return EOK
+ */
+ if (!extra_isize) {
+ finder->s.not_found = true;
+ return EOK;
}
- xattr_ref->ea_size = 0;
- if (ext4_xattr_inode_space(xattr_ref) <
- sizeof(struct ext4_xattr_ibody_header))
- xattr_ref->inode_size_rem = 0;
- else
- xattr_ref->inode_size_rem =
- ext4_xattr_inode_space(xattr_ref) -
- sizeof(struct ext4_xattr_ibody_header);
- xattr_ref->block_size_rem =
- ext4_xattr_block_space(xattr_ref) -
- sizeof(struct ext4_xattr_header);
+ /* Check the validity of the buffer */
+ if (!ext4_xattr_is_ibody_valid(inode_ref))
+ return EIO;
+
+ iheader = EXT4_XATTR_IHDR(&fs->sb, inode_ref->inode);
+ finder->s.base = EXT4_XATTR_IFIRST(iheader);
+ finder->s.end = (char *)inode_ref->inode + inode_size;
+ finder->s.first = EXT4_XATTR_IFIRST(iheader);
+ ext4_xattr_find_entry(&finder->i, &finder->s);
+ return EOK;
}
-static int ext4_xattr_try_alloc_block(struct ext4_xattr_ref *xattr_ref)
+/**
+ * @brief Try to allocate a block holding EA entries.
+ *
+ * @param inode_ref Inode reference
+ *
+ * @return Error code
+ */
+static int ext4_xattr_try_alloc_block(struct ext4_inode_ref *inode_ref)
{
int ret = EOK;
ext4_fsblk_t xattr_block = 0;
- xattr_block = ext4_inode_get_file_acl(xattr_ref->inode_ref->inode,
- &xattr_ref->fs->sb);
+ xattr_block =
+ ext4_inode_get_file_acl(inode_ref->inode, &inode_ref->fs->sb);
+
+ /*
+ * Only allocate a xattr block when there is no xattr block
+ * used by the inode.
+ */
if (!xattr_block) {
- ext4_fsblk_t goal =
- ext4_fs_inode_to_goal_block(xattr_ref->inode_ref);
+ ext4_fsblk_t goal = ext4_fs_inode_to_goal_block(inode_ref);
- ret = ext4_balloc_alloc_block(xattr_ref->inode_ref,
- goal,
- &xattr_block);
+ ret = ext4_balloc_alloc_block(inode_ref, goal, &xattr_block);
if (ret != EOK)
goto Finish;
- ret = ext4_trans_block_get(xattr_ref->fs->bdev, &xattr_ref->block,
- xattr_block);
- if (ret != EOK) {
- ext4_balloc_free_block(xattr_ref->inode_ref,
- xattr_block);
- goto Finish;
- }
-
- ext4_inode_set_file_acl(xattr_ref->inode_ref->inode,
- &xattr_ref->fs->sb, xattr_block);
- xattr_ref->inode_ref->dirty = true;
- xattr_ref->block_loaded = true;
+ ext4_inode_set_file_acl(inode_ref->inode, &inode_ref->fs->sb,
+ xattr_block);
}
Finish:
@@ -678,410 +756,734 @@
return ret;
}
-static void ext4_xattr_try_free_block(struct ext4_xattr_ref *xattr_ref)
+/**
+ * @brief Try to free a block holding EA entries.
+ *
+ * @param inode_ref Inode reference
+ *
+ * @return Error code
+ */
+static void ext4_xattr_try_free_block(struct ext4_inode_ref *inode_ref)
{
ext4_fsblk_t xattr_block;
- xattr_block = ext4_inode_get_file_acl(xattr_ref->inode_ref->inode,
- &xattr_ref->fs->sb);
- ext4_inode_set_file_acl(xattr_ref->inode_ref->inode, &xattr_ref->fs->sb,
- 0);
- ext4_block_set(xattr_ref->fs->bdev, &xattr_ref->block);
- ext4_balloc_free_block(xattr_ref->inode_ref, xattr_block);
- xattr_ref->inode_ref->dirty = true;
- xattr_ref->block_loaded = false;
+ xattr_block =
+ ext4_inode_get_file_acl(inode_ref->inode, &inode_ref->fs->sb);
+ /*
+ * Free the xattr block used by the inode when there is one.
+ */
+ if (xattr_block) {
+ ext4_inode_set_file_acl(inode_ref->inode, &inode_ref->fs->sb,
+ 0);
+ ext4_balloc_free_block(inode_ref, xattr_block);
+ inode_ref->dirty = true;
+ }
}
-static void ext4_xattr_set_block_header(struct ext4_xattr_ref *xattr_ref)
+/**
+ * @brief Put a list of EA entries into a caller-provided buffer
+ * In order to make sure that @list buffer can fit in the data,
+ * the routine should be called twice.
+ *
+ * @param inode_ref Inode reference
+ * @param list A caller-provided buffer to hold a list of EA entries.
+ * If list == NULL, list_len will contain the size of
+ * the buffer required to hold these entries
+ * @param list_len The length of the data written to @list
+ * @return Error code
+ */
+int ext4_xattr_list(struct ext4_inode_ref *inode_ref,
+ struct ext4_xattr_list_entry *list, size_t *list_len)
{
- struct ext4_xattr_header *block_header = NULL;
- block_header = EXT4_XATTR_BHDR(&xattr_ref->block);
+ int ret = EOK;
+ size_t buf_len = 0;
+ struct ext4_fs *fs = inode_ref->fs;
+ struct ext4_xattr_ibody_header *iheader;
+ size_t extra_isize =
+ ext4_inode_get_extra_isize(&fs->sb, inode_ref->inode);
+ struct ext4_block block;
+ bool block_loaded = false;
+ ext4_fsblk_t xattr_block = 0;
+ struct ext4_xattr_entry *entry;
+ struct ext4_xattr_list_entry *list_prev = NULL;
+ xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb);
- memset(block_header, 0, sizeof(struct ext4_xattr_header));
- block_header->h_magic = EXT4_XATTR_MAGIC;
- block_header->h_refcount = to_le32(1);
- block_header->h_blocks = to_le32(1);
-}
+ /*
+ * If there is extra inode space and the xattr buffer in the
+ * inode is valid.
+ */
+ if (extra_isize && ext4_xattr_is_ibody_valid(inode_ref)) {
+ iheader = EXT4_XATTR_IHDR(&fs->sb, inode_ref->inode);
+ entry = EXT4_XATTR_IFIRST(iheader);
-static void
-ext4_xattr_set_inode_entry(struct ext4_xattr_item *item,
- struct ext4_xattr_ibody_header *ibody_header,
- struct ext4_xattr_entry *entry, void *ibody_data_ptr)
-{
- entry->e_name_len = (uint8_t)item->name_len;
- entry->e_name_index = item->name_index;
- entry->e_value_offs =
- to_le16((char *)ibody_data_ptr - (char *)EXT4_XATTR_IFIRST(ibody_header));
- entry->e_value_block = 0;
- entry->e_value_size = to_le32(item->data_size);
-}
+ /*
+ * The format of the list should be like this:
+ *
+ * name_len indicates the length in bytes of the name
+ * of the EA entry. The string is null-terminated.
+ *
+ * list->name => (char *)(list + 1);
+ * list->next => (void *)((char *)(list + 1) + name_len + 1);
+ */
+ for (; !EXT4_XATTR_IS_LAST_ENTRY(entry);
+ entry = EXT4_XATTR_NEXT(entry)) {
+ size_t name_len = entry->e_name_len;
+ if (list) {
+ list->name_index = entry->e_name_index;
+ list->name_len = name_len;
+ list->name = (char *)(list + 1);
+ memcpy(list->name, EXT4_XATTR_NAME(entry),
+ list->name_len);
-static void ext4_xattr_set_block_entry(struct ext4_xattr_item *item,
- struct ext4_xattr_header *block_header,
- struct ext4_xattr_entry *block_entry,
- void *block_data_ptr)
-{
- block_entry->e_name_len = (uint8_t)item->name_len;
- block_entry->e_name_index = item->name_index;
- block_entry->e_value_offs =
- to_le16((char *)block_data_ptr - (char *)block_header);
- block_entry->e_value_block = 0;
- block_entry->e_value_size = to_le32(item->data_size);
-}
+ if (list_prev)
+ list_prev->next = list;
-static int ext4_xattr_write_to_disk(struct ext4_xattr_ref *xattr_ref)
-{
- int ret = EOK;
- bool block_modified = false;
- void *ibody_data = NULL;
- void *block_data = NULL;
- struct ext4_xattr_item *item, *save_item;
- size_t inode_size_rem, block_size_rem;
- struct ext4_xattr_ibody_header *ibody_header = NULL;
- struct ext4_xattr_header *block_header = NULL;
- struct ext4_xattr_entry *entry = NULL;
- struct ext4_xattr_entry *block_entry = NULL;
+ list_prev = list;
+ list = (struct ext4_xattr_list_entry
+ *)(list->name + name_len + 1);
+ }
- inode_size_rem = ext4_xattr_inode_space(xattr_ref);
- block_size_rem = ext4_xattr_block_space(xattr_ref);
- if (inode_size_rem > sizeof(struct ext4_xattr_ibody_header)) {
- ibody_header = EXT4_XATTR_IHDR(&xattr_ref->fs->sb,
- xattr_ref->inode_ref->inode);
- entry = EXT4_XATTR_IFIRST(ibody_header);
+ /*
+ * Size calculation by pointer arithmetics.
+ */
+ buf_len +=
+ (char *)((struct ext4_xattr_list_entry *)0 + 1) +
+ name_len + 1 -
+ (char *)(struct ext4_xattr_list_entry *)0;
+ }
}
- if (!xattr_ref->dirty)
- goto Finish;
- /* If there are enough spaces in the ibody EA table.*/
- if (inode_size_rem > sizeof(struct ext4_xattr_ibody_header)) {
- memset(ibody_header, 0, inode_size_rem);
- ibody_header->h_magic = EXT4_XATTR_MAGIC;
- ibody_data = (char *)ibody_header + inode_size_rem;
- inode_size_rem -= sizeof(struct ext4_xattr_ibody_header);
+ /*
+ * If there is a xattr block used by the inode
+ */
+ if (xattr_block) {
+ ret = ext4_trans_block_get(fs->bdev, &block, xattr_block);
+ if (ret != EOK)
+ goto out;
- xattr_ref->inode_ref->dirty = true;
- }
- /* If we need an extra block to hold the EA entries*/
- if (xattr_ref->ea_size > inode_size_rem) {
- if (!xattr_ref->block_loaded) {
- ret = ext4_xattr_try_alloc_block(xattr_ref);
- if (ret != EOK)
- goto Finish;
+ block_loaded = true;
+
+ /*
+ * As we don't allow the content in the block being invalid,
+ * bail out.
+ */
+ if (!ext4_xattr_is_block_valid(inode_ref, &block)) {
+ ret = EIO;
+ goto out;
}
- memset(xattr_ref->block.data, 0,
- ext4_sb_get_block_size(&xattr_ref->fs->sb));
- block_header = EXT4_XATTR_BHDR(&xattr_ref->block);
- block_entry = EXT4_XATTR_BFIRST(&xattr_ref->block);
- ext4_xattr_set_block_header(xattr_ref);
- block_data = (char *)block_header + block_size_rem;
- block_size_rem -= sizeof(struct ext4_xattr_header);
- ext4_trans_set_block_dirty(xattr_ref->block.buf);
- } else {
- /* We don't need an extra block.*/
- if (xattr_ref->block_loaded) {
- block_header = EXT4_XATTR_BHDR(&xattr_ref->block);
- block_header->h_refcount =
- to_le32(to_le32(block_header->h_refcount) - 1);
- if (!block_header->h_refcount) {
- ext4_xattr_try_free_block(xattr_ref);
- block_header = NULL;
- } else {
- block_entry =
- EXT4_XATTR_BFIRST(&xattr_ref->block);
- block_data =
- (char *)block_header + block_size_rem;
- block_size_rem -=
- sizeof(struct ext4_xattr_header);
- ext4_inode_set_file_acl(
- xattr_ref->inode_ref->inode,
- &xattr_ref->fs->sb, 0);
+ entry = EXT4_XATTR_BFIRST(&block);
- xattr_ref->inode_ref->dirty = true;
- ext4_trans_set_block_dirty(xattr_ref->block.buf);
+ /*
+ * The format of the list should be like this:
+ *
+ * name_len indicates the length in bytes of the name
+ * of the EA entry. The string is null-terminated.
+ *
+ * list->name => (char *)(list + 1);
+ * list->next => (void *)((char *)(list + 1) + name_len + 1);
+ *
+ * Same as above actually.
+ */
+ for (; !EXT4_XATTR_IS_LAST_ENTRY(entry);
+ entry = EXT4_XATTR_NEXT(entry)) {
+ size_t name_len = entry->e_name_len;
+ if (list) {
+ list->name_index = entry->e_name_index;
+ list->name_len = name_len;
+ list->name = (char *)(list + 1);
+ memcpy(list->name, EXT4_XATTR_NAME(entry),
+ list->name_len);
+
+ if (list_prev)
+ list_prev->next = list;
+
+ list_prev = list;
+ list = (struct ext4_xattr_list_entry
+ *)(list->name + name_len + 1);
}
+
+ /*
+ * Size calculation by pointer arithmetics.
+ */
+ buf_len +=
+ (char *)((struct ext4_xattr_list_entry *)0 + 1) +
+ name_len + 1 -
+ (char *)(struct ext4_xattr_list_entry *)0;
}
}
- RB_FOREACH_SAFE(item, ext4_xattr_tree, &xattr_ref->root, save_item)
- {
- if (item->in_inode) {
- ibody_data = (char *)ibody_data -
- EXT4_XATTR_SIZE(item->data_size);
- ext4_xattr_set_inode_entry(item, ibody_header, entry,
- ibody_data);
- memcpy(EXT4_XATTR_NAME(entry), item->name,
- item->name_len);
- memcpy(ibody_data, item->data, item->data_size);
- entry = EXT4_XATTR_NEXT(entry);
- inode_size_rem -= EXT4_XATTR_SIZE(item->data_size) +
- EXT4_XATTR_LEN(item->name_len);
+ if (list_prev)
+ list_prev->next = NULL;
+out:
+ if (ret == EOK && list_len)
+ *list_len = buf_len;
- xattr_ref->inode_ref->dirty = true;
- continue;
+ if (block_loaded)
+ ext4_block_set(fs->bdev, &block);
+
+ return ret;
+}
+
+/**
+ * @brief Query EA entry's value with given name-index and name
+ *
+ * @param inode_ref Inode reference
+ * @param name_index Name-index
+ * @param name Name of the EA entry to be queried
+ * @param name_len Length of name in bytes
+ * @param buf Output buffer to hold content
+ * @param buf_len Output buffer's length
+ * @param data_len The length of data of the EA entry found
+ *
+ * @return Error code
+ */
+int ext4_xattr_get(struct ext4_inode_ref *inode_ref, uint8_t name_index,
+ const char *name, size_t name_len, void *buf, size_t buf_len,
+ size_t *data_len)
+{
+ int ret = EOK;
+ struct ext4_xattr_finder ibody_finder;
+ struct ext4_xattr_finder block_finder;
+ struct ext4_xattr_info i;
+ size_t value_len = 0;
+ size_t value_offs = 0;
+ struct ext4_fs *fs = inode_ref->fs;
+ ext4_fsblk_t xattr_block;
+ xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb);
+
+ i.name_index = name_index;
+ i.name = name;
+ i.name_len = name_len;
+ i.value = 0;
+ i.value_len = 0;
+ if (data_len)
+ *data_len = 0;
+
+ ibody_finder.i = i;
+ ret = ext4_xattr_ibody_find_entry(inode_ref, &ibody_finder);
+ if (ret != EOK)
+ goto out;
+
+ if (!ibody_finder.s.not_found) {
+ value_len = to_le32(ibody_finder.s.here->e_value_size);
+ value_offs = to_le32(ibody_finder.s.here->e_value_offs);
+ if (buf_len && buf) {
+ void *data_loc =
+ (char *)ibody_finder.s.base + value_offs;
+ memcpy(buf, data_loc,
+ (buf_len < value_len) ? buf_len : value_len);
}
- if (EXT4_XATTR_SIZE(item->data_size) +
- EXT4_XATTR_LEN(item->name_len) >
- block_size_rem) {
- ret = ENOSPC;
- ext4_dbg(DEBUG_XATTR, "IMPOSSIBLE ENOSPC AS WE DID INSPECTION!\n");
- ext4_assert(0);
+ } else {
+ struct ext4_block block;
+ block_finder.i = i;
+ ret = ext4_trans_block_get(fs->bdev, &block, xattr_block);
+ if (ret != EOK)
+ goto out;
+
+ ret = ext4_xattr_block_find_entry(inode_ref, &block_finder,
+ &block);
+ if (ret != EOK) {
+ ext4_block_set(fs->bdev, &block);
+ goto out;
}
- block_data =
- (char *)block_data - EXT4_XATTR_SIZE(item->data_size);
- ext4_xattr_set_block_entry(item, block_header, block_entry,
- block_data);
- memcpy(EXT4_XATTR_NAME(block_entry), item->name,
- item->name_len);
- memcpy(block_data, item->data, item->data_size);
- ext4_xattr_compute_hash(block_header, block_entry);
- block_entry = EXT4_XATTR_NEXT(block_entry);
- block_size_rem -= EXT4_XATTR_SIZE(item->data_size) +
- EXT4_XATTR_LEN(item->name_len);
- block_modified = true;
+ /* Return ENODATA if entry is not found */
+ if (block_finder.s.not_found) {
+ ext4_block_set(fs->bdev, &block);
+ ret = ENODATA;
+ goto out;
+ }
+
+ value_len = to_le32(block_finder.s.here->e_value_size);
+ value_offs = to_le32(block_finder.s.here->e_value_offs);
+ if (buf_len && buf) {
+ void *data_loc =
+ (char *)block_finder.s.base + value_offs;
+ memcpy(buf, data_loc,
+ (buf_len < value_len) ? buf_len : value_len);
+ }
+
+ /*
+ * Free the xattr block buffer returned by
+ * ext4_xattr_block_find_entry.
+ */
+ ext4_block_set(fs->bdev, &block);
}
- xattr_ref->dirty = false;
- if (block_modified) {
- ext4_xattr_rehash(block_header,
- EXT4_XATTR_BFIRST(&xattr_ref->block));
- ext4_xattr_set_block_checksum(xattr_ref->inode_ref,
- xattr_ref->block.lb_id,
- block_header);
- ext4_trans_set_block_dirty(xattr_ref->block.buf);
- }
-Finish:
+out:
+ if (ret == EOK && data_len)
+ *data_len = value_len;
+
return ret;
}
-void ext4_fs_xattr_iterate(struct ext4_xattr_ref *ref,
- int (*iter)(struct ext4_xattr_ref *ref,
- struct ext4_xattr_item *item))
+/**
+ * @brief Try to copy the content of an xattr block to a newly-allocated
+ * block. If the operation fails, the block buffer provided by
+ * caller will be freed
+ *
+ * @param inode_ref Inode reference
+ * @param block The block buffer reference
+ * @param new_block The newly-allocated block buffer reference
+ * @param orig_block The block number of @block
+ * @param allocated a new block is allocated
+ *
+ * @return Error code
+ */
+static int ext4_xattr_copy_new_block(struct ext4_inode_ref *inode_ref,
+ struct ext4_block *block,
+ struct ext4_block *new_block,
+ ext4_fsblk_t *orig_block, bool *allocated)
{
- struct ext4_xattr_item *item;
- if (!ref->iter_from)
- ref->iter_from = RB_MIN(ext4_xattr_tree, &ref->root);
+ int ret = EOK;
+ ext4_fsblk_t xattr_block = 0;
+ struct ext4_xattr_header *header;
+ struct ext4_fs *fs = inode_ref->fs;
+ header = EXT4_XATTR_BHDR(block);
- RB_FOREACH_FROM(item, ext4_xattr_tree, ref->iter_from)
- {
- int ret = EXT4_XATTR_ITERATE_CONT;
- if (iter)
- ret = iter(ref, item);
+ if (orig_block)
+ *orig_block = block->lb_id;
- if (ret != EXT4_XATTR_ITERATE_CONT) {
- if (ret == EXT4_XATTR_ITERATE_STOP)
- ref->iter_from = NULL;
+ if (allocated)
+ *allocated = false;
- break;
+ /* Only do copy when a block is referenced by more than one inode. */
+ if (to_le32(header->h_refcount) > 1) {
+ ext4_fsblk_t goal = ext4_fs_inode_to_goal_block(inode_ref);
+
+ /* Allocate a new block to be used by this inode */
+ ret = ext4_balloc_alloc_block(inode_ref, goal, &xattr_block);
+ if (ret != EOK)
+ goto out;
+
+ ret = ext4_trans_block_get(fs->bdev, new_block, xattr_block);
+ if (ret != EOK)
+ goto out;
+
+ /* Copy the content of the whole block */
+ memcpy(new_block->data, block->data,
+ ext4_sb_get_block_size(&inode_ref->fs->sb));
+
+ /*
+ * Decrement the reference count of the original xattr block
+ * by one
+ */
+ header->h_refcount = to_le32(to_le32(header->h_refcount) - 1);
+ ext4_trans_set_block_dirty(block->buf);
+ ext4_trans_set_block_dirty(new_block->buf);
+
+ header = EXT4_XATTR_BHDR(new_block);
+ header->h_refcount = to_le32(1);
+
+ if (allocated)
+ *allocated = true;
+ }
+out:
+ if (xattr_block) {
+ if (ret != EOK)
+ ext4_balloc_free_block(inode_ref, xattr_block);
+ else {
+ /*
+ * Modify the in-inode pointer to point to the new xattr block
+ */
+ ext4_inode_set_file_acl(inode_ref->inode, &fs->sb, xattr_block);
+ inode_ref->dirty = true;
}
}
-}
-void ext4_fs_xattr_iterate_reset(struct ext4_xattr_ref *ref)
-{
- ref->iter_from = NULL;
+ return ret;
}
-int ext4_fs_set_xattr(struct ext4_xattr_ref *ref, uint8_t name_index,
- const char *name, size_t name_len, const void *data,
- size_t data_size, bool replace)
+/**
+ * @brief Given an EA entry's name, remove the EA entry
+ *
+ * @param inode_ref Inode reference
+ * @param name_index Name-index
+ * @param name Name of the EA entry to be removed
+ * @param name_len Length of name in bytes
+ *
+ * @return Error code
+ */
+int ext4_xattr_remove(struct ext4_inode_ref *inode_ref, uint8_t name_index,
+ const char *name, size_t name_len)
{
int ret = EOK;
- struct ext4_xattr_item *item =
- ext4_xattr_lookup_item(ref, name_index, name, name_len);
- if (replace) {
- if (!item) {
+ struct ext4_block block;
+ struct ext4_xattr_finder ibody_finder;
+ struct ext4_xattr_finder block_finder;
+ bool use_block = false;
+ bool block_loaded = false;
+ struct ext4_xattr_info i;
+ struct ext4_fs *fs = inode_ref->fs;
+ ext4_fsblk_t xattr_block;
+
+ xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb);
+
+ i.name_index = name_index;
+ i.name = name;
+ i.name_len = name_len;
+ i.value = NULL;
+ i.value_len = 0;
+
+ ibody_finder.i = i;
+ block_finder.i = i;
+
+ ret = ext4_xattr_ibody_find_entry(inode_ref, &ibody_finder);
+ if (ret != EOK)
+ goto out;
+
+ if (ibody_finder.s.not_found && xattr_block) {
+ ret = ext4_trans_block_get(fs->bdev, &block, xattr_block);
+ if (ret != EOK)
+ goto out;
+
+ block_loaded = true;
+ block_finder.i = i;
+ ret = ext4_xattr_block_find_entry(inode_ref, &block_finder,
+ &block);
+ if (ret != EOK)
+ goto out;
+
+ /* Return ENODATA if entry is not found */
+ if (block_finder.s.not_found) {
ret = ENODATA;
- goto Finish;
+ goto out;
}
- if (item->data_size != data_size)
- ret = ext4_xattr_resize_item(ref, item, data_size);
+ use_block = true;
+ }
- if (ret != EOK) {
- goto Finish;
+ if (use_block) {
+ bool allocated = false;
+ struct ext4_block new_block;
+
+ /*
+ * There will be no effect when the xattr block is only referenced
+ * once.
+ */
+ ret = ext4_xattr_copy_new_block(inode_ref, &block, &new_block,
+ &xattr_block, &allocated);
+ if (ret != EOK)
+ goto out;
+
+ if (!allocated) {
+ /* Prevent double-freeing */
+ block_loaded = false;
+ new_block = block;
}
- memcpy(item->data, data, data_size);
- } else {
- if (item) {
- ret = EEXIST;
- goto Finish;
+
+ ret = ext4_xattr_block_find_entry(inode_ref, &block_finder,
+ &new_block);
+ if (ret != EOK)
+ goto out;
+
+ /* Now remove the entry */
+ ext4_xattr_set_entry(&i, &block_finder.s, false);
+
+ if (ext4_xattr_is_empty(&block_finder.s)) {
+ ext4_block_set(fs->bdev, &new_block);
+ ext4_xattr_try_free_block(inode_ref);
+ } else {
+ struct ext4_xattr_header *header =
+ EXT4_XATTR_BHDR(&new_block);
+ header = EXT4_XATTR_BHDR(&new_block);
+ ext4_assert(block_finder.s.first);
+ ext4_xattr_rehash(header, block_finder.s.first);
+ ext4_xattr_set_block_checksum(inode_ref,
+ block.lb_id,
+ header);
+
+ ext4_trans_set_block_dirty(new_block.buf);
+ ext4_block_set(fs->bdev, &new_block);
}
- item = ext4_xattr_insert_item(ref, name_index, name, name_len,
- data, data_size, &ret);
+
+ } else {
+ /* Now remove the entry */
+ ext4_xattr_set_entry(&i, &block_finder.s, false);
+ inode_ref->dirty = true;
}
-Finish:
+out:
+ if (block_loaded)
+ ext4_block_set(fs->bdev, &block);
+
return ret;
}
-int ext4_fs_remove_xattr(struct ext4_xattr_ref *ref, uint8_t name_index,
- const char *name, size_t name_len)
+/**
+ * @brief Insert/overwrite an EA entry into/in a xattr block
+ *
+ * @param inode_ref Inode reference
+ * @param i The information of the given EA entry
+ *
+ * @return Error code
+ */
+static int ext4_xattr_block_set(struct ext4_inode_ref *inode_ref,
+ struct ext4_xattr_info *i,
+ bool no_insert)
{
- return ext4_xattr_remove_item(ref, name_index, name, name_len);
-}
-
-int ext4_fs_get_xattr(struct ext4_xattr_ref *ref, uint8_t name_index,
- const char *name, size_t name_len, void *buf,
- size_t buf_size, size_t *data_size)
-{
int ret = EOK;
- size_t item_size = 0;
- struct ext4_xattr_item *item =
- ext4_xattr_lookup_item(ref, name_index, name, name_len);
+ bool allocated = false;
+ struct ext4_fs *fs = inode_ref->fs;
+ struct ext4_block block, new_block;
+ ext4_fsblk_t orig_xattr_block;
- if (!item) {
- ret = ENODATA;
- goto Finish;
- }
- item_size = item->data_size;
- if (buf_size > item_size)
- buf_size = item_size;
+ orig_xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb);
- if (buf)
- memcpy(buf, item->data, buf_size);
+ ext4_assert(i->value);
+ if (!orig_xattr_block) {
+ struct ext4_xattr_search s;
+ struct ext4_xattr_header *header;
-Finish:
- if (data_size)
- *data_size = item_size;
+ /* If insertion of new entry is not allowed... */
+ if (no_insert) {
+ ret = ENODATA;
+ goto out;
+ }
- return ret;
-}
+ ret = ext4_xattr_try_alloc_block(inode_ref);
+ if (ret != EOK)
+ goto out;
-int ext4_fs_get_xattr_ref(struct ext4_fs *fs, struct ext4_inode_ref *inode_ref,
- struct ext4_xattr_ref *ref)
-{
- int rc;
- ext4_fsblk_t xattr_block;
- xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb);
- RB_INIT(&ref->root);
- ref->ea_size = 0;
- ref->iter_from = NULL;
- if (xattr_block) {
- rc = ext4_trans_block_get(fs->bdev, &ref->block, xattr_block);
- if (rc != EOK)
- return EIO;
+ orig_xattr_block =
+ ext4_inode_get_file_acl(inode_ref->inode, &fs->sb);
+ ret = ext4_trans_block_get(fs->bdev, &block, orig_xattr_block);
+ if (ret != EOK) {
+ ext4_xattr_try_free_block(inode_ref);
+ goto out;
+ }
- ref->block_loaded = true;
- } else
- ref->block_loaded = false;
+ ext4_xattr_block_initialize(inode_ref, &block);
+ ext4_xattr_block_init_search(inode_ref, &s, &block);
- ref->inode_ref = inode_ref;
- ref->fs = fs;
+ ret = ext4_xattr_set_entry(i, &s, false);
+ if (ret == EOK) {
+ header = EXT4_XATTR_BHDR(&block);
- if (ext4_xattr_inode_space(ref) <
- sizeof(struct ext4_xattr_ibody_header))
- ref->inode_size_rem = 0;
- else
- ref->inode_size_rem =
- ext4_xattr_inode_space(ref) -
- sizeof(struct ext4_xattr_ibody_header);
+ ext4_assert(s.here);
+ ext4_assert(s.first);
+ ext4_xattr_compute_hash(header, s.here);
+ ext4_xattr_rehash(header, s.first);
+ ext4_xattr_set_block_checksum(inode_ref,
+ block.lb_id,
+ header);
+ ext4_trans_set_block_dirty(block.buf);
+ }
+ ext4_block_set(fs->bdev, &block);
+ if (ret != EOK)
+ ext4_xattr_try_free_block(inode_ref);
- ref->block_size_rem =
- ext4_xattr_block_space(ref) -
- sizeof(struct ext4_xattr_header);
+ } else {
+ struct ext4_xattr_finder finder;
+ struct ext4_xattr_header *header;
+ finder.i = *i;
+ ret = ext4_trans_block_get(fs->bdev, &block, orig_xattr_block);
+ if (ret != EOK)
+ goto out;
- rc = ext4_xattr_fetch(ref);
- if (rc != EOK) {
- ext4_xattr_purge_items(ref);
- if (xattr_block)
- ext4_block_set(fs->bdev, &ref->block);
+ header = EXT4_XATTR_BHDR(&block);
- ref->block_loaded = false;
- return rc;
+ /*
+ * Consider the following case when insertion of new
+ * entry is not allowed
+ */
+ if (to_le32(header->h_refcount) > 1 && no_insert) {
+ /*
+ * There are other people referencing the
+ * same xattr block
+ */
+ ret = ext4_xattr_block_find_entry(inode_ref, &finder, &block);
+ if (ret != EOK) {
+ ext4_block_set(fs->bdev, &block);
+ goto out;
+ }
+ if (finder.s.not_found) {
+ ext4_block_set(fs->bdev, &block);
+ ret = ENODATA;
+ goto out;
+ }
+ }
+
+ /*
+ * There will be no effect when the xattr block is only referenced
+ * once.
+ */
+ ret = ext4_xattr_copy_new_block(inode_ref, &block, &new_block,
+ &orig_xattr_block, &allocated);
+ if (ret != EOK) {
+ ext4_block_set(fs->bdev, &block);
+ goto out;
+ }
+
+ if (allocated) {
+ ext4_block_set(fs->bdev, &block);
+ new_block = block;
+ }
+
+ ret = ext4_xattr_block_find_entry(inode_ref, &finder, &block);
+ if (ret != EOK) {
+ ext4_block_set(fs->bdev, &block);
+ goto out;
+ }
+
+ ret = ext4_xattr_set_entry(i, &finder.s, false);
+ if (ret == EOK) {
+ header = EXT4_XATTR_BHDR(&block);
+
+ ext4_assert(finder.s.here);
+ ext4_assert(finder.s.first);
+ ext4_xattr_compute_hash(header, finder.s.here);
+ ext4_xattr_rehash(header, finder.s.first);
+ ext4_xattr_set_block_checksum(inode_ref,
+ block.lb_id,
+ header);
+ ext4_trans_set_block_dirty(block.buf);
+ }
+ ext4_block_set(fs->bdev, &block);
}
- return EOK;
+out:
+ return ret;
}
-void ext4_fs_put_xattr_ref(struct ext4_xattr_ref *ref)
+/**
+ * @brief Remove an EA entry from a xattr block
+ *
+ * @param inode_ref Inode reference
+ * @param i The information of the given EA entry
+ *
+ * @return Error code
+ */
+static int ext4_xattr_block_remove(struct ext4_inode_ref *inode_ref,
+ struct ext4_xattr_info *i)
{
- int rc = ext4_xattr_write_to_disk(ref);
- if (ref->block_loaded) {
- if (rc != EOK)
- ext4_bcache_clear_dirty(ref->block.buf);
+ int ret = EOK;
+ bool allocated = false;
+ const void *value = i->value;
+ struct ext4_fs *fs = inode_ref->fs;
+ struct ext4_xattr_finder finder;
+ struct ext4_block block, new_block;
+ struct ext4_xattr_header *header;
+ ext4_fsblk_t orig_xattr_block;
+ orig_xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb);
- ext4_block_set(ref->fs->bdev, &ref->block);
- ref->block_loaded = false;
+ ext4_assert(orig_xattr_block);
+ ret = ext4_trans_block_get(fs->bdev, &block, orig_xattr_block);
+ if (ret != EOK)
+ goto out;
+
+ /*
+ * There will be no effect when the xattr block is only referenced
+ * once.
+ */
+ ret = ext4_xattr_copy_new_block(inode_ref, &block, &new_block,
+ &orig_xattr_block, &allocated);
+ if (ret != EOK) {
+ ext4_block_set(fs->bdev, &block);
+ goto out;
}
- ext4_xattr_purge_items(ref);
- ref->inode_ref = NULL;
- ref->fs = NULL;
-}
-struct xattr_prefix {
- const char *prefix;
- uint8_t name_index;
-};
+ if (allocated) {
+ ext4_block_set(fs->bdev, &block);
+ block = new_block;
+ }
-static const struct xattr_prefix prefix_tbl[] = {
- {"user.", EXT4_XATTR_INDEX_USER},
- {"system.posix_acl_access", EXT4_XATTR_INDEX_POSIX_ACL_ACCESS},
- {"system.posix_acl_default", EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT},
- {"trusted.", EXT4_XATTR_INDEX_TRUSTED},
- {"security.", EXT4_XATTR_INDEX_SECURITY},
- {"system.", EXT4_XATTR_INDEX_SYSTEM},
- {"system.richacl", EXT4_XATTR_INDEX_RICHACL},
- {NULL, 0},
-};
+ ext4_xattr_block_find_entry(inode_ref, &finder, &block);
-const char *ext4_extract_xattr_name(const char *full_name, size_t full_name_len,
- uint8_t *name_index, size_t *name_len,
- bool *found)
-{
- int i;
- ext4_assert(name_index);
- ext4_assert(found);
+ if (!finder.s.not_found) {
+ i->value = NULL;
+ ret = ext4_xattr_set_entry(i, &finder.s, false);
+ i->value = value;
- *found = false;
+ header = EXT4_XATTR_BHDR(&block);
+ ext4_assert(finder.s.first);
+ ext4_xattr_rehash(header, finder.s.first);
+ ext4_xattr_set_block_checksum(inode_ref,
+ block.lb_id,
+ header);
+ ext4_trans_set_block_dirty(block.buf);
+ }
- if (!full_name_len) {
- if (name_len)
- *name_len = 0;
+ ext4_block_set(fs->bdev, &block);
+out:
+ return ret;
+}
- return NULL;
- }
+/**
+ * @brief Insert an EA entry into a given inode reference
+ *
+ * @param inode_ref Inode reference
+ * @param name_index Name-index
+ * @param name Name of the EA entry to be inserted
+ * @param name_len Length of name in bytes
+ * @param value Input buffer to hold content
+ * @param value_len Length of input content
+ *
+ * @return Error code
+ */
+int ext4_xattr_set(struct ext4_inode_ref *inode_ref, uint8_t name_index,
+ const char *name, size_t name_len, const void *value,
+ size_t value_len)
+{
+ int ret = EOK;
+ struct ext4_fs *fs = inode_ref->fs;
+ struct ext4_xattr_finder ibody_finder;
+ struct ext4_xattr_info i;
+ bool block_found = false;
+ ext4_fsblk_t orig_xattr_block;
- for (i = 0; prefix_tbl[i].prefix; i++) {
- size_t prefix_len = strlen(prefix_tbl[i].prefix);
- if (full_name_len >= prefix_len &&
- !memcmp(full_name, prefix_tbl[i].prefix, prefix_len)) {
- bool require_name =
- prefix_tbl[i].prefix[prefix_len - 1] == '.';
- *name_index = prefix_tbl[i].name_index;
- if (name_len)
- *name_len = full_name_len - prefix_len;
+ i.name_index = name_index;
+ i.name = name;
+ i.name_len = name_len;
+ i.value = (value_len) ? value : &ext4_xattr_empty_value;
+ i.value_len = value_len;
- if (!(full_name_len - prefix_len) && require_name)
- return NULL;
+ ibody_finder.i = i;
- *found = true;
- if (require_name)
- return full_name + prefix_len;
+ orig_xattr_block = ext4_inode_get_file_acl(inode_ref->inode, &fs->sb);
- return NULL;
- }
+ /*
+ * Even if entry is not found, search context block inside the
+ * finder is still valid and can be used to insert entry.
+ */
+ ret = ext4_xattr_ibody_find_entry(inode_ref, &ibody_finder);
+ if (ret != EOK) {
+ ext4_xattr_ibody_initialize(inode_ref);
+ ext4_xattr_ibody_find_entry(inode_ref, &ibody_finder);
}
- if (name_len)
- *name_len = 0;
- return NULL;
-}
+ if (ibody_finder.s.not_found) {
+ if (orig_xattr_block) {
+ block_found = true;
+ ret = ext4_xattr_block_set(inode_ref, &i, true);
+ if (ret == ENOSPC)
+ goto try_insert;
+ else if (ret == ENODATA)
+ goto try_insert;
+ else if (ret != EOK)
+ goto out;
-const char *ext4_get_xattr_name_prefix(uint8_t name_index,
- size_t *ret_prefix_len)
-{
- int i;
+ } else
+ goto try_insert;
- for (i = 0; prefix_tbl[i].prefix; i++) {
- size_t prefix_len = strlen(prefix_tbl[i].prefix);
- if (prefix_tbl[i].name_index == name_index) {
- if (ret_prefix_len)
- *ret_prefix_len = prefix_len;
+ } else {
+ try_insert:
+ ret = ext4_xattr_set_entry(&i, &ibody_finder.s, false);
+ if (ret == ENOSPC) {
+ if (!block_found) {
+ ret = ext4_xattr_block_set(inode_ref, &i, false);
+ ibody_finder.i.value = NULL;
+ ext4_xattr_set_entry(&ibody_finder.i,
+ &ibody_finder.s, false);
+ inode_ref->dirty = true;
+ }
- return prefix_tbl[i].prefix;
+ } else if (ret == EOK) {
+ if (block_found)
+ ret = ext4_xattr_block_remove(inode_ref, &i);
+
+ inode_ref->dirty = true;
}
}
- if (ret_prefix_len)
- *ret_prefix_len = 0;
- return NULL;
+out:
+ return ret;
}
/**