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