mirror of
https://github.com/koverstreet/bcachefs-tools.git
synced 2025-02-23 00:00:02 +03:00
163 lines
3.6 KiB
C
163 lines
3.6 KiB
C
|
|
#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);
|
|
}
|