xref: /freebsd/usr.bin/mkimg/vhd.c (revision 95d45410b5100e07f6f98450bcd841a8945d4726)
1 /*-
2  * Copyright (c) 2014 Marcel Moolenaar
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29 
30 #include <sys/types.h>
31 #include <sys/endian.h>
32 #include <sys/errno.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <time.h>
36 #include <unistd.h>
37 #include <uuid.h>
38 
39 #include "image.h"
40 #include "format.h"
41 #include "mkimg.h"
42 
43 /*
44  * General notes:
45  * o   File is in network byte order.
46  * o   The timestamp is seconds since 1/1/2000 12:00:00 AM UTC
47  *
48  * This file is divided in 3 parts:
49  * 1.  Common definitions
50  * 2.  Dynamic VHD support
51  * 3.  Fixed VHD support
52  */
53 
54 /*
55  * PART 1: Common definitions
56  */
57 
58 #define	VHD_SECTOR_SIZE	512
59 #define	VHD_BLOCK_SIZE	(4096 * VHD_SECTOR_SIZE)	/* 2MB blocks */
60 
61 struct vhd_footer {
62 	uint64_t	cookie;
63 #define	VHD_FOOTER_COOKIE	0x636f6e6563746978
64 	uint32_t	features;
65 #define	VHD_FEATURES_TEMPORARY	0x01
66 #define	VHD_FEATURES_RESERVED	0x02
67 	uint32_t	version;
68 #define	VHD_VERSION		0x00010000
69 	uint64_t	data_offset;
70 	uint32_t	timestamp;
71 	uint32_t	creator_tool;
72 #define	VHD_CREATOR_TOOL	0x2a696d67	/* FreeBSD mkimg */
73 	uint32_t	creator_version;
74 #define	VHD_CREATOR_VERSION	0x00010000
75 	uint32_t	creator_os;
76 #define	VHD_CREATOR_OS		0x46425344
77 	uint64_t	original_size;
78 	uint64_t	current_size;
79 	uint16_t	cylinders;
80 	uint8_t		heads;
81 	uint8_t		sectors;
82 	uint32_t	disk_type;
83 #define	VHD_DISK_TYPE_FIXED	2
84 #define	VHD_DISK_TYPE_DYNAMIC	3
85 #define	VHD_DISK_TYPE_DIFF	4
86 	uint32_t	checksum;
87 	uuid_t		id;
88 	uint8_t		saved_state;
89 	uint8_t		_reserved[427];
90 };
91 _Static_assert(sizeof(struct vhd_footer) == VHD_SECTOR_SIZE,
92     "Wrong size for footer");
93 
94 static uint32_t
95 vhd_checksum(void *buf, size_t sz)
96 {
97 	uint8_t *p = buf;
98 	uint32_t sum;
99 	size_t ofs;
100 
101 	sum = 0;
102 	for (ofs = 0; ofs < sz; ofs++)
103 		sum += p[ofs];
104 	return (~sum);
105 }
106 
107 static void
108 vhd_geometry(struct vhd_footer *footer, uint64_t image_size)
109 {
110 	lba_t imgsz;
111 	long cth;
112 
113 	/* Respect command line options if possible. */
114 	if (nheads > 1 && nheads < 256 &&
115 	    nsecs > 1 && nsecs < 256 &&
116 	    ncyls < 65536) {
117 		be16enc(&footer->cylinders, ncyls);
118 		footer->heads = nheads;
119 		footer->sectors = nsecs;
120 		return;
121 	}
122 
123 	imgsz = image_size / VHD_SECTOR_SIZE;
124 	if (imgsz > 65536 * 16 * 255)
125 		imgsz = 65536 * 16 * 255;
126 	if (imgsz >= 65535 * 16 * 63) {
127 		be16enc(&footer->cylinders, imgsz / (16 * 255));
128 		footer->heads = 16;
129 		footer->sectors = 255;
130 		return;
131 	}
132 	footer->sectors = 17;
133 	cth = imgsz / 17;
134 	footer->heads = (cth + 1023) / 1024;
135 	if (footer->heads < 4)
136 		footer->heads = 4;
137 	if (cth >= (footer->heads * 1024) || footer->heads > 16) {
138 		footer->heads = 16;
139 		footer->sectors = 31;
140 		cth = imgsz / 31;
141 	}
142 	if (cth >= (footer->heads * 1024)) {
143 		footer->heads = 16;
144 		footer->sectors = 63;
145 		cth = imgsz / 63;
146 	}
147 	be16enc(&footer->cylinders, cth / footer->heads);
148 }
149 
150 static uint32_t
151 vhd_timestamp(void)
152 {
153 	time_t t;
154 
155 	if (!unit_testing) {
156 		t = time(NULL);
157 		return (t - 0x386d4380);
158 	}
159 
160 	return (0x01234567);
161 }
162 
163 static void
164 vhd_uuid_enc(void *buf, const uuid_t *uuid)
165 {
166 	uint8_t *p = buf;
167 	int i;
168 
169 	be32enc(p, uuid->time_low);
170 	be16enc(p + 4, uuid->time_mid);
171 	be16enc(p + 6, uuid->time_hi_and_version);
172 	p[8] = uuid->clock_seq_hi_and_reserved;
173 	p[9] = uuid->clock_seq_low;
174 	for (i = 0; i < _UUID_NODE_LEN; i++)
175 		p[10 + i] = uuid->node[i];
176 }
177 
178 static void
179 vhd_make_footer(struct vhd_footer *footer, uint64_t image_size,
180     uint32_t disk_type, uint64_t data_offset)
181 {
182 	uuid_t id;
183 
184 	memset(footer, 0, sizeof(*footer));
185 	be64enc(&footer->cookie, VHD_FOOTER_COOKIE);
186 	be32enc(&footer->features, VHD_FEATURES_RESERVED);
187 	be32enc(&footer->version, VHD_VERSION);
188 	be64enc(&footer->data_offset, data_offset);
189 	be32enc(&footer->timestamp, vhd_timestamp());
190 	be32enc(&footer->creator_tool, VHD_CREATOR_TOOL);
191 	be32enc(&footer->creator_version, VHD_CREATOR_VERSION);
192 	be32enc(&footer->creator_os, VHD_CREATOR_OS);
193 	be64enc(&footer->original_size, image_size);
194 	be64enc(&footer->current_size, image_size);
195 	vhd_geometry(footer, image_size);
196 	be32enc(&footer->disk_type, disk_type);
197 	mkimg_uuid(&id);
198 	vhd_uuid_enc(&footer->id, &id);
199 	be32enc(&footer->checksum, vhd_checksum(footer, sizeof(*footer)));
200 }
201 
202 /*
203  * We round the image size to 2MB for both the dynamic and
204  * fixed VHD formats. For dynamic VHD, this is needed to
205  * have the image size be a multiple of the grain size. For
206  * fixed VHD this is not really needed, but makes sure that
207  * it's easy to convert from fixed VHD to dynamic VHD.
208  */
209 static int
210 vhd_resize(lba_t imgsz)
211 {
212 	uint64_t imagesz;
213 
214 	imagesz = imgsz * secsz;
215 	imagesz = (imagesz + VHD_BLOCK_SIZE - 1) & ~(VHD_BLOCK_SIZE - 1);
216 	return (image_set_size(imagesz / secsz));
217 }
218 
219 /*
220  * PART 2: Dynamic VHD support
221  *
222  * Notes:
223  * o   File layout:
224  *	copy of disk footer
225  *	dynamic disk header
226  *	block allocation table (BAT)
227  *	data blocks
228  *	disk footer
229  */
230 
231 struct vhd_dyn_header {
232 	uint64_t	cookie;
233 #define	VHD_HEADER_COOKIE	0x6378737061727365
234 	uint64_t	data_offset;
235 	uint64_t	table_offset;
236 	uint32_t	version;
237 	uint32_t	max_entries;
238 	uint32_t	block_size;
239 	uint32_t	checksum;
240 	uuid_t		parent_id;
241 	uint32_t	parent_timestamp;
242 	char		_reserved1[4];
243 	uint16_t	parent_name[256];	/* UTF-16 */
244 	struct {
245 		uint32_t	code;
246 		uint32_t	data_space;
247 		uint32_t	data_length;
248 		uint32_t	_reserved;
249 		uint64_t	data_offset;
250 	} parent_locator[8];
251 	char		_reserved2[256];
252 };
253 _Static_assert(sizeof(struct vhd_dyn_header) == VHD_SECTOR_SIZE * 2,
254     "Wrong size for header");
255 
256 static int
257 vhd_dyn_write(int fd)
258 {
259 	struct vhd_footer footer;
260 	struct vhd_dyn_header header;
261 	uint64_t imgsz;
262 	lba_t blk, blkcnt, nblks;
263 	uint32_t *bat;
264 	void *bitmap;
265 	size_t batsz;
266 	uint32_t sector;
267 	int bat_entries, error, entry;
268 
269 	imgsz = image_get_size() * secsz;
270 	bat_entries = imgsz / VHD_BLOCK_SIZE;
271 
272 	vhd_make_footer(&footer, imgsz, VHD_DISK_TYPE_DYNAMIC, sizeof(footer));
273 	if (sparse_write(fd, &footer, sizeof(footer)) < 0)
274 		return (errno);
275 
276 	memset(&header, 0, sizeof(header));
277 	be64enc(&header.cookie, VHD_HEADER_COOKIE);
278 	be64enc(&header.data_offset, ~0ULL);
279 	be64enc(&header.table_offset, sizeof(footer) + sizeof(header));
280 	be32enc(&header.version, VHD_VERSION);
281 	be32enc(&header.max_entries, bat_entries);
282 	be32enc(&header.block_size, VHD_BLOCK_SIZE);
283 	be32enc(&header.checksum, vhd_checksum(&header, sizeof(header)));
284 	if (sparse_write(fd, &header, sizeof(header)) < 0)
285 		return (errno);
286 
287 	batsz = bat_entries * sizeof(uint32_t);
288 	batsz = (batsz + VHD_SECTOR_SIZE - 1) & ~(VHD_SECTOR_SIZE - 1);
289 	bat = malloc(batsz);
290 	if (bat == NULL)
291 		return (errno);
292 	memset(bat, 0xff, batsz);
293 	blkcnt = VHD_BLOCK_SIZE / secsz;
294 	sector = (sizeof(footer) + sizeof(header) + batsz) / VHD_SECTOR_SIZE;
295 	for (entry = 0; entry < bat_entries; entry++) {
296 		blk = entry * blkcnt;
297 		if (image_data(blk, blkcnt)) {
298 			be32enc(&bat[entry], sector);
299 			sector += (VHD_BLOCK_SIZE / VHD_SECTOR_SIZE) + 1;
300 		}
301 	}
302 	if (sparse_write(fd, bat, batsz) < 0) {
303 		free(bat);
304 		return (errno);
305 	}
306 	free(bat);
307 
308 	bitmap = malloc(VHD_SECTOR_SIZE);
309 	if (bitmap == NULL)
310 		return (errno);
311 	memset(bitmap, 0xff, VHD_SECTOR_SIZE);
312 
313 	blk = 0;
314 	blkcnt = VHD_BLOCK_SIZE / secsz;
315 	error = 0;
316 	nblks = image_get_size();
317 	while (blk < nblks) {
318 		if (!image_data(blk, blkcnt)) {
319 			blk += blkcnt;
320 			continue;
321 		}
322 		if (sparse_write(fd, bitmap, VHD_SECTOR_SIZE) < 0) {
323 			error = errno;
324 			break;
325 		}
326 		error = image_copyout_region(fd, blk, blkcnt);
327 		if (error)
328 			break;
329 		blk += blkcnt;
330 	}
331 	free(bitmap);
332 	if (blk != nblks)
333 		return (error);
334 
335 	if (sparse_write(fd, &footer, sizeof(footer)) < 0)
336 		return (errno);
337 
338 	return (0);
339 }
340 
341 static struct mkimg_format vhd_dyn_format = {
342 	.name = "vhd",
343 	.description = "Virtual Hard Disk",
344 	.resize = vhd_resize,
345 	.write = vhd_dyn_write,
346 };
347 
348 FORMAT_DEFINE(vhd_dyn_format);
349 
350 /*
351  * PART 2: Fixed VHD
352  */
353 
354 static int
355 vhd_fix_write(int fd)
356 {
357 	struct vhd_footer footer;
358 	uint64_t imgsz;
359 	int error;
360 
361 	error = image_copyout(fd);
362 	if (!error) {
363 		imgsz = image_get_size() * secsz;
364 		vhd_make_footer(&footer, imgsz, VHD_DISK_TYPE_FIXED, ~0ULL);
365 		if (sparse_write(fd, &footer, sizeof(footer)) < 0)
366 			error = errno;
367 	}
368 	return (error);
369 }
370 
371 static struct mkimg_format vhd_fix_format = {
372         .name = "vhdf",
373         .description = "Fixed Virtual Hard Disk",
374         .resize = vhd_resize,
375         .write = vhd_fix_write,
376 };
377 
378 FORMAT_DEFINE(vhd_fix_format);
379