Yan Zheng
2008-Jan-10 06:06 UTC
[Btrfs-devel][PATCH]xattr support for the ext3->btrfs converter
Hello, This patch adds xattrs/acl's support for the converter. The supporting codes are based on codes in fs/ext3/xattr.c and fs/ext3/acl.c and were tested by converting a F7 installation. (compare xattrs created by selinux and beagle) Regards YZ ---- diff -r 94f19b1859e1 convert.c --- a/convert.c Tue Jan 08 15:56:32 2008 -0500 +++ b/convert.c Thu Jan 10 22:05:20 2008 +0800 @@ -25,6 +25,7 @@ #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> @@ -35,8 +36,9 @@ #include "transaction.h" #include "crc32c.h" #include "utils.h" -#include "ext2fs/ext2_fs.h" -#include "ext2fs/ext2fs.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) @@ -446,7 +448,8 @@ static int create_file_extents(struct bt 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) + ext2_filsys ext2_fs, ext2_ino_t ext2_ino, + int datacsum, int packing) { int ret; char *buffer = NULL; @@ -454,7 +457,6 @@ static int create_file_extents(struct bt u32 last_block; u32 sectorsize = root->sectorsize; u64 inode_size = btrfs_stack_inode_size(btrfs_inode); - u32 inode_flags = btrfs_stack_inode_flags(btrfs_inode); struct blk_iterate_data data = { .trans = trans, .root = root, @@ -463,12 +465,10 @@ static int create_file_extents(struct bt .first_block = 0, .disk_block = 0, .num_blocks = 0, - .checksum = 1, + .checksum = datacsum, .errcode = 0, }; - if (inode_flags & BTRFS_INODE_NODATASUM) - data.checksum = 0; err = ext2fs_block_iterate2(ext2_fs, ext2_ino, BLOCK_FLAG_DATA_ONLY, NULL, __block_iterate_proc, &data); if (err) @@ -477,7 +477,7 @@ static int create_file_extents(struct bt if (ret) goto fail; - if (data.first_block == 0 && data.num_blocks > 0 && + if (packing && data.first_block == 0 && data.num_blocks > 0 && inode_size <= BTRFS_MAX_INLINE_DATA_SIZE(root)) { u64 num_bytes = data.num_blocks * sectorsize; u64 disk_bytenr = data.disk_block * sectorsize; @@ -528,8 +528,8 @@ static int create_symbol_link(struct btr 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); + 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; } @@ -538,6 +538,325 @@ static int create_symbol_link(struct btr BUG_ON(pathname[inode_size] != 0); ret = btrfs_insert_inline_extent(trans, root, objectid, 0, pathname, 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; + } + 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; + } + strcpy(namebuf, xattr_prefix_table[name_index]); + strncat(namebuf, EXT2_EXT_ATTR_NAME(entry), entry->e_name_len); + 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; } @@ -604,7 +923,7 @@ static int copy_single_inode(struct btrf 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, - int datacsum) + int datacsum, int packing, int noxattr) { int ret; errcode_t err; @@ -631,7 +950,7 @@ static int copy_single_inode(struct btrf switch (ext2_inode.i_mode & S_IFMT) { case S_IFREG: ret = create_file_extents(trans, root, objectid, &btrfs_inode, - ext2_fs, ext2_ino); + ext2_fs, ext2_ino, datacsum, packing); break; case S_IFDIR: ret = create_dir_entries(trans, root, objectid, &btrfs_inode, @@ -647,6 +966,13 @@ static int copy_single_inode(struct btrf } 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); @@ -680,12 +1006,11 @@ fail: ret = -1; return ret; } - /* * scan ext2's inode bitmap and copy all used inode. */ static int copy_inodes(struct btrfs_root *root, ext2_filsys ext2_fs, - int datacsum) + int datacsum, int packing, int noxattr) { int ret; ext2_ino_t ext2_ino; @@ -704,8 +1029,9 @@ static int copy_inodes(struct btrfs_root ext2_ino != EXT2_ROOT_INO) continue; objectid = ext2_ino + INO_OFFSET; - ret = copy_single_inode(trans, root, objectid, - ext2_fs, ext2_ino, datacsum); + ret = copy_single_inode(trans, root, + objectid, ext2_fs, ext2_ino, + datacsum, packing, noxattr); if (ret) return ret; } @@ -737,7 +1063,6 @@ static int lookup_extent_item(struct btr btrfs_release_path(root, &path); return ret; } - /* * Construct a range of ext2fs image file. * scan block allocation bitmap, find all blocks used by the ext2fs @@ -804,7 +1129,6 @@ fail: fail: return 0; } - /* * Create the ext2fs image file. */ @@ -970,7 +1294,7 @@ again: /* * otime isn't used currently, so we can store some data in it. * These data are used by do_rollback to check whether the image - * file have been modified. + * file has been modified. */ btrfs_set_stack_timespec_sec(&btrfs_inode.otime, trans->transid); btrfs_set_stack_timespec_nsec(&btrfs_inode.otime, @@ -1059,7 +1383,6 @@ fail: fail: return NULL; } - /* * Fixup block accounting. The initial block accounting created by * make_block_groups isn't accuracy in this case. @@ -1219,7 +1542,8 @@ fail: ret = -1; return ret; } -int do_convert(const char *devname, int datacsum) + +int do_convert(const char *devname, int datacsum, int packing, int noxattr) { int i, fd, ret; u32 blocksize; @@ -1289,7 +1613,7 @@ int do_convert(const char *devname, int goto fail; } printf("creating btrfs metadata.\n"); - ret = copy_inodes(root, ext2_fs, datacsum); + ret = copy_inodes(root, ext2_fs, datacsum, packing, noxattr); if (ret) { fprintf(stderr, "error during copy_inodes %d\n", ret); goto fail; @@ -1420,7 +1744,8 @@ int do_rollback(const char *devname, int total_bytes = btrfs_timespec_nsec(leaf, tspec); total_bytes *= root->sectorsize; btrfs_release_path(ext2_root, &path); - if (total_bytes != btrfs_inode_size(leaf, inode)) { + if (total_bytes < first_free || + total_bytes != btrfs_inode_size(leaf, inode)) { fprintf(stderr, "image file size mismatch\n"); goto fail; } @@ -1431,6 +1756,7 @@ int do_rollback(const char *devname, int ret = btrfs_search_slot(NULL, ext2_root, &key, &path, 0, 0); if (ret != 0) { fprintf(stderr, "unable to find first file extent\n"); + btrfs_release_path(ext2_root, &path); goto fail; } @@ -1519,8 +1845,10 @@ fail: static void print_usage(void) { - printf("usage: btrfs-convert [-d] [-r] device\n"); + printf("usage: btrfs-convert [-d] [-i] [-n] [-r] device\n"); printf("\t-d disable data checksum\n"); + printf("\t-i ignore xattrs and ACLs\n"); + printf("\t-n disable packing of small files\n"); printf("\t-r roll back to ext2fs\n"); exit(1); } @@ -1528,18 +1856,26 @@ int main(int argc, char *argv[]) int main(int argc, char *argv[]) { int ret; + int packing = 1; + int noxattr = 0; int datacsum = 1; int rollback = 0; char *file; while(1) { - int c = getopt(argc, argv, "dr"); + int c = getopt(argc, argv, "dinr"); if (c < 0) break; switch(c) { case 'd': datacsum = 0; break; + case 'i': + noxattr = 1; + break; + case 'n': + packing = 0; + break; case 'r': rollback = 1; break; @@ -1556,7 +1892,7 @@ int main(int argc, char *argv[]) if (rollback) { ret = do_rollback(file, 0); } else { - ret = do_convert(file, datacsum); + ret = do_convert(file, datacsum, packing, noxattr); } return ret; }