vhd.c revision 269177
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: stable/10/usr.bin/mkimg/vhd.c 269177 2014-07-28 02:07:16Z 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_footer {
66	uint64_t	cookie;
67#define	VHD_FOOTER_COOKIE	0x636f6e6563746978
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
100static uint32_t
101vhd_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
113static void
114vhd_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
156static uint32_t
157vhd_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
169static void
170vhd_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
184static void
185vhd_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 */
215static int
216vhd_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
237struct vhd_dyn_header {
238	uint64_t	cookie;
239#define	VHD_HEADER_COOKIE	0x6378737061727365
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
264static int
265vhd_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	nblks = image_get_size();
324	while (blk < nblks) {
325		if (!image_data(blk, blkcnt)) {
326			blk += blkcnt;
327			continue;
328		}
329		if (sparse_write(fd, bitmap, VHD_SECTOR_SIZE) < 0) {
330			error = errno;
331			break;
332		}
333		error = image_copyout_region(fd, blk, blkcnt);
334		if (error)
335			break;
336		blk += blkcnt;
337	}
338	free(bitmap);
339	if (blk != nblks)
340		return (error);
341
342	if (sparse_write(fd, &footer, sizeof(footer)) < 0)
343		return (errno);
344
345	return (0);
346}
347
348static struct mkimg_format vhd_dyn_format = {
349	.name = "vhd",
350	.description = "Virtual Hard Disk",
351	.resize = vhd_resize,
352	.write = vhd_dyn_write,
353};
354
355FORMAT_DEFINE(vhd_dyn_format);
356
357/*
358 * PART 2: Fixed VHD
359 */
360
361static int
362vhd_fix_write(int fd)
363{
364	struct vhd_footer footer;
365	uint64_t imgsz;
366	int error;
367
368	error = image_copyout(fd);
369	if (!error) {
370		imgsz = image_get_size() * secsz;
371		vhd_make_footer(&footer, imgsz, VHD_DISK_TYPE_FIXED, ~0ULL);
372		if (sparse_write(fd, &footer, sizeof(footer)) < 0)
373			error = errno;
374	}
375	return (error);
376}
377
378static struct mkimg_format vhd_fix_format = {
379        .name = "vhdf",
380        .description = "Fixed Virtual Hard Disk",
381        .resize = vhd_resize,
382        .write = vhd_fix_write,
383};
384
385FORMAT_DEFINE(vhd_fix_format);
386