vhd.c revision 287122
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: stable/10/usr.bin/mkimg/vhd.c 287122 2015-08-25 04:03:51Z marcel $");
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
65struct vhd_geom {
66	uint16_t	cylinders;
67	uint8_t		heads;
68	uint8_t		sectors;
69};
70
71struct vhd_footer {
72	uint64_t	cookie;
73#define	VHD_FOOTER_COOKIE	0x636f6e6563746978
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
104static uint32_t
105vhd_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
117static void
118vhd_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
162static uint64_t
163vhd_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
190static uint32_t
191vhd_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
203static void
204vhd_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
218static void
219vhd_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
255struct vhd_dyn_header {
256	uint64_t	cookie;
257#define	VHD_HEADER_COOKIE	0x6378737061727365
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
282static int
283vhd_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
291static int
292vhd_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
382static 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
389FORMAT_DEFINE(vhd_dyn_format);
390
391/*
392 * PART 3: Fixed VHD
393 */
394
395static int
396vhd_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
408static int
409vhd_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
425static 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
432FORMAT_DEFINE(vhd_fix_format);
433