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