diff --git a/Makefile b/Makefile
index 755cc24..c31c219 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 CC=gcc
 AM_CFLAGS = -Wall -D_FILE_OFFSET_BITS=64 -D_FORTIFY_SOURCE=2
-CFLAGS = -g -Werror -Os
+CFLAGS = -g -Werror -Os -I.
 objects = ctree.o disk-io.o radix-tree.o extent-tree.o print-tree.o \
 	  root-tree.o dir-item.o file-item.o inode-item.o \
 	  inode-map.o crc32c.o rbtree.o extent-cache.o extent_io.o \
@@ -29,7 +29,7 @@ endif
 
 .c.o:
 	$(check) $<
-	$(CC) $(DEPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c $<
+	$(CC) $(DEPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c $< -o $@
 
 
 all: version $(progs) manpages
@@ -74,8 +74,8 @@ dir-test: $(objects) dir-test.o
 quick-test: $(objects) quick-test.o
 	gcc $(CFLAGS) -o quick-test $(objects) quick-test.o $(LDFLAGS) $(LIBS)
 
-convert: $(objects) convert.o
-	gcc $(CFLAGS) -o btrfs-convert $(objects) convert.o -lext2fs -lblkid $(LDFLAGS) $(LIBS)
+convert: $(objects) $(patsubst %.c,%.o,$(wildcard convert/*.c))
+	gcc $(CFLAGS) -o btrfs-convert $^ -lext2fs -lblkid $(LDFLAGS) $(LIBS)
 
 ioctl-test: $(objects) ioctl-test.o
 	gcc $(CFLAGS) -o ioctl-test $(objects) ioctl-test.o $(LDFLAGS) $(LIBS)
@@ -87,7 +87,7 @@ install-man:
 	cd man; make install
 
 clean :
-	rm -f $(progs) cscope.out *.o .*.d btrfs-convert
+	rm -f $(progs) cscope.out *.o .*.d btrfs-convert convert/*.o convert/.*.d
 	cd man; make clean
 
 install: $(progs) install-man
diff --git a/convert.c b/convert/convert.c
similarity index 74%
rename from convert.c
rename to convert/convert.c
index 6dfcb97..aaf3c56 100644
--- a/convert.c
+++ b/convert/convert.c
@@ -24,117 +24,21 @@
 #endif
 #include <stdio.h>
 #include <stdlib.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/acl.h>
 #include <fcntl.h>
 #include <unistd.h>
-#include <uuid/uuid.h>
-#include <linux/fs.h>
 #include <blkid/blkid.h>
 #include "kerncompat.h"
+#include "convert.h"
 #include "ctree.h"
 #include "disk-io.h"
 #include "volumes.h"
 #include "transaction.h"
 #include "crc32c.h"
 #include "utils.h"
-#include <ext2fs/ext2_fs.h>
-#include <ext2fs/ext2fs.h>
-#include <ext2fs/ext2_ext_attr.h>
 
-struct convert_fs {
-	u64 total_bytes;
-	u64 blocksize;
-	const char *label;
-
-	/* Close the FS */
-	int (*close)(struct convert_fs *fs);
-	/* Mark free extents as dirty */
-	int (*cache_free_extents)(struct convert_fs *fs,
-				  struct extent_io_tree *tree);
-	/* Copy everything over */
-	int (*copy_inodes)(struct convert_fs *fs, struct btrfs_root *root,
-			   int datacsum, int packing, int noxattr);
-
-	void *privdata;
-};
-
-#define INO_OFFSET (BTRFS_FIRST_FREE_OBJECTID - EXT2_ROOT_INO)
 #define STRIPE_LEN (64 * 1024)
 #define ORIG_IMAGE_SUBVOL_OBJECTID BTRFS_FIRST_FREE_OBJECTID
 
-/*
- * Open Ext2fs in readonly mode, read block allocation bitmap and
- * inode bitmap into memory.
- */
-static int open_ext2fs(const char *name, ext2_filsys *ret_fs)
-{
-	errcode_t ret;
-	ext2_filsys ext2_fs;
-	ext2_ino_t ino;
-	ret = ext2fs_open(name, 0, 0, 0, unix_io_manager, &ext2_fs);
-	if (ret) {
-		fprintf(stderr, "ext2fs_open: %s\n", error_message(ret));
-		goto fail;
-	}
-	ret = ext2fs_read_inode_bitmap(ext2_fs);
-	if (ret) {
-		fprintf(stderr, "ext2fs_read_inode_bitmap: %s\n",
-			error_message(ret));
-		goto fail;
-	}
-	ret = ext2fs_read_block_bitmap(ext2_fs);
-	if (ret) {
-		fprintf(stderr, "ext2fs_read_block_bitmap: %s\n",
-			error_message(ret));
-		goto fail;
-	}
-	/*
-	 * search each block group for a free inode. this set up
-	 * uninit block/inode bitmaps appropriately.
-	 */
-	ino = 1;
-	while (ino <= ext2_fs->super->s_inodes_count) {
-		ext2_ino_t foo;
-		ext2fs_new_inode(ext2_fs, ino, 0, NULL, &foo);
-		ino += EXT2_INODES_PER_GROUP(ext2_fs->super);
-	}
-
-	*ret_fs = ext2_fs;
-	return 0;
-fail:
-	return -1;
-}
-
-static int ext2_close(struct convert_fs *fs)
-{
-	ext2fs_close((ext2_filsys)fs->privdata);
-	return 0;
-}
-
-static int ext2_cache_free_extents(struct convert_fs *fs,
-				   struct extent_io_tree *free_tree)
-{
-	ext2_filsys ext2_fs = fs->privdata;
-	int ret = 0;
-	blk_t block;
-	u64 bytenr;
-	u64 blocksize = ext2_fs->blocksize;
-
-	block = ext2_fs->super->s_first_data_block;
-	for (; block < ext2_fs->super->s_blocks_count; block++) {
-		if (ext2fs_fast_test_block_bitmap(ext2_fs->block_map, block))
-			continue;
-		bytenr = block * blocksize;
-		ret = set_extent_dirty(free_tree, bytenr,
-				       bytenr + blocksize - 1, 0);
-		BUG_ON(ret);
-	}
-
-	return 0;
-}
-
 /* mark btrfs-reserved blocks as used */
 static void adjust_free_extents(struct convert_fs *fs,
 				struct extent_io_tree *free_tree)
@@ -267,113 +171,6 @@ struct btrfs_extent_ops extent_ops = {
 	.free_extent = custom_free_extent,
 };
 
-struct dir_iterate_data {
-	struct btrfs_trans_handle *trans;
-	struct btrfs_root *root;
-	struct btrfs_inode_item *inode;
-	u64 objectid;
-	u64 index_cnt;
-	u64 parent;
-	int errcode;
-};
-
-static u8 filetype_conversion_table[EXT2_FT_MAX] = {
-	[EXT2_FT_UNKNOWN]	= BTRFS_FT_UNKNOWN,
-	[EXT2_FT_REG_FILE]	= BTRFS_FT_REG_FILE,
-	[EXT2_FT_DIR]		= BTRFS_FT_DIR,
-	[EXT2_FT_CHRDEV]	= BTRFS_FT_CHRDEV,
-	[EXT2_FT_BLKDEV]	= BTRFS_FT_BLKDEV,
-	[EXT2_FT_FIFO]		= BTRFS_FT_FIFO,
-	[EXT2_FT_SOCK]		= BTRFS_FT_SOCK,
-	[EXT2_FT_SYMLINK]	= BTRFS_FT_SYMLINK,
-};
-
-static int dir_iterate_proc(ext2_ino_t dir, int entry,
-			    struct ext2_dir_entry *old,
-			    int offset, int blocksize,
-			    char *buf,void *priv_data)
-{
-	int ret;
-	int file_type;
-	u64 objectid;
-        u64 inode_size;
-	char dotdot[] = "..";
-	struct btrfs_key location;
-	struct ext2_dir_entry_2 *dirent = (struct ext2_dir_entry_2 *)old;
-	struct dir_iterate_data *idata = (struct dir_iterate_data *)priv_data;
-
-	objectid = dirent->inode + INO_OFFSET;
-	if (!strncmp(dirent->name, dotdot, dirent->name_len)) {
-		if (dirent->name_len == 2) {
-			BUG_ON(idata->parent != 0);
-			idata->parent = objectid;
-		}
-		return 0;
-	}
-	if (dirent->inode < EXT2_GOOD_OLD_FIRST_INO)
-		return 0;
-
-	location.objectid = objectid;
-	location.offset = 0;
-	btrfs_set_key_type(&location, BTRFS_INODE_ITEM_KEY);
-
-	file_type = dirent->file_type;
-	BUG_ON(file_type > EXT2_FT_SYMLINK);
-	ret = btrfs_insert_dir_item(idata->trans, idata->root,
-				    dirent->name, dirent->name_len,
-				    idata->objectid, &location,
-				    filetype_conversion_table[file_type],
-				    idata->index_cnt);
-	if (ret)
-		goto fail;
-	ret = btrfs_insert_inode_ref(idata->trans, idata->root,
-				     dirent->name, dirent->name_len,
-				     objectid, idata->objectid,
-				     idata->index_cnt);
-	if (ret)
-		goto fail;
-	idata->index_cnt++;
-	inode_size = btrfs_stack_inode_size(idata->inode) +
-		     dirent->name_len * 2;
-	btrfs_set_stack_inode_size(idata->inode, inode_size);
-	return 0;
-fail:
-	idata->errcode = ret;
-	return BLOCK_ABORT;
-}
-
-static int create_dir_entries(struct btrfs_trans_handle *trans,
-			      struct btrfs_root *root, u64 objectid,
-			      struct btrfs_inode_item *btrfs_inode,
-			      ext2_filsys ext2_fs, ext2_ino_t ext2_ino)
-{
-	int ret;
-	errcode_t err;
-	struct dir_iterate_data data = {
-		.trans		= trans,
-		.root		= root,
-		.inode		= btrfs_inode,
-		.objectid	= objectid,
-		.index_cnt	= 2,
-		.parent		= 0,
-		.errcode	= 0,
-	};
-
-	err = ext2fs_dir_iterate2(ext2_fs, ext2_ino, 0, NULL,
-				  dir_iterate_proc, &data);
-	if (err)
-		goto error;
-	ret = data.errcode;
-	if (ret == 0 && data.parent == objectid) {
-		ret = btrfs_insert_inode_ref(trans, root, "..", 2,
-					     objectid, objectid, 0);
-	}
-	return ret;
-error:
-	fprintf(stderr, "ext2fs_dir_iterate2: %s\n", error_message(err));
-	return -1;
-}
-
 static int read_disk_extent(struct btrfs_root *root, u64 bytenr,
 		            u64 num_bytes, char *buffer)
 {
@@ -524,22 +321,6 @@ fail:
 	return ret;
 }
 
-struct extent_iterate_data {
-	struct btrfs_trans_handle *trans;
-	struct btrfs_root *root;
-	u64 *inode_nbytes;
-	u64 objectid;
-	int checksum, packing;
-	u64 last_file_off;
-	u64 total_size;
-	enum {EXTENT_ITERATE_TYPE_NONE, EXTENT_ITERATE_TYPE_MEM,
-	      EXTENT_ITERATE_TYPE_DISK} type;
-	u64 size;
-	u64 file_off; /* always aligned to sectorsize */
-	char *data; /* for mem */
-	u64 disk_off; /* for disk */
-};
-
 static u64 extent_boundary(struct btrfs_root *root, u64 extent_start)
 {
 	int i;
@@ -710,9 +491,6 @@ int finish_file_extents(struct extent_iterate_data *priv)
 	return 0;
 }
 
-int add_file_mem_extent(struct extent_iterate_data *priv, u64 file_off,
-			u64 size, char *data);
-
 int add_file_disk_extent(struct extent_iterate_data *priv, u64 file_off,
 			 u64 disk_off, u64 size)
 {
@@ -861,510 +639,6 @@ int add_file_mem_extent(struct extent_iterate_data *priv, u64 file_off,
 	return 0;
 }
 
-static int __block_iterate_proc(ext2_filsys fs, blk_t *blocknr,
-			        e2_blkcnt_t blockcnt, blk_t ref_block,
-			        int ref_offset, void *priv_data)
-{
-	struct extent_iterate_data *idata;
-	idata = (struct extent_iterate_data *)priv_data;
-	u64 blocksize = fs->blocksize;
-	int ret = add_file_disk_extent(idata, blocksize * blockcnt,
-				       blocksize * *blocknr, blocksize);
-	if (ret)
-		return BLOCK_ABORT;
-	return 0;
-}
-
-/*
- * traverse file's data blocks, record these data blocks as file extents.
- */
-static int create_file_extents(struct btrfs_trans_handle *trans,
-			       struct btrfs_root *root, u64 objectid,
-			       struct btrfs_inode_item *btrfs_inode,
-			       ext2_filsys ext2_fs, ext2_ino_t ext2_ino,
-			       int datacsum, int packing)
-{
-	int ret;
-	errcode_t err;
-	u64 inode_nbytes = 0;
-	u64 inode_size = btrfs_stack_inode_size(btrfs_inode);
-	struct extent_iterate_data data;
-	ret = start_file_extents(&data, trans, root, &inode_nbytes, objectid,
-				 datacsum, packing, inode_size);
-	if (ret)
-		return ret;
-	err = ext2fs_block_iterate2(ext2_fs, ext2_ino, BLOCK_FLAG_DATA_ONLY,
-				    NULL, __block_iterate_proc, &data);
-	if (err)
-		goto error;
-	ret = finish_file_extents(&data);
-	if (ret)
-		return ret;
-	btrfs_set_stack_inode_nbytes(btrfs_inode, inode_nbytes);
-	return 0;
-error:
-	fprintf(stderr, "ext2fs_block_iterate2: %s\n", error_message(err));
-	return -1;
-}
-
-static int create_symbol_link(struct btrfs_trans_handle *trans,
-			      struct btrfs_root *root, u64 objectid,
-			      struct btrfs_inode_item *btrfs_inode,
-			      ext2_filsys ext2_fs, ext2_ino_t ext2_ino,
-			      struct ext2_inode *ext2_inode)
-{
-	int ret;
-	char *pathname;
-	u64 inode_size = btrfs_stack_inode_size(btrfs_inode);
-	if (ext2fs_inode_data_blocks(ext2_fs, ext2_inode)) {
-		btrfs_set_stack_inode_size(btrfs_inode, inode_size + 1);
-		ret = create_file_extents(trans, root, objectid, btrfs_inode,
-					  ext2_fs, ext2_ino, 1, 1);
-		btrfs_set_stack_inode_size(btrfs_inode, inode_size);
-		return ret;
-	}
-
-	pathname = (char *)&(ext2_inode->i_block[0]);
-	BUG_ON(pathname[inode_size] != 0);
-	ret = btrfs_insert_inline_extent(trans, root, objectid, 0,
-					 pathname, inode_size + 1);
-	btrfs_set_stack_inode_nbytes(btrfs_inode, inode_size + 1);
-	return ret;
-}
-
-/*
- * Following xattr/acl related codes are based on codes in
- * fs/ext3/xattr.c and fs/ext3/acl.c
- */
-#define EXT2_XATTR_BHDR(ptr) ((struct ext2_ext_attr_header *)(ptr))
-#define EXT2_XATTR_BFIRST(ptr) \
-	((struct ext2_ext_attr_entry *)(EXT2_XATTR_BHDR(ptr) + 1))
-#define EXT2_XATTR_IHDR(inode) \
-	((struct ext2_ext_attr_header *) ((void *)(inode) + \
-		EXT2_GOOD_OLD_INODE_SIZE + (inode)->i_extra_isize))
-#define EXT2_XATTR_IFIRST(inode) \
-	((struct ext2_ext_attr_entry *) ((void *)EXT2_XATTR_IHDR(inode) + \
-		sizeof(EXT2_XATTR_IHDR(inode)->h_magic)))
-
-static int ext2_xattr_check_names(struct ext2_ext_attr_entry *entry,
-				  const void *end)
-{
-	struct ext2_ext_attr_entry *next;
-
-	while (!EXT2_EXT_IS_LAST_ENTRY(entry)) {
-		next = EXT2_EXT_ATTR_NEXT(entry);
-		if ((void *)next >= end)
-			return -EIO;
-		entry = next;
-	}
-	return 0;
-}
-
-static int ext2_xattr_check_block(const char *buf, size_t size)
-{
-	int error;
-	struct ext2_ext_attr_header *header = EXT2_XATTR_BHDR(buf);
-
-	if (header->h_magic != EXT2_EXT_ATTR_MAGIC ||
-	    header->h_blocks != 1)
-		return -EIO;
-	error = ext2_xattr_check_names(EXT2_XATTR_BFIRST(buf), buf + size);
-	return error;
-}
-
-static int ext2_xattr_check_entry(struct ext2_ext_attr_entry *entry,
-				  size_t size)
-{
-	size_t value_size = entry->e_value_size;
-
-	if (entry->e_value_block != 0 || value_size > size ||
-	    entry->e_value_offs + value_size > size)
-		return -EIO;
-	return 0;
-}
-
-#define EXT2_ACL_VERSION	0x0001
-
-typedef struct {
-	__le16		e_tag;
-	__le16		e_perm;
-	__le32		e_id;
-} ext2_acl_entry;
-
-typedef struct {
-	__le16		e_tag;
-	__le16		e_perm;
-} ext2_acl_entry_short;
-
-typedef struct {
-	__le32		a_version;
-} ext2_acl_header;
-
-static inline int ext2_acl_count(size_t size)
-{
-	ssize_t s;
-	size -= sizeof(ext2_acl_header);
-	s = size - 4 * sizeof(ext2_acl_entry_short);
-	if (s < 0) {
-		if (size % sizeof(ext2_acl_entry_short))
-			return -1;
-		return size / sizeof(ext2_acl_entry_short);
-	} else {
-		if (s % sizeof(ext2_acl_entry))
-			return -1;
-		return s / sizeof(ext2_acl_entry) + 4;
-	}
-}
-
-#define ACL_EA_VERSION		0x0002
-
-typedef struct {
-	__le16		e_tag;
-	__le16		e_perm;
-	__le32		e_id;
-} acl_ea_entry;
-
-typedef struct {
-	__le32		a_version;
-	acl_ea_entry	a_entries[0];
-} acl_ea_header;
-
-static inline size_t acl_ea_size(int count)
-{
-	return sizeof(acl_ea_header) + count * sizeof(acl_ea_entry);
-}
-
-static int ext2_acl_to_xattr(void *dst, const void *src,
-			     size_t dst_size, size_t src_size)
-{
-	int i, count;
-	const void *end = src + src_size;
-	acl_ea_header *ext_acl = (acl_ea_header *)dst;
-	acl_ea_entry *dst_entry = ext_acl->a_entries;
-	ext2_acl_entry *src_entry;
-
-	if (src_size < sizeof(ext2_acl_header))
-		goto fail;
-	if (((ext2_acl_header *)src)->a_version !=
-	    cpu_to_le32(EXT2_ACL_VERSION))
-		goto fail;
-	src += sizeof(ext2_acl_header);
-	count = ext2_acl_count(src_size);
-	if (count <= 0)
-		goto fail;
-
-	BUG_ON(dst_size < acl_ea_size(count));
-	ext_acl->a_version = cpu_to_le32(ACL_EA_VERSION);
-	for (i = 0; i < count; i++, dst_entry++) {
-		src_entry = (ext2_acl_entry *)src;
-		if (src + sizeof(ext2_acl_entry_short) > end)
-			goto fail;
-		dst_entry->e_tag = src_entry->e_tag;
-		dst_entry->e_perm = src_entry->e_perm;
-		switch (le16_to_cpu(src_entry->e_tag)) {
-		case ACL_USER_OBJ:
-		case ACL_GROUP_OBJ:
-		case ACL_MASK:
-		case ACL_OTHER:
-			src += sizeof(ext2_acl_entry_short);
-			dst_entry->e_id = cpu_to_le32(ACL_UNDEFINED_ID);
-			break;
-		case ACL_USER:
-		case ACL_GROUP:
-			src += sizeof(ext2_acl_entry);
-			if (src > end)
-				goto fail;
-			dst_entry->e_id = src_entry->e_id;
-			break;
-		default:
-			goto fail;
-		}
-	}
-	if (src != end)
-		goto fail;
-	return 0;
-fail:
-	return -EINVAL;
-}
-
-static char *xattr_prefix_table[] = {
-	[1] =	"user.",
-	[2] =	"system.posix_acl_access",
-	[3] =	"system.posix_acl_default",
-	[4] =	"trusted.",
-	[6] =	"security.",
-};
-
-static int copy_single_xattr(struct btrfs_trans_handle *trans,
-			     struct btrfs_root *root, u64 objectid,
-			     struct ext2_ext_attr_entry *entry,
-			     const void *data, u32 datalen)
-{
-	int ret = 0;
-	int name_len;
-	int name_index;
-	void *databuf = NULL;
-	char namebuf[XATTR_NAME_MAX + 1];
-
-	name_index = entry->e_name_index;
-	if (name_index >= ARRAY_SIZE(xattr_prefix_table) ||
-	    xattr_prefix_table[name_index] == NULL)
-		return -EOPNOTSUPP;
-	name_len = strlen(xattr_prefix_table[name_index]) +
-		   entry->e_name_len;
-	if (name_len >= sizeof(namebuf))
-		return -ERANGE;
-
-	if (name_index == 2 || name_index == 3) {
-		size_t bufsize = acl_ea_size(ext2_acl_count(datalen));
-		databuf = malloc(bufsize);
-		if (!databuf)
-		       return -ENOMEM;
-		ret = ext2_acl_to_xattr(databuf, data, bufsize, datalen);
-		if (ret)
-			goto out;
-		data = databuf;
-		datalen = bufsize;
-	}
-	strcpy(namebuf, xattr_prefix_table[name_index]);
-	strncat(namebuf, EXT2_EXT_ATTR_NAME(entry), entry->e_name_len);
-	if (name_len + datalen > BTRFS_LEAF_DATA_SIZE(root) -
-	    sizeof(struct btrfs_item) - sizeof(struct btrfs_dir_item)) {
-		fprintf(stderr, "skip large xattr on inode %Lu name %.*s\n",
-			objectid - INO_OFFSET, name_len, namebuf);
-		goto out;
-	}
-	ret = btrfs_insert_xattr_item(trans, root, namebuf, name_len,
-				      data, datalen, objectid);
-out:
-	if (databuf)
-		free(databuf);
-	return ret;
-}
-
-static int copy_extended_attrs(struct btrfs_trans_handle *trans,
-			       struct btrfs_root *root, u64 objectid,
-			       struct btrfs_inode_item *btrfs_inode,
-			       ext2_filsys ext2_fs, ext2_ino_t ext2_ino)
-{
-	int ret = 0;
-	int inline_ea = 0;
-	errcode_t err;
-	u32 datalen;
-	u32 block_size = ext2_fs->blocksize;
-	u32 inode_size = EXT2_INODE_SIZE(ext2_fs->super);
-	struct ext2_inode_large *ext2_inode;
-	struct ext2_ext_attr_entry *entry;
-	void *data;
-	char *buffer = NULL;
-	char inode_buf[EXT2_GOOD_OLD_INODE_SIZE];
-
-	if (inode_size <= EXT2_GOOD_OLD_INODE_SIZE) {
-		ext2_inode = (struct ext2_inode_large *)inode_buf;
-	} else {
-		ext2_inode = (struct ext2_inode_large *)malloc(inode_size);
-		if (!ext2_inode)
-		       return -ENOMEM;
-	}
-	err = ext2fs_read_inode_full(ext2_fs, ext2_ino, (void *)ext2_inode,
-				     inode_size);
-	if (err) {
-		fprintf(stderr, "ext2fs_read_inode_full: %s\n",
-			error_message(err));
-		ret = -1;
-		goto out;
-	}
-
-	if (ext2_ino > ext2_fs->super->s_first_ino &&
-	    inode_size > EXT2_GOOD_OLD_INODE_SIZE) {
-		if (EXT2_GOOD_OLD_INODE_SIZE +
-		    ext2_inode->i_extra_isize > inode_size) {
-			ret = -EIO;
-			goto out;
-		}
-		if (ext2_inode->i_extra_isize != 0 &&
-		    EXT2_XATTR_IHDR(ext2_inode)->h_magic ==
-		    EXT2_EXT_ATTR_MAGIC) {
-			inline_ea = 1;
-		}
-	}
-	if (inline_ea) {
-		int total;
-		void *end = (void *)ext2_inode + inode_size;
-		entry = EXT2_XATTR_IFIRST(ext2_inode);
-		total = end - (void *)entry;
-		ret = ext2_xattr_check_names(entry, end);
-		if (ret)
-			goto out;
-		while (!EXT2_EXT_IS_LAST_ENTRY(entry)) {
-			ret = ext2_xattr_check_entry(entry, total);
-			if (ret)
-				goto out;
-			data = (void *)EXT2_XATTR_IFIRST(ext2_inode) +
-				entry->e_value_offs;
-			datalen = entry->e_value_size;
-			ret = copy_single_xattr(trans, root, objectid,
-						entry, data, datalen);
-			if (ret)
-				goto out;
-			entry = EXT2_EXT_ATTR_NEXT(entry);
-		}
-	}
-
-	if (ext2_inode->i_file_acl == 0)
-		goto out;
-
-	buffer = malloc(block_size);
-	if (!buffer) {
-		ret = -ENOMEM;
-		goto out;
-	}
-	err = ext2fs_read_ext_attr(ext2_fs, ext2_inode->i_file_acl, buffer);
-	if (err) {
-		fprintf(stderr, "ext2fs_read_ext_attr: %s\n",
-			error_message(err));
-		ret = -1;
-		goto out;
-	}
-	ret = ext2_xattr_check_block(buffer, block_size);
-	if (ret)
-		goto out;
-
-	entry = EXT2_XATTR_BFIRST(buffer);
-	while (!EXT2_EXT_IS_LAST_ENTRY(entry)) {
-		ret = ext2_xattr_check_entry(entry, block_size);
-		if (ret)
-			goto out;
-		data = buffer + entry->e_value_offs;
-		datalen = entry->e_value_size;
-		ret = copy_single_xattr(trans, root, objectid,
-					entry, data, datalen);
-		if (ret)
-			goto out;
-		entry = EXT2_EXT_ATTR_NEXT(entry);
-	}
-out:
-	if (buffer != NULL)
-		free(buffer);
-	if ((void *)ext2_inode != inode_buf)
-		free(ext2_inode);
-	return ret;
-}
-#define MINORBITS	20
-#define MKDEV(ma, mi)	(((ma) << MINORBITS) | (mi))
-
-static inline dev_t old_decode_dev(u16 val)
-{
-	return MKDEV((val >> 8) & 255, val & 255);
-}
-
-static inline dev_t new_decode_dev(u32 dev)
-{
-	unsigned major = (dev & 0xfff00) >> 8;
-	unsigned minor = (dev & 0xff) | ((dev >> 12) & 0xfff00);
-	return MKDEV(major, minor);
-}
-
-static int copy_inode_item(struct btrfs_inode_item *dst,
-			   struct ext2_inode *src, u32 blocksize)
-{
-	btrfs_set_stack_inode_generation(dst, 1);
-	btrfs_set_stack_inode_size(dst, src->i_size);
-	btrfs_set_stack_inode_nbytes(dst, 0);
-	btrfs_set_stack_inode_block_group(dst, 0);
-	btrfs_set_stack_inode_nlink(dst, src->i_links_count);
-	btrfs_set_stack_inode_uid(dst, src->i_uid | (src->i_uid_high << 16));
-	btrfs_set_stack_inode_gid(dst, src->i_gid | (src->i_gid_high << 16));
-	btrfs_set_stack_inode_mode(dst, src->i_mode);
-	btrfs_set_stack_inode_rdev(dst, 0);
-	btrfs_set_stack_inode_flags(dst, 0);
-	btrfs_set_stack_timespec_sec(&dst->atime, src->i_atime);
-	btrfs_set_stack_timespec_nsec(&dst->atime, 0);
-	btrfs_set_stack_timespec_sec(&dst->ctime, src->i_ctime);
-	btrfs_set_stack_timespec_nsec(&dst->ctime, 0);
-	btrfs_set_stack_timespec_sec(&dst->mtime, src->i_mtime);
-	btrfs_set_stack_timespec_nsec(&dst->mtime, 0);
-	btrfs_set_stack_timespec_sec(&dst->otime, 0);
-	btrfs_set_stack_timespec_nsec(&dst->otime, 0);
-
-	if (S_ISDIR(src->i_mode)) {
-		btrfs_set_stack_inode_size(dst, 0);
-		btrfs_set_stack_inode_nlink(dst, 1);
-	}
-	if (S_ISREG(src->i_mode)) {
-		btrfs_set_stack_inode_size(dst, (u64)src->i_size_high << 32 |
-					   (u64)src->i_size);
-	}
-	if (!S_ISREG(src->i_mode) && !S_ISDIR(src->i_mode) &&
-	    !S_ISLNK(src->i_mode)) {
-		if (src->i_block[0]) {
-			btrfs_set_stack_inode_rdev(dst,
-				old_decode_dev(src->i_block[0]));
-		} else {
-			btrfs_set_stack_inode_rdev(dst,
-				new_decode_dev(src->i_block[1]));
-		}
-	}
-	return 0;
-}
-
-/*
- * copy a single inode. do all the required works, such as cloning
- * inode item, creating file extents and creating directory entries.
- */
-static int copy_single_inode(struct btrfs_trans_handle *trans,
-			     struct btrfs_root *root, u64 objectid,
-			     ext2_filsys ext2_fs, ext2_ino_t ext2_ino,
-			     struct ext2_inode *ext2_inode,
-			     int datacsum, int packing, int noxattr)
-{
-	int ret;
-	struct btrfs_key inode_key;
-	struct btrfs_inode_item btrfs_inode;
-
-	if (ext2_inode->i_links_count == 0)
-		return 0;
-
-	copy_inode_item(&btrfs_inode, ext2_inode, ext2_fs->blocksize);
-	if (!datacsum && S_ISREG(ext2_inode->i_mode)) {
-		u32 flags = btrfs_stack_inode_flags(&btrfs_inode) |
-			    BTRFS_INODE_NODATASUM;
-		btrfs_set_stack_inode_flags(&btrfs_inode, flags);
-	}
-
-	switch (ext2_inode->i_mode & S_IFMT) {
-	case S_IFREG:
-		ret = create_file_extents(trans, root, objectid, &btrfs_inode,
-					ext2_fs, ext2_ino, datacsum, packing);
-		break;
-	case S_IFDIR:
-		ret = create_dir_entries(trans, root, objectid, &btrfs_inode,
-					 ext2_fs, ext2_ino);
-		break;
-	case S_IFLNK:
-		ret = create_symbol_link(trans, root, objectid, &btrfs_inode,
-					 ext2_fs, ext2_ino, ext2_inode);
-		break;
-	default:
-		ret = 0;
-		break;
-	}
-	if (ret)
-		return ret;
-
-	if (!noxattr) {
-		ret = copy_extended_attrs(trans, root, objectid, &btrfs_inode,
-					  ext2_fs, ext2_ino);
-		if (ret)
-			return ret;
-	}
-	inode_key.objectid = objectid;
-	inode_key.offset = 0;
-	btrfs_set_key_type(&inode_key, BTRFS_INODE_ITEM_KEY);
-	ret = btrfs_insert_inode(trans, root, objectid, &btrfs_inode);
-	return ret;
-}
-
 static int copy_disk_extent(struct btrfs_root *root, u64 dst_bytenr,
 		            u64 src_bytenr, u32 num_bytes)
 {
@@ -1388,61 +662,6 @@ fail:
 		ret = -1;
 	return ret;
 }
-/*
- * scan ext2's inode bitmap and copy all used inode.
- */
-static int ext2_copy_inodes(struct convert_fs *fs, struct btrfs_root *root,
-			    int datacsum, int packing, int noxattr)
-{
-	ext2_filsys ext2_fs = fs->privdata;
-	int ret;
-	errcode_t err;
-	ext2_inode_scan ext2_scan;
-	struct ext2_inode ext2_inode;
-	ext2_ino_t ext2_ino;
-	u64 objectid;
-	struct btrfs_trans_handle *trans;
-
-	trans = btrfs_start_transaction(root, 1);
-	if (!trans)
-		return -ENOMEM;
-	err = ext2fs_open_inode_scan(ext2_fs, 0, &ext2_scan);
-	if (err) {
-		fprintf(stderr, "ext2fs_open_inode_scan: %s\n", error_message(err));
-		return -1;
-	}
-	while (!(err = ext2fs_get_next_inode(ext2_scan, &ext2_ino,
-					     &ext2_inode))) {
-		/* no more inodes */
-		if (ext2_ino == 0)
-			break;
-		/* skip special inode in ext2fs */
-		if (ext2_ino < EXT2_GOOD_OLD_FIRST_INO &&
-		    ext2_ino != EXT2_ROOT_INO)
-			continue;
-		objectid = ext2_ino + INO_OFFSET;
-		ret = copy_single_inode(trans, root,
-					objectid, ext2_fs, ext2_ino,
-					&ext2_inode, datacsum, packing,
-					noxattr);
-		if (ret)
-			return ret;
-		if (trans->blocks_used >= 4096) {
-			ret = btrfs_commit_transaction(trans, root);
-			BUG_ON(ret);
-			trans = btrfs_start_transaction(root, 1);
-			BUG_ON(!trans);
-		}
-	}
-	if (err) {
-		fprintf(stderr, "ext2fs_get_next_inode: %s\n", error_message(err));
-		return -1;
-	}
-	ret = btrfs_commit_transaction(trans, root);
-	BUG_ON(ret);
-
-	return ret;
-}
 
 /*
  * Construct a range of the image file.
@@ -2586,26 +1805,6 @@ static int copy_dirtiness(struct extent_io_tree *out,
 	return 0;
 }
 
-int ext2_open(struct convert_fs *fs, const char *name)
-{
-	int ret;
-	ext2_filsys ext2_fs;
-	ret = open_ext2fs(name, &ext2_fs);
-	if (ret)
-		return ret;
-
-	fs->privdata = ext2_fs;
-	fs->blocksize = ext2_fs->blocksize;
-	fs->label = ext2_fs->super->s_volume_name;
-	fs->total_bytes = ext2_fs->super->s_blocks_count * fs->blocksize;
-
-	fs->cache_free_extents = ext2_cache_free_extents;
-	fs->close = ext2_close;
-	fs->copy_inodes = ext2_copy_inodes;
-
-	return 0;
-}
-
 static int open_fs(struct convert_fs *fs, const char *devname)
 {
 	static struct {
diff --git a/convert/convert.h b/convert/convert.h
new file mode 100644
index 0000000..4f31775
--- /dev/null
+++ b/convert/convert.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2007 Oracle.  All rights reserved.
+ * Copyright (C) 2010 Sean Bartell.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 021110-1307, USA.
+ */
+#ifndef BTRFS_CONVERT_H
+#define BTRFS_CONVERT_H
+
+#include "ctree.h"
+#include "kerncompat.h"
+#include "transaction.h"
+
+struct convert_fs {
+	u64 total_bytes;
+	u64 blocksize;
+	const char *label;
+
+	/* Close the FS */
+	int (*close)(struct convert_fs *fs);
+	/* Mark free extents as dirty */
+	int (*cache_free_extents)(struct convert_fs *fs,
+				  struct extent_io_tree *tree);
+	/* Copy everything over */
+	int (*copy_inodes)(struct convert_fs *fs, struct btrfs_root *root,
+			   int datacsum, int packing, int noxattr);
+
+	void *privdata;
+};
+
+int ext2_open(struct convert_fs *fs, const char *name);
+
+struct extent_iterate_data {
+	struct btrfs_trans_handle *trans;
+	struct btrfs_root *root;
+	u64 *inode_nbytes;
+	u64 objectid;
+	int checksum, packing;
+	u64 last_file_off;
+	u64 total_size;
+	enum {EXTENT_ITERATE_TYPE_NONE, EXTENT_ITERATE_TYPE_MEM,
+	      EXTENT_ITERATE_TYPE_DISK} type;
+	u64 size;
+	u64 file_off; /* always aligned to sectorsize */
+	char *data; /* for mem */
+	u64 disk_off; /* for disk */
+};
+
+int start_file_extents(struct extent_iterate_data *priv,
+		       struct btrfs_trans_handle *trans,
+		       struct btrfs_root *root, u64 *inode_nbytes,
+		       u64 objectid, int checksum, int packing,
+		       u64 total_size);
+int start_file_extents_range(struct extent_iterate_data *priv,
+			     struct btrfs_trans_handle *trans,
+			     struct btrfs_root *root, u64 *inode_nbytes,
+			     u64 objectid, int checksum, u64 start, u64 end);
+int finish_file_extents(struct extent_iterate_data *priv);
+int add_file_mem_extent(struct extent_iterate_data *priv, u64 file_off,
+			u64 size, char *data);
+int add_file_disk_extent(struct extent_iterate_data *priv, u64 file_off,
+			 u64 disk_off, u64 size);
+
+#endif
diff --git a/convert/ext2.c b/convert/ext2.c
new file mode 100644
index 0000000..7584701
--- /dev/null
+++ b/convert/ext2.c
@@ -0,0 +1,791 @@
+/*
+ * Copyright (C) 2007 Oracle.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 021110-1307, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/acl.h>
+#include <linux/fs.h>
+#include "kerncompat.h"
+#include "convert.h"
+#include "ctree.h"
+#include "disk-io.h"
+#include "transaction.h"
+#include <ext2fs/ext2_fs.h>
+#include <ext2fs/ext2fs.h>
+#include <ext2fs/ext2_ext_attr.h>
+
+#define INO_OFFSET (BTRFS_FIRST_FREE_OBJECTID - EXT2_ROOT_INO)
+
+/*
+ * Open Ext2fs in readonly mode, read block allocation bitmap and
+ * inode bitmap into memory.
+ */
+static int open_ext2fs(const char *name, ext2_filsys *ret_fs)
+{
+	errcode_t ret;
+	ext2_filsys ext2_fs;
+	ext2_ino_t ino;
+	ret = ext2fs_open(name, 0, 0, 0, unix_io_manager, &ext2_fs);
+	if (ret) {
+		fprintf(stderr, "ext2fs_open: %s\n", error_message(ret));
+		goto fail;
+	}
+	ret = ext2fs_read_inode_bitmap(ext2_fs);
+	if (ret) {
+		fprintf(stderr, "ext2fs_read_inode_bitmap: %s\n",
+			error_message(ret));
+		goto fail;
+	}
+	ret = ext2fs_read_block_bitmap(ext2_fs);
+	if (ret) {
+		fprintf(stderr, "ext2fs_read_block_bitmap: %s\n",
+			error_message(ret));
+		goto fail;
+	}
+	/*
+	 * search each block group for a free inode. this set up
+	 * uninit block/inode bitmaps appropriately.
+	 */
+	ino = 1;
+	while (ino <= ext2_fs->super->s_inodes_count) {
+		ext2_ino_t foo;
+		ext2fs_new_inode(ext2_fs, ino, 0, NULL, &foo);
+		ino += EXT2_INODES_PER_GROUP(ext2_fs->super);
+	}
+
+	*ret_fs = ext2_fs;
+	return 0;
+fail:
+	return -1;
+}
+
+static int ext2_close(struct convert_fs *fs)
+{
+	ext2fs_close((ext2_filsys)fs->privdata);
+	return 0;
+}
+
+static int ext2_cache_free_extents(struct convert_fs *fs,
+				   struct extent_io_tree *free_tree)
+{
+	ext2_filsys ext2_fs = fs->privdata;
+	int ret = 0;
+	blk_t block;
+	u64 bytenr;
+	u64 blocksize = ext2_fs->blocksize;
+
+	block = ext2_fs->super->s_first_data_block;
+	for (; block < ext2_fs->super->s_blocks_count; block++) {
+		if (ext2fs_fast_test_block_bitmap(ext2_fs->block_map, block))
+			continue;
+		bytenr = block * blocksize;
+		ret = set_extent_dirty(free_tree, bytenr,
+				       bytenr + blocksize - 1, 0);
+		BUG_ON(ret);
+	}
+
+	return 0;
+}
+
+struct dir_iterate_data {
+	struct btrfs_trans_handle *trans;
+	struct btrfs_root *root;
+	struct btrfs_inode_item *inode;
+	u64 objectid;
+	u64 index_cnt;
+	u64 parent;
+	int errcode;
+};
+
+static u8 filetype_conversion_table[EXT2_FT_MAX] = {
+	[EXT2_FT_UNKNOWN]	= BTRFS_FT_UNKNOWN,
+	[EXT2_FT_REG_FILE]	= BTRFS_FT_REG_FILE,
+	[EXT2_FT_DIR]		= BTRFS_FT_DIR,
+	[EXT2_FT_CHRDEV]	= BTRFS_FT_CHRDEV,
+	[EXT2_FT_BLKDEV]	= BTRFS_FT_BLKDEV,
+	[EXT2_FT_FIFO]		= BTRFS_FT_FIFO,
+	[EXT2_FT_SOCK]		= BTRFS_FT_SOCK,
+	[EXT2_FT_SYMLINK]	= BTRFS_FT_SYMLINK,
+};
+
+static int dir_iterate_proc(ext2_ino_t dir, int entry,
+			    struct ext2_dir_entry *old,
+			    int offset, int blocksize,
+			    char *buf,void *priv_data)
+{
+	int ret;
+	int file_type;
+	u64 objectid;
+        u64 inode_size;
+	char dotdot[] = "..";
+	struct btrfs_key location;
+	struct ext2_dir_entry_2 *dirent = (struct ext2_dir_entry_2 *)old;
+	struct dir_iterate_data *idata = (struct dir_iterate_data *)priv_data;
+
+	objectid = dirent->inode + INO_OFFSET;
+	if (!strncmp(dirent->name, dotdot, dirent->name_len)) {
+		if (dirent->name_len == 2) {
+			BUG_ON(idata->parent != 0);
+			idata->parent = objectid;
+		}
+		return 0;
+	}
+	if (dirent->inode < EXT2_GOOD_OLD_FIRST_INO)
+		return 0;
+
+	location.objectid = objectid;
+	location.offset = 0;
+	btrfs_set_key_type(&location, BTRFS_INODE_ITEM_KEY);
+
+	file_type = dirent->file_type;
+	BUG_ON(file_type > EXT2_FT_SYMLINK);
+	ret = btrfs_insert_dir_item(idata->trans, idata->root,
+				    dirent->name, dirent->name_len,
+				    idata->objectid, &location,
+				    filetype_conversion_table[file_type],
+				    idata->index_cnt);
+	if (ret)
+		goto fail;
+	ret = btrfs_insert_inode_ref(idata->trans, idata->root,
+				     dirent->name, dirent->name_len,
+				     objectid, idata->objectid,
+				     idata->index_cnt);
+	if (ret)
+		goto fail;
+	idata->index_cnt++;
+	inode_size = btrfs_stack_inode_size(idata->inode) +
+		     dirent->name_len * 2;
+	btrfs_set_stack_inode_size(idata->inode, inode_size);
+	return 0;
+fail:
+	idata->errcode = ret;
+	return BLOCK_ABORT;
+}
+
+static int create_dir_entries(struct btrfs_trans_handle *trans,
+			      struct btrfs_root *root, u64 objectid,
+			      struct btrfs_inode_item *btrfs_inode,
+			      ext2_filsys ext2_fs, ext2_ino_t ext2_ino)
+{
+	int ret;
+	errcode_t err;
+	struct dir_iterate_data data = {
+		.trans		= trans,
+		.root		= root,
+		.inode		= btrfs_inode,
+		.objectid	= objectid,
+		.index_cnt	= 2,
+		.parent		= 0,
+		.errcode	= 0,
+	};
+
+	err = ext2fs_dir_iterate2(ext2_fs, ext2_ino, 0, NULL,
+				  dir_iterate_proc, &data);
+	if (err)
+		goto error;
+	ret = data.errcode;
+	if (ret == 0 && data.parent == objectid) {
+		ret = btrfs_insert_inode_ref(trans, root, "..", 2,
+					     objectid, objectid, 0);
+	}
+	return ret;
+error:
+	fprintf(stderr, "ext2fs_dir_iterate2: %s\n", error_message(err));
+	return -1;
+}
+
+static int __block_iterate_proc(ext2_filsys fs, blk_t *blocknr,
+			        e2_blkcnt_t blockcnt, blk_t ref_block,
+			        int ref_offset, void *priv_data)
+{
+	struct extent_iterate_data *idata;
+	idata = (struct extent_iterate_data *)priv_data;
+	u64 blocksize = fs->blocksize;
+	int ret = add_file_disk_extent(idata, blocksize * blockcnt,
+				       blocksize * *blocknr, blocksize);
+	if (ret)
+		return BLOCK_ABORT;
+	return 0;
+}
+
+/*
+ * traverse file's data blocks, record these data blocks as file extents.
+ */
+static int create_file_extents(struct btrfs_trans_handle *trans,
+			       struct btrfs_root *root, u64 objectid,
+			       struct btrfs_inode_item *btrfs_inode,
+			       ext2_filsys ext2_fs, ext2_ino_t ext2_ino,
+			       int datacsum, int packing)
+{
+	int ret;
+	errcode_t err;
+	u64 inode_nbytes = 0;
+	u64 inode_size = btrfs_stack_inode_size(btrfs_inode);
+	struct extent_iterate_data data;
+	ret = start_file_extents(&data, trans, root, &inode_nbytes, objectid,
+				 datacsum, packing, inode_size);
+	if (ret)
+		return ret;
+	err = ext2fs_block_iterate2(ext2_fs, ext2_ino, BLOCK_FLAG_DATA_ONLY,
+				    NULL, __block_iterate_proc, &data);
+	if (err)
+		goto error;
+	ret = finish_file_extents(&data);
+	if (ret)
+		return ret;
+	btrfs_set_stack_inode_nbytes(btrfs_inode, inode_nbytes);
+	return 0;
+error:
+	fprintf(stderr, "ext2fs_block_iterate2: %s\n", error_message(err));
+	return -1;
+}
+
+static int create_symbol_link(struct btrfs_trans_handle *trans,
+			      struct btrfs_root *root, u64 objectid,
+			      struct btrfs_inode_item *btrfs_inode,
+			      ext2_filsys ext2_fs, ext2_ino_t ext2_ino,
+			      struct ext2_inode *ext2_inode)
+{
+	int ret;
+	char *pathname;
+	u64 inode_size = btrfs_stack_inode_size(btrfs_inode);
+	if (ext2fs_inode_data_blocks(ext2_fs, ext2_inode)) {
+		btrfs_set_stack_inode_size(btrfs_inode, inode_size + 1);
+		ret = create_file_extents(trans, root, objectid, btrfs_inode,
+					  ext2_fs, ext2_ino, 1, 1);
+		btrfs_set_stack_inode_size(btrfs_inode, inode_size);
+		return ret;
+	}
+
+	pathname = (char *)&(ext2_inode->i_block[0]);
+	BUG_ON(pathname[inode_size] != 0);
+	ret = btrfs_insert_inline_extent(trans, root, objectid, 0,
+					 pathname, inode_size + 1);
+	btrfs_set_stack_inode_nbytes(btrfs_inode, inode_size + 1);
+	return ret;
+}
+
+/*
+ * Following xattr/acl related codes are based on codes in
+ * fs/ext3/xattr.c and fs/ext3/acl.c
+ */
+#define EXT2_XATTR_BHDR(ptr) ((struct ext2_ext_attr_header *)(ptr))
+#define EXT2_XATTR_BFIRST(ptr) \
+	((struct ext2_ext_attr_entry *)(EXT2_XATTR_BHDR(ptr) + 1))
+#define EXT2_XATTR_IHDR(inode) \
+	((struct ext2_ext_attr_header *) ((void *)(inode) + \
+		EXT2_GOOD_OLD_INODE_SIZE + (inode)->i_extra_isize))
+#define EXT2_XATTR_IFIRST(inode) \
+	((struct ext2_ext_attr_entry *) ((void *)EXT2_XATTR_IHDR(inode) + \
+		sizeof(EXT2_XATTR_IHDR(inode)->h_magic)))
+
+static int ext2_xattr_check_names(struct ext2_ext_attr_entry *entry,
+				  const void *end)
+{
+	struct ext2_ext_attr_entry *next;
+
+	while (!EXT2_EXT_IS_LAST_ENTRY(entry)) {
+		next = EXT2_EXT_ATTR_NEXT(entry);
+		if ((void *)next >= end)
+			return -EIO;
+		entry = next;
+	}
+	return 0;
+}
+
+static int ext2_xattr_check_block(const char *buf, size_t size)
+{
+	int error;
+	struct ext2_ext_attr_header *header = EXT2_XATTR_BHDR(buf);
+
+	if (header->h_magic != EXT2_EXT_ATTR_MAGIC ||
+	    header->h_blocks != 1)
+		return -EIO;
+	error = ext2_xattr_check_names(EXT2_XATTR_BFIRST(buf), buf + size);
+	return error;
+}
+
+static int ext2_xattr_check_entry(struct ext2_ext_attr_entry *entry,
+				  size_t size)
+{
+	size_t value_size = entry->e_value_size;
+
+	if (entry->e_value_block != 0 || value_size > size ||
+	    entry->e_value_offs + value_size > size)
+		return -EIO;
+	return 0;
+}
+
+#define EXT2_ACL_VERSION	0x0001
+
+typedef struct {
+	__le16		e_tag;
+	__le16		e_perm;
+	__le32		e_id;
+} ext2_acl_entry;
+
+typedef struct {
+	__le16		e_tag;
+	__le16		e_perm;
+} ext2_acl_entry_short;
+
+typedef struct {
+	__le32		a_version;
+} ext2_acl_header;
+
+static inline int ext2_acl_count(size_t size)
+{
+	ssize_t s;
+	size -= sizeof(ext2_acl_header);
+	s = size - 4 * sizeof(ext2_acl_entry_short);
+	if (s < 0) {
+		if (size % sizeof(ext2_acl_entry_short))
+			return -1;
+		return size / sizeof(ext2_acl_entry_short);
+	} else {
+		if (s % sizeof(ext2_acl_entry))
+			return -1;
+		return s / sizeof(ext2_acl_entry) + 4;
+	}
+}
+
+#define ACL_EA_VERSION		0x0002
+
+typedef struct {
+	__le16		e_tag;
+	__le16		e_perm;
+	__le32		e_id;
+} acl_ea_entry;
+
+typedef struct {
+	__le32		a_version;
+	acl_ea_entry	a_entries[0];
+} acl_ea_header;
+
+static inline size_t acl_ea_size(int count)
+{
+	return sizeof(acl_ea_header) + count * sizeof(acl_ea_entry);
+}
+
+static int ext2_acl_to_xattr(void *dst, const void *src,
+			     size_t dst_size, size_t src_size)
+{
+	int i, count;
+	const void *end = src + src_size;
+	acl_ea_header *ext_acl = (acl_ea_header *)dst;
+	acl_ea_entry *dst_entry = ext_acl->a_entries;
+	ext2_acl_entry *src_entry;
+
+	if (src_size < sizeof(ext2_acl_header))
+		goto fail;
+	if (((ext2_acl_header *)src)->a_version !=
+	    cpu_to_le32(EXT2_ACL_VERSION))
+		goto fail;
+	src += sizeof(ext2_acl_header);
+	count = ext2_acl_count(src_size);
+	if (count <= 0)
+		goto fail;
+
+	BUG_ON(dst_size < acl_ea_size(count));
+	ext_acl->a_version = cpu_to_le32(ACL_EA_VERSION);
+	for (i = 0; i < count; i++, dst_entry++) {
+		src_entry = (ext2_acl_entry *)src;
+		if (src + sizeof(ext2_acl_entry_short) > end)
+			goto fail;
+		dst_entry->e_tag = src_entry->e_tag;
+		dst_entry->e_perm = src_entry->e_perm;
+		switch (le16_to_cpu(src_entry->e_tag)) {
+		case ACL_USER_OBJ:
+		case ACL_GROUP_OBJ:
+		case ACL_MASK:
+		case ACL_OTHER:
+			src += sizeof(ext2_acl_entry_short);
+			dst_entry->e_id = cpu_to_le32(ACL_UNDEFINED_ID);
+			break;
+		case ACL_USER:
+		case ACL_GROUP:
+			src += sizeof(ext2_acl_entry);
+			if (src > end)
+				goto fail;
+			dst_entry->e_id = src_entry->e_id;
+			break;
+		default:
+			goto fail;
+		}
+	}
+	if (src != end)
+		goto fail;
+	return 0;
+fail:
+	return -EINVAL;
+}
+
+static char *xattr_prefix_table[] = {
+	[1] =	"user.",
+	[2] =	"system.posix_acl_access",
+	[3] =	"system.posix_acl_default",
+	[4] =	"trusted.",
+	[6] =	"security.",
+};
+
+static int copy_single_xattr(struct btrfs_trans_handle *trans,
+			     struct btrfs_root *root, u64 objectid,
+			     struct ext2_ext_attr_entry *entry,
+			     const void *data, u32 datalen)
+{
+	int ret = 0;
+	int name_len;
+	int name_index;
+	void *databuf = NULL;
+	char namebuf[XATTR_NAME_MAX + 1];
+
+	name_index = entry->e_name_index;
+	if (name_index >= ARRAY_SIZE(xattr_prefix_table) ||
+	    xattr_prefix_table[name_index] == NULL)
+		return -EOPNOTSUPP;
+	name_len = strlen(xattr_prefix_table[name_index]) +
+		   entry->e_name_len;
+	if (name_len >= sizeof(namebuf))
+		return -ERANGE;
+
+	if (name_index == 2 || name_index == 3) {
+		size_t bufsize = acl_ea_size(ext2_acl_count(datalen));
+		databuf = malloc(bufsize);
+		if (!databuf)
+		       return -ENOMEM;
+		ret = ext2_acl_to_xattr(databuf, data, bufsize, datalen);
+		if (ret)
+			goto out;
+		data = databuf;
+		datalen = bufsize;
+	}
+	strcpy(namebuf, xattr_prefix_table[name_index]);
+	strncat(namebuf, EXT2_EXT_ATTR_NAME(entry), entry->e_name_len);
+	if (name_len + datalen > BTRFS_LEAF_DATA_SIZE(root) -
+	    sizeof(struct btrfs_item) - sizeof(struct btrfs_dir_item)) {
+		fprintf(stderr, "skip large xattr on inode %Lu name %.*s\n",
+			objectid - INO_OFFSET, name_len, namebuf);
+		goto out;
+	}
+	ret = btrfs_insert_xattr_item(trans, root, namebuf, name_len,
+				      data, datalen, objectid);
+out:
+	if (databuf)
+		free(databuf);
+	return ret;
+}
+
+static int copy_extended_attrs(struct btrfs_trans_handle *trans,
+			       struct btrfs_root *root, u64 objectid,
+			       struct btrfs_inode_item *btrfs_inode,
+			       ext2_filsys ext2_fs, ext2_ino_t ext2_ino)
+{
+	int ret = 0;
+	int inline_ea = 0;
+	errcode_t err;
+	u32 datalen;
+	u32 block_size = ext2_fs->blocksize;
+	u32 inode_size = EXT2_INODE_SIZE(ext2_fs->super);
+	struct ext2_inode_large *ext2_inode;
+	struct ext2_ext_attr_entry *entry;
+	void *data;
+	char *buffer = NULL;
+	char inode_buf[EXT2_GOOD_OLD_INODE_SIZE];
+
+	if (inode_size <= EXT2_GOOD_OLD_INODE_SIZE) {
+		ext2_inode = (struct ext2_inode_large *)inode_buf;
+	} else {
+		ext2_inode = (struct ext2_inode_large *)malloc(inode_size);
+		if (!ext2_inode)
+		       return -ENOMEM;
+	}
+	err = ext2fs_read_inode_full(ext2_fs, ext2_ino, (void *)ext2_inode,
+				     inode_size);
+	if (err) {
+		fprintf(stderr, "ext2fs_read_inode_full: %s\n",
+			error_message(err));
+		ret = -1;
+		goto out;
+	}
+
+	if (ext2_ino > ext2_fs->super->s_first_ino &&
+	    inode_size > EXT2_GOOD_OLD_INODE_SIZE) {
+		if (EXT2_GOOD_OLD_INODE_SIZE +
+		    ext2_inode->i_extra_isize > inode_size) {
+			ret = -EIO;
+			goto out;
+		}
+		if (ext2_inode->i_extra_isize != 0 &&
+		    EXT2_XATTR_IHDR(ext2_inode)->h_magic ==
+		    EXT2_EXT_ATTR_MAGIC) {
+			inline_ea = 1;
+		}
+	}
+	if (inline_ea) {
+		int total;
+		void *end = (void *)ext2_inode + inode_size;
+		entry = EXT2_XATTR_IFIRST(ext2_inode);
+		total = end - (void *)entry;
+		ret = ext2_xattr_check_names(entry, end);
+		if (ret)
+			goto out;
+		while (!EXT2_EXT_IS_LAST_ENTRY(entry)) {
+			ret = ext2_xattr_check_entry(entry, total);
+			if (ret)
+				goto out;
+			data = (void *)EXT2_XATTR_IFIRST(ext2_inode) +
+				entry->e_value_offs;
+			datalen = entry->e_value_size;
+			ret = copy_single_xattr(trans, root, objectid,
+						entry, data, datalen);
+			if (ret)
+				goto out;
+			entry = EXT2_EXT_ATTR_NEXT(entry);
+		}
+	}
+
+	if (ext2_inode->i_file_acl == 0)
+		goto out;
+
+	buffer = malloc(block_size);
+	if (!buffer) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	err = ext2fs_read_ext_attr(ext2_fs, ext2_inode->i_file_acl, buffer);
+	if (err) {
+		fprintf(stderr, "ext2fs_read_ext_attr: %s\n",
+			error_message(err));
+		ret = -1;
+		goto out;
+	}
+	ret = ext2_xattr_check_block(buffer, block_size);
+	if (ret)
+		goto out;
+
+	entry = EXT2_XATTR_BFIRST(buffer);
+	while (!EXT2_EXT_IS_LAST_ENTRY(entry)) {
+		ret = ext2_xattr_check_entry(entry, block_size);
+		if (ret)
+			goto out;
+		data = buffer + entry->e_value_offs;
+		datalen = entry->e_value_size;
+		ret = copy_single_xattr(trans, root, objectid,
+					entry, data, datalen);
+		if (ret)
+			goto out;
+		entry = EXT2_EXT_ATTR_NEXT(entry);
+	}
+out:
+	if (buffer != NULL)
+		free(buffer);
+	if ((void *)ext2_inode != inode_buf)
+		free(ext2_inode);
+	return ret;
+}
+#define MINORBITS	20
+#define MKDEV(ma, mi)	(((ma) << MINORBITS) | (mi))
+
+static inline dev_t old_decode_dev(u16 val)
+{
+	return MKDEV((val >> 8) & 255, val & 255);
+}
+
+static inline dev_t new_decode_dev(u32 dev)
+{
+	unsigned major = (dev & 0xfff00) >> 8;
+	unsigned minor = (dev & 0xff) | ((dev >> 12) & 0xfff00);
+	return MKDEV(major, minor);
+}
+
+static int copy_inode_item(struct btrfs_inode_item *dst,
+			   struct ext2_inode *src, u32 blocksize)
+{
+	btrfs_set_stack_inode_generation(dst, 1);
+	btrfs_set_stack_inode_size(dst, src->i_size);
+	btrfs_set_stack_inode_nbytes(dst, 0);
+	btrfs_set_stack_inode_block_group(dst, 0);
+	btrfs_set_stack_inode_nlink(dst, src->i_links_count);
+	btrfs_set_stack_inode_uid(dst, src->i_uid | (src->i_uid_high << 16));
+	btrfs_set_stack_inode_gid(dst, src->i_gid | (src->i_gid_high << 16));
+	btrfs_set_stack_inode_mode(dst, src->i_mode);
+	btrfs_set_stack_inode_rdev(dst, 0);
+	btrfs_set_stack_inode_flags(dst, 0);
+	btrfs_set_stack_timespec_sec(&dst->atime, src->i_atime);
+	btrfs_set_stack_timespec_nsec(&dst->atime, 0);
+	btrfs_set_stack_timespec_sec(&dst->ctime, src->i_ctime);
+	btrfs_set_stack_timespec_nsec(&dst->ctime, 0);
+	btrfs_set_stack_timespec_sec(&dst->mtime, src->i_mtime);
+	btrfs_set_stack_timespec_nsec(&dst->mtime, 0);
+	btrfs_set_stack_timespec_sec(&dst->otime, 0);
+	btrfs_set_stack_timespec_nsec(&dst->otime, 0);
+
+	if (S_ISDIR(src->i_mode)) {
+		btrfs_set_stack_inode_size(dst, 0);
+		btrfs_set_stack_inode_nlink(dst, 1);
+	}
+	if (S_ISREG(src->i_mode)) {
+		btrfs_set_stack_inode_size(dst, (u64)src->i_size_high << 32 |
+					   (u64)src->i_size);
+	}
+	if (!S_ISREG(src->i_mode) && !S_ISDIR(src->i_mode) &&
+	    !S_ISLNK(src->i_mode)) {
+		if (src->i_block[0]) {
+			btrfs_set_stack_inode_rdev(dst,
+				old_decode_dev(src->i_block[0]));
+		} else {
+			btrfs_set_stack_inode_rdev(dst,
+				new_decode_dev(src->i_block[1]));
+		}
+	}
+	return 0;
+}
+
+/*
+ * copy a single inode. do all the required works, such as cloning
+ * inode item, creating file extents and creating directory entries.
+ */
+static int copy_single_inode(struct btrfs_trans_handle *trans,
+			     struct btrfs_root *root, u64 objectid,
+			     ext2_filsys ext2_fs, ext2_ino_t ext2_ino,
+			     struct ext2_inode *ext2_inode,
+			     int datacsum, int packing, int noxattr)
+{
+	int ret;
+	struct btrfs_key inode_key;
+	struct btrfs_inode_item btrfs_inode;
+
+	if (ext2_inode->i_links_count == 0)
+		return 0;
+
+	copy_inode_item(&btrfs_inode, ext2_inode, ext2_fs->blocksize);
+	if (!datacsum && S_ISREG(ext2_inode->i_mode)) {
+		u32 flags = btrfs_stack_inode_flags(&btrfs_inode) |
+			    BTRFS_INODE_NODATASUM;
+		btrfs_set_stack_inode_flags(&btrfs_inode, flags);
+	}
+
+	switch (ext2_inode->i_mode & S_IFMT) {
+	case S_IFREG:
+		ret = create_file_extents(trans, root, objectid, &btrfs_inode,
+					ext2_fs, ext2_ino, datacsum, packing);
+		break;
+	case S_IFDIR:
+		ret = create_dir_entries(trans, root, objectid, &btrfs_inode,
+					 ext2_fs, ext2_ino);
+		break;
+	case S_IFLNK:
+		ret = create_symbol_link(trans, root, objectid, &btrfs_inode,
+					 ext2_fs, ext2_ino, ext2_inode);
+		break;
+	default:
+		ret = 0;
+		break;
+	}
+	if (ret)
+		return ret;
+
+	if (!noxattr) {
+		ret = copy_extended_attrs(trans, root, objectid, &btrfs_inode,
+					  ext2_fs, ext2_ino);
+		if (ret)
+			return ret;
+	}
+	inode_key.objectid = objectid;
+	inode_key.offset = 0;
+	btrfs_set_key_type(&inode_key, BTRFS_INODE_ITEM_KEY);
+	ret = btrfs_insert_inode(trans, root, objectid, &btrfs_inode);
+	return ret;
+}
+
+/*
+ * scan ext2's inode bitmap and copy all used inode.
+ */
+static int ext2_copy_inodes(struct convert_fs *fs, struct btrfs_root *root,
+			    int datacsum, int packing, int noxattr)
+{
+	ext2_filsys ext2_fs = fs->privdata;
+	int ret;
+	errcode_t err;
+	ext2_inode_scan ext2_scan;
+	struct ext2_inode ext2_inode;
+	ext2_ino_t ext2_ino;
+	u64 objectid;
+	struct btrfs_trans_handle *trans;
+
+	trans = btrfs_start_transaction(root, 1);
+	if (!trans)
+		return -ENOMEM;
+	err = ext2fs_open_inode_scan(ext2_fs, 0, &ext2_scan);
+	if (err) {
+		fprintf(stderr, "ext2fs_open_inode_scan: %s\n", error_message(err));
+		return -1;
+	}
+	while (!(err = ext2fs_get_next_inode(ext2_scan, &ext2_ino,
+					     &ext2_inode))) {
+		/* no more inodes */
+		if (ext2_ino == 0)
+			break;
+		/* skip special inode in ext2fs */
+		if (ext2_ino < EXT2_GOOD_OLD_FIRST_INO &&
+		    ext2_ino != EXT2_ROOT_INO)
+			continue;
+		objectid = ext2_ino + INO_OFFSET;
+		ret = copy_single_inode(trans, root,
+					objectid, ext2_fs, ext2_ino,
+					&ext2_inode, datacsum, packing,
+					noxattr);
+		if (ret)
+			return ret;
+		if (trans->blocks_used >= 4096) {
+			ret = btrfs_commit_transaction(trans, root);
+			BUG_ON(ret);
+			trans = btrfs_start_transaction(root, 1);
+			BUG_ON(!trans);
+		}
+	}
+	if (err) {
+		fprintf(stderr, "ext2fs_get_next_inode: %s\n", error_message(err));
+		return -1;
+	}
+	ret = btrfs_commit_transaction(trans, root);
+	BUG_ON(ret);
+
+	return ret;
+}
+
+int ext2_open(struct convert_fs *fs, const char *name)
+{
+	int ret;
+	ext2_filsys ext2_fs;
+	ret = open_ext2fs(name, &ext2_fs);
+	if (ret)
+		return ret;
+
+	fs->privdata = ext2_fs;
+	fs->blocksize = ext2_fs->blocksize;
+	fs->label = ext2_fs->super->s_volume_name;
+	fs->total_bytes = ext2_fs->super->s_blocks_count * fs->blocksize;
+
+	fs->cache_free_extents = ext2_cache_free_extents;
+	fs->close = ext2_close;
+	fs->copy_inodes = ext2_copy_inodes;
+
+	return 0;
+}
