diff options
Diffstat (limited to 'qcow2.c')
-rw-r--r-- | qcow2.c | 162 |
1 files changed, 162 insertions, 0 deletions
@@ -0,0 +1,162 @@ + +#include <errno.h> +#include <sys/types.h> +#include <unistd.h> +#include <linux/sort.h> + +#include "qcow2.h" +#include "tools-util.h" + +#define QCOW_MAGIC (('Q' << 24) | ('F' << 16) | ('I' << 8) | 0xfb) +#define QCOW_VERSION 2 +#define QCOW_OFLAG_COPIED (1LL << 63) + +struct qcow2_hdr { + u32 magic; + u32 version; + + u64 backing_file_offset; + u32 backing_file_size; + + u32 block_bits; + u64 size; + u32 crypt_method; + + u32 l1_size; + u64 l1_table_offset; + + u64 refcount_table_offset; + u32 refcount_table_blocks; + + u32 nb_snapshots; + u64 snapshots_offset; +}; + +struct qcow2_image { + int fd; + u32 block_size; + u64 *l1_table; + u64 l1_offset; + u32 l1_index; + u64 *l2_table; + u64 offset; +}; + +static void flush_l2(struct qcow2_image *img) +{ + if (img->l1_index != -1) { + img->l1_table[img->l1_index] = + cpu_to_be64(img->offset|QCOW_OFLAG_COPIED); + xpwrite(img->fd, img->l2_table, img->block_size, img->offset); + img->offset += img->block_size; + + memset(img->l2_table, 0, img->block_size); + img->l1_index = -1; + } +} + +static void add_l2(struct qcow2_image *img, u64 src_blk, u64 dst_offset) +{ + unsigned l2_size = img->block_size / sizeof(u64); + u64 l1_index = src_blk / l2_size; + u64 l2_index = src_blk & (l2_size - 1); + + if (img->l1_index != l1_index) { + flush_l2(img); + img->l1_index = l1_index; + } + + img->l2_table[l2_index] = cpu_to_be64(dst_offset|QCOW_OFLAG_COPIED); +} + +static int range_cmp(const void *_l, const void *_r) +{ + const struct range *l = _l, *r = _r; + + if (l->start < r->start) + return -1; + if (l->start > r->start) + return 1; + return 0; +} + +void qcow2_write_image(int infd, int outfd, sparse_data *data, + unsigned block_size) +{ + u64 image_size = get_size(NULL, infd); + unsigned l2_size = block_size / sizeof(u64); + unsigned l1_size = DIV_ROUND_UP(image_size, (u64) block_size * l2_size); + struct qcow2_hdr hdr = { 0 }; + struct qcow2_image img = { + .fd = outfd, + .block_size = block_size, + .l2_table = xcalloc(l2_size, sizeof(u64)), + .l1_table = xcalloc(l1_size, sizeof(u64)), + .l1_index = -1, + .offset = round_up(sizeof(hdr), block_size), + }; + struct range *r; + char *buf = xmalloc(block_size); + u64 src_offset, dst_offset; + sparse_data m; + + assert(is_power_of_2(block_size)); + + sort(&darray_item(*data, 0), + darray_size(*data), + sizeof(darray_item(*data, 0)), + range_cmp, NULL); + + /* Round to blocksize, merge contiguous ranges: */ + darray_init(m); + darray_foreach(r, *data) { + struct range *l = m.size ? &m.item[m.size - 1] : NULL; + + r->start = round_down(r->start, block_size); + r->end = round_up(r->end, block_size); + + if (l && l->end >= r->start) + l->end = max(l->end, r->end); + else + darray_append(m, *r); + } + darray_free(*data); + *data = m; + + /* Write data: */ + darray_foreach(r, *data) + for (src_offset = r->start; + src_offset < r->end; + src_offset += block_size) { + dst_offset = img.offset; + img.offset += img.block_size; + + xpread(infd, buf, block_size, src_offset); + xpwrite(outfd, buf, block_size, dst_offset); + + add_l2(&img, src_offset / block_size, dst_offset); + } + + flush_l2(&img); + + /* Write L1 table: */ + dst_offset = img.offset; + img.offset += round_up(l1_size * sizeof(u64), block_size); + xpwrite(img.fd, img.l1_table, l1_size * sizeof(u64), dst_offset); + + /* Write header: */ + hdr.magic = cpu_to_be32(QCOW_MAGIC); + hdr.version = cpu_to_be32(QCOW_VERSION); + hdr.block_bits = cpu_to_be32(ilog2(block_size)); + hdr.size = cpu_to_be64(image_size); + hdr.l1_size = cpu_to_be32(l1_size); + hdr.l1_table_offset = cpu_to_be64(dst_offset); + + memset(buf, 0, block_size); + memcpy(buf, &hdr, sizeof(hdr)); + xpwrite(img.fd, buf, block_size, 0); + + free(img.l2_table); + free(img.l1_table); + free(buf); +} |