shithub: lwext4

Download patch

ref: d6816b4c0d80b8068b22db6161ec660506ad58d7
parent: 61098c719f30316c4bac054170c262d0419849a9
author: Kaho Ng <ngkaho1234@gmail.com>
date: Sun May 15 20:16:37 EDT 2016

ext4_xattr: better handling on some corner error case

--- a/include/ext4_xattr.h
+++ b/include/ext4_xattr.h
@@ -50,6 +50,7 @@
 struct ext4_xattr_item {
 	/* This attribute should be stored in inode body */
 	bool in_inode;
+	bool is_data;
 
 	uint8_t name_index;
 	char  *name;
@@ -66,6 +67,8 @@
 	struct ext4_inode_ref *inode_ref;
 	bool   dirty;
 	size_t ea_size;
+	int32_t block_size_rem;
+	int32_t inode_size_rem;
 	struct ext4_fs *fs;
 
 	void *iter_arg;
--- a/src/ext4_xattr.c
+++ b/src/ext4_xattr.c
@@ -167,10 +167,10 @@
 			       struct ext4_xattr_item *b)
 {
 	int result;
-	if (a->in_inode && !b->in_inode)
+	if (a->is_data && !b->is_data)
 		return -1;
 	
-	if (!a->in_inode && b->in_inode)
+	if (!a->is_data && b->is_data)
 		return 1;
 
 	result = a->name_index - b->name_index;
@@ -200,6 +200,7 @@
 	item->name_len = name_len;
 	item->data = NULL;
 	item->data_size = 0;
+	item->in_inode = false;
 
 	memset(&item->node, 0, sizeof(item->node));
 	memcpy(item->name, name, name_len);
@@ -207,9 +208,9 @@
 	if (name_index == EXT4_XATTR_INDEX_SYSTEM &&
 	    name_len == 4 &&
 	    !memcmp(name, "data", 4))
-		item->in_inode = true;
+		item->is_data = true;
 	else
-		item->in_inode = false;
+		item->is_data = false;
 
 	return item;
 }
@@ -328,6 +329,9 @@
 			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);
 	}
@@ -377,7 +381,11 @@
 			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);
 	}
@@ -430,7 +438,7 @@
 	if (name_index == EXT4_XATTR_INDEX_SYSTEM &&
 	    name_len == 4 &&
 	    !memcmp(name, "data", 4))
-		tmp.in_inode = true;
+		tmp.is_data = true;
 
 	return RB_FIND(ext4_xattr_tree, &xattr_ref->root, &tmp);
 }
@@ -459,6 +467,17 @@
 
 		return NULL;
 	}
+	item->in_inode = true;
+	if (xattr_ref->inode_size_rem -
+	    (int32_t)EXT4_XATTR_SIZE(data_size) -
+	    (int32_t)EXT4_XATTR_LEN(item->name_len) < 0) {
+		if (xattr_ref->block_size_rem -
+		    (int32_t)EXT4_XATTR_SIZE(data_size) -
+		    (int32_t)EXT4_XATTR_LEN(item->name_len) < 0)
+			return NULL;
+
+		item->in_inode = false;
+	}
 	if (ext4_xattr_item_alloc_data(item, data, data_size) != EOK) {
 		ext4_xattr_item_free(item);
 		return NULL;
@@ -466,6 +485,15 @@
 	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;
 	return item;
 }
@@ -485,6 +513,16 @@
 		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);
+		}
+
 		RB_REMOVE(ext4_xattr_tree, &xattr_ref->root, item);
 		ext4_xattr_item_free(item);
 		xattr_ref->dirty = true;
@@ -498,29 +536,103 @@
 				  size_t new_data_size)
 {
 	int ret = EOK;
+	bool to_inode = false, to_block = false;
 	size_t old_data_size = item->data_size;
+	int32_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
+	 */
 	if ((xattr_ref->ea_size - EXT4_XATTR_SIZE(old_data_size) +
 		EXT4_XATTR_SIZE(new_data_size)
 			>
 	    ext4_xattr_inode_space(xattr_ref) -
-	    	sizeof(struct ext4_xattr_ibody_header))
+		sizeof(struct ext4_xattr_ibody_header))
 		&&
 	    (xattr_ref->ea_size - EXT4_XATTR_SIZE(old_data_size) +
 		EXT4_XATTR_SIZE(new_data_size)
 			>
 	    ext4_xattr_block_space(xattr_ref) -
-	    	sizeof(struct ext4_xattr_header))) {
+		sizeof(struct ext4_xattr_header))) {
 
 		return ENOSPC;
 	}
+
+	/*
+	 * 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 +
+				(int32_t)EXT4_XATTR_SIZE(old_data_size) -
+				(int32_t)EXT4_XATTR_SIZE(new_data_size) < 0) {
+			if (xattr_ref->block_size_rem -
+					(int32_t)EXT4_XATTR_SIZE(new_data_size) -
+					(int32_t)EXT4_XATTR_LEN(item->name_len) < 0)
+				return ENOSPC;
+
+			to_block = true;
+		}
+	} else {
+		if (xattr_ref->block_size_rem +
+				(int32_t)EXT4_XATTR_SIZE(old_data_size) -
+				(int32_t)EXT4_XATTR_SIZE(new_data_size) < 0) {
+			if (xattr_ref->inode_size_rem -
+					(int32_t)EXT4_XATTR_SIZE(new_data_size) -
+					(int32_t)EXT4_XATTR_LEN(item->name_len) < 0)
+				return ENOSPC;
+
+			to_inode = true;
+		}
+	}
 	ret = ext4_xattr_item_resize_data(item, new_data_size);
-	if (ret != EOK) {
+	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);
+
+	/*
+	 * 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;
 }
@@ -528,12 +640,18 @@
 static void ext4_xattr_purge_items(struct ext4_xattr_ref *xattr_ref)
 {
 	struct ext4_xattr_item *item, *save_item;
-	RB_FOREACH_SAFE(item, ext4_xattr_tree, &xattr_ref->root, 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);
 	}
 	xattr_ref->ea_size = 0;
+	xattr_ref->inode_size_rem = ext4_xattr_inode_space(xattr_ref) -
+		sizeof(struct ext4_xattr_ibody_header);
+	if (xattr_ref->inode_size_rem < 0)
+		xattr_ref->inode_size_rem = 0;
+
+	xattr_ref->block_size_rem = ext4_xattr_block_space(xattr_ref) -
+		sizeof(struct ext4_xattr_header);
 }
 
 static int ext4_xattr_try_alloc_block(struct ext4_xattr_ref *xattr_ref)
@@ -715,7 +833,8 @@
 			EXT4_XATTR_LEN(item->name_len) >
 		    block_size_rem) {
 			ret = ENOSPC;
-			goto Finish;
+			ext4_dbg(DEBUG_XATTR, "IMPOSSIBLE ENOSPC AS WE DID INSPECTION!\n");
+			ext4_assert(0);
 		}
 		block_data =
 		    (char *)block_data - EXT4_XATTR_SIZE(item->data_size);
@@ -859,6 +978,13 @@
 	ref->inode_ref = inode_ref;
 	ref->fs = fs;
 
+	ref->inode_size_rem = ext4_xattr_inode_space(ref) -
+		sizeof(struct ext4_xattr_ibody_header);
+	if (ref->inode_size_rem < 0)
+		ref->inode_size_rem = 0;
+
+	ref->block_size_rem = ext4_xattr_block_space(ref) -
+		sizeof(struct ext4_xattr_header);
 	rc = ext4_xattr_fetch(ref);
 	if (rc != EOK) {
 		ext4_xattr_purge_items(ref);