e2fsck: add support for large xattrs in external inodes

Message ID 015F68EE-7A9F-4111-A5DD-0F3FFE42F726@dilger.ca
State Superseded
Headers show

Commit Message

Andreas Dilger April 13, 2017, 8:10 p.m.
Add support for the INCOMPAT_EA_INODE feature, which stores large
extended attributes into an external inode instead of data blocks.
The inode is referenced by the e_value_inum field (formerly the
unused e_value_block field) from the extent header, and stores the
xattr data starting at byte offset 0 in the inode data block.

The xattr inode stores the referring inode number in its i_mtime,
and the parent i_generation in its own i_generation, so that there
is a solid linkage between the two that e2fsck can verify.  The
xattr inode is itself marked with EXT4_EA_INODE_FL as well.

Signed-off-by: Kalpak Shah <kalpak@clusterfs.com>
Signed-off-by: Andreas Dilger <andreas.dilger@intel.com>
---

This patch is still based on the old "maint" branch, and is in the
middle of a patch series, so will not apply to master e2fsprogs at
this point, but is mostly for review with the matching ext4 patch.

 debugfs/debugfs.c          |  16 +++-
 e2fsck/e2fsck.h            |   1 +
 e2fsck/pass1.c             | 194 +++++++++++++++++++++++++++++++++++++--------
 e2fsck/pass4.c             |  17 ++++
 e2fsck/problem.c           |  21 +++++
 e2fsck/problem.h           |  13 +++
 lib/e2p/feature.c          |   2 +
 lib/ext2fs/ext2_ext_attr.h |   5 +-
 lib/ext2fs/ext2_fs.h       |   3 +-
 lib/ext2fs/ext2fs.h        |   2 +
 lib/ext2fs/ext_attr.c      |  27 +++++--
 lib/ext2fs/swapfs.c        |   2 +-
 misc/mke2fs.c              |   1 +
 misc/tune2fs.c             |   5 ++
 14 files changed, 263 insertions(+), 46 deletions(-)

--
1.8.0



Cheers, Andreas

Patch

diff --git a/debugfs/debugfs.c b/debugfs/debugfs.c
index 5423634..5baa93c 100644
--- a/debugfs/debugfs.c
+++ b/debugfs/debugfs.c
@@ -550,22 +550,32 @@  static void internal_dump_inode_extra(FILE *out,
 		end = (char *) inode + EXT2_INODE_SIZE(current_fs->super);
 		start = (char *) magic + sizeof(__u32);
 		entry = (struct ext2_ext_attr_entry *) start;
+
 		while (!EXT2_EXT_IS_LAST_ENTRY(entry)) {
 			struct ext2_ext_attr_entry *next =
 				EXT2_EXT_ATTR_NEXT(entry);
 			char *name = EXT2_EXT_ATTR_NAME(entry);
 			char *value = start + entry->e_value_offs;
+			char ea_inode =
+				EXT2_HAS_INCOMPAT_FEATURE(current_fs->super,
+					EXT4_FEATURE_INCOMPAT_EA_INODE) &&
+					entry->e_value_offs == 0 &&
+					entry->e_value_inum != 0;

 			if (name + entry->e_name_len >= end ||
-			    value + entry->e_value_size >= end ||
-			    (char *) next >= end) {
+			    (!ea_inode && value + entry->e_value_size >= end) ||
+			    (char *)next >= end) {
 				fprintf(out, "invalid EA entry in inode\n");
 				return;
 			}
 			fprintf(out, "  ");
 			dump_xattr_string(out, name, entry->e_name_len);
 			fprintf(out, " = \"");
-			dump_xattr_string(out, value, entry->e_value_size);
+			if (ea_inode)
+				fprintf(out, "inode <%u>", entry->e_value_inum);
+			else
+				dump_xattr_string(out, value,
+						  entry->e_value_size);
 			fprintf(out, "\" (%u)\n", entry->e_value_size);
 			entry = next;
 		}
diff --git a/e2fsck/e2fsck.h b/e2fsck/e2fsck.h
index fca41a1..a3e6c63 100644
--- a/e2fsck/e2fsck.h
+++ b/e2fsck/e2fsck.h
@@ -279,6 +279,7 @@  struct e2fsck_struct {
 	ext2fs_inode_bitmap inode_bb_map; /* Inodes which are in bad blocks */
 	ext2fs_inode_bitmap inode_imagic_map; /* AFS inodes */
 	ext2fs_inode_bitmap inode_reg_map; /* Inodes which are regular files*/
+	ext2fs_inode_bitmap inode_ea_map; /* EA inodes which are non-orphan */

 	ext2fs_block_bitmap block_found_map; /* Blocks which are in use */
 	ext2fs_block_bitmap block_dup_map; /* Blks referenced more than once */
diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c
index e171df4..e1d5c02 100644
--- a/e2fsck/pass1.c
+++ b/e2fsck/pass1.c
@@ -28,6 +28,7 @@ 
  * 	- A bitmap of which blocks are in use.		(block_found_map)
  * 	- A bitmap of which blocks are in use by two inodes	(block_dup_map)
  * 	- The data blocks of the directory inodes.	(dir_map)
+ *	- A bitmap of EA inodes.			(inode_ea_map)
  *
  * Pass 1 is designed to stash away enough information so that the
  * other passes should not need to read in the inode information
@@ -272,6 +273,120 @@  static void check_size(e2fsck_t ctx, struct problem_context *pctx)
 	e2fsck_write_inode(ctx, pctx->ino, pctx->inode, "pass1");
 }

+static void e2fsck_block_alloc_stats(ext2_filsys fs, blk64_t blk, int inuse)
+{
+	e2fsck_t ctx = (e2fsck_t) fs->priv_data;
+
+	if (ctx->block_found_map) {
+		if (inuse > 0)
+			ext2fs_mark_block_bitmap2(ctx->block_found_map, blk);
+		else
+			ext2fs_unmark_block_bitmap2(ctx->block_found_map, blk);
+	}
+}
+
+static void mark_inode_ea_map(e2fsck_t ctx, struct problem_context *pctx,
+			      ext2_ino_t ino)
+{
+	if (!ctx->inode_ea_map) {
+		pctx->errcode = ext2fs_allocate_inode_bitmap(ctx->fs,
+					 _("EA inode map"),
+					 &ctx->inode_ea_map);
+		if (pctx->errcode) {
+			fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR,
+				    pctx);
+			exit(1);
+		}
+	}
+
+	ext2fs_mark_inode_bitmap2(ctx->inode_ea_map, ino);
+}
+
+/*
+ * Delete an EA entry. If this is the last entry to be deleted, then i_file_acl
+ * must have been freed, so we must update e2fsck block statistics and set
+ * i_file_acl_deleted.
+ * When we delete the entry successfully, this function returns 0, else
+ * non-zero value.
+ */
+
+static int e2fsck_ea_entry_delete(e2fsck_t ctx,
+				  struct ext2_ext_attr_entry *entry,
+				  struct problem_context *pctx,
+				  int *i_file_acl_deleted, problem_t prob)
+{
+	blk_t i_file_acl = pctx->inode->i_file_acl;
+	int err = 1;
+
+	pctx->num = entry->e_value_inum;
+
+	if (fix_problem(ctx, prob, pctx)) {
+		/* Delete corrupt EA entry */
+		err = ext2fs_attr_set(ctx->fs, pctx->ino, pctx->inode,
+				      entry->e_name_index, entry->e_name,
+				      0, 0, 0);
+		if (err == 0) {
+			if (i_file_acl && pctx->inode->i_file_acl == 0) {
+				e2fsck_block_alloc_stats(ctx->fs, i_file_acl,
+							 -1);
+				*i_file_acl_deleted = 1;
+			}
+			return 0;
+		}
+	}
+
+	return err;
+}
+
+/*
+ * Check validity of EA inode. Return 0 if EA inode is valid, nonzero otherwise.
+ */
+static int check_large_ea_inode(e2fsck_t ctx, struct ext2_ext_attr_entry *entry,
+				struct problem_context *pctx,
+				int *i_file_acl_deleted)
+{
+	struct ext2_inode inode;
+	int ret = 0;
+
+	/* Check if inode is within valid range */
+	if ((entry->e_value_inum < EXT2_FIRST_INODE(ctx->fs->super)) ||
+	    (entry->e_value_inum > ctx->fs->super->s_inodes_count)) {
+		ret = e2fsck_ea_entry_delete(ctx, entry, pctx,
+					     i_file_acl_deleted,
+					     PR_1_ATTR_VALUE_EA_INODE);
+		/* If user refuses to delete this entry, caller may try to set
+		 * the bit for this out-of-bound inode in inode_ea_map, so
+		 * always return failure */
+		return 1;
+	}
+
+	e2fsck_read_inode(ctx, entry->e_value_inum, &inode, "pass1");
+	if (!(inode.i_flags & EXT4_EA_INODE_FL)) {
+		/* If EXT4_EA_INODE_FL flag is not present but back-pointer
+		 * matches then we should set this flag */
+		if (inode.i_mtime == pctx->ino &&
+		    inode.i_generation == pctx->inode->i_generation &&
+		    fix_problem(ctx, PR_1_ATTR_SET_EA_INODE_FL, pctx)) {
+			inode.i_flags |= EXT4_EA_INODE_FL;
+			ext2fs_write_inode(ctx->fs, entry->e_value_inum,&inode);
+		} else {
+			ret = e2fsck_ea_entry_delete(ctx, entry, pctx,
+						     i_file_acl_deleted,
+						     PR_1_ATTR_NO_EA_INODE_FL);
+			goto out;
+		}
+	} else if (inode.i_mtime != pctx->ino ||
+		   inode.i_generation != pctx->inode->i_generation) {
+		ret = e2fsck_ea_entry_delete(ctx, entry, pctx,
+					     i_file_acl_deleted,
+					     PR_1_ATTR_INVAL_EA_INODE);
+		goto out;
+	}
+
+out:
+	return ret;
+}
+
 static void check_ea_in_inode(e2fsck_t ctx, struct problem_context *pctx)
 {
 	struct ext2_super_block *sb = ctx->fs->super;
@@ -315,16 +430,26 @@  static void check_ea_in_inode(e2fsck_t ctx, struct problem_context *pctx)
 			goto fix;
 		}

-		/* e_value_block must be 0 in inode's ea */
-		if (entry->e_value_block != 0) {
-			pctx->num = entry->e_value_block;
-			problem = PR_1_ATTR_VALUE_BLOCK;
-			goto fix;
+		if (entry->e_value_inum == 0) {
+			/* check value size */
+			if (entry->e_value_size > remain) {
+				pctx->num = entry->e_value_size;
+				problem = PR_1_ATTR_VALUE_SIZE;
+				goto fix;
+			}
+		} else {
+			int ret, tmp;
+
+			ret = check_large_ea_inode(ctx, entry, pctx, &tmp);
+			if (ret == 0)
+				mark_inode_ea_map(ctx, pctx,
+						  entry->e_value_inum);
 		}

 		/* Value size cannot be larger than EA space in inode */
 		if (entry->e_value_offs > storage_size ||
-		    entry->e_value_offs + entry->e_value_size > storage_size) {
+		    (entry->e_value_inum == 0 &&
+		    entry->e_value_offs + entry->e_value_size > storage_size)) {
 			problem = PR_1_INODE_EA_BAD_VALUE;
 			goto fix;
 		}
@@ -339,7 +464,10 @@  static void check_ea_in_inode(e2fsck_t ctx, struct problem_context *pctx)
 			goto fix;
 		}

-		remain -= entry->e_value_size;
+		/* If EA value is stored in external inode then it does not
+		 * consume space here */
+		if (entry->e_value_inum == 0)
+			remain -= entry->e_value_size;

 		entry = EXT2_EXT_ATTR_NEXT(entry);
 	}
@@ -628,7 +756,7 @@  int e2fsck_pass1_delete_attr(e2fsck_t ctx, struct ext2_inode_large *inode,
 		if (EXT2_EXT_IS_LAST_ENTRY(entry)) {
 			if (in_inode) {
 				entry = entry_blk;
-			        len = sizeof(entry->e_name);
+				len = sizeof(entry->e_name);
 				entry_size = ext2fs_attr_get_next_attr(entry,
 							index, name, len, 1);
 				in_inode = 0;
@@ -1774,6 +1902,7 @@  static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx,
 	struct ext2_ext_attr_entry *entry;
 	int		count;
 	region_t	region = 0;
+	int ret;

 	blk = ext2fs_file_acl_block(fs, inode);
 	if (blk == 0)
@@ -1896,19 +2025,30 @@  static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx,
 				goto clear_extattr;
 			break;
 		}
-		if (entry->e_value_block != 0) {
-			if (fix_problem(ctx, PR_1_EA_BAD_VALUE, pctx))
-				goto clear_extattr;
-		}
-		if (entry->e_value_offs + entry->e_value_size > fs->blocksize) {
-			if (fix_problem(ctx, PR_1_EA_BAD_VALUE, pctx))
-				goto clear_extattr;
-			break;
-		}
-		if (entry->e_value_size &&
-		    region_allocate(region, entry->e_value_offs,
-				    EXT2_EXT_ATTR_SIZE(entry->e_value_size))) {
-			if (fix_problem(ctx, PR_1_EA_ALLOC_COLLISION, pctx))
+		if (entry->e_value_inum == 0) {
+			if (entry->e_value_offs + entry->e_value_size >
+			    fs->blocksize) {
+				if (fix_problem(ctx, PR_1_EA_BAD_VALUE, pctx))
+					goto clear_extattr;
+				break;
+			}
+			if (entry->e_value_size &&
+			    region_allocate(region, entry->e_value_offs,
+					    EXT2_EXT_ATTR_SIZE(entry->e_value_size))) {
+				if (fix_problem(ctx, PR_1_EA_ALLOC_COLLISION,
+						pctx))
+					goto clear_extattr;
+			}
+		} else {
+			int i_file_acl_deleted = 0;
+
+			ret = check_large_ea_inode(ctx, entry, pctx,
+						   &i_file_acl_deleted);
+			if (ret == 0)
+				mark_inode_ea_map(ctx, pctx,
+						  entry->e_value_inum);
+
+			if (i_file_acl_deleted)
 				goto clear_extattr;
 		}

@@ -3338,18 +3478,6 @@  static errcode_t e2fsck_get_alloc_block(ext2_filsys fs, blk64_t goal,
 	return (0);
 }

-static void e2fsck_block_alloc_stats(ext2_filsys fs, blk64_t blk, int inuse)
-{
-	e2fsck_t ctx = (e2fsck_t) fs->priv_data;
-
-	if (ctx->block_found_map) {
-		if (inuse > 0)
-			ext2fs_mark_block_bitmap2(ctx->block_found_map, blk);
-		else
-			ext2fs_unmark_block_bitmap2(ctx->block_found_map, blk);
-	}
-}
-
 void e2fsck_use_inode_shortcuts(e2fsck_t ctx, int use_shortcuts)
 {
 	ext2_filsys fs = ctx->fs;
diff --git a/e2fsck/pass4.c b/e2fsck/pass4.c
index 09714ad..81fd846 100644
--- a/e2fsck/pass4.c
+++ b/e2fsck/pass4.c
@@ -11,6 +11,7 @@ 
  * Pass 4 frees the following data structures:
  * 	- A bitmap of which inodes are in bad blocks.	(inode_bb_map)
  * 	- A bitmap of which inodes are imagic inodes.	(inode_imagic_map)
+ *	- A bitmap of EA inodes.			(inode_ea_map)
  */

 #include "config.h"
@@ -40,6 +41,20 @@  static int disconnect_inode(e2fsck_t ctx, ext2_ino_t i,
 	} else {
 		e2fsck_read_inode(ctx, i, inode, "pass4: disconnect_inode");
 	}
+
+	if (inode->i_flags & EXT4_EA_INODE_FL) {
+		if (ext2fs_test_inode_bitmap2(ctx->inode_ea_map, i)) {
+			ext2fs_icount_store(ctx->inode_count, i, 1);
+			return 0;
+		} else {
+			/* Zero the link count so that when inode is linked to
+			 * lost+found it has correct link count */
+			inode->i_links_count = 0;
+			e2fsck_write_inode(ctx, i, inode, "disconnect_inode");
+			ext2fs_icount_store(ctx->inode_link_info, i, 0);
+		}
+	}
+
 	clear_problem_context(&pctx);
 	pctx.ino = i;
 	pctx.inode = inode;
@@ -185,6 +200,8 @@  void e2fsck_pass4(e2fsck_t ctx)
 	ext2fs_free_icount(ctx->inode_link_info); ctx->inode_link_info = 0;
 	ext2fs_free_icount(ctx->inode_count); ctx->inode_count = 0;
 	ext2fs_free_icount(ctx->inode_badness); ctx->inode_badness = 0;
+	ext2fs_free_inode_bitmap(ctx->inode_ea_map);
+	ctx->inode_ea_map = 0;
 	ext2fs_free_inode_bitmap(ctx->inode_bb_map);
 	ctx->inode_bb_map = 0;
 	ext2fs_free_inode_bitmap(ctx->inode_imagic_map);
diff --git a/e2fsck/problem.c b/e2fsck/problem.c
index dd68dfe..812cc18 100644
--- a/e2fsck/problem.c
+++ b/e2fsck/problem.c
@@ -1035,6 +1035,27 @@  static struct e2fsck_problem problem_table[] = {
 	  N_("@i %i creation time (%t) invalid.\n"),
 	  PROMPT_CLEAR, PR_PREEN_OK | PR_NO_OK },

+	/* Inode has illegal extended attribute value inode */
+	{ PR_1_ATTR_VALUE_EA_INODE,
+	  N_("@i %i has @I @a value @i %N.\n"),
+	  PROMPT_FIX, PR_PREEN_OK },
+
+	/* Invalid backpointer from extended attribute inode to parent inode */
+	{ PR_1_ATTR_INVAL_EA_INODE,
+	  N_("@n backpointer from @a @i %N to parent @i %i.\n"),
+	  PROMPT_CLEAR, PR_PREEN_OK },
+
+	/* Inode has invalid extended attribute. EA inode missing
+	 * EA_INODE flag. */
+	{ PR_1_ATTR_NO_EA_INODE_FL,
+	  N_("@i %i has @n @a. EA @i %N missing EA_INODE flag.\n"),
+	  PROMPT_CLEAR, PR_PREEN_OK },
+
+	/* EA inode for parent inode missing EA_INODE flag. */
+	{ PR_1_ATTR_SET_EA_INODE_FL,
+	  N_("EA @i %N for parent @i %i missing EA_INODE flag.\n "),
+	  PROMPT_FIX, PR_PREEN_OK },
+

 	/* Pass 1b errors */

diff --git a/e2fsck/problem.h b/e2fsck/problem.h
index 8b57560..5d71557 100644
--- a/e2fsck/problem.h
+++ b/e2fsck/problem.h
@@ -621,6 +621,19 @@  struct problem_context {
 /* invalid inode creation time */
 #define PR_1_CRTIME_BAD			0x010089

+/* Inode has illegal EA value inode */
+#define PR_1_ATTR_VALUE_EA_INODE	0x01008A
+
+/* Invalid backpointer from EA inode to parent inode */
+#define PR_1_ATTR_INVAL_EA_INODE	0x01008B
+
+/* Parent inode has invalid EA entry. EA inode does not have
+ * EXT4_EA_INODE_FL flag. Delete EA entry? */
+#define PR_1_ATTR_NO_EA_INODE_FL	0x01008C
+
+/* EA inode for parent inode does not have EXT4_EA_INODE_FL flag */
+#define PR_1_ATTR_SET_EA_INODE_FL	0x01008D
+

 /*
  * Pass 1b errors
diff --git a/lib/e2p/feature.c b/lib/e2p/feature.c
index 1d3e689..2ff95dc 100644
--- a/lib/e2p/feature.c
+++ b/lib/e2p/feature.c
@@ -91,6 +91,8 @@  static struct feature feature_list[] = {
 			"flex_bg"},
 	{       E2P_FEATURE_INCOMPAT, EXT4_FEATURE_INCOMPAT_EA_INODE,
 			"ea_inode"},
+	{	E2P_FEATURE_INCOMPAT, EXT4_FEATURE_INCOMPAT_EA_INODE,
+			"large_xattr" },
 	{       E2P_FEATURE_INCOMPAT, EXT4_FEATURE_INCOMPAT_DIRDATA,
 			"dirdata"},
 	{       E2P_FEATURE_INCOMPAT, EXT4_FEATURE_INCOMPAT_LARGEDIR,
diff --git a/lib/ext2fs/ext2_ext_attr.h b/lib/ext2fs/ext2_ext_attr.h
index 755024e..2cd17fc 100644
--- a/lib/ext2fs/ext2_ext_attr.h
+++ b/lib/ext2fs/ext2_ext_attr.h
@@ -30,7 +30,7 @@  struct ext2_ext_attr_entry {
 	__u8	e_name_len;	/* length of name */
 	__u8	e_name_index;	/* attribute name index */
 	__u16	e_value_offs;	/* offset in disk block of value */
-	__u32	e_value_block;	/* disk block attribute is stored on (n/i) */
+	__u32	e_value_inum;	/* inode in which the value is stored */
 	__u32	e_value_size;	/* size of attribute value */
 	__u32	e_hash;		/* hash value of name and value */
 #if 1
@@ -48,6 +48,9 @@  struct ext2_xattr_ibody_header {
 		    EXT2_GOOD_OLD_INODE_SIZE + (inode)->i_extra_isize))
 #define ENTRY(ptr) ((struct ext2_ext_attr_entry *)(ptr))

+#define EXT4_XATTR_MIN_LARGE_EA_SIZE(b)	((b) >> 1)
+#define EXT4_XATTR_MAX_LARGE_EA_SIZE	(1024 * 1024)
+
 /* Name indexes */
 #define EXT2_ATTR_INDEX_USER			1
 #define EXT2_ATTR_INDEX_POSIX_ACL_ACCESS	2
diff --git a/lib/ext2fs/ext2_fs.h b/lib/ext2fs/ext2_fs.h
index 1d591ec..cbc230f 100644
--- a/lib/ext2fs/ext2_fs.h
+++ b/lib/ext2fs/ext2_fs.h
@@ -741,7 +741,8 @@  struct ext2_super_block {

 #define EXT2_FEATURE_COMPAT_SUPP	0
 #define EXT2_FEATURE_INCOMPAT_SUPP    (EXT2_FEATURE_INCOMPAT_FILETYPE| \
-				       EXT4_FEATURE_INCOMPAT_MMP)
+				       EXT4_FEATURE_INCOMPAT_MMP| \
+				       EXT4_FEATURE_INCOMPAT_EA_INODE)
 #define EXT2_FEATURE_RO_COMPAT_SUPP	(EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER| \
 					 EXT2_FEATURE_RO_COMPAT_LARGE_FILE| \
 					 EXT4_FEATURE_RO_COMPAT_DIR_NLINK| \
diff --git a/lib/ext2fs/ext2fs.h b/lib/ext2fs/ext2fs.h
index 15b4ee5..501b610 100644
--- a/lib/ext2fs/ext2fs.h
+++ b/lib/ext2fs/ext2fs.h
@@ -577,6 +577,7 @@  typedef struct ext2_icount *ext2_icount_t;
 					 EXT3_FEATURE_INCOMPAT_EXTENTS|\
 					 EXT4_FEATURE_INCOMPAT_FLEX_BG|\
 					 EXT4_FEATURE_INCOMPAT_MMP|\
+					 EXT4_FEATURE_INCOMPAT_EA_INODE|\
 					 EXT4_FEATURE_INCOMPAT_64BIT)
 #else
 #define EXT2_LIB_FEATURE_INCOMPAT_SUPP	(EXT2_FEATURE_INCOMPAT_FILETYPE|\
@@ -586,6 +587,7 @@  typedef struct ext2_icount *ext2_icount_t;
 					 EXT3_FEATURE_INCOMPAT_EXTENTS|\
 					 EXT4_FEATURE_INCOMPAT_FLEX_BG|\
 					 EXT4_FEATURE_INCOMPAT_MMP|\
+					 EXT4_FEATURE_INCOMPAT_EA_INODE|\
 					 EXT4_FEATURE_INCOMPAT_64BIT)
 #endif
 #ifdef CONFIG_QUOTA
diff --git a/lib/ext2fs/ext_attr.c b/lib/ext2fs/ext_attr.c
index e69c087..a9d0b62 100644
--- a/lib/ext2fs/ext_attr.c
+++ b/lib/ext2fs/ext_attr.c
@@ -46,7 +46,7 @@  __u32 ext2fs_ext_attr_hash_entry(struct ext2_ext_attr_entry *entry, void *data)
 	}

 	/* The hash needs to be calculated on the data in little-endian. */
-	if (entry->e_value_block == 0 && entry->e_value_size != 0) {
+	if (entry->e_value_inum == 0 && entry->e_value_size != 0) {
 		__u32 *value = (__u32 *)data;
 		for (n = (entry->e_value_size + EXT2_EXT_ATTR_ROUND) >>
 			 EXT2_EXT_ATTR_PAD_BITS; n; n--) {
@@ -209,6 +209,7 @@  struct ext2_attr_ibody_find {
 };

 struct ext2_attr_block_find {
+	ext2_ino_t ino;
 	struct ext2_attr_search s;
 	char *block;
 };
@@ -221,7 +222,7 @@  void ext2fs_attr_shift_entries(struct ext2_ext_attr_entry *entry,

 	/* Adjust the value offsets of the entries */
 	for (; !EXT2_EXT_IS_LAST_ENTRY(last); last = EXT2_EXT_ATTR_NEXT(last)) {
-		if (!last->e_value_block && last->e_value_size) {
+		if (last->e_value_inum == 0 && last->e_value_size) {
 			last->e_value_offs = last->e_value_offs +
 							value_offs_shift;
 		}
@@ -242,7 +243,7 @@  int ext2fs_attr_free_space(struct ext2_ext_attr_entry *last,
 {
 	for (; !EXT2_EXT_IS_LAST_ENTRY(last); last = EXT2_EXT_ATTR_NEXT(last)) {
 		*total += EXT2_EXT_ATTR_LEN(last->e_name_len);
-		if (!last->e_value_block && last->e_value_size) {
+		if (last->e_value_inum == 0 && last->e_value_size) {
 			int offs = last->e_value_offs;
 			if (offs < *min_offs)
 				*min_offs = offs;
@@ -378,7 +379,7 @@  static errcode_t ext2fs_attr_set_entry(ext2_filsys fs, struct ext2_attr_info *i,
 	/* Compute min_offs and last. */
 	for (last = s->first; !EXT2_EXT_IS_LAST_ENTRY(last);
 	     last = EXT2_EXT_ATTR_NEXT(last)) {
-		if (!last->e_value_block && last->e_value_size) {
+		if (last->e_value_inum == 0 && last->e_value_size) {
 			int offs = last->e_value_offs;

 			if (offs < min_offs)
@@ -388,7 +389,7 @@  static errcode_t ext2fs_attr_set_entry(ext2_filsys fs, struct ext2_attr_info *i,
 	free = min_offs - ((char *)last - s->base) - sizeof(__u32);

 	if (!s->not_found) {
-		if (!s->here->e_value_block && s->here->e_value_size) {
+		if (s->here->e_value_inum == 0 && s->here->e_value_size) {
 			int size = s->here->e_value_size;
 			free += EXT2_EXT_ATTR_SIZE(size);
 		}
@@ -411,7 +412,7 @@  static errcode_t ext2fs_attr_set_entry(ext2_filsys fs, struct ext2_attr_info *i,
 		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) {
+		if (s->here->e_value_inum == 0 && s->here->e_value_size) {
 			char *first_val = s->base + min_offs;
 			int offs = s->here->e_value_offs;
 			char *val = s->base + offs;
@@ -440,7 +441,7 @@  static errcode_t ext2fs_attr_set_entry(ext2_filsys fs, struct ext2_attr_info *i,
 			while (!EXT2_EXT_IS_LAST_ENTRY(last)) {
 				int o = last->e_value_offs;

-				if (!last->e_value_block &&
+				if (last->e_value_inum == 0 &&
 				    last->e_value_size && o < offs)
 					last->e_value_offs = o + size;
 				last = EXT2_EXT_ATTR_NEXT(last);
@@ -558,9 +559,20 @@  static errcode_t ext2fs_attr_block_set(ext2_filsys fs, struct ext2_inode *inode,
 	/* Update the i_blocks if we added a new EA block */
 	if (!inode->i_file_acl && new_buf)
 		inode->i_blocks += fs->blocksize / 512;
+
+	/* Drop the previous xattr block. */
+	if (!new_buf) {
+		if (!fs->block_map)
+			ext2fs_read_block_bitmap(fs);
+		ext2fs_block_alloc_stats(fs, inode->i_file_acl, -1);
+		inode->i_blocks -= fs->blocksize / 512;
+	}
+
 	/* Update the inode. */
 	inode->i_file_acl = new_buf ? blk : 0;

+	ext2fs_write_inode(fs, bs->ino, inode);
+
 cleanup:
 	if (clear_flag)
 		ext2fs_free_mem(&s->base);
@@ -870,6 +882,7 @@  errcode_t ext2fs_expand_extra_isize(ext2_filsys fs, ext2_ino_t ino,
 		.s = { .not_found = EXT2_ET_EA_NO_SPACE, },
 	};
 	struct ext2_attr_block_find bs = {
+		.ino = ino,
 		.s = { .not_found = EXT2_ET_EA_NO_SPACE, },
 	};
 	char *start, *end, *block_buf = NULL, *buffer =NULL, *b_entry_name=NULL;
diff --git a/lib/ext2fs/swapfs.c b/lib/ext2fs/swapfs.c
index 2a7b768..dfb1cab 100644
--- a/lib/ext2fs/swapfs.c
+++ b/lib/ext2fs/swapfs.c
@@ -168,7 +168,7 @@  void ext2fs_swap_ext_attr_entry(struct ext2_ext_attr_entry *to_entry,
 				struct ext2_ext_attr_entry *from_entry)
 {
 	to_entry->e_value_offs  = ext2fs_swab16(from_entry->e_value_offs);
-	to_entry->e_value_block = ext2fs_swab32(from_entry->e_value_block);
+	to_entry->e_value_inum  = ext2fs_swab32(from_entry->e_value_inum);
 	to_entry->e_value_size  = ext2fs_swab32(from_entry->e_value_size);
 	to_entry->e_hash	= ext2fs_swab32(from_entry->e_hash);
 }
diff --git a/misc/mke2fs.c b/misc/mke2fs.c
index a14e62e..c92ff58 100644
--- a/misc/mke2fs.c
+++ b/misc/mke2fs.c
@@ -1032,6 +1032,7 @@  static __u32 ok_features[3] = {
 		EXT3_FEATURE_INCOMPAT_JOURNAL_DEV|
 		EXT2_FEATURE_INCOMPAT_META_BG|
 		EXT4_FEATURE_INCOMPAT_FLEX_BG|
+		EXT4_FEATURE_INCOMPAT_EA_INODE|
 		EXT4_FEATURE_INCOMPAT_MMP |
 		EXT4_FEATURE_INCOMPAT_64BIT,
 	/* R/O compat */
diff --git a/misc/tune2fs.c b/misc/tune2fs.c
index edf496a..ddc7880 100644
--- a/misc/tune2fs.c
+++ b/misc/tune2fs.c
@@ -142,6 +142,7 @@  static __u32 ok_features[3] = {
 	EXT2_FEATURE_INCOMPAT_FILETYPE |
 		EXT3_FEATURE_INCOMPAT_EXTENTS |
 		EXT4_FEATURE_INCOMPAT_FLEX_BG |
+		EXT4_FEATURE_INCOMPAT_EA_INODE|
 		EXT4_FEATURE_INCOMPAT_MMP,
 	/* R/O compat */
 	EXT2_FEATURE_RO_COMPAT_LARGE_FILE |
@@ -163,6 +164,7 @@  static __u32 clear_ok_features[3] = {
 	/* Incompat */
 	EXT2_FEATURE_INCOMPAT_FILETYPE |
 		EXT4_FEATURE_INCOMPAT_FLEX_BG |
+		EXT4_FEATURE_INCOMPAT_EA_INODE|
 		EXT4_FEATURE_INCOMPAT_MMP,
 	/* R/O compat */
 	EXT2_FEATURE_RO_COMPAT_LARGE_FILE |
@@ -565,6 +567,9 @@  mmp_error:
 		sb->s_mmp_update_interval = 0;
 	}

+	if (FEATURE_ON(E2P_FEATURE_INCOMPAT, EXT4_FEATURE_INCOMPAT_EA_INODE))
+		sb->s_feature_incompat |= EXT4_FEATURE_INCOMPAT_EA_INODE;
+
 	if (FEATURE_ON(E2P_FEATURE_COMPAT, EXT3_FEATURE_COMPAT_HAS_JOURNAL)) {
 		/*
 		 * If adding a journal flag, let the create journal