/* * 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 "kerncompat.h" #include #include #include #include "kernel-lib/sizes.h" #include "kernel-shared/disk-io.h" #include "kernel-shared/volumes.h" #include "common/internal.h" #include "common/messages.h" #include "common/extent-cache.h" #include "common/extent-tree-utils.h" #include "convert/common.h" #include "convert/source-fs.h" const struct simple_range btrfs_reserved_ranges[3] = { { 0, SZ_1M }, { BTRFS_SB_MIRROR_OFFSET(1), SZ_64K }, { BTRFS_SB_MIRROR_OFFSET(2), SZ_64K } }; dev_t decode_dev(u32 dev) { unsigned major = (dev & 0xfff00) >> 8; unsigned minor = (dev & 0xff) | ((dev >> 12) & 0xfff00); return MKDEV(major, minor); } 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; } } const struct simple_range *intersect_with_reserved(u64 bytenr, u64 num_bytes) { int i; for (i = 0; i < ARRAY_SIZE(btrfs_reserved_ranges); i++) { const struct simple_range *range = &btrfs_reserved_ranges[i]; if (bytenr < range_end(range) && bytenr + num_bytes > range->start) return range; } return NULL; } void init_convert_context(struct btrfs_convert_context *cctx) { memset(cctx, 0, sizeof(*cctx)); cache_tree_init(&cctx->used_space); cache_tree_init(&cctx->data_chunks); cache_tree_init(&cctx->free_space); cache_tree_init(&cctx->free_space_initial); } void clean_convert_context(struct btrfs_convert_context *cctx) { free_extent_cache_tree(&cctx->used_space); free_extent_cache_tree(&cctx->data_chunks); free_extent_cache_tree(&cctx->free_space); free_extent_cache_tree(&cctx->free_space_initial); } int block_iterate_proc(u64 disk_block, u64 file_block, struct blk_iterate_data *idata) { int ret = 0; const struct simple_range *reserved; int do_barrier; struct btrfs_root *root = idata->root; struct btrfs_block_group *cache; u32 sectorsize = root->fs_info->sectorsize; u64 bytenr = disk_block * sectorsize; reserved = intersect_with_reserved(bytenr, sectorsize); do_barrier = reserved || disk_block >= idata->boundary; if ((idata->num_blocks > 0 && do_barrier) || (file_block > idata->first_block + idata->num_blocks) || (disk_block != idata->disk_block + idata->num_blocks)) { if (idata->num_blocks > 0) { ret = record_file_blocks(idata, idata->first_block, idata->disk_block, idata->num_blocks); if (ret) goto fail; idata->first_block += idata->num_blocks; idata->num_blocks = 0; } if (file_block > idata->first_block) { ret = record_file_blocks(idata, idata->first_block, 0, file_block - idata->first_block); if (ret) goto fail; } if (reserved) { bytenr = range_end(reserved); } else { cache = btrfs_lookup_block_group(root->fs_info, bytenr); if (!cache) { error("block group %llu not found", bytenr); ret = -EUCLEAN; goto fail; } bytenr = cache->start + cache->length; } idata->first_block = file_block; idata->disk_block = disk_block; idata->boundary = bytenr / sectorsize; } idata->num_blocks++; fail: return ret; } void init_blk_iterate_data(struct blk_iterate_data *data, struct btrfs_trans_handle *trans, struct btrfs_root *root, struct btrfs_inode_item *inode, u64 objectid, int checksum) { struct btrfs_key key; data->trans = trans; data->root = root; data->inode = inode; data->objectid = objectid; data->first_block = 0; data->disk_block = 0; data->num_blocks = 0; data->boundary = (u64)-1; data->checksum = checksum; data->errcode = 0; key.objectid = CONV_IMAGE_SUBVOL_OBJECTID; key.type = BTRFS_ROOT_ITEM_KEY; key.offset = (u64)-1; data->convert_root = btrfs_read_fs_root(root->fs_info, &key); /* Impossible as we just opened it before */ BUG_ON(!data->convert_root || IS_ERR(data->convert_root)); data->convert_ino = BTRFS_FIRST_FREE_OBJECTID + 1; } int convert_insert_dirent(struct btrfs_trans_handle *trans, struct btrfs_root *root, const char *name, size_t name_len, u64 dir, u64 objectid, u8 file_type, u64 index_cnt, struct btrfs_inode_item *inode) { int ret; u64 inode_size; struct btrfs_key location = { .objectid = objectid, .offset = 0, .type = BTRFS_INODE_ITEM_KEY, }; ret = btrfs_insert_dir_item(trans, root, name, name_len, dir, &location, file_type, index_cnt); if (ret) return ret; ret = btrfs_insert_inode_ref(trans, root, name, name_len, objectid, dir, index_cnt); if (ret) return ret; inode_size = btrfs_stack_inode_size(inode) + name_len * 2; btrfs_set_stack_inode_size(inode, inode_size); return 0; } int read_disk_extent(struct btrfs_root *root, u64 bytenr, u32 num_bytes, char *buffer) { int ret; struct btrfs_fs_devices *fs_devs = root->fs_info->fs_devices; ret = pread(fs_devs->latest_bdev, buffer, num_bytes, bytenr); if (ret != num_bytes) goto fail; ret = 0; fail: if (ret > 0) ret = -EIO; return ret; } /* * Record a file extent in original filesystem into btrfs one. * The special point is, old disk_block can point to a reserved range. * So here, we don't use disk_block directly but search convert_root * to get the real disk_bytenr. */ int record_file_blocks(struct blk_iterate_data *data, u64 file_block, u64 disk_block, u64 num_blocks) { int ret = 0; struct btrfs_root *root = data->root; struct btrfs_root *convert_root = data->convert_root; struct btrfs_path path = { 0 }; u32 sectorsize = root->fs_info->sectorsize; u64 file_pos = file_block * sectorsize; u64 old_disk_bytenr = disk_block * sectorsize; u64 num_bytes = num_blocks * sectorsize; u64 cur_off = old_disk_bytenr; /* Hole, pass it to record_file_extent directly */ if (old_disk_bytenr == 0) return btrfs_record_file_extent(data->trans, root, data->objectid, data->inode, file_pos, 0, num_bytes); /* * Search real disk bytenr from convert root */ while (cur_off < old_disk_bytenr + num_bytes) { struct btrfs_key key; struct btrfs_file_extent_item *fi; struct extent_buffer *node; int slot; u64 extent_disk_bytenr; u64 extent_num_bytes; u64 real_disk_bytenr; u64 cur_len; key.objectid = data->convert_ino; key.type = BTRFS_EXTENT_DATA_KEY; key.offset = cur_off; ret = btrfs_search_slot(NULL, convert_root, &key, &path, 0, 0); if (ret < 0) break; if (ret > 0) { ret = btrfs_previous_item(convert_root, &path, data->convert_ino, BTRFS_EXTENT_DATA_KEY); if (ret < 0) break; if (ret > 0) { ret = -ENOENT; break; } } node = path.nodes[0]; slot = path.slots[0]; btrfs_item_key_to_cpu(node, &key, slot); BUG_ON(key.type != BTRFS_EXTENT_DATA_KEY || key.objectid != data->convert_ino || key.offset > cur_off); fi = btrfs_item_ptr(node, slot, struct btrfs_file_extent_item); extent_disk_bytenr = btrfs_file_extent_disk_bytenr(node, fi); extent_num_bytes = btrfs_file_extent_num_bytes(node, fi); BUG_ON(cur_off - key.offset >= extent_num_bytes); btrfs_release_path(&path); if (extent_disk_bytenr) real_disk_bytenr = cur_off - key.offset + extent_disk_bytenr; else real_disk_bytenr = 0; cur_len = min(key.offset + extent_num_bytes, old_disk_bytenr + num_bytes) - cur_off; ret = btrfs_record_file_extent(data->trans, data->root, data->objectid, data->inode, file_pos, real_disk_bytenr, cur_len); if (ret < 0) break; cur_off += cur_len; file_pos += cur_len; /* * No need to care about csum * As every byte of old fs image is calculated for csum, no * need to waste CPU cycles now. */ } btrfs_release_path(&path); return ret; }