xref: /freebsd/usr.bin/mkimg/vhd.c (revision 7431dfd4580e850375fe5478d92ec770344db098)
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 #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_footer {
66 	uint64_t	cookie;
67 #define	VHD_FOOTER_COOKIE	0x636f6e6563746978ULL
68 	uint32_t	features;
69 #define	VHD_FEATURES_TEMPORARY	0x01
70 #define	VHD_FEATURES_RESERVED	0x02
71 	uint32_t	version;
72 #define	VHD_VERSION		0x00010000
73 	uint64_t	data_offset;
74 	uint32_t	timestamp;
75 	uint32_t	creator_tool;
76 #define	VHD_CREATOR_TOOL	0x2a696d67	/* FreeBSD mkimg */
77 	uint32_t	creator_version;
78 #define	VHD_CREATOR_VERSION	0x00010000
79 	uint32_t	creator_os;
80 #define	VHD_CREATOR_OS		0x46425344
81 	uint64_t	original_size;
82 	uint64_t	current_size;
83 	uint16_t	cylinders;
84 	uint8_t		heads;
85 	uint8_t		sectors;
86 	uint32_t	disk_type;
87 #define	VHD_DISK_TYPE_FIXED	2
88 #define	VHD_DISK_TYPE_DYNAMIC	3
89 #define	VHD_DISK_TYPE_DIFF	4
90 	uint32_t	checksum;
91 	uuid_t		id;
92 	uint8_t		saved_state;
93 	uint8_t		_reserved[427];
94 };
95 #if __has_extension(c_static_assert)
96 _Static_assert(sizeof(struct vhd_footer) == VHD_SECTOR_SIZE,
97     "Wrong size for footer");
98 #endif
99 
100 static uint32_t
101 vhd_checksum(void *buf, size_t sz)
102 {
103 	uint8_t *p = buf;
104 	uint32_t sum;
105 	size_t ofs;
106 
107 	sum = 0;
108 	for (ofs = 0; ofs < sz; ofs++)
109 		sum += p[ofs];
110 	return (~sum);
111 }
112 
113 static void
114 vhd_geometry(struct vhd_footer *footer, uint64_t image_size)
115 {
116 	lba_t imgsz;
117 	long cth;
118 
119 	/* Respect command line options if possible. */
120 	if (nheads > 1 && nheads < 256 &&
121 	    nsecs > 1 && nsecs < 256 &&
122 	    ncyls < 65536) {
123 		be16enc(&footer->cylinders, ncyls);
124 		footer->heads = nheads;
125 		footer->sectors = nsecs;
126 		return;
127 	}
128 
129 	imgsz = image_size / VHD_SECTOR_SIZE;
130 	if (imgsz > 65536 * 16 * 255)
131 		imgsz = 65536 * 16 * 255;
132 	if (imgsz >= 65535 * 16 * 63) {
133 		be16enc(&footer->cylinders, imgsz / (16 * 255));
134 		footer->heads = 16;
135 		footer->sectors = 255;
136 		return;
137 	}
138 	footer->sectors = 17;
139 	cth = imgsz / 17;
140 	footer->heads = (cth + 1023) / 1024;
141 	if (footer->heads < 4)
142 		footer->heads = 4;
143 	if (cth >= (footer->heads * 1024) || footer->heads > 16) {
144 		footer->heads = 16;
145 		footer->sectors = 31;
146 		cth = imgsz / 31;
147 	}
148 	if (cth >= (footer->heads * 1024)) {
149 		footer->heads = 16;
150 		footer->sectors = 63;
151 		cth = imgsz / 63;
152 	}
153 	be16enc(&footer->cylinders, cth / footer->heads);
154 }
155 
156 static uint32_t
157 vhd_timestamp(void)
158 {
159 	time_t t;
160 
161 	if (!unit_testing) {
162 		t = time(NULL);
163 		return (t - 0x386d4380);
164 	}
165 
166 	return (0x01234567);
167 }
168 
169 static void
170 vhd_uuid_enc(void *buf, const uuid_t *uuid)
171 {
172 	uint8_t *p = buf;
173 	int i;
174 
175 	be32enc(p, uuid->time_low);
176 	be16enc(p + 4, uuid->time_mid);
177 	be16enc(p + 6, uuid->time_hi_and_version);
178 	p[8] = uuid->clock_seq_hi_and_reserved;
179 	p[9] = uuid->clock_seq_low;
180 	for (i = 0; i < _UUID_NODE_LEN; i++)
181 		p[10 + i] = uuid->node[i];
182 }
183 
184 static void
185 vhd_make_footer(struct vhd_footer *footer, uint64_t image_size,
186     uint32_t disk_type, uint64_t data_offset)
187 {
188 	uuid_t id;
189 
190 	memset(footer, 0, sizeof(*footer));
191 	be64enc(&footer->cookie, VHD_FOOTER_COOKIE);
192 	be32enc(&footer->features, VHD_FEATURES_RESERVED);
193 	be32enc(&footer->version, VHD_VERSION);
194 	be64enc(&footer->data_offset, data_offset);
195 	be32enc(&footer->timestamp, vhd_timestamp());
196 	be32enc(&footer->creator_tool, VHD_CREATOR_TOOL);
197 	be32enc(&footer->creator_version, VHD_CREATOR_VERSION);
198 	be32enc(&footer->creator_os, VHD_CREATOR_OS);
199 	be64enc(&footer->original_size, image_size);
200 	be64enc(&footer->current_size, image_size);
201 	vhd_geometry(footer, image_size);
202 	be32enc(&footer->disk_type, disk_type);
203 	mkimg_uuid(&id);
204 	vhd_uuid_enc(&footer->id, &id);
205 	be32enc(&footer->checksum, vhd_checksum(footer, sizeof(*footer)));
206 }
207 
208 /*
209  * We round the image size to 2MB for both the dynamic and
210  * fixed VHD formats. For dynamic VHD, this is needed to
211  * have the image size be a multiple of the grain size. For
212  * fixed VHD this is not really needed, but makes sure that
213  * it's easy to convert from fixed VHD to dynamic VHD.
214  */
215 static int
216 vhd_resize(lba_t imgsz)
217 {
218 	uint64_t imagesz;
219 
220 	imagesz = imgsz * secsz;
221 	imagesz = (imagesz + VHD_BLOCK_SIZE - 1) & ~(VHD_BLOCK_SIZE - 1);
222 	return (image_set_size(imagesz / secsz));
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 	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_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_resize,
353 	.write = vhd_dyn_write,
354 };
355 
356 FORMAT_DEFINE(vhd_dyn_format);
357 
358 /*
359  * PART 2: Fixed VHD
360  */
361 
362 static int
363 vhd_fix_write(int fd)
364 {
365 	struct vhd_footer footer;
366 	uint64_t imgsz;
367 	int error;
368 
369 	error = image_copyout(fd);
370 	if (!error) {
371 		imgsz = image_get_size() * secsz;
372 		vhd_make_footer(&footer, imgsz, VHD_DISK_TYPE_FIXED, ~0ULL);
373 		if (sparse_write(fd, &footer, sizeof(footer)) < 0)
374 			error = errno;
375 	}
376 	return (error);
377 }
378 
379 static struct mkimg_format vhd_fix_format = {
380         .name = "vhdf",
381         .description = "Fixed Virtual Hard Disk",
382         .resize = vhd_resize,
383         .write = vhd_fix_write,
384 };
385 
386 FORMAT_DEFINE(vhd_fix_format);
387