xref: /freebsd/usr.bin/mkimg/vhd.c (revision 8b37e7ca92934197c838c4239d0e4d889fbbe4e7)
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 uint32_t
163 vhd_timestamp(void)
164 {
165 	time_t t;
166 
167 	if (!unit_testing) {
168 		t = time(NULL);
169 		return (t - 0x386d4380);
170 	}
171 
172 	return (0x01234567);
173 }
174 
175 static void
176 vhd_uuid_enc(void *buf, const uuid_t *uuid)
177 {
178 	uint8_t *p = buf;
179 	int i;
180 
181 	be32enc(p, uuid->time_low);
182 	be16enc(p + 4, uuid->time_mid);
183 	be16enc(p + 6, uuid->time_hi_and_version);
184 	p[8] = uuid->clock_seq_hi_and_reserved;
185 	p[9] = uuid->clock_seq_low;
186 	for (i = 0; i < _UUID_NODE_LEN; i++)
187 		p[10 + i] = uuid->node[i];
188 }
189 
190 static void
191 vhd_make_footer(struct vhd_footer *footer, uint64_t image_size,
192     uint32_t disk_type, uint64_t data_offset)
193 {
194 	uuid_t id;
195 
196 	memset(footer, 0, sizeof(*footer));
197 	be64enc(&footer->cookie, VHD_FOOTER_COOKIE);
198 	be32enc(&footer->features, VHD_FEATURES_RESERVED);
199 	be32enc(&footer->version, VHD_VERSION);
200 	be64enc(&footer->data_offset, data_offset);
201 	be32enc(&footer->timestamp, vhd_timestamp());
202 	be32enc(&footer->creator_tool, VHD_CREATOR_TOOL);
203 	be32enc(&footer->creator_version, VHD_CREATOR_VERSION);
204 	be32enc(&footer->creator_os, VHD_CREATOR_OS);
205 	be64enc(&footer->original_size, image_size);
206 	be64enc(&footer->current_size, image_size);
207 	vhd_geometry(image_size, &footer->geometry);
208 	be16enc(&footer->geometry.cylinders, footer->geometry.cylinders);
209 	be32enc(&footer->disk_type, disk_type);
210 	mkimg_uuid(&id);
211 	vhd_uuid_enc(&footer->id, &id);
212 	be32enc(&footer->checksum, vhd_checksum(footer, sizeof(*footer)));
213 }
214 
215 /*
216  * PART 2: Dynamic VHD support
217  *
218  * Notes:
219  * o   File layout:
220  *	copy of disk footer
221  *	dynamic disk header
222  *	block allocation table (BAT)
223  *	data blocks
224  *	disk footer
225  */
226 
227 struct vhd_dyn_header {
228 	uint64_t	cookie;
229 #define	VHD_HEADER_COOKIE	0x6378737061727365ULL
230 	uint64_t	data_offset;
231 	uint64_t	table_offset;
232 	uint32_t	version;
233 	uint32_t	max_entries;
234 	uint32_t	block_size;
235 	uint32_t	checksum;
236 	uuid_t		parent_id;
237 	uint32_t	parent_timestamp;
238 	char		_reserved1[4];
239 	uint16_t	parent_name[256];	/* UTF-16 */
240 	struct {
241 		uint32_t	code;
242 		uint32_t	data_space;
243 		uint32_t	data_length;
244 		uint32_t	_reserved;
245 		uint64_t	data_offset;
246 	} parent_locator[8];
247 	char		_reserved2[256];
248 };
249 #if __has_extension(c_static_assert)
250 _Static_assert(sizeof(struct vhd_dyn_header) == VHD_SECTOR_SIZE * 2,
251     "Wrong size for header");
252 #endif
253 
254 static int
255 vhd_dyn_resize(lba_t imgsz)
256 {
257 	uint64_t imagesz;
258 
259 	imagesz = imgsz * secsz;
260 	imagesz = (imagesz + VHD_BLOCK_SIZE - 1) & ~(VHD_BLOCK_SIZE - 1);
261 	return (image_set_size(imagesz / secsz));
262 }
263 
264 static int
265 vhd_dyn_write(int fd)
266 {
267 	struct vhd_footer footer;
268 	struct vhd_dyn_header header;
269 	uint64_t imgsz;
270 	lba_t blk, blkcnt, nblks;
271 	uint32_t *bat;
272 	void *bitmap;
273 	size_t batsz;
274 	uint32_t sector;
275 	int bat_entries, error, entry;
276 
277 	imgsz = image_get_size() * secsz;
278 	bat_entries = imgsz / VHD_BLOCK_SIZE;
279 
280 	vhd_make_footer(&footer, imgsz, VHD_DISK_TYPE_DYNAMIC, sizeof(footer));
281 	if (sparse_write(fd, &footer, sizeof(footer)) < 0)
282 		return (errno);
283 
284 	memset(&header, 0, sizeof(header));
285 	be64enc(&header.cookie, VHD_HEADER_COOKIE);
286 	be64enc(&header.data_offset, ~0ULL);
287 	be64enc(&header.table_offset, sizeof(footer) + sizeof(header));
288 	be32enc(&header.version, VHD_VERSION);
289 	be32enc(&header.max_entries, bat_entries);
290 	be32enc(&header.block_size, VHD_BLOCK_SIZE);
291 	be32enc(&header.checksum, vhd_checksum(&header, sizeof(header)));
292 	if (sparse_write(fd, &header, sizeof(header)) < 0)
293 		return (errno);
294 
295 	batsz = bat_entries * sizeof(uint32_t);
296 	batsz = (batsz + VHD_SECTOR_SIZE - 1) & ~(VHD_SECTOR_SIZE - 1);
297 	bat = malloc(batsz);
298 	if (bat == NULL)
299 		return (errno);
300 	memset(bat, 0xff, batsz);
301 	blkcnt = VHD_BLOCK_SIZE / secsz;
302 	sector = (sizeof(footer) + sizeof(header) + batsz) / VHD_SECTOR_SIZE;
303 	for (entry = 0; entry < bat_entries; entry++) {
304 		blk = entry * blkcnt;
305 		if (image_data(blk, blkcnt)) {
306 			be32enc(&bat[entry], sector);
307 			sector += (VHD_BLOCK_SIZE / VHD_SECTOR_SIZE) + 1;
308 		}
309 	}
310 	if (sparse_write(fd, bat, batsz) < 0) {
311 		free(bat);
312 		return (errno);
313 	}
314 	free(bat);
315 
316 	bitmap = malloc(VHD_SECTOR_SIZE);
317 	if (bitmap == NULL)
318 		return (errno);
319 	memset(bitmap, 0xff, VHD_SECTOR_SIZE);
320 
321 	blk = 0;
322 	blkcnt = VHD_BLOCK_SIZE / secsz;
323 	error = 0;
324 	nblks = image_get_size();
325 	while (blk < nblks) {
326 		if (!image_data(blk, blkcnt)) {
327 			blk += blkcnt;
328 			continue;
329 		}
330 		if (sparse_write(fd, bitmap, VHD_SECTOR_SIZE) < 0) {
331 			error = errno;
332 			break;
333 		}
334 		error = image_copyout_region(fd, blk, blkcnt);
335 		if (error)
336 			break;
337 		blk += blkcnt;
338 	}
339 	free(bitmap);
340 	if (blk != nblks)
341 		return (error);
342 
343 	if (sparse_write(fd, &footer, sizeof(footer)) < 0)
344 		return (errno);
345 
346 	return (0);
347 }
348 
349 static struct mkimg_format vhd_dyn_format = {
350 	.name = "vhd",
351 	.description = "Virtual Hard Disk",
352 	.resize = vhd_dyn_resize,
353 	.write = vhd_dyn_write,
354 };
355 
356 FORMAT_DEFINE(vhd_dyn_format);
357 
358 /*
359  * PART 3: Fixed VHD
360  */
361 
362 static int
363 vhd_fix_resize(lba_t imgsz)
364 {
365 	struct vhd_geom geom;
366 	int64_t imagesz;
367 
368 	/*
369 	 * Round the image size to the pre-determined geometry that
370 	 * matches the image size. This circular dependency implies
371 	 * that we need to loop to handle boundary conditions.
372 	 */
373 	imgsz *= secsz;
374 	imagesz = imgsz;
375 	while (1) {
376 		vhd_geometry(imagesz, &geom);
377 		imagesz = (int64_t)geom.cylinders * geom.heads *
378 		    geom.sectors * VHD_SECTOR_SIZE;
379 		if (imagesz >= imgsz)
380 			break;
381 		imagesz += geom.heads * geom.sectors * VHD_SECTOR_SIZE;
382 	}
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 imgsz;
395 	int error;
396 
397 	error = image_copyout(fd);
398 	if (!error) {
399 		imgsz = image_get_size() * secsz;
400 		vhd_make_footer(&footer, imgsz, VHD_DISK_TYPE_FIXED, ~0ULL);
401 		if (sparse_write(fd, &footer, sizeof(footer)) < 0)
402 			error = errno;
403 	}
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