From: Andreas Gruenbacher <agruen@suse.de>

Clean up several things in the xattr code, and prepare it for
in-inode attributes:

* Add the ext3_xattr_check_names, ext3_xattr_check_block, and
  ext3_xattr_check_entry functions for checking xattr data
  structures.
* Add the ext3_xattr_find_entry, ext3_xattr_list_entries, and
  ext3_xattr_set_entry functions for manipulating xattr entries.
  Switch to using these functions in ext3_xattr_get,
  ext3_xattr_list, and ext3_xattr_set_handle.
* Merge ext3_xattr_set_handle and ext3_xattr_set_handle2.
* Rename the HDR and FIRST_ENTRY macros.
* We have no way to deal with a ext3_xattr_cache_insert failure,
  so make it return void.
* Make the debug messages more useful.

Signed-off-by: Andreas Gruenbacher <agruen@suse.de>
Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 25-akpm/fs/ext2/xattr.c |    2 
 25-akpm/fs/ext3/xattr.c |  715 +++++++++++++++++++++++-------------------------
 2 files changed, 354 insertions(+), 363 deletions(-)

diff -puN fs/ext2/xattr.c~ext3-ea-cleanup-and-prepare-ext3-for-in-inode-xattrs fs/ext2/xattr.c
--- 25/fs/ext2/xattr.c~ext3-ea-cleanup-and-prepare-ext3-for-in-inode-xattrs	Tue Jan 11 14:39:49 2005
+++ 25-akpm/fs/ext2/xattr.c	Tue Jan 11 14:39:49 2005
@@ -24,7 +24,7 @@
  *
  *   +------------------+
  *   | header           |
- *   Ś entry 1          | |
+ *   | entry 1          | |
  *   | entry 2          | | growing downwards
  *   | entry 3          | v
  *   | four null bytes  |
diff -puN fs/ext3/xattr.c~ext3-ea-cleanup-and-prepare-ext3-for-in-inode-xattrs fs/ext3/xattr.c
--- 25/fs/ext3/xattr.c~ext3-ea-cleanup-and-prepare-ext3-for-in-inode-xattrs	Tue Jan 11 14:39:49 2005
+++ 25-akpm/fs/ext3/xattr.c	Tue Jan 11 14:39:49 2005
@@ -24,7 +24,7 @@
  *
  *   +------------------+
  *   | header           |
- *   Ś entry 1          | |
+ *   | entry 1          | |
  *   | entry 2          | | growing downwards
  *   | entry 3          | v
  *   | four null bytes  |
@@ -64,9 +64,9 @@
 #include "xattr.h"
 #include "acl.h"
 
-#define HDR(bh) ((struct ext3_xattr_header *)((bh)->b_data))
+#define BHDR(bh) ((struct ext3_xattr_header *)((bh)->b_data))
 #define ENTRY(ptr) ((struct ext3_xattr_entry *)(ptr))
-#define FIRST_ENTRY(bh) ENTRY(HDR(bh)+1)
+#define BFIRST(bh) ENTRY(BHDR(bh)+1)
 #define IS_LAST_ENTRY(entry) (*(__u32 *)(entry) == 0)
 
 #ifdef EXT3_XATTR_DEBUG
@@ -89,11 +89,7 @@
 # define ea_bdebug(f...)
 #endif
 
-static int ext3_xattr_set_handle2(handle_t *, struct inode *,
-				  struct buffer_head *,
-				  struct ext3_xattr_header *);
-
-static int ext3_xattr_cache_insert(struct buffer_head *);
+static void ext3_xattr_cache_insert(struct buffer_head *);
 static struct buffer_head *ext3_xattr_cache_find(struct inode *,
 						 struct ext3_xattr_header *,
 						 struct mb_cache_entry **);
@@ -148,6 +144,68 @@ ext3_listxattr(struct dentry *dentry, ch
 	return ext3_xattr_list(dentry->d_inode, buffer, size);
 }
 
+static int
+ext3_xattr_check_names(struct ext3_xattr_entry *entry, void *end)
+{
+	while (!IS_LAST_ENTRY(entry)) {
+		struct ext3_xattr_entry *next = EXT3_XATTR_NEXT(entry);
+		if ((void *)next >= end)
+			return -EIO;
+		entry = next;
+	}
+	return 0;
+}
+
+static inline int
+ext3_xattr_check_block(struct buffer_head *bh)
+{
+	int error;
+
+	if (BHDR(bh)->h_magic != cpu_to_le32(EXT3_XATTR_MAGIC) ||
+	    BHDR(bh)->h_blocks != cpu_to_le32(1))
+		return -EIO;
+	error = ext3_xattr_check_names(BFIRST(bh), bh->b_data + bh->b_size);
+	return error;
+}
+
+static inline int
+ext3_xattr_check_entry(struct ext3_xattr_entry *entry, size_t size)
+{
+	size_t value_size = le32_to_cpu(entry->e_value_size);
+
+	if (entry->e_value_block != 0 || value_size > size ||
+	    le16_to_cpu(entry->e_value_offs) + value_size > size)
+		return -EIO;
+	return 0;
+}
+
+static int
+ext3_xattr_find_entry(struct ext3_xattr_entry **pentry, int name_index,
+		      const char *name, size_t size, int sorted)
+{
+	struct ext3_xattr_entry *entry;
+	size_t name_len;
+	int cmp = 1;
+
+	if (name == NULL)
+		return -EINVAL;
+	name_len = strlen(name);
+	entry = *pentry;
+	for (; !IS_LAST_ENTRY(entry); entry = EXT3_XATTR_NEXT(entry)) {
+		cmp = name_index - entry->e_name_index;
+		if (!cmp)
+			cmp = name_len - entry->e_name_len;
+		if (!cmp)
+			cmp = memcmp(name, entry->e_name, name_len);
+		if (cmp <= 0 && (sorted || cmp == 0))
+			break;
+	}
+	*pentry = entry;
+	if (!cmp && ext3_xattr_check_entry(entry, size))
+			return -EIO;
+	return cmp ? -ENODATA : 0;
+}
+
 /*
  * ext3_xattr_get()
  *
@@ -164,8 +222,7 @@ ext3_xattr_get(struct inode *inode, int 
 {
 	struct buffer_head *bh = NULL;
 	struct ext3_xattr_entry *entry;
-	size_t name_len, size;
-	char *end;
+	size_t size;
 	int error;
 
 	ea_idebug(inode, "name=%d.%s, buffer=%p, buffer_size=%ld",
@@ -179,78 +236,65 @@ ext3_xattr_get(struct inode *inode, int 
 		goto cleanup;
 	ea_idebug(inode, "reading block %d", EXT3_I(inode)->i_file_acl);
 	bh = sb_bread(inode->i_sb, EXT3_I(inode)->i_file_acl);
-	error = -EIO;
 	if (!bh)
 		goto cleanup;
 	ea_bdebug(bh, "b_count=%d, refcount=%d",
-		atomic_read(&(bh->b_count)), le32_to_cpu(HDR(bh)->h_refcount));
-	end = bh->b_data + bh->b_size;
-	if (HDR(bh)->h_magic != cpu_to_le32(EXT3_XATTR_MAGIC) ||
-	    HDR(bh)->h_blocks != cpu_to_le32(1)) {
-bad_block:	ext3_error(inode->i_sb, "ext3_xattr_get",
-			"inode %ld: bad block %d", inode->i_ino,
-			EXT3_I(inode)->i_file_acl);
+		atomic_read(&(bh->b_count)), le32_to_cpu(BHDR(bh)->h_refcount));
+	if (ext3_xattr_check_block(bh)) {
+bad_block:	ext3_error(inode->i_sb, __FUNCTION__,
+			   "inode %ld: bad block %d", inode->i_ino,
+			   EXT3_I(inode)->i_file_acl);
 		error = -EIO;
 		goto cleanup;
 	}
-	/* find named attribute */
-	name_len = strlen(name);
-
-	error = -ERANGE;
-	if (name_len > 255)
-		goto cleanup;
-	entry = FIRST_ENTRY(bh);
-	while (!IS_LAST_ENTRY(entry)) {
-		struct ext3_xattr_entry *next =
-			EXT3_XATTR_NEXT(entry);
-		if ((char *)next >= end)
-			goto bad_block;
-		if (name_index == entry->e_name_index &&
-		    name_len == entry->e_name_len &&
-		    memcmp(name, entry->e_name, name_len) == 0)
-			goto found;
-		entry = next;
-	}
-	/* Check the remaining name entries */
-	while (!IS_LAST_ENTRY(entry)) {
-		struct ext3_xattr_entry *next =
-			EXT3_XATTR_NEXT(entry);
-		if ((char *)next >= end)
-			goto bad_block;
-		entry = next;
-	}
-	if (ext3_xattr_cache_insert(bh))
-		ea_idebug(inode, "cache insert failed");
-	error = -ENODATA;
-	goto cleanup;
-found:
-	/* check the buffer size */
-	if (entry->e_value_block != 0)
+	ext3_xattr_cache_insert(bh);
+	entry = BFIRST(bh);
+	error = ext3_xattr_find_entry(&entry, name_index, name, bh->b_size, 1);
+	if (error == -EIO)
 		goto bad_block;
+	if (error)
+		goto cleanup;
 	size = le32_to_cpu(entry->e_value_size);
-	if (size > inode->i_sb->s_blocksize ||
-	    le16_to_cpu(entry->e_value_offs) + size > inode->i_sb->s_blocksize)
-		goto bad_block;
-
-	if (ext3_xattr_cache_insert(bh))
-		ea_idebug(inode, "cache insert failed");
 	if (buffer) {
 		error = -ERANGE;
 		if (size > buffer_size)
 			goto cleanup;
-		/* return value of attribute */
 		memcpy(buffer, bh->b_data + le16_to_cpu(entry->e_value_offs),
-			size);
+		       size);
 	}
 	error = size;
 
 cleanup:
 	brelse(bh);
 	up_read(&EXT3_I(inode)->xattr_sem);
-
 	return error;
 }
 
+static int
+ext3_xattr_list_entries(struct inode *inode, struct ext3_xattr_entry *entry,
+			char *buffer, size_t buffer_size)
+{
+	size_t rest = buffer_size;
+
+	for (; !IS_LAST_ENTRY(entry); entry = EXT3_XATTR_NEXT(entry)) {
+		struct xattr_handler *handler =
+			ext3_xattr_handler(entry->e_name_index);
+
+		if (handler) {
+			size_t size = handler->list(inode, buffer, rest,
+						    entry->e_name,
+						    entry->e_name_len);
+			if (buffer) {
+				if (size > rest)
+					return -ERANGE;
+				buffer += size;
+			}
+			rest -= size;
+		}
+	}
+	return buffer_size - rest;
+}
+
 /*
  * ext3_xattr_list()
  *
@@ -265,9 +309,6 @@ int
 ext3_xattr_list(struct inode *inode, char *buffer, size_t buffer_size)
 {
 	struct buffer_head *bh = NULL;
-	struct ext3_xattr_entry *entry;
-	char *end;
-	size_t rest = buffer_size;
 	int error;
 
 	ea_idebug(inode, "buffer=%p, buffer_size=%ld",
@@ -283,50 +324,16 @@ ext3_xattr_list(struct inode *inode, cha
 	if (!bh)
 		goto cleanup;
 	ea_bdebug(bh, "b_count=%d, refcount=%d",
-		atomic_read(&(bh->b_count)), le32_to_cpu(HDR(bh)->h_refcount));
-	end = bh->b_data + bh->b_size;
-	if (HDR(bh)->h_magic != cpu_to_le32(EXT3_XATTR_MAGIC) ||
-	    HDR(bh)->h_blocks != cpu_to_le32(1)) {
-bad_block:	ext3_error(inode->i_sb, "ext3_xattr_list",
-			"inode %ld: bad block %d", inode->i_ino,
-			EXT3_I(inode)->i_file_acl);
+		atomic_read(&(bh->b_count)), le32_to_cpu(BHDR(bh)->h_refcount));
+	if (ext3_xattr_check_block(bh)) {
+		ext3_error(inode->i_sb, __FUNCTION__,
+			   "inode %ld: bad block %d", inode->i_ino,
+			   EXT3_I(inode)->i_file_acl);
 		error = -EIO;
 		goto cleanup;
 	}
-
-	/* check the on-disk data structure */
-	entry = FIRST_ENTRY(bh);
-	while (!IS_LAST_ENTRY(entry)) {
-		struct ext3_xattr_entry *next = EXT3_XATTR_NEXT(entry);
-
-		if ((char *)next >= end)
-			goto bad_block;
-		entry = next;
-	}
-	if (ext3_xattr_cache_insert(bh))
-		ea_idebug(inode, "cache insert failed");
-
-	/* list the attribute names */
-	for (entry = FIRST_ENTRY(bh); !IS_LAST_ENTRY(entry);
-	     entry = EXT3_XATTR_NEXT(entry)) {
-		struct xattr_handler *handler =
-			ext3_xattr_handler(entry->e_name_index);
-
-		if (handler) {
-			size_t size = handler->list(inode, buffer, rest,
-						    entry->e_name,
-						    entry->e_name_len);
-			if (buffer) {
-				if (size > rest) {
-					error = -ERANGE;
-					goto cleanup;
-				}
-				buffer += size;
-			}
-			rest -= size;
-		}
-	}
-	error = buffer_size - rest;  /* total size */
+	ext3_xattr_cache_insert(bh);
+	error = ext3_xattr_list_entries(inode, BFIRST(bh), buffer, buffer_size);
 
 cleanup:
 	brelse(bh);
@@ -366,8 +373,8 @@ ext3_xattr_release_block(handle_t *handl
 	struct mb_cache_entry *ce = NULL;
 
 	ce = mb_cache_entry_get(ext3_xattr_cache, bh->b_bdev, bh->b_blocknr);
-	if (HDR(bh)->h_refcount == cpu_to_le32(1)) {
-		ea_bdebug(bh, "freeing");
+	if (BHDR(bh)->h_refcount == cpu_to_le32(1)) {
+		ea_bdebug(bh, "refcount now=0; freeing");
 		if (ce)
 			mb_cache_entry_free(ce);
 		ext3_free_blocks(handle, inode, bh->b_blocknr, 1);
@@ -376,20 +383,137 @@ ext3_xattr_release_block(handle_t *handl
 	} else {
 		if (ext3_journal_get_write_access(handle, bh) == 0) {
 			lock_buffer(bh);
-			HDR(bh)->h_refcount = cpu_to_le32(
-				le32_to_cpu(HDR(bh)->h_refcount) - 1);
+			BHDR(bh)->h_refcount = cpu_to_le32(
+				le32_to_cpu(BHDR(bh)->h_refcount) - 1);
 			ext3_journal_dirty_metadata(handle, bh);
 			if (IS_SYNC(inode))
 				handle->h_sync = 1;
 			DQUOT_FREE_BLOCK(inode, 1);
 			unlock_buffer(bh);
-			ea_bdebug(bh, "refcount now=%d",
-				  le32_to_cpu(HDR(bh)->h_refcount) - 1);
+			ea_bdebug(bh, "refcount now=%d; releasing",
+				  le32_to_cpu(BHDR(bh)->h_refcount));
 		}
 		if (ce)
 			mb_cache_entry_release(ce);
 	}
 }
+
+struct ext3_xattr_info {
+	int name_index;
+	const char *name;
+	const void *value;
+	size_t value_len;
+};
+
+struct ext3_xattr_search {
+	struct ext3_xattr_entry *first;
+	void *base;
+	void *end;
+	struct ext3_xattr_entry *here;
+	int not_found;
+};
+
+static int
+ext3_xattr_set_entry(struct ext3_xattr_info *i, struct ext3_xattr_search *s)
+{
+	struct ext3_xattr_entry *last;
+	size_t free, min_offs = s->end - s->base, name_len = strlen(i->name);
+
+	/* Compute min_offs and last. */
+	last = s->first;
+	for (; !IS_LAST_ENTRY(last); last = EXT3_XATTR_NEXT(last)) {
+		if (!last->e_value_block && last->e_value_size) {
+			size_t offs = le16_to_cpu(last->e_value_offs);
+			if (offs < min_offs)
+				min_offs = offs;
+		}
+	}
+	free = min_offs - ((void *)last - s->base) - sizeof(__u32);
+	if (!s->not_found) {
+		if (!s->here->e_value_block && s->here->e_value_size) {
+			size_t size = le32_to_cpu(s->here->e_value_size);
+			free += EXT3_XATTR_SIZE(size);
+		}
+		free += EXT3_XATTR_LEN(name_len);
+	}
+	if (i->value) {
+		if (free < EXT3_XATTR_SIZE(i->value_len) ||
+		    free < EXT3_XATTR_LEN(name_len) +
+			   EXT3_XATTR_SIZE(i->value_len))
+			return -ENOSPC;
+	}
+
+	if (i->value && s->not_found) {
+		/* Insert the new name. */
+		size_t size = EXT3_XATTR_LEN(name_len);
+		size_t rest = (void *)last - (void *)s->here + sizeof(__u32);
+		memmove((void *)s->here + size, s->here, rest);
+		memset(s->here, 0, size);
+		s->here->e_name_index = i->name_index;
+		s->here->e_name_len = name_len;
+		memcpy(s->here->e_name, i->name, name_len);
+	} else {
+		if (!s->here->e_value_block && s->here->e_value_size) {
+			void *first_val = s->base + min_offs;
+			size_t offs = le16_to_cpu(s->here->e_value_offs);
+			void *val = s->base + offs;
+			size_t size = EXT3_XATTR_SIZE(
+				le32_to_cpu(s->here->e_value_size));
+
+			if (i->value && size == EXT3_XATTR_SIZE(i->value_len)) {
+				/* The old and the new value have the same
+				   size. Just replace. */
+				s->here->e_value_size =
+					cpu_to_le32(i->value_len);
+				memset(val + size - EXT3_XATTR_PAD, 0,
+				       EXT3_XATTR_PAD); /* Clear pad bytes. */
+				memcpy(val, i->value, i->value_len);
+				return 0;
+			}
+
+			/* Remove the old value. */
+			memmove(first_val + size, first_val, val - first_val);
+			memset(first_val, 0, size);
+			s->here->e_value_size = 0;
+			s->here->e_value_offs = 0;
+			min_offs += size;
+
+			/* Adjust all value offsets. */
+			last = s->first;
+			while (!IS_LAST_ENTRY(last)) {
+				size_t o = le16_to_cpu(last->e_value_offs);
+				if (!last->e_value_block &&
+				    last->e_value_size && o < offs)
+					last->e_value_offs =
+						cpu_to_le16(o + size);
+				last = EXT3_XATTR_NEXT(last);
+			}
+		}
+		if (!i->value) {
+			/* Remove the old name. */
+			size_t size = EXT3_XATTR_LEN(name_len);
+			last = ENTRY((void *)last - size);
+			memmove(s->here, (void *)s->here + size,
+				(void *)last - (void *)s->here + sizeof(__u32));
+			memset(last, 0, size);
+		}
+	}
+
+	if (i->value) {
+		/* Insert the new value. */
+		s->here->e_value_size = cpu_to_le32(i->value_len);
+		if (i->value_len) {
+			size_t size = EXT3_XATTR_SIZE(i->value_len);
+			void *val = s->base + min_offs - size;
+			s->here->e_value_offs = cpu_to_le16(min_offs - size);
+			memset(val + size - EXT3_XATTR_PAD, 0,
+			       EXT3_XATTR_PAD); /* Clear the pad bytes. */
+			memcpy(val, i->value, i->value_len);
+		}
+	}
+	return 0;
+}
+
 /*
  * ext3_xattr_set_handle()
  *
@@ -408,23 +532,24 @@ ext3_xattr_set_handle(handle_t *handle, 
 		      int flags)
 {
 	struct super_block *sb = inode->i_sb;
-	struct buffer_head *bh = NULL;
-	struct ext3_xattr_header *header = NULL;
-	struct ext3_xattr_entry *here, *last;
-	size_t name_len, free, min_offs = sb->s_blocksize;
-	int not_found = 1, error;
-	char *end;
+	struct buffer_head *old_bh = NULL, *new_bh = NULL;
+	struct ext3_xattr_info i = {
+		.name_index = name_index,
+		.name = name,
+		.value = value,
+		.value_len = value_len,
+	};
+	struct ext3_xattr_search s = {
+		.not_found = 1,
+	};
+	struct mb_cache_entry *ce = NULL;
+	int error;
+
+#define header ((struct ext3_xattr_header *)(s.base))
 
 	/*
 	 * header -- Points either into bh, or to a temporarily
 	 *           allocated buffer.
-	 * here -- The named entry found, or the place for inserting, within
-	 *         the block pointed to by header.
-	 * last -- Points right after the last named entry within the block
-	 *         pointed to by header.
-	 * min_offs -- The offset of the first value (values are aligned
-	 *             towards the end of the block).
-	 * end -- Points right after the block pointed to by header.
 	 */
 
 	ea_idebug(inode, "name=%d.%s, value=%p, value_len=%ld",
@@ -434,77 +559,38 @@ ext3_xattr_set_handle(handle_t *handle, 
 		return -EROFS;
 	if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
 		return -EPERM;
-	if (value == NULL)
-		value_len = 0;
-	if (name == NULL)
-		return -EINVAL;
-	name_len = strlen(name);
-	if (name_len > 255 || value_len > sb->s_blocksize)
-		return -ERANGE;
+	if (i.value == NULL)
+		i.value_len = 0;
 	down_write(&EXT3_I(inode)->xattr_sem);
 	if (EXT3_I(inode)->i_file_acl) {
 		/* The inode already has an extended attribute block. */
-		bh = sb_bread(sb, EXT3_I(inode)->i_file_acl);
+		old_bh = sb_bread(sb, EXT3_I(inode)->i_file_acl);
 		error = -EIO;
-		if (!bh)
+		if (!old_bh)
 			goto cleanup;
-		ea_bdebug(bh, "b_count=%d, refcount=%d",
-			atomic_read(&(bh->b_count)),
-			le32_to_cpu(HDR(bh)->h_refcount));
-		header = HDR(bh);
-		end = bh->b_data + bh->b_size;
-		if (header->h_magic != cpu_to_le32(EXT3_XATTR_MAGIC) ||
-		    header->h_blocks != cpu_to_le32(1)) {
-bad_block:		ext3_error(sb, "ext3_xattr_set",
+		ea_bdebug(old_bh, "b_count=%d, refcount=%d",
+			atomic_read(&(old_bh->b_count)),
+			le32_to_cpu(BHDR(old_bh)->h_refcount));
+		if (ext3_xattr_check_block(old_bh)) {
+bad_block:		ext3_error(sb, __FUNCTION__,
 				"inode %ld: bad block %d", inode->i_ino,
 				EXT3_I(inode)->i_file_acl);
 			error = -EIO;
 			goto cleanup;
 		}
 		/* Find the named attribute. */
-		here = FIRST_ENTRY(bh);
-		while (!IS_LAST_ENTRY(here)) {
-			struct ext3_xattr_entry *next = EXT3_XATTR_NEXT(here);
-			if ((char *)next >= end)
-				goto bad_block;
-			if (!here->e_value_block && here->e_value_size) {
-				size_t offs = le16_to_cpu(here->e_value_offs);
-				if (offs < min_offs)
-					min_offs = offs;
-			}
-			not_found = name_index - here->e_name_index;
-			if (!not_found)
-				not_found = name_len - here->e_name_len;
-			if (!not_found)
-				not_found = memcmp(name, here->e_name,name_len);
-			if (not_found <= 0)
-				break;
-			here = next;
-		}
-		last = here;
-		/* We still need to compute min_offs and last. */
-		while (!IS_LAST_ENTRY(last)) {
-			struct ext3_xattr_entry *next = EXT3_XATTR_NEXT(last);
-			if ((char *)next >= end)
-				goto bad_block;
-			if (!last->e_value_block && last->e_value_size) {
-				size_t offs = le16_to_cpu(last->e_value_offs);
-				if (offs < min_offs)
-					min_offs = offs;
-			}
-			last = next;
-		}
-
-		/* Check whether we have enough space left. */
-		free = min_offs - ((char*)last - (char*)header) - sizeof(__u32);
-	} else {
-		/* We will use a new extended attribute block. */
-		free = sb->s_blocksize -
-			sizeof(struct ext3_xattr_header) - sizeof(__u32);
-		here = last = NULL;  /* avoid gcc uninitialized warning. */
+		s.base = BHDR(old_bh);
+		s.first = BFIRST(old_bh);
+		s.end = old_bh->b_data + old_bh->b_size;
+		s.here = BFIRST(old_bh);
+		error = ext3_xattr_find_entry(&s.here, name_index, name,
+					      old_bh->b_size, 1);
+		if (error && error != -ENODATA)
+			goto cleanup;
+		s.not_found = error;
 	}
 
-	if (not_found) {
+	if (s.not_found) {
 		/* Request to remove a nonexistent attribute? */
 		error = -ENODATA;
 		if (flags & XATTR_REPLACE)
@@ -517,183 +603,90 @@ bad_block:		ext3_error(sb, "ext3_xattr_s
 		error = -EEXIST;
 		if (flags & XATTR_CREATE)
 			goto cleanup;
-		if (!here->e_value_block && here->e_value_size) {
-			size_t size = le32_to_cpu(here->e_value_size);
-
-			if (le16_to_cpu(here->e_value_offs) + size > 
-			    sb->s_blocksize || size > sb->s_blocksize)
-				goto bad_block;
-			free += EXT3_XATTR_SIZE(size);
-		}
-		free += EXT3_XATTR_LEN(name_len);
 	}
-	error = -ENOSPC;
-	if (free < EXT3_XATTR_LEN(name_len) + EXT3_XATTR_SIZE(value_len))
-		goto cleanup;
-
-	/* Here we know that we can set the new attribute. */
 
 	if (header) {
-		struct mb_cache_entry *ce;
-
-		/* assert(header == HDR(bh)); */
-		ce = mb_cache_entry_get(ext3_xattr_cache, bh->b_bdev,
-					bh->b_blocknr);
+		/* assert(header == BHDR(old_bh)); */
+		ce = mb_cache_entry_get(ext3_xattr_cache, old_bh->b_bdev,
+					old_bh->b_blocknr);
 		if (header->h_refcount == cpu_to_le32(1)) {
-			if (ce)
+			if (ce) {
 				mb_cache_entry_free(ce);
-			ea_bdebug(bh, "modifying in-place");
-			error = ext3_journal_get_write_access(handle, bh);
+				ce = NULL;
+			}
+			ea_bdebug(old_bh, "modifying in-place");
+			error = ext3_journal_get_write_access(handle, old_bh);
 			if (error)
 				goto cleanup;
-			lock_buffer(bh);
-			/* keep the buffer locked while modifying it. */
+			lock_buffer(old_bh);
+			error = ext3_xattr_set_entry(&i, &s);
+			if (!error) {
+				if (!IS_LAST_ENTRY(s.first))
+					ext3_xattr_rehash(header, s.here);
+				ext3_xattr_cache_insert(old_bh);
+			}
+			unlock_buffer(old_bh);
+			if (error == -EIO)
+				goto bad_block;
+			if (!error && old_bh && header == BHDR(old_bh)) {
+				error = ext3_journal_dirty_metadata(handle,
+								    old_bh);
+			}
+			if (error)
+				goto cleanup;
+			goto inserted;
 		} else {
-			int offset;
+			int offset = (char *)s.here - old_bh->b_data;
 
-			if (ce)
+			if (ce) {
 				mb_cache_entry_release(ce);
-			ea_bdebug(bh, "cloning");
-			header = kmalloc(bh->b_size, GFP_KERNEL);
+				ce = NULL;
+			}
+			ea_bdebug(old_bh, "cloning");
+			s.base = kmalloc(old_bh->b_size, GFP_KERNEL);
+			/*assert(header == s.base)*/
 			error = -ENOMEM;
 			if (header == NULL)
 				goto cleanup;
-			memcpy(header, HDR(bh), bh->b_size);
+			memcpy(header, BHDR(old_bh), old_bh->b_size);
+			s.first = ENTRY(header+1);
 			header->h_refcount = cpu_to_le32(1);
-			offset = (char *)here - bh->b_data;
-			here = ENTRY((char *)header + offset);
-			offset = (char *)last - bh->b_data;
-			last = ENTRY((char *)header + offset);
+			s.here = ENTRY(s.base + offset);
+			s.end = header + old_bh->b_size;
 		}
 	} else {
 		/* Allocate a buffer where we construct the new block. */
-		header = kmalloc(sb->s_blocksize, GFP_KERNEL);
+		s.base = kmalloc(sb->s_blocksize, GFP_KERNEL);
+		/*assert(header == s.base)*/
 		error = -ENOMEM;
 		if (header == NULL)
 			goto cleanup;
 		memset(header, 0, sb->s_blocksize);
-		end = (char *)header + sb->s_blocksize;
 		header->h_magic = cpu_to_le32(EXT3_XATTR_MAGIC);
 		header->h_blocks = header->h_refcount = cpu_to_le32(1);
-		last = here = ENTRY(header+1);
-	}
-
-	/* Iff we are modifying the block in-place, bh is locked here. */
-
-	if (not_found) {
-		/* Insert the new name. */
-		size_t size = EXT3_XATTR_LEN(name_len);
-		size_t rest = (char *)last - (char *)here;
-		memmove((char *)here + size, here, rest);
-		memset(here, 0, size);
-		here->e_name_index = name_index;
-		here->e_name_len = name_len;
-		memcpy(here->e_name, name, name_len);
-	} else {
-		if (!here->e_value_block && here->e_value_size) {
-			char *first_val = (char *)header + min_offs;
-			size_t offs = le16_to_cpu(here->e_value_offs);
-			char *val = (char *)header + offs;
-			size_t size = EXT3_XATTR_SIZE(
-				le32_to_cpu(here->e_value_size));
-
-			if (size == EXT3_XATTR_SIZE(value_len)) {
-				/* The old and the new value have the same
-				   size. Just replace. */
-				here->e_value_size = cpu_to_le32(value_len);
-				memset(val + size - EXT3_XATTR_PAD, 0,
-				       EXT3_XATTR_PAD); /* Clear pad bytes. */
-				memcpy(val, value, value_len);
-				goto skip_replace;
-			}
-
-			/* Remove the old value. */
-			memmove(first_val + size, first_val, val - first_val);
-			memset(first_val, 0, size);
-			here->e_value_offs = 0;
-			min_offs += size;
-
-			/* Adjust all value offsets. */
-			last = ENTRY(header+1);
-			while (!IS_LAST_ENTRY(last)) {
-				size_t o = le16_to_cpu(last->e_value_offs);
-				if (!last->e_value_block && o < offs)
-					last->e_value_offs =
-						cpu_to_le16(o + size);
-				last = EXT3_XATTR_NEXT(last);
-			}
-		}
-		if (value == NULL) {
-			/* Remove the old name. */
-			size_t size = EXT3_XATTR_LEN(name_len);
-			last = ENTRY((char *)last - size);
-			memmove(here, (char*)here + size,
-				(char*)last - (char*)here);
-			memset(last, 0, size);
-		}
-	}
-
-	if (value != NULL) {
-		/* Insert the new value. */
-		here->e_value_size = cpu_to_le32(value_len);
-		if (value_len) {
-			size_t size = EXT3_XATTR_SIZE(value_len);
-			char *val = (char *)header + min_offs - size;
-			here->e_value_offs =
-				cpu_to_le16((char *)val - (char *)header);
-			memset(val + size - EXT3_XATTR_PAD, 0,
-			       EXT3_XATTR_PAD); /* Clear the pad bytes. */
-			memcpy(val, value, value_len);
-		}
-	}
-
-skip_replace:
-	if (!IS_LAST_ENTRY(ENTRY(header+1)))
-		ext3_xattr_rehash(header, here);
-	if (bh && header == HDR(bh)) {
-		/* we were modifying in-place. */
-		unlock_buffer(bh);
-		error = ext3_journal_dirty_metadata(handle, bh);
-		if (error)
-			goto cleanup;
+		s.first = ENTRY(header+1);
+		s.here = ENTRY(header+1);
+		s.end = (void *)header + sb->s_blocksize;
 	}
-	error = ext3_xattr_set_handle2(handle, inode, bh,
-				       IS_LAST_ENTRY(ENTRY(header+1)) ?
-				       NULL : header);
 
-cleanup:
-	brelse(bh);
-	if (!(bh && header == HDR(bh)))
-		kfree(header);
-	up_write(&EXT3_I(inode)->xattr_sem);
-
-	return error;
-}
-
-/*
- * Second half of ext3_xattr_set_handle(): Update the file system.
- */
-static int
-ext3_xattr_set_handle2(handle_t *handle, struct inode *inode,
-		       struct buffer_head *old_bh,
-		       struct ext3_xattr_header *header)
-{
-	struct super_block *sb = inode->i_sb;
-	struct buffer_head *new_bh = NULL;
-	struct mb_cache_entry *ce = NULL;
-	int error;
+	error = ext3_xattr_set_entry(&i, &s);
+	if (error == -EIO)
+		goto bad_block;
+	if (error)
+		goto cleanup;
+	if (!IS_LAST_ENTRY(s.first))
+		ext3_xattr_rehash(header, s.here);
 
-	if (header) {
+inserted:
+	if (!IS_LAST_ENTRY(s.first)) {
 		new_bh = ext3_xattr_cache_find(inode, header, &ce);
 		if (new_bh) {
 			/* We found an identical block in the cache. */
 			if (new_bh == old_bh)
-				ea_bdebug(new_bh, "keeping this block");
+				ea_bdebug(new_bh, "keeping");
 			else {
 				/* The old block is released after updating
 				   the inode. */
-				ea_bdebug(new_bh, "reusing block");
-
 				error = -EDQUOT;
 				if (DQUOT_ALLOC_BLOCK(inode, 1))
 					goto cleanup;
@@ -701,25 +694,23 @@ ext3_xattr_set_handle2(handle_t *handle,
 				if (error)
 					goto cleanup;
 				lock_buffer(new_bh);
-				HDR(new_bh)->h_refcount = cpu_to_le32(1 +
-					le32_to_cpu(HDR(new_bh)->h_refcount));
-				ea_bdebug(new_bh, "refcount now=%d",
-					le32_to_cpu(HDR(new_bh)->h_refcount));
+				BHDR(new_bh)->h_refcount = cpu_to_le32(1 +
+					le32_to_cpu(BHDR(new_bh)->h_refcount));
+				ea_bdebug(new_bh, "reusing; refcount now=%d",
+					le32_to_cpu(BHDR(new_bh)->h_refcount));
 				unlock_buffer(new_bh);
-				error = ext3_journal_dirty_metadata(handle, new_bh);
+				error = ext3_journal_dirty_metadata(handle,
+								    new_bh);
 				if (error)
 					goto cleanup;
 			}
 			mb_cache_entry_release(ce);
 			ce = NULL;
-		} else if (old_bh && header == HDR(old_bh)) {
+		} else if (old_bh && header == BHDR(old_bh)) {
 			/* We were modifying this block in-place. */
-
-			/* Keep this block. No need to lock the block as we
-			 * don't need to change the reference count. */
+			ea_bdebug(old_bh, "keeping this block");
 			new_bh = old_bh;
 			get_bh(new_bh);
-			ext3_xattr_cache_insert(new_bh);
 		} else {
 			/* We need to allocate a new block */
 			int goal = le32_to_cpu(
@@ -748,11 +739,10 @@ getblk_failed:
 			set_buffer_uptodate(new_bh);
 			unlock_buffer(new_bh);
 			ext3_xattr_cache_insert(new_bh);
-
-			ext3_xattr_update_super_block(handle, sb);
 			error = ext3_journal_dirty_metadata(handle, new_bh);
 			if (error)
 				goto cleanup;
+			ext3_xattr_update_super_block(handle, sb);
 		}
 	}
 
@@ -763,21 +753,23 @@ getblk_failed:
 	if (IS_SYNC(inode))
 		handle->h_sync = 1;
 
-	error = 0;
-	if (old_bh && old_bh != new_bh) {
-		/*
-		 * If there was an old block, and we are no longer using it,
-		 * release the old block.
-		 */
+	/* Drop the previous xattr block. */
+	if (old_bh && old_bh != new_bh)
 		ext3_xattr_release_block(handle, inode, old_bh);
-	}
+	error = 0;
 
 cleanup:
 	if (ce)
 		mb_cache_entry_release(ce);
 	brelse(new_bh);
+	brelse(old_bh);
+	if (!(old_bh && header == BHDR(old_bh)))
+		kfree(header);
+	up_write(&EXT3_I(inode)->xattr_sem);
 
 	return error;
+
+#undef header
 }
 
 /*
@@ -832,14 +824,14 @@ ext3_xattr_delete_inode(handle_t *handle
 		goto cleanup;
 	bh = sb_bread(inode->i_sb, EXT3_I(inode)->i_file_acl);
 	if (!bh) {
-		ext3_error(inode->i_sb, "ext3_xattr_delete_inode",
+		ext3_error(inode->i_sb, __FUNCTION__,
 			"inode %ld: block %d read error", inode->i_ino,
 			EXT3_I(inode)->i_file_acl);
 		goto cleanup;
 	}
-	if (HDR(bh)->h_magic != cpu_to_le32(EXT3_XATTR_MAGIC) ||
-	    HDR(bh)->h_blocks != cpu_to_le32(1)) {
-		ext3_error(inode->i_sb, "ext3_xattr_delete_inode",
+	if (BHDR(bh)->h_magic != cpu_to_le32(EXT3_XATTR_MAGIC) ||
+	    BHDR(bh)->h_blocks != cpu_to_le32(1)) {
+		ext3_error(inode->i_sb, __FUNCTION__,
 			"inode %ld: bad block %d", inode->i_ino,
 			EXT3_I(inode)->i_file_acl);
 		goto cleanup;
@@ -871,30 +863,29 @@ ext3_xattr_put_super(struct super_block 
  *
  * Returns 0, or a negative error number on failure.
  */
-static int
+static void
 ext3_xattr_cache_insert(struct buffer_head *bh)
 {
-	__u32 hash = le32_to_cpu(HDR(bh)->h_hash);
+	__u32 hash = le32_to_cpu(BHDR(bh)->h_hash);
 	struct mb_cache_entry *ce;
 	int error;
 
 	ce = mb_cache_entry_alloc(ext3_xattr_cache);
-	if (!ce)
-		return -ENOMEM;
+	if (!ce) {
+		ea_bdebug(bh, "out of memory");
+		return;
+	}
 	error = mb_cache_entry_insert(ce, bh->b_bdev, bh->b_blocknr, &hash);
 	if (error) {
 		mb_cache_entry_free(ce);
 		if (error == -EBUSY) {
-			ea_bdebug(bh, "already in cache (%d cache entries)",
-				atomic_read(&ext3_xattr_cache->c_entry_count));
+			ea_bdebug(bh, "already in cache");
 			error = 0;
 		}
 	} else {
-		ea_bdebug(bh, "inserting [%x] (%d cache entries)", (int)hash,
-			  atomic_read(&ext3_xattr_cache->c_entry_count));
+		ea_bdebug(bh, "inserting [%x]", (int)hash);
 		mb_cache_entry_release(ce);
 	}
-	return error;
 }
 
 /*
@@ -967,16 +958,16 @@ again:
 		}
 		bh = sb_bread(inode->i_sb, ce->e_block);
 		if (!bh) {
-			ext3_error(inode->i_sb, "ext3_xattr_cache_find",
+			ext3_error(inode->i_sb, __FUNCTION__,
 				"inode %ld: block %ld read error",
 				inode->i_ino, (unsigned long) ce->e_block);
-		} else if (le32_to_cpu(HDR(bh)->h_refcount) >
+		} else if (le32_to_cpu(BHDR(bh)->h_refcount) >=
 				EXT3_XATTR_REFCOUNT_MAX) {
-			ea_idebug(inode, "block %ld refcount %d>%d",
+			ea_idebug(inode, "block %ld refcount %d>=%d",
 				  (unsigned long) ce->e_block,
-				  le32_to_cpu(HDR(bh)->h_refcount),
+				  le32_to_cpu(BHDR(bh)->h_refcount),
 					  EXT3_XATTR_REFCOUNT_MAX);
-		} else if (ext3_xattr_cmp(header, HDR(bh)) == 0) {
+		} else if (ext3_xattr_cmp(header, BHDR(bh)) == 0) {
 			*pce = ce;
 			return bh;
 		}
_