xref: /freebsd/usr.bin/mkimg/vhd.c (revision 9034852c84a13f0e3b5527e1c886ca94b2863b2b)
1 /*-
2  * Copyright (c) 2014, 2015 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 #ifndef __has_extension
44 #define	__has_extension(x)	0
45 #endif
46 
47 /*
48  * General notes:
49  * o   File is in network byte order.
50  * o   The timestamp is seconds since 1/1/2000 12:00:00 AM UTC
51  *
52  * This file is divided in 3 parts:
53  * 1.  Common definitions
54  * 2.  Dynamic VHD support
55  * 3.  Fixed VHD support
56  */
57 
58 /*
59  * PART 1: Common definitions
60  */
61 
62 #define	VHD_SECTOR_SIZE	512
63 #define	VHD_BLOCK_SIZE	(4096 * VHD_SECTOR_SIZE)	/* 2MB blocks */
64 
65 struct vhd_geom {
66 	uint16_t	cylinders;
67 	uint8_t		heads;
68 	uint8_t		sectors;
69 };
70 
71 struct vhd_footer {
72 	uint64_t	cookie;
73 #define	VHD_FOOTER_COOKIE	0x636f6e6563746978ULL
74 	uint32_t	features;
75 #define	VHD_FEATURES_TEMPORARY	0x01
76 #define	VHD_FEATURES_RESERVED	0x02
77 	uint32_t	version;
78 #define	VHD_VERSION		0x00010000
79 	uint64_t	data_offset;
80 	uint32_t	timestamp;
81 	uint32_t	creator_tool;
82 #define	VHD_CREATOR_TOOL	0x2a696d67	/* FreeBSD mkimg */
83 	uint32_t	creator_version;
84 #define	VHD_CREATOR_VERSION	0x00020000
85 	uint32_t	creator_os;
86 #define	VHD_CREATOR_OS		0x5769326b	/* Wi2k */
87 	uint64_t	original_size;
88 	uint64_t	current_size;
89 	struct vhd_geom	geometry;
90 	uint32_t	disk_type;
91 #define	VHD_DISK_TYPE_FIXED	2
92 #define	VHD_DISK_TYPE_DYNAMIC	3
93 #define	VHD_DISK_TYPE_DIFF	4
94 	uint32_t	checksum;
95 	uuid_t		id;
96 	uint8_t		saved_state;
97 	uint8_t		_reserved[427];
98 };
99 #if __has_extension(c_static_assert)
100 _Static_assert(sizeof(struct vhd_footer) == VHD_SECTOR_SIZE,
101     "Wrong size for footer");
102 #endif
103 
104 static uint32_t
105 vhd_checksum(void *buf, size_t sz)
106 {
107 	uint8_t *p = buf;
108 	uint32_t sum;
109 	size_t ofs;
110 
111 	sum = 0;
112 	for (ofs = 0; ofs < sz; ofs++)
113 		sum += p[ofs];
114 	return (~sum);
115 }
116 
117 static void
118 vhd_geometry(uint64_t image_size, struct vhd_geom *geom)
119 {
120 	lba_t imgsz;
121 	long cth;
122 
123 	imgsz = image_size / VHD_SECTOR_SIZE;
124 
125 	/* Respect command line options if possible. */
126 	if (nheads > 1 && nheads < 256 &&
127 	    nsecs > 1 && nsecs < 256 &&
128 	    ncyls < 65536) {
129 		geom->cylinders = (ncyls != 0) ? ncyls :
130 		    imgsz / (nheads * nsecs);
131 		geom->heads = nheads;
132 		geom->sectors = nsecs;
133 		return;
134 	}
135 
136 	if (imgsz > 65536 * 16 * 255)
137 		imgsz = 65536 * 16 * 255;
138 	if (imgsz >= 65535 * 16 * 63) {
139 		geom->cylinders = imgsz / (16 * 255);
140 		geom->heads = 16;
141 		geom->sectors = 255;
142 		return;
143 	}
144 	geom->sectors = 17;
145 	cth = imgsz / 17;
146 	geom->heads = (cth + 1023) / 1024;
147 	if (geom->heads < 4)
148 		geom->heads = 4;
149 	if (cth >= (geom->heads * 1024) || geom->heads > 16) {
150 		geom->heads = 16;
151 		geom->sectors = 31;
152 		cth = imgsz / 31;
153 	}
154 	if (cth >= (geom->heads * 1024)) {
155 		geom->heads = 16;
156 		geom->sectors = 63;
157 		cth = imgsz / 63;
158 	}
159 	geom->cylinders = cth / geom->heads;
160 }
161 
162 static uint64_t
163 vhd_resize(uint64_t origsz)
164 {
165 	struct vhd_geom geom;
166 	uint64_t newsz;
167 
168 	/*
169 	 * Round the image size to the pre-determined geometry that
170 	 * matches the image size. This circular dependency implies
171 	 * that we need to loop to handle boundary conditions.
172 	 * The first time, newsz equals origsz and the geometry will
173 	 * typically yield a new size that's smaller. We keep adding
174 	 * cylinder's worth of sectors to the new size until its
175 	 * larger or equal or origsz. But during those iterations,
176 	 * the geometry can change, so we need to account for that.
177 	 */
178 	newsz = origsz;
179 	while (1) {
180 		vhd_geometry(newsz, &geom);
181 		newsz = (int64_t)geom.cylinders * geom.heads *
182 		    geom.sectors * VHD_SECTOR_SIZE;
183 		if (newsz >= origsz)
184 			break;
185 		newsz += geom.heads * geom.sectors * VHD_SECTOR_SIZE;
186 	}
187 	return (newsz);
188 }
189 
190 static uint32_t
191 vhd_timestamp(void)
192 {
193 	time_t t;
194 
195 	if (!unit_testing) {
196 		t = time(NULL);
197 		return (t - 0x386d4380);
198 	}
199 
200 	return (0x01234567);
201 }
202 
203 static void
204 vhd_uuid_enc(void *buf, const uuid_t *uuid)
205 {
206 	uint8_t *p = buf;
207 	int i;
208 
209 	be32enc(p, uuid->time_low);
210 	be16enc(p + 4, uuid->time_mid);
211 	be16enc(p + 6, uuid->time_hi_and_version);
212 	p[8] = uuid->clock_seq_hi_and_reserved;
213 	p[9] = uuid->clock_seq_low;
214 	for (i = 0; i < _UUID_NODE_LEN; i++)
215 		p[10 + i] = uuid->node[i];
216 }
217 
218 static void
219 vhd_make_footer(struct vhd_footer *footer, uint64_t image_size,
220     uint32_t disk_type, uint64_t data_offset)
221 {
222 	uuid_t id;
223 
224 	memset(footer, 0, sizeof(*footer));
225 	be64enc(&footer->cookie, VHD_FOOTER_COOKIE);
226 	be32enc(&footer->features, VHD_FEATURES_RESERVED);
227 	be32enc(&footer->version, VHD_VERSION);
228 	be64enc(&footer->data_offset, data_offset);
229 	be32enc(&footer->timestamp, vhd_timestamp());
230 	be32enc(&footer->creator_tool, VHD_CREATOR_TOOL);
231 	be32enc(&footer->creator_version, VHD_CREATOR_VERSION);
232 	be32enc(&footer->creator_os, VHD_CREATOR_OS);
233 	be64enc(&footer->original_size, image_size);
234 	be64enc(&footer->current_size, image_size);
235 	vhd_geometry(image_size, &footer->geometry);
236 	be16enc(&footer->geometry.cylinders, footer->geometry.cylinders);
237 	be32enc(&footer->disk_type, disk_type);
238 	mkimg_uuid(&id);
239 	vhd_uuid_enc(&footer->id, &id);
240 	be32enc(&footer->checksum, vhd_checksum(footer, sizeof(*footer)));
241 }
242 
243 /*
244  * PART 2: Dynamic VHD support
245  *
246  * Notes:
247  * o   File layout:
248  *	copy of disk footer
249  *	dynamic disk header
250  *	block allocation table (BAT)
251  *	data blocks
252  *	disk footer
253  */
254 
255 struct vhd_dyn_header {
256 	uint64_t	cookie;
257 #define	VHD_HEADER_COOKIE	0x6378737061727365ULL
258 	uint64_t	data_offset;
259 	uint64_t	table_offset;
260 	uint32_t	version;
261 	uint32_t	max_entries;
262 	uint32_t	block_size;
263 	uint32_t	checksum;
264 	uuid_t		parent_id;
265 	uint32_t	parent_timestamp;
266 	char		_reserved1[4];
267 	uint16_t	parent_name[256];	/* UTF-16 */
268 	struct {
269 		uint32_t	code;
270 		uint32_t	data_space;
271 		uint32_t	data_length;
272 		uint32_t	_reserved;
273 		uint64_t	data_offset;
274 	} parent_locator[8];
275 	char		_reserved2[256];
276 };
277 #if __has_extension(c_static_assert)
278 _Static_assert(sizeof(struct vhd_dyn_header) == VHD_SECTOR_SIZE * 2,
279     "Wrong size for header");
280 #endif
281 
282 static int
283 vhd_dyn_resize(lba_t imgsz)
284 {
285 	uint64_t imagesz;
286 
287 	imagesz = vhd_resize(imgsz * secsz);
288 	return (image_set_size(imagesz / secsz));
289 }
290 
291 static int
292 vhd_dyn_write(int fd)
293 {
294 	struct vhd_footer footer;
295 	struct vhd_dyn_header header;
296 	uint64_t imgsz, rawsz;
297 	lba_t blk, blkcnt, nblks;
298 	uint32_t *bat;
299 	void *bitmap;
300 	size_t batsz;
301 	uint32_t sector;
302 	int bat_entries, error, entry;
303 
304 	rawsz = image_get_size() * secsz;
305 	imgsz = (rawsz + VHD_BLOCK_SIZE - 1) & ~(VHD_BLOCK_SIZE - 1);
306 
307 	vhd_make_footer(&footer, rawsz, VHD_DISK_TYPE_DYNAMIC, sizeof(footer));
308 	if (sparse_write(fd, &footer, sizeof(footer)) < 0)
309 		return (errno);
310 
311 	bat_entries = imgsz / VHD_BLOCK_SIZE;
312 	memset(&header, 0, sizeof(header));
313 	be64enc(&header.cookie, VHD_HEADER_COOKIE);
314 	be64enc(&header.data_offset, ~0ULL);
315 	be64enc(&header.table_offset, sizeof(footer) + sizeof(header));
316 	be32enc(&header.version, VHD_VERSION);
317 	be32enc(&header.max_entries, bat_entries);
318 	be32enc(&header.block_size, VHD_BLOCK_SIZE);
319 	be32enc(&header.checksum, vhd_checksum(&header, sizeof(header)));
320 	if (sparse_write(fd, &header, sizeof(header)) < 0)
321 		return (errno);
322 
323 	batsz = bat_entries * sizeof(uint32_t);
324 	batsz = (batsz + VHD_SECTOR_SIZE - 1) & ~(VHD_SECTOR_SIZE - 1);
325 	bat = malloc(batsz);
326 	if (bat == NULL)
327 		return (errno);
328 	memset(bat, 0xff, batsz);
329 	blkcnt = VHD_BLOCK_SIZE / secsz;
330 	sector = (sizeof(footer) + sizeof(header) + batsz) / VHD_SECTOR_SIZE;
331 	for (entry = 0; entry < bat_entries; entry++) {
332 		blk = entry * blkcnt;
333 		if (image_data(blk, blkcnt)) {
334 			be32enc(&bat[entry], sector);
335 			sector += (VHD_BLOCK_SIZE / VHD_SECTOR_SIZE) + 1;
336 		}
337 	}
338 	if (sparse_write(fd, bat, batsz) < 0) {
339 		free(bat);
340 		return (errno);
341 	}
342 	free(bat);
343 
344 	bitmap = malloc(VHD_SECTOR_SIZE);
345 	if (bitmap == NULL)
346 		return (errno);
347 	memset(bitmap, 0xff, VHD_SECTOR_SIZE);
348 
349 	blk = 0;
350 	blkcnt = VHD_BLOCK_SIZE / secsz;
351 	error = 0;
352 	nblks = rawsz / secsz;
353 	while (blk < nblks) {
354 		if (!image_data(blk, blkcnt)) {
355 			blk += blkcnt;
356 			continue;
357 		}
358 		if (sparse_write(fd, bitmap, VHD_SECTOR_SIZE) < 0) {
359 			error = errno;
360 			break;
361 		}
362 		/* Handle partial last block */
363 		if (blk + blkcnt > nblks)
364 			blkcnt = nblks - blk;
365 		error = image_copyout_region(fd, blk, blkcnt);
366 		if (error)
367 			break;
368 		blk += blkcnt;
369 	}
370 	free(bitmap);
371 	if (error)
372 		return (error);
373 	error = image_copyout_zeroes(fd, imgsz - rawsz);
374 	if (error)
375 		return (error);
376 	if (sparse_write(fd, &footer, sizeof(footer)) < 0)
377 		return (errno);
378 
379 	return (0);
380 }
381 
382 static struct mkimg_format vhd_dyn_format = {
383 	.name = "vhd",
384 	.description = "Virtual Hard Disk",
385 	.resize = vhd_dyn_resize,
386 	.write = vhd_dyn_write,
387 };
388 
389 FORMAT_DEFINE(vhd_dyn_format);
390 
391 /*
392  * PART 3: Fixed VHD
393  */
394 
395 static int
396 vhd_fix_resize(lba_t imgsz)
397 {
398 	uint64_t imagesz;
399 
400 	imagesz = vhd_resize(imgsz * secsz);
401 	/*
402 	 * Azure demands that images are a whole number of megabytes.
403 	 */
404 	imagesz = (imagesz + 0xfffffULL) & ~0xfffffULL;
405 	return (image_set_size(imagesz / secsz));
406 }
407 
408 static int
409 vhd_fix_write(int fd)
410 {
411 	struct vhd_footer footer;
412 	uint64_t imagesz;
413 	int error;
414 
415 	error = image_copyout(fd);
416 	if (error)
417 		return (error);
418 
419 	imagesz = image_get_size() * secsz;
420 	vhd_make_footer(&footer, imagesz, VHD_DISK_TYPE_FIXED, ~0ULL);
421 	error = (sparse_write(fd, &footer, sizeof(footer)) < 0) ? errno : 0;
422 	return (error);
423 }
424 
425 static struct mkimg_format vhd_fix_format = {
426 	.name = "vhdf",
427 	.description = "Fixed Virtual Hard Disk",
428 	.resize = vhd_fix_resize,
429 	.write = vhd_fix_write,
430 };
431 
432 FORMAT_DEFINE(vhd_fix_format);
433