summaryrefslogtreecommitdiff
path: root/qcow2.c
blob: cbc8d4c4004ce24d35835ed86c1416c63ea46dd5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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);
}