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