Btrfs: Fix a number of inline extent problems that Yan Zheng reported.

The fixes do a number of things:

1) Most btrfs_drop_extent callers will try to leave the inline extents in
place.  It can truncate bytes off the beginning of the inline extent if
required.

2) writepage can now update the inline extent, allowing mmap writes to
go directly into the inline extent.

3) btrfs_truncate_in_transaction truncates inline extents

4) extent_map.c fixed to not merge inline extent mappings and hole
mappings together

Signed-off-by: Chris Mason <chris.mason@oracle.com>
diff --git a/fs/btrfs/ctree.c b/fs/btrfs/ctree.c
index 516b902..eef9c92 100644
--- a/fs/btrfs/ctree.c
+++ b/fs/btrfs/ctree.c
@@ -1930,7 +1930,7 @@
 int btrfs_truncate_item(struct btrfs_trans_handle *trans,
 			struct btrfs_root *root,
 			struct btrfs_path *path,
-			u32 new_size)
+			u32 new_size, int from_end)
 {
 	int ret = 0;
 	int slot;
@@ -1946,13 +1946,17 @@
 
 	slot_orig = path->slots[0];
 	leaf = path->nodes[0];
+	slot = path->slots[0];
+
+	old_size = btrfs_item_size_nr(leaf, slot);
+	if (old_size == new_size)
+		return 0;
 
 	nritems = btrfs_header_nritems(leaf);
 	data_end = leaf_data_end(root, leaf);
 
-	slot = path->slots[0];
 	old_data_start = btrfs_item_offset_nr(leaf, slot);
-	old_size = btrfs_item_size_nr(leaf, slot); BUG_ON(old_size <= new_size);
+
 	size_diff = old_size - new_size;
 
 	BUG_ON(slot < 0);
@@ -1984,9 +1988,45 @@
 	}
 
 	/* shift the data */
-	memmove_extent_buffer(leaf, btrfs_leaf_data(leaf) +
-		      data_end + size_diff, btrfs_leaf_data(leaf) +
-		      data_end, old_data_start + new_size - data_end);
+	if (from_end) {
+		memmove_extent_buffer(leaf, btrfs_leaf_data(leaf) +
+			      data_end + size_diff, btrfs_leaf_data(leaf) +
+			      data_end, old_data_start + new_size - data_end);
+	} else {
+		struct btrfs_disk_key disk_key;
+		u64 offset;
+
+		btrfs_item_key(leaf, &disk_key, slot);
+
+		if (btrfs_disk_key_type(&disk_key) == BTRFS_EXTENT_DATA_KEY) {
+			unsigned long ptr;
+			struct btrfs_file_extent_item *fi;
+
+			fi = btrfs_item_ptr(leaf, slot,
+					    struct btrfs_file_extent_item);
+			fi = (struct btrfs_file_extent_item *)(
+			     (unsigned long)fi - size_diff);
+
+			if (btrfs_file_extent_type(leaf, fi) ==
+			    BTRFS_FILE_EXTENT_INLINE) {
+				ptr = btrfs_item_ptr_offset(leaf, slot);
+				memmove_extent_buffer(leaf, ptr,
+				        (unsigned long)fi,
+				        offsetof(struct btrfs_file_extent_item,
+						 disk_bytenr));
+			}
+		}
+
+		memmove_extent_buffer(leaf, btrfs_leaf_data(leaf) +
+			      data_end + size_diff, btrfs_leaf_data(leaf) +
+			      data_end, old_data_start - data_end);
+
+		offset = btrfs_disk_key_offset(&disk_key);
+		btrfs_set_disk_key_offset(&disk_key, offset + size_diff);
+		btrfs_set_item_key(leaf, &disk_key, slot);
+		if (slot == 0)
+			fixup_low_keys(trans, root, path, &disk_key, 1);
+	}
 
 	item = btrfs_item_nr(leaf, slot);
 	btrfs_set_item_size(leaf, item, new_size);
diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h
index 70e143b..d82afb6 100644
--- a/fs/btrfs/ctree.h
+++ b/fs/btrfs/ctree.h
@@ -907,7 +907,7 @@
 int btrfs_truncate_item(struct btrfs_trans_handle *trans,
 			struct btrfs_root *root,
 			struct btrfs_path *path,
-			u32 new_size);
+			u32 new_size, int from_end);
 int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root
 		      *root, struct btrfs_key *key, struct btrfs_path *p, int
 		      ins_len, int cow);
diff --git a/fs/btrfs/dir-item.c b/fs/btrfs/dir-item.c
index 6f19de4..514a1dc 100644
--- a/fs/btrfs/dir-item.c
+++ b/fs/btrfs/dir-item.c
@@ -249,7 +249,7 @@
 		memmove_extent_buffer(leaf, ptr, ptr + sub_item_len,
 			item_len - (ptr + sub_item_len - start));
 		ret = btrfs_truncate_item(trans, root, path,
-					  item_len - sub_item_len);
+					  item_len - sub_item_len, 1);
 	}
 	return 0;
 }
diff --git a/fs/btrfs/extent_map.c b/fs/btrfs/extent_map.c
index 238cb1d..44be9cfd3 100644
--- a/fs/btrfs/extent_map.c
+++ b/fs/btrfs/extent_map.c
@@ -263,7 +263,12 @@
 		if (prev && prev->end + 1 == em->start &&
 		    ((em->block_start == EXTENT_MAP_HOLE &&
 		      prev->block_start == EXTENT_MAP_HOLE) ||
-			     (em->block_start == prev->block_end + 1))) {
+		     (em->block_start == EXTENT_MAP_INLINE &&
+		      prev->block_start == EXTENT_MAP_INLINE) ||
+		     (em->block_start == EXTENT_MAP_DELALLOC &&
+		      prev->block_start == EXTENT_MAP_DELALLOC) ||
+		     (em->block_start < EXTENT_MAP_DELALLOC - 1 &&
+		      em->block_start == prev->block_end + 1))) {
 			em->start = prev->start;
 			em->block_start = prev->block_start;
 			rb_erase(&prev->rb_node, &tree->map);
@@ -1618,13 +1623,13 @@
 	u64 extent_offset;
 	u64 last_byte = i_size_read(inode);
 	u64 block_start;
+	u64 iosize;
 	sector_t sector;
 	struct extent_map *em;
 	struct block_device *bdev;
 	int ret;
 	int nr = 0;
 	size_t page_offset = 0;
-	size_t iosize;
 	size_t blocksize;
 	loff_t i_size = i_size_read(inode);
 	unsigned long end_index = i_size >> PAGE_CACHE_SHIFT;
@@ -1684,7 +1689,7 @@
 			clear_extent_dirty(tree, cur, page_end, GFP_NOFS);
 			break;
 		}
-		em = get_extent(inode, page, page_offset, cur, end, 0);
+		em = get_extent(inode, page, page_offset, cur, end, 1);
 		if (IS_ERR(em) || !em) {
 			SetPageError(page);
 			break;
diff --git a/fs/btrfs/extent_map.h b/fs/btrfs/extent_map.h
index 39d78d3..b6f6519 100644
--- a/fs/btrfs/extent_map.h
+++ b/fs/btrfs/extent_map.h
@@ -24,7 +24,6 @@
  * page->private values.  Every page that is controlled by the extent
  * map has page->private set to one.
  */
-
 #define EXTENT_PAGE_PRIVATE 1
 #define EXTENT_PAGE_PRIVATE_FIRST_PAGE 3
 
diff --git a/fs/btrfs/file-item.c b/fs/btrfs/file-item.c
index 7eb9a5412..614176e 100644
--- a/fs/btrfs/file-item.c
+++ b/fs/btrfs/file-item.c
@@ -291,7 +291,7 @@
 	new_item_size = blocks * BTRFS_CRC32_SIZE;
 	if (new_item_size >= btrfs_item_size_nr(leaf, slot))
 		return 0;
-	ret = btrfs_truncate_item(trans, root, path, new_item_size);
+	ret = btrfs_truncate_item(trans, root, path, new_item_size, 1);
 	BUG_ON(ret);
 	return ret;
 }
diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index abdd9ca..4e52f7e 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -115,8 +115,20 @@
 		goto fail;
 	}
 	if (ret == 1) {
+		struct btrfs_key found_key;
+
+		if (path->slots[0] == 0)
+			goto insert;
+
 		path->slots[0]--;
 		leaf = path->nodes[0];
+		btrfs_item_key_to_cpu(leaf, &found_key, path->slots[0]);
+
+		if (found_key.objectid != inode->i_ino)
+			goto insert;
+
+		if (found_key.type != BTRFS_EXTENT_DATA_KEY)
+			goto insert;
 		ei = btrfs_item_ptr(leaf, path->slots[0],
 				    struct btrfs_file_extent_item);
 
@@ -152,6 +164,7 @@
 			ret = btrfs_search_slot(trans, root, &key, path,
 						offset + size - found_end, 1);
 			BUG_ON(ret != 0);
+
 			ret = btrfs_extend_item(trans, root, path,
 						offset + size - found_end);
 			if (ret) {
@@ -292,7 +305,7 @@
 	 */
 	inline_size = end_pos;
 	if (isize >= BTRFS_MAX_INLINE_DATA_SIZE(root) ||
-	    inline_size > 8192 ||
+	    inline_size > 32768 ||
 	    inline_size >= BTRFS_MAX_INLINE_DATA_SIZE(root)) {
 		u64 last_end;
 
@@ -312,7 +325,7 @@
 		aligned_end = (pos + write_bytes + root->sectorsize - 1) &
 			~((u64)root->sectorsize - 1);
 		err = btrfs_drop_extents(trans, root, inode, start_pos,
-					 aligned_end, end_pos, &hint_byte);
+					 aligned_end, aligned_end, &hint_byte);
 		if (err)
 			goto failed;
 		err = insert_inline_extent(trans, root, inode, start_pos,
@@ -456,13 +469,15 @@
 			goto next_slot;
 		}
 
-		/* FIXME, there's only one inline extent allowed right now */
 		if (found_inline) {
 			u64 mask = root->sectorsize - 1;
 			search_start = (extent_end + mask) & ~mask;
 		} else
 			search_start = extent_end;
 
+		if (end <= extent_end && start >= key.offset && found_inline) {
+			*hint_byte = EXTENT_MAP_INLINE;
+		}
 		if (end < extent_end && end >= key.offset) {
 			if (found_extent) {
 				u64 disk_bytenr =
@@ -479,8 +494,10 @@
 					BUG_ON(ret);
 				}
 			}
-			if (!found_inline)
-				bookend = 1;
+			bookend = 1;
+			if (found_inline && start <= key.offset &&
+			    inline_end < extent_end)
+				keep = 1;
 		}
 		/* truncate existing extent */
 		if (start > key.offset) {
@@ -510,7 +527,7 @@
 				new_size = btrfs_file_extent_calc_inline_size(
 						   inline_end - key.offset);
 				btrfs_truncate_item(trans, root, path,
-						    new_size);
+						    new_size, 1);
 			}
 		}
 		/* delete the entire extent */
@@ -551,6 +568,13 @@
 			if (!bookend)
 				continue;
 		}
+		if (bookend && found_inline && start <= key.offset &&
+		    inline_end < extent_end) {
+			u32 new_size;
+			new_size = btrfs_file_extent_calc_inline_size(
+						   extent_end - inline_end);
+			btrfs_truncate_item(trans, root, path, new_size, 0);
+		}
 		/* create bookend, splitting the extent in two */
 		if (bookend && found_extent) {
 			struct btrfs_key ins;
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 5d10b64..0c65141 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -89,6 +89,9 @@
 	ret = btrfs_drop_extents(trans, root, inode,
 				 start, start + num_bytes, start, &alloc_hint);
 
+	if (alloc_hint == EXTENT_MAP_INLINE)
+		goto out;
+
 	ret = btrfs_alloc_extent(trans, root, inode->i_ino, num_bytes, 0,
 				 alloc_hint, (u64)-1, &ins, 1);
 	if (ret) {
@@ -558,6 +561,7 @@
 	u64 item_end = 0;
 	int found_extent;
 	int del_item;
+	int extent_type = -1;
 
 	btrfs_drop_extent_cache(inode, inode->i_size, (u64)-1);
 	path = btrfs_alloc_path();
@@ -597,10 +601,15 @@
 		if (found_type == BTRFS_EXTENT_DATA_KEY) {
 			fi = btrfs_item_ptr(leaf, path->slots[0],
 					    struct btrfs_file_extent_item);
-			if (btrfs_file_extent_type(leaf, fi) !=
-			    BTRFS_FILE_EXTENT_INLINE) {
+			extent_type = btrfs_file_extent_type(leaf, fi);
+			if (extent_type != BTRFS_FILE_EXTENT_INLINE) {
 				item_end +=
 				    btrfs_file_extent_num_bytes(leaf, fi);
+			} else if (extent_type == BTRFS_FILE_EXTENT_INLINE) {
+				struct btrfs_item *item = btrfs_item_nr(leaf,
+							        path->slots[0]);
+				item_end += btrfs_file_extent_inline_len(leaf,
+									 item);
 			}
 		}
 		if (found_type == BTRFS_CSUM_ITEM_KEY) {
@@ -608,7 +617,7 @@
 						  inode->i_size);
 			BUG_ON(ret);
 		}
-		if (item_end < inode->i_size) {
+		if (item_end <= inode->i_size) {
 			if (found_type == BTRFS_DIR_ITEM_KEY) {
 				found_type = BTRFS_INODE_ITEM_KEY;
 			} else if (found_type == BTRFS_EXTENT_ITEM_KEY) {
@@ -629,9 +638,10 @@
 		found_extent = 0;
 
 		/* FIXME, shrink the extent if the ref count is only 1 */
-		if (found_type == BTRFS_EXTENT_DATA_KEY &&
-			   btrfs_file_extent_type(leaf, fi) !=
-			   BTRFS_FILE_EXTENT_INLINE) {
+		if (found_type != BTRFS_EXTENT_DATA_KEY)
+			goto delete;
+
+		if (extent_type != BTRFS_FILE_EXTENT_INLINE) {
 			u64 num_dec;
 			extent_start = btrfs_file_extent_disk_bytenr(leaf, fi);
 			if (!del_item) {
@@ -659,7 +669,15 @@
 					inode->i_blocks -= num_dec;
 				}
 			}
+		} else if (extent_type == BTRFS_FILE_EXTENT_INLINE &&
+			   !del_item) {
+			u32 newsize = inode->i_size - found_key.offset;
+			newsize = btrfs_file_extent_calc_inline_size(newsize);
+			ret = btrfs_truncate_item(trans, root, path,
+						  newsize, 1);
+			BUG_ON(ret);
 		}
+delete:
 		if (del_item) {
 			ret = btrfs_del_item(trans, root, path);
 			if (ret)
@@ -769,7 +787,7 @@
 		u64 pos = (inode->i_size + mask) & ~mask;
 		u64 block_end = attr->ia_size | mask;
 		u64 hole_size;
-		u64 alloc_hint;
+		u64 alloc_hint = 0;
 
 		if (attr->ia_size <= pos)
 			goto out;
@@ -786,8 +804,11 @@
 					 pos, pos + hole_size, pos,
 					 &alloc_hint);
 
-		err = btrfs_insert_file_extent(trans, root, inode->i_ino,
-					       pos, 0, 0, hole_size);
+		if (alloc_hint != EXTENT_MAP_INLINE) {
+			err = btrfs_insert_file_extent(trans, root,
+						       inode->i_ino,
+						       pos, 0, 0, hole_size);
+		}
 		btrfs_end_transaction(trans, root);
 		mutex_unlock(&root->fs_info->fs_mutex);
 		unlock_extent(em_tree, pos, block_end, GFP_NOFS);
@@ -1531,8 +1552,8 @@
 		em->end = EXTENT_MAP_HOLE;
 	}
 	em->bdev = inode->i_sb->s_bdev;
-	ret = btrfs_lookup_file_extent(NULL, root, path,
-				       objectid, start, 0);
+	ret = btrfs_lookup_file_extent(trans, root, path,
+				       objectid, start, trans != NULL);
 	if (ret < 0) {
 		err = ret;
 		goto out;
@@ -1627,15 +1648,23 @@
 			((u64)root->sectorsize -1);
 		map = kmap(page);
 		ptr = btrfs_file_extent_inline_start(item) + extent_offset;
-		read_extent_buffer(leaf, map + page_offset, ptr, copy_size);
-		
-		if (em->start + copy_size <= em->end) {
-			size = min_t(u64, em->end + 1 - em->start,
-				PAGE_CACHE_SIZE - page_offset) - copy_size;
-			memset(map + page_offset + copy_size, 0, size);
+		if (create == 0 && !PageUptodate(page)) {
+			read_extent_buffer(leaf, map + page_offset, ptr,
+					   copy_size);
+			flush_dcache_page(page);
+		} else if (create && PageUptodate(page)) {
+			if (!trans) {
+				kunmap(page);
+				free_extent_map(em);
+				em = NULL;
+				btrfs_release_path(root, path);
+				trans = btrfs_start_transaction(root, 1);
+				goto again;
+			}
+			write_extent_buffer(leaf, map + page_offset, ptr,
+					    copy_size);
+			btrfs_mark_buffer_dirty(leaf);
 		}
-
-		flush_dcache_page(page);
 		kunmap(page);
 		set_extent_uptodate(em_tree, em->start, em->end, GFP_NOFS);
 		goto insert;