vhd.c revision 284773
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 284773 2015-06-24 18:40:34Z 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 uint32_t
163vhd_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
175static void
176vhd_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
190static void
191vhd_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
227struct vhd_dyn_header {
228	uint64_t	cookie;
229#define	VHD_HEADER_COOKIE	0x6378737061727365
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
254static int
255vhd_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
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	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
349static 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
356FORMAT_DEFINE(vhd_dyn_format);
357
358/*
359 * PART 3: Fixed VHD
360 */
361
362static int
363vhd_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
390static int
391vhd_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
407static 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
414FORMAT_DEFINE(vhd_fix_format);
415