313 lines
8.4 KiB
C
313 lines
8.4 KiB
C
/*
|
|
* 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 <errno.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#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;
|
|
}
|
|
|