1267889Smarcel/*-
2284773Smarcel * Copyright (c) 2014, 2015 Marcel Moolenaar
3267889Smarcel * All rights reserved.
4267889Smarcel *
5267889Smarcel * Redistribution and use in source and binary forms, with or without
6267889Smarcel * modification, are permitted provided that the following conditions
7267889Smarcel * are met:
8267889Smarcel * 1. Redistributions of source code must retain the above copyright
9267889Smarcel *    notice, this list of conditions and the following disclaimer.
10267889Smarcel * 2. Redistributions in binary form must reproduce the above copyright
11267889Smarcel *    notice, this list of conditions and the following disclaimer in the
12267889Smarcel *    documentation and/or other materials provided with the distribution.
13267889Smarcel *
14267889Smarcel * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15267889Smarcel * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16267889Smarcel * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17267889Smarcel * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18267889Smarcel * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19267889Smarcel * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20267889Smarcel * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21267889Smarcel * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22267889Smarcel * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23267889Smarcel * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24267889Smarcel * SUCH DAMAGE.
25267889Smarcel */
26267889Smarcel
27267889Smarcel#include <sys/cdefs.h>
28267889Smarcel__FBSDID("$FreeBSD$");
29267889Smarcel
30267889Smarcel#include <sys/types.h>
31267889Smarcel#include <sys/endian.h>
32267889Smarcel#include <sys/errno.h>
33267889Smarcel#include <stdlib.h>
34267889Smarcel#include <string.h>
35267916Smarcel#include <time.h>
36267889Smarcel#include <unistd.h>
37267893Smarcel#include <uuid.h>
38267889Smarcel
39267889Smarcel#include "image.h"
40267889Smarcel#include "format.h"
41267889Smarcel#include "mkimg.h"
42267889Smarcel
43269177Smarcel#ifndef __has_extension
44269177Smarcel#define	__has_extension(x)	0
45269177Smarcel#endif
46269177Smarcel
47267893Smarcel/*
48269177Smarcel * General notes:
49267893Smarcel * o   File is in network byte order.
50267893Smarcel * o   The timestamp is seconds since 1/1/2000 12:00:00 AM UTC
51269177Smarcel *
52269177Smarcel * This file is divided in 3 parts:
53269177Smarcel * 1.  Common definitions
54269177Smarcel * 2.  Dynamic VHD support
55269177Smarcel * 3.  Fixed VHD support
56267893Smarcel */
57267893Smarcel
58269177Smarcel/*
59269177Smarcel * PART 1: Common definitions
60269177Smarcel */
61269177Smarcel
62267916Smarcel#define	VHD_SECTOR_SIZE	512
63267945Smarcel#define	VHD_BLOCK_SIZE	(4096 * VHD_SECTOR_SIZE)	/* 2MB blocks */
64267894Smarcel
65284773Smarcelstruct vhd_geom {
66284773Smarcel	uint16_t	cylinders;
67284773Smarcel	uint8_t		heads;
68284773Smarcel	uint8_t		sectors;
69284773Smarcel};
70284773Smarcel
71267893Smarcelstruct vhd_footer {
72267945Smarcel	uint64_t	cookie;
73267945Smarcel#define	VHD_FOOTER_COOKIE	0x636f6e6563746978
74267893Smarcel	uint32_t	features;
75267893Smarcel#define	VHD_FEATURES_TEMPORARY	0x01
76267893Smarcel#define	VHD_FEATURES_RESERVED	0x02
77267893Smarcel	uint32_t	version;
78267893Smarcel#define	VHD_VERSION		0x00010000
79267893Smarcel	uint64_t	data_offset;
80267893Smarcel	uint32_t	timestamp;
81267945Smarcel	uint32_t	creator_tool;
82267945Smarcel#define	VHD_CREATOR_TOOL	0x2a696d67	/* FreeBSD mkimg */
83267893Smarcel	uint32_t	creator_version;
84284773Smarcel#define	VHD_CREATOR_VERSION	0x00020000
85267945Smarcel	uint32_t	creator_os;
86284773Smarcel#define	VHD_CREATOR_OS		0x5769326b	/* Wi2k */
87267893Smarcel	uint64_t	original_size;
88267893Smarcel	uint64_t	current_size;
89284773Smarcel	struct vhd_geom	geometry;
90267893Smarcel	uint32_t	disk_type;
91267893Smarcel#define	VHD_DISK_TYPE_FIXED	2
92267893Smarcel#define	VHD_DISK_TYPE_DYNAMIC	3
93267893Smarcel#define	VHD_DISK_TYPE_DIFF	4
94267893Smarcel	uint32_t	checksum;
95267893Smarcel	uuid_t		id;
96267893Smarcel	uint8_t		saved_state;
97267893Smarcel	uint8_t		_reserved[427];
98267893Smarcel};
99269177Smarcel#if __has_extension(c_static_assert)
100267916Smarcel_Static_assert(sizeof(struct vhd_footer) == VHD_SECTOR_SIZE,
101267916Smarcel    "Wrong size for footer");
102269177Smarcel#endif
103267893Smarcel
104267916Smarcelstatic uint32_t
105267916Smarcelvhd_checksum(void *buf, size_t sz)
106267916Smarcel{
107267916Smarcel	uint8_t *p = buf;
108267916Smarcel	uint32_t sum;
109267916Smarcel	size_t ofs;
110267916Smarcel
111267916Smarcel	sum = 0;
112267916Smarcel	for (ofs = 0; ofs < sz; ofs++)
113267916Smarcel		sum += p[ofs];
114267916Smarcel	return (~sum);
115267916Smarcel}
116267916Smarcel
117267916Smarcelstatic void
118284773Smarcelvhd_geometry(uint64_t image_size, struct vhd_geom *geom)
119267916Smarcel{
120267998Smarcel	lba_t imgsz;
121267998Smarcel	long cth;
122267998Smarcel
123284773Smarcel	imgsz = image_size / VHD_SECTOR_SIZE;
124284773Smarcel
125267998Smarcel	/* Respect command line options if possible. */
126267998Smarcel	if (nheads > 1 && nheads < 256 &&
127267998Smarcel	    nsecs > 1 && nsecs < 256 &&
128267998Smarcel	    ncyls < 65536) {
129284773Smarcel		geom->cylinders = (ncyls != 0) ? ncyls :
130284773Smarcel		    imgsz / (nheads * nsecs);
131284773Smarcel		geom->heads = nheads;
132284773Smarcel		geom->sectors = nsecs;
133267998Smarcel		return;
134267998Smarcel	}
135267998Smarcel
136267998Smarcel	if (imgsz > 65536 * 16 * 255)
137267998Smarcel		imgsz = 65536 * 16 * 255;
138267998Smarcel	if (imgsz >= 65535 * 16 * 63) {
139284773Smarcel		geom->cylinders = imgsz / (16 * 255);
140284773Smarcel		geom->heads = 16;
141284773Smarcel		geom->sectors = 255;
142267998Smarcel		return;
143267998Smarcel	}
144284773Smarcel	geom->sectors = 17;
145267998Smarcel	cth = imgsz / 17;
146284773Smarcel	geom->heads = (cth + 1023) / 1024;
147284773Smarcel	if (geom->heads < 4)
148284773Smarcel		geom->heads = 4;
149284773Smarcel	if (cth >= (geom->heads * 1024) || geom->heads > 16) {
150284773Smarcel		geom->heads = 16;
151284773Smarcel		geom->sectors = 31;
152267998Smarcel		cth = imgsz / 31;
153267998Smarcel	}
154284773Smarcel	if (cth >= (geom->heads * 1024)) {
155284773Smarcel		geom->heads = 16;
156284773Smarcel		geom->sectors = 63;
157267998Smarcel		cth = imgsz / 63;
158267998Smarcel	}
159284773Smarcel	geom->cylinders = cth / geom->heads;
160267998Smarcel}
161267998Smarcel
162287122Smarcelstatic uint64_t
163287122Smarcelvhd_resize(uint64_t origsz)
164287122Smarcel{
165287122Smarcel	struct vhd_geom geom;
166287122Smarcel	uint64_t newsz;
167287122Smarcel
168287122Smarcel	/*
169287122Smarcel	 * Round the image size to the pre-determined geometry that
170287122Smarcel	 * matches the image size. This circular dependency implies
171287122Smarcel	 * that we need to loop to handle boundary conditions.
172287122Smarcel	 * The first time, newsz equals origsz and the geometry will
173287122Smarcel	 * typically yield a new size that's smaller. We keep adding
174287122Smarcel	 * cylinder's worth of sectors to the new size until its
175287122Smarcel	 * larger or equal or origsz. But during those iterations,
176287122Smarcel	 * the geometry can change, so we need to account for that.
177287122Smarcel	 */
178287122Smarcel	newsz = origsz;
179287122Smarcel	while (1) {
180287122Smarcel		vhd_geometry(newsz, &geom);
181287122Smarcel		newsz = (int64_t)geom.cylinders * geom.heads *
182287122Smarcel		    geom.sectors * VHD_SECTOR_SIZE;
183287122Smarcel		if (newsz >= origsz)
184287122Smarcel			break;
185287122Smarcel		newsz += geom.heads * geom.sectors * VHD_SECTOR_SIZE;
186287122Smarcel	}
187287122Smarcel	return (newsz);
188287122Smarcel}
189287122Smarcel
190269177Smarcelstatic uint32_t
191269177Smarcelvhd_timestamp(void)
192269177Smarcel{
193269177Smarcel	time_t t;
194269177Smarcel
195269177Smarcel	if (!unit_testing) {
196269177Smarcel		t = time(NULL);
197269177Smarcel		return (t - 0x386d4380);
198269177Smarcel	}
199269177Smarcel
200269177Smarcel	return (0x01234567);
201269177Smarcel}
202269177Smarcel
203269177Smarcelstatic void
204269177Smarcelvhd_uuid_enc(void *buf, const uuid_t *uuid)
205269177Smarcel{
206269177Smarcel	uint8_t *p = buf;
207269177Smarcel	int i;
208269177Smarcel
209269177Smarcel	be32enc(p, uuid->time_low);
210269177Smarcel	be16enc(p + 4, uuid->time_mid);
211269177Smarcel	be16enc(p + 6, uuid->time_hi_and_version);
212269177Smarcel	p[8] = uuid->clock_seq_hi_and_reserved;
213269177Smarcel	p[9] = uuid->clock_seq_low;
214269177Smarcel	for (i = 0; i < _UUID_NODE_LEN; i++)
215269177Smarcel		p[10 + i] = uuid->node[i];
216269177Smarcel}
217269177Smarcel
218269177Smarcelstatic void
219269177Smarcelvhd_make_footer(struct vhd_footer *footer, uint64_t image_size,
220269177Smarcel    uint32_t disk_type, uint64_t data_offset)
221269177Smarcel{
222269177Smarcel	uuid_t id;
223269177Smarcel
224269177Smarcel	memset(footer, 0, sizeof(*footer));
225269177Smarcel	be64enc(&footer->cookie, VHD_FOOTER_COOKIE);
226269177Smarcel	be32enc(&footer->features, VHD_FEATURES_RESERVED);
227269177Smarcel	be32enc(&footer->version, VHD_VERSION);
228269177Smarcel	be64enc(&footer->data_offset, data_offset);
229269177Smarcel	be32enc(&footer->timestamp, vhd_timestamp());
230269177Smarcel	be32enc(&footer->creator_tool, VHD_CREATOR_TOOL);
231269177Smarcel	be32enc(&footer->creator_version, VHD_CREATOR_VERSION);
232269177Smarcel	be32enc(&footer->creator_os, VHD_CREATOR_OS);
233269177Smarcel	be64enc(&footer->original_size, image_size);
234269177Smarcel	be64enc(&footer->current_size, image_size);
235284773Smarcel	vhd_geometry(image_size, &footer->geometry);
236284773Smarcel	be16enc(&footer->geometry.cylinders, footer->geometry.cylinders);
237269177Smarcel	be32enc(&footer->disk_type, disk_type);
238269177Smarcel	mkimg_uuid(&id);
239269177Smarcel	vhd_uuid_enc(&footer->id, &id);
240269177Smarcel	be32enc(&footer->checksum, vhd_checksum(footer, sizeof(*footer)));
241269177Smarcel}
242269177Smarcel
243269177Smarcel/*
244269177Smarcel * PART 2: Dynamic VHD support
245269177Smarcel *
246269177Smarcel * Notes:
247269177Smarcel * o   File layout:
248269177Smarcel *	copy of disk footer
249269177Smarcel *	dynamic disk header
250269177Smarcel *	block allocation table (BAT)
251269177Smarcel *	data blocks
252269177Smarcel *	disk footer
253269177Smarcel */
254269177Smarcel
255269177Smarcelstruct vhd_dyn_header {
256269177Smarcel	uint64_t	cookie;
257269177Smarcel#define	VHD_HEADER_COOKIE	0x6378737061727365
258269177Smarcel	uint64_t	data_offset;
259269177Smarcel	uint64_t	table_offset;
260269177Smarcel	uint32_t	version;
261269177Smarcel	uint32_t	max_entries;
262269177Smarcel	uint32_t	block_size;
263269177Smarcel	uint32_t	checksum;
264269177Smarcel	uuid_t		parent_id;
265269177Smarcel	uint32_t	parent_timestamp;
266269177Smarcel	char		_reserved1[4];
267269177Smarcel	uint16_t	parent_name[256];	/* UTF-16 */
268269177Smarcel	struct {
269269177Smarcel		uint32_t	code;
270269177Smarcel		uint32_t	data_space;
271269177Smarcel		uint32_t	data_length;
272269177Smarcel		uint32_t	_reserved;
273269177Smarcel		uint64_t	data_offset;
274269177Smarcel	} parent_locator[8];
275269177Smarcel	char		_reserved2[256];
276269177Smarcel};
277269177Smarcel#if __has_extension(c_static_assert)
278269177Smarcel_Static_assert(sizeof(struct vhd_dyn_header) == VHD_SECTOR_SIZE * 2,
279269177Smarcel    "Wrong size for header");
280269177Smarcel#endif
281269177Smarcel
282269177Smarcelstatic int
283284773Smarcelvhd_dyn_resize(lba_t imgsz)
284284773Smarcel{
285284773Smarcel	uint64_t imagesz;
286284773Smarcel
287287122Smarcel	imagesz = vhd_resize(imgsz * secsz);
288284773Smarcel	return (image_set_size(imagesz / secsz));
289284773Smarcel}
290284773Smarcel
291284773Smarcelstatic int
292269177Smarcelvhd_dyn_write(int fd)
293269177Smarcel{
294267916Smarcel	struct vhd_footer footer;
295267916Smarcel	struct vhd_dyn_header header;
296287122Smarcel	uint64_t imgsz, rawsz;
297269177Smarcel	lba_t blk, blkcnt, nblks;
298267946Smarcel	uint32_t *bat;
299267946Smarcel	void *bitmap;
300267946Smarcel	size_t batsz;
301267946Smarcel	uint32_t sector;
302267948Smarcel	int bat_entries, error, entry;
303267889Smarcel
304287122Smarcel	rawsz = image_get_size() * secsz;
305287122Smarcel	imgsz = (rawsz + VHD_BLOCK_SIZE - 1) & ~(VHD_BLOCK_SIZE - 1);
306267916Smarcel
307287122Smarcel	vhd_make_footer(&footer, rawsz, VHD_DISK_TYPE_DYNAMIC, sizeof(footer));
308267945Smarcel	if (sparse_write(fd, &footer, sizeof(footer)) < 0)
309267945Smarcel		return (errno);
310267916Smarcel
311287122Smarcel	bat_entries = imgsz / VHD_BLOCK_SIZE;
312267945Smarcel	memset(&header, 0, sizeof(header));
313267945Smarcel	be64enc(&header.cookie, VHD_HEADER_COOKIE);
314267945Smarcel	be64enc(&header.data_offset, ~0ULL);
315267945Smarcel	be64enc(&header.table_offset, sizeof(footer) + sizeof(header));
316267945Smarcel	be32enc(&header.version, VHD_VERSION);
317267946Smarcel	be32enc(&header.max_entries, bat_entries);
318267945Smarcel	be32enc(&header.block_size, VHD_BLOCK_SIZE);
319267945Smarcel	be32enc(&header.checksum, vhd_checksum(&header, sizeof(header)));
320267945Smarcel	if (sparse_write(fd, &header, sizeof(header)) < 0)
321267916Smarcel		return (errno);
322267916Smarcel
323267946Smarcel	batsz = bat_entries * sizeof(uint32_t);
324267946Smarcel	batsz = (batsz + VHD_SECTOR_SIZE - 1) & ~(VHD_SECTOR_SIZE - 1);
325267946Smarcel	bat = malloc(batsz);
326267946Smarcel	if (bat == NULL)
327267946Smarcel		return (errno);
328267946Smarcel	memset(bat, 0xff, batsz);
329269177Smarcel	blkcnt = VHD_BLOCK_SIZE / secsz;
330267946Smarcel	sector = (sizeof(footer) + sizeof(header) + batsz) / VHD_SECTOR_SIZE;
331267946Smarcel	for (entry = 0; entry < bat_entries; entry++) {
332269177Smarcel		blk = entry * blkcnt;
333269177Smarcel		if (image_data(blk, blkcnt)) {
334269177Smarcel			be32enc(&bat[entry], sector);
335269177Smarcel			sector += (VHD_BLOCK_SIZE / VHD_SECTOR_SIZE) + 1;
336269177Smarcel		}
337267946Smarcel	}
338267946Smarcel	if (sparse_write(fd, bat, batsz) < 0) {
339267946Smarcel		free(bat);
340267946Smarcel		return (errno);
341267946Smarcel	}
342267946Smarcel	free(bat);
343267946Smarcel
344267946Smarcel	bitmap = malloc(VHD_SECTOR_SIZE);
345267946Smarcel	if (bitmap == NULL)
346267946Smarcel		return (errno);
347267946Smarcel	memset(bitmap, 0xff, VHD_SECTOR_SIZE);
348267999Smarcel
349267999Smarcel	blk = 0;
350269177Smarcel	blkcnt = VHD_BLOCK_SIZE / secsz;
351269225Spluknet	error = 0;
352287122Smarcel	nblks = rawsz / secsz;
353267999Smarcel	while (blk < nblks) {
354269177Smarcel		if (!image_data(blk, blkcnt)) {
355269177Smarcel			blk += blkcnt;
356269177Smarcel			continue;
357269177Smarcel		}
358267999Smarcel		if (sparse_write(fd, bitmap, VHD_SECTOR_SIZE) < 0) {
359267999Smarcel			error = errno;
360267999Smarcel			break;
361267999Smarcel		}
362287122Smarcel		/* Handle partial last block */
363287122Smarcel		if (blk + blkcnt > nblks)
364287122Smarcel			blkcnt = nblks - blk;
365269177Smarcel		error = image_copyout_region(fd, blk, blkcnt);
366267999Smarcel		if (error)
367267999Smarcel			break;
368269177Smarcel		blk += blkcnt;
369267946Smarcel	}
370267946Smarcel	free(bitmap);
371287122Smarcel	if (error)
372267948Smarcel		return (error);
373287122Smarcel	error = image_copyout_zeroes(fd, imgsz - rawsz);
374287122Smarcel	if (error)
375287122Smarcel		return (error);
376267948Smarcel	if (sparse_write(fd, &footer, sizeof(footer)) < 0)
377267948Smarcel		return (errno);
378267948Smarcel
379267948Smarcel	return (0);
380267889Smarcel}
381267889Smarcel
382269177Smarcelstatic struct mkimg_format vhd_dyn_format = {
383267889Smarcel	.name = "vhd",
384267889Smarcel	.description = "Virtual Hard Disk",
385284773Smarcel	.resize = vhd_dyn_resize,
386269177Smarcel	.write = vhd_dyn_write,
387267889Smarcel};
388267889Smarcel
389269177SmarcelFORMAT_DEFINE(vhd_dyn_format);
390269177Smarcel
391269177Smarcel/*
392284773Smarcel * PART 3: Fixed VHD
393269177Smarcel */
394269177Smarcel
395269177Smarcelstatic int
396284773Smarcelvhd_fix_resize(lba_t imgsz)
397284773Smarcel{
398287122Smarcel	uint64_t imagesz;
399284773Smarcel
400287122Smarcel	imagesz = vhd_resize(imgsz * secsz);
401284773Smarcel	/*
402284773Smarcel	 * Azure demands that images are a whole number of megabytes.
403284773Smarcel	 */
404284773Smarcel	imagesz = (imagesz + 0xfffffULL) & ~0xfffffULL;
405284773Smarcel	return (image_set_size(imagesz / secsz));
406284773Smarcel}
407284773Smarcel
408284773Smarcelstatic int
409269177Smarcelvhd_fix_write(int fd)
410269177Smarcel{
411269177Smarcel	struct vhd_footer footer;
412287122Smarcel	uint64_t imagesz;
413269177Smarcel	int error;
414269177Smarcel
415269177Smarcel	error = image_copyout(fd);
416287122Smarcel	if (error)
417287122Smarcel		return (error);
418287122Smarcel
419287122Smarcel	imagesz = image_get_size() * secsz;
420287122Smarcel	vhd_make_footer(&footer, imagesz, VHD_DISK_TYPE_FIXED, ~0ULL);
421287122Smarcel	error = (sparse_write(fd, &footer, sizeof(footer)) < 0) ? errno : 0;
422269177Smarcel	return (error);
423269177Smarcel}
424269177Smarcel
425269177Smarcelstatic struct mkimg_format vhd_fix_format = {
426287122Smarcel	.name = "vhdf",
427287122Smarcel	.description = "Fixed Virtual Hard Disk",
428287122Smarcel	.resize = vhd_fix_resize,
429287122Smarcel	.write = vhd_fix_write,
430269177Smarcel};
431269177Smarcel
432269177SmarcelFORMAT_DEFINE(vhd_fix_format);
433