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#include <sys/errno.h>
29#include <stdlib.h>
30#include <string.h>
31#include <time.h>
32
33#include "endian.h"
34#include "image.h"
35#include "format.h"
36#include "mkimg.h"
37
38#ifndef __has_extension
39#define	__has_extension(x)	0
40#endif
41
42/*
43 * General notes:
44 * o   File is in network byte order.
45 * o   The timestamp is seconds since 1/1/2000 12:00:00 AM UTC
46 *
47 * This file is divided in 3 parts:
48 * 1.  Common definitions
49 * 2.  Dynamic VHD support
50 * 3.  Fixed VHD support
51 */
52
53/*
54 * PART 1: Common definitions
55 */
56
57#define	VHD_SECTOR_SIZE	512
58#define	VHD_BLOCK_SIZE	(4096 * VHD_SECTOR_SIZE)	/* 2MB blocks */
59
60struct vhd_geom {
61	uint16_t	cylinders;
62	uint8_t		heads;
63	uint8_t		sectors;
64};
65
66struct vhd_footer {
67	uint64_t	cookie;
68#define	VHD_FOOTER_COOKIE	0x636f6e6563746978ULL
69	uint32_t	features;
70#define	VHD_FEATURES_TEMPORARY	0x01
71#define	VHD_FEATURES_RESERVED	0x02
72	uint32_t	version;
73#define	VHD_VERSION		0x00010000
74	uint64_t	data_offset;
75	uint32_t	timestamp;
76	uint32_t	creator_tool;
77#define	VHD_CREATOR_TOOL	0x2a696d67	/* FreeBSD mkimg */
78	uint32_t	creator_version;
79#define	VHD_CREATOR_VERSION	0x00020000
80	uint32_t	creator_os;
81#define	VHD_CREATOR_OS		0x5769326b	/* Wi2k */
82	uint64_t	original_size;
83	uint64_t	current_size;
84	struct vhd_geom	geometry;
85	uint32_t	disk_type;
86#define	VHD_DISK_TYPE_FIXED	2
87#define	VHD_DISK_TYPE_DYNAMIC	3
88#define	VHD_DISK_TYPE_DIFF	4
89	uint32_t	checksum;
90	mkimg_uuid_t	id;
91	uint8_t		saved_state;
92	uint8_t		_reserved[427];
93};
94#if __has_extension(c_static_assert)
95_Static_assert(sizeof(struct vhd_footer) == VHD_SECTOR_SIZE,
96    "Wrong size for footer");
97#endif
98
99static uint32_t
100vhd_checksum(void *buf, size_t sz)
101{
102	uint8_t *p = buf;
103	uint32_t sum;
104	size_t ofs;
105
106	sum = 0;
107	for (ofs = 0; ofs < sz; ofs++)
108		sum += p[ofs];
109	return (~sum);
110}
111
112static void
113vhd_geometry(uint64_t image_size, struct vhd_geom *geom)
114{
115	lba_t imgsz;
116	long cth;
117
118	imgsz = image_size / VHD_SECTOR_SIZE;
119
120	/* Respect command line options if possible. */
121	if (nheads > 1 && nheads < 256 &&
122	    nsecs > 1 && nsecs < 256 &&
123	    ncyls < 65536) {
124		geom->cylinders = (ncyls != 0) ? ncyls :
125		    imgsz / (nheads * nsecs);
126		geom->heads = nheads;
127		geom->sectors = nsecs;
128		return;
129	}
130
131	if (imgsz > 65536 * 16 * 255)
132		imgsz = 65536 * 16 * 255;
133	if (imgsz >= 65535 * 16 * 63) {
134		geom->cylinders = imgsz / (16 * 255);
135		geom->heads = 16;
136		geom->sectors = 255;
137		return;
138	}
139	geom->sectors = 17;
140	cth = imgsz / 17;
141	geom->heads = (cth + 1023) / 1024;
142	if (geom->heads < 4)
143		geom->heads = 4;
144	if (cth >= (geom->heads * 1024) || geom->heads > 16) {
145		geom->heads = 16;
146		geom->sectors = 31;
147		cth = imgsz / 31;
148	}
149	if (cth >= (geom->heads * 1024)) {
150		geom->heads = 16;
151		geom->sectors = 63;
152		cth = imgsz / 63;
153	}
154	geom->cylinders = cth / geom->heads;
155}
156
157static uint64_t
158vhd_resize(uint64_t origsz)
159{
160	struct vhd_geom geom;
161	uint64_t newsz;
162
163	/*
164	 * Round the image size to the pre-determined geometry that
165	 * matches the image size. This circular dependency implies
166	 * that we need to loop to handle boundary conditions.
167	 * The first time, newsz equals origsz and the geometry will
168	 * typically yield a new size that's smaller. We keep adding
169	 * cylinder's worth of sectors to the new size until its
170	 * larger or equal or origsz. But during those iterations,
171	 * the geometry can change, so we need to account for that.
172	 */
173	newsz = origsz;
174	while (1) {
175		vhd_geometry(newsz, &geom);
176		newsz = (int64_t)geom.cylinders * geom.heads *
177		    geom.sectors * VHD_SECTOR_SIZE;
178		if (newsz >= origsz)
179			break;
180		newsz += geom.heads * geom.sectors * VHD_SECTOR_SIZE;
181	}
182	return (newsz);
183}
184
185static uint32_t
186vhd_timestamp(void)
187{
188	time_t t;
189
190	if (!unit_testing) {
191		t = time(NULL);
192		return (t - 0x386d4380);
193	}
194
195	return (0x01234567);
196}
197
198static void
199vhd_make_footer(struct vhd_footer *footer, uint64_t image_size,
200    uint32_t disk_type, uint64_t data_offset)
201{
202	mkimg_uuid_t id;
203
204	memset(footer, 0, sizeof(*footer));
205	be64enc(&footer->cookie, VHD_FOOTER_COOKIE);
206	be32enc(&footer->features, VHD_FEATURES_RESERVED);
207	be32enc(&footer->version, VHD_VERSION);
208	be64enc(&footer->data_offset, data_offset);
209	be32enc(&footer->timestamp, vhd_timestamp());
210	be32enc(&footer->creator_tool, VHD_CREATOR_TOOL);
211	be32enc(&footer->creator_version, VHD_CREATOR_VERSION);
212	be32enc(&footer->creator_os, VHD_CREATOR_OS);
213	be64enc(&footer->original_size, image_size);
214	be64enc(&footer->current_size, image_size);
215	vhd_geometry(image_size, &footer->geometry);
216	be16enc(&footer->geometry.cylinders, footer->geometry.cylinders);
217	be32enc(&footer->disk_type, disk_type);
218	mkimg_uuid(&id);
219	mkimg_uuid_enc(&footer->id, &id);
220	be32enc(&footer->checksum, vhd_checksum(footer, sizeof(*footer)));
221}
222
223/*
224 * PART 2: Dynamic VHD support
225 *
226 * Notes:
227 * o   File layout:
228 *	copy of disk footer
229 *	dynamic disk header
230 *	block allocation table (BAT)
231 *	data blocks
232 *	disk footer
233 */
234
235struct vhd_dyn_header {
236	uint64_t	cookie;
237#define	VHD_HEADER_COOKIE	0x6378737061727365ULL
238	uint64_t	data_offset;
239	uint64_t	table_offset;
240	uint32_t	version;
241	uint32_t	max_entries;
242	uint32_t	block_size;
243	uint32_t	checksum;
244	mkimg_uuid_t	parent_id;
245	uint32_t	parent_timestamp;
246	char		_reserved1[4];
247	uint16_t	parent_name[256];	/* UTF-16 */
248	struct {
249		uint32_t	code;
250		uint32_t	data_space;
251		uint32_t	data_length;
252		uint32_t	_reserved;
253		uint64_t	data_offset;
254	} parent_locator[8];
255	char		_reserved2[256];
256};
257#if __has_extension(c_static_assert)
258_Static_assert(sizeof(struct vhd_dyn_header) == VHD_SECTOR_SIZE * 2,
259    "Wrong size for header");
260#endif
261
262static int
263vhd_dyn_resize(lba_t imgsz)
264{
265	uint64_t imagesz;
266
267	imagesz = vhd_resize(imgsz * secsz);
268	return (image_set_size(imagesz / secsz));
269}
270
271static int
272vhd_dyn_write(int fd)
273{
274	struct vhd_footer footer;
275	struct vhd_dyn_header header;
276	uint64_t imgsz, rawsz;
277	lba_t blk, blkcnt, nblks;
278	uint32_t *bat;
279	void *bitmap;
280	size_t batsz;
281	uint32_t sector;
282	int bat_entries, error, entry;
283
284	rawsz = image_get_size() * secsz;
285	imgsz = (rawsz + VHD_BLOCK_SIZE - 1) & ~(VHD_BLOCK_SIZE - 1);
286
287	vhd_make_footer(&footer, rawsz, VHD_DISK_TYPE_DYNAMIC, sizeof(footer));
288	if (sparse_write(fd, &footer, sizeof(footer)) < 0)
289		return (errno);
290
291	bat_entries = imgsz / VHD_BLOCK_SIZE;
292	memset(&header, 0, sizeof(header));
293	be64enc(&header.cookie, VHD_HEADER_COOKIE);
294	be64enc(&header.data_offset, ~0ULL);
295	be64enc(&header.table_offset, sizeof(footer) + sizeof(header));
296	be32enc(&header.version, VHD_VERSION);
297	be32enc(&header.max_entries, bat_entries);
298	be32enc(&header.block_size, VHD_BLOCK_SIZE);
299	be32enc(&header.checksum, vhd_checksum(&header, sizeof(header)));
300	if (sparse_write(fd, &header, sizeof(header)) < 0)
301		return (errno);
302
303	batsz = bat_entries * sizeof(uint32_t);
304	batsz = (batsz + VHD_SECTOR_SIZE - 1) & ~(VHD_SECTOR_SIZE - 1);
305	bat = malloc(batsz);
306	if (bat == NULL)
307		return (errno);
308	memset(bat, 0xff, batsz);
309	blkcnt = VHD_BLOCK_SIZE / secsz;
310	sector = (sizeof(footer) + sizeof(header) + batsz) / VHD_SECTOR_SIZE;
311	for (entry = 0; entry < bat_entries; entry++) {
312		blk = entry * blkcnt;
313		if (image_data(blk, blkcnt)) {
314			be32enc(&bat[entry], sector);
315			sector += (VHD_BLOCK_SIZE / VHD_SECTOR_SIZE) + 1;
316		}
317	}
318	if (sparse_write(fd, bat, batsz) < 0) {
319		free(bat);
320		return (errno);
321	}
322	free(bat);
323
324	bitmap = malloc(VHD_SECTOR_SIZE);
325	if (bitmap == NULL)
326		return (errno);
327	memset(bitmap, 0xff, VHD_SECTOR_SIZE);
328
329	blk = 0;
330	blkcnt = VHD_BLOCK_SIZE / secsz;
331	error = 0;
332	nblks = rawsz / secsz;
333	while (blk < nblks) {
334		if (!image_data(blk, blkcnt)) {
335			blk += blkcnt;
336			continue;
337		}
338		if (sparse_write(fd, bitmap, VHD_SECTOR_SIZE) < 0) {
339			error = errno;
340			break;
341		}
342		/* Handle partial last block */
343		if (blk + blkcnt > nblks)
344			blkcnt = nblks - blk;
345		error = image_copyout_region(fd, blk, blkcnt);
346		if (error)
347			break;
348		blk += blkcnt;
349	}
350	free(bitmap);
351	if (error)
352		return (error);
353	error = image_copyout_zeroes(fd, imgsz - rawsz);
354	if (error)
355		return (error);
356	if (sparse_write(fd, &footer, sizeof(footer)) < 0)
357		return (errno);
358
359	return (0);
360}
361
362static struct mkimg_format vhd_dyn_format = {
363	.name = "vhd",
364	.description = "Virtual Hard Disk",
365	.resize = vhd_dyn_resize,
366	.write = vhd_dyn_write,
367};
368
369FORMAT_DEFINE(vhd_dyn_format);
370
371/*
372 * PART 3: Fixed VHD
373 */
374
375static int
376vhd_fix_resize(lba_t imgsz)
377{
378	uint64_t imagesz;
379
380	imagesz = vhd_resize(imgsz * secsz);
381	/*
382	 * Azure demands that images are a whole number of megabytes.
383	 */
384	imagesz = (imagesz + 0xfffffULL) & ~0xfffffULL;
385	return (image_set_size(imagesz / secsz));
386}
387
388static int
389vhd_fix_write(int fd)
390{
391	struct vhd_footer footer;
392	uint64_t imagesz;
393	int error;
394
395	error = image_copyout(fd);
396	if (error)
397		return (error);
398
399	imagesz = image_get_size() * secsz;
400	vhd_make_footer(&footer, imagesz, VHD_DISK_TYPE_FIXED, ~0ULL);
401	error = (sparse_write(fd, &footer, sizeof(footer)) < 0) ? errno : 0;
402	return (error);
403}
404
405static struct mkimg_format vhd_fix_format = {
406	.name = "vhdf",
407	.description = "Fixed Virtual Hard Disk",
408	.resize = vhd_fix_resize,
409	.write = vhd_fix_write,
410};
411
412FORMAT_DEFINE(vhd_fix_format);
413