shithub: lwext4

Download patch

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;
 }
 
 /**