12f77485marcel/*-
2d7cd5cemarcel * Copyright (c) 2014, 2015 Marcel Moolenaar
32f77485marcel * All rights reserved.
42f77485marcel *
52f77485marcel * Redistribution and use in source and binary forms, with or without
62f77485marcel * modification, are permitted provided that the following conditions
72f77485marcel * are met:
82f77485marcel * 1. Redistributions of source code must retain the above copyright
92f77485marcel *    notice, this list of conditions and the following disclaimer.
102f77485marcel * 2. Redistributions in binary form must reproduce the above copyright
112f77485marcel *    notice, this list of conditions and the following disclaimer in the
122f77485marcel *    documentation and/or other materials provided with the distribution.
132f77485marcel *
142f77485marcel * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
152f77485marcel * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
162f77485marcel * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
172f77485marcel * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
182f77485marcel * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
192f77485marcel * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
202f77485marcel * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
212f77485marcel * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
222f77485marcel * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
232f77485marcel * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
242f77485marcel * SUCH DAMAGE.
252f77485marcel */
262f77485marcel
272f77485marcel#include <sys/cdefs.h>
282f77485marcel__FBSDID("$FreeBSD$");
292f77485marcel
302f77485marcel#include <sys/errno.h>
312f77485marcel#include <stdlib.h>
322f77485marcel#include <string.h>
339cadcaemarcel#include <time.h>
342f77485marcel
35741bf12marcel#include "endian.h"
362f77485marcel#include "image.h"
372f77485marcel#include "format.h"
382f77485marcel#include "mkimg.h"
392f77485marcel
40b85c677marcel#ifndef __has_extension
41b85c677marcel#define	__has_extension(x)	0
42b85c677marcel#endif
43b85c677marcel
44fd9cbcemarcel/*
459e79786marcel * General notes:
46fd9cbcemarcel * o   File is in network byte order.
47fd9cbcemarcel * o   The timestamp is seconds since 1/1/2000 12:00:00 AM UTC
489e79786marcel *
499e79786marcel * This file is divided in 3 parts:
509e79786marcel * 1.  Common definitions
519e79786marcel * 2.  Dynamic VHD support
529e79786marcel * 3.  Fixed VHD support
539e79786marcel */
549e79786marcel
559e79786marcel/*
569e79786marcel * PART 1: Common definitions
57fd9cbcemarcel */
58fd9cbcemarcel
599cadcaemarcel#define	VHD_SECTOR_SIZE	512
607c000c1marcel#define	VHD_BLOCK_SIZE	(4096 * VHD_SECTOR_SIZE)	/* 2MB blocks */
61363855dmarcel
627577833marcelstruct vhd_geom {
637577833marcel	uint16_t	cylinders;
647577833marcel	uint8_t		heads;
657577833marcel	uint8_t		sectors;
667577833marcel};
677577833marcel
68fd9cbcemarcelstruct vhd_footer {
697c000c1marcel	uint64_t	cookie;
70a74e992marcel#define	VHD_FOOTER_COOKIE	0x636f6e6563746978ULL
71fd9cbcemarcel	uint32_t	features;
72fd9cbcemarcel#define	VHD_FEATURES_TEMPORARY	0x01
73fd9cbcemarcel#define	VHD_FEATURES_RESERVED	0x02
74fd9cbcemarcel	uint32_t	version;
75fd9cbcemarcel#define	VHD_VERSION		0x00010000
76fd9cbcemarcel	uint64_t	data_offset;
77fd9cbcemarcel	uint32_t	timestamp;
787c000c1marcel	uint32_t	creator_tool;
797c000c1marcel#define	VHD_CREATOR_TOOL	0x2a696d67	/* FreeBSD mkimg */
80fd9cbcemarcel	uint32_t	creator_version;
81dd87255marcel#define	VHD_CREATOR_VERSION	0x00020000
827c000c1marcel	uint32_t	creator_os;
83dd87255marcel#define	VHD_CREATOR_OS		0x5769326b	/* Wi2k */
84fd9cbcemarcel	uint64_t	original_size;
85fd9cbcemarcel	uint64_t	current_size;
867577833marcel	struct vhd_geom	geometry;
87fd9cbcemarcel	uint32_t	disk_type;
88fd9cbcemarcel#define	VHD_DISK_TYPE_FIXED	2
89fd9cbcemarcel#define	VHD_DISK_TYPE_DYNAMIC	3
90fd9cbcemarcel#define	VHD_DISK_TYPE_DIFF	4
91fd9cbcemarcel	uint32_t	checksum;
92a31300amarcel	mkimg_uuid_t	id;
93fd9cbcemarcel	uint8_t		saved_state;
94fd9cbcemarcel	uint8_t		_reserved[427];
95fd9cbcemarcel};
96b85c677marcel#if __has_extension(c_static_assert)
979cadcaemarcel_Static_assert(sizeof(struct vhd_footer) == VHD_SECTOR_SIZE,
989cadcaemarcel    "Wrong size for footer");
99b85c677marcel#endif
100fd9cbcemarcel
1019cadcaemarcelstatic uint32_t
1029cadcaemarcelvhd_checksum(void *buf, size_t sz)
1039cadcaemarcel{
1049cadcaemarcel	uint8_t *p = buf;
1059cadcaemarcel	uint32_t sum;
1069cadcaemarcel	size_t ofs;
1079cadcaemarcel
1089cadcaemarcel	sum = 0;
1099cadcaemarcel	for (ofs = 0; ofs < sz; ofs++)
1109cadcaemarcel		sum += p[ofs];
1119cadcaemarcel	return (~sum);
1129cadcaemarcel}
1139cadcaemarcel
1149cadcaemarcelstatic void
1157577833marcelvhd_geometry(uint64_t image_size, struct vhd_geom *geom)
116ddac24dmarcel{
117ddac24dmarcel	lba_t imgsz;
118ddac24dmarcel	long cth;
119ddac24dmarcel
120d7cd5cemarcel	imgsz = image_size / VHD_SECTOR_SIZE;
121d7cd5cemarcel
122ddac24dmarcel	/* Respect command line options if possible. */
123ddac24dmarcel	if (nheads > 1 && nheads < 256 &&
124ddac24dmarcel	    nsecs > 1 && nsecs < 256 &&
125ddac24dmarcel	    ncyls < 65536) {
126d7cd5cemarcel		geom->cylinders = (ncyls != 0) ? ncyls :
127d7cd5cemarcel		    imgsz / (nheads * nsecs);
1287577833marcel		geom->heads = nheads;
1297577833marcel		geom->sectors = nsecs;
130ddac24dmarcel		return;
131ddac24dmarcel	}
132ddac24dmarcel
133ddac24dmarcel	if (imgsz > 65536 * 16 * 255)
134ddac24dmarcel		imgsz = 65536 * 16 * 255;
135ddac24dmarcel	if (imgsz >= 65535 * 16 * 63) {
1367577833marcel		geom->cylinders = imgsz / (16 * 255);
1377577833marcel		geom->heads = 16;
1387577833marcel		geom->sectors = 255;
139ddac24dmarcel		return;
140ddac24dmarcel	}
1417577833marcel	geom->sectors = 17;
142ddac24dmarcel	cth = imgsz / 17;
1437577833marcel	geom->heads = (cth + 1023) / 1024;
1447577833marcel	if (geom->heads < 4)
1457577833marcel		geom->heads = 4;
1467577833marcel	if (cth >= (geom->heads * 1024) || geom->heads > 16) {
1477577833marcel		geom->heads = 16;
1487577833marcel		geom->sectors = 31;
149ddac24dmarcel		cth = imgsz / 31;
150ddac24dmarcel	}
1517577833marcel	if (cth >= (geom->heads * 1024)) {
1527577833marcel		geom->heads = 16;
1537577833marcel		geom->sectors = 63;
154ddac24dmarcel		cth = imgsz / 63;
155ddac24dmarcel	}
1567577833marcel	geom->cylinders = cth / geom->heads;
157ddac24dmarcel}
158ddac24dmarcel
159c071cf5marcelstatic uint64_t
160c071cf5marcelvhd_resize(uint64_t origsz)
161c071cf5marcel{
162c071cf5marcel	struct vhd_geom geom;
163c071cf5marcel	uint64_t newsz;
164c071cf5marcel
165c071cf5marcel	/*
166c071cf5marcel	 * Round the image size to the pre-determined geometry that
167c071cf5marcel	 * matches the image size. This circular dependency implies
168c071cf5marcel	 * that we need to loop to handle boundary conditions.
169c071cf5marcel	 * The first time, newsz equals origsz and the geometry will
170c071cf5marcel	 * typically yield a new size that's smaller. We keep adding
171c071cf5marcel	 * cylinder's worth of sectors to the new size until its
172c071cf5marcel	 * larger or equal or origsz. But during those iterations,
173c071cf5marcel	 * the geometry can change, so we need to account for that.
174c071cf5marcel	 */
175c071cf5marcel	newsz = origsz;
176c071cf5marcel	while (1) {
177c071cf5marcel		vhd_geometry(newsz, &geom);
178c071cf5marcel		newsz = (int64_t)geom.cylinders * geom.heads *
179c071cf5marcel		    geom.sectors * VHD_SECTOR_SIZE;
180c071cf5marcel		if (newsz >= origsz)
181c071cf5marcel			break;
182c071cf5marcel		newsz += geom.heads * geom.sectors * VHD_SECTOR_SIZE;
183c071cf5marcel	}
184c071cf5marcel	return (newsz);
185c071cf5marcel}
186c071cf5marcel
1879e79786marcelstatic uint32_t
1889e79786marcelvhd_timestamp(void)
1899e79786marcel{
1909e79786marcel	time_t t;
1919e79786marcel
1929e79786marcel	if (!unit_testing) {
1939e79786marcel		t = time(NULL);
1949e79786marcel		return (t - 0x386d4380);
1959e79786marcel	}
1969e79786marcel
1979e79786marcel	return (0x01234567);
1989e79786marcel}
1999e79786marcel
2009e79786marcelstatic void
2019e79786marcelvhd_make_footer(struct vhd_footer *footer, uint64_t image_size,
2029e79786marcel    uint32_t disk_type, uint64_t data_offset)
2039e79786marcel{
204a31300amarcel	mkimg_uuid_t id;
2059e79786marcel
2069e79786marcel	memset(footer, 0, sizeof(*footer));
2079e79786marcel	be64enc(&footer->cookie, VHD_FOOTER_COOKIE);
2089e79786marcel	be32enc(&footer->features, VHD_FEATURES_RESERVED);
2099e79786marcel	be32enc(&footer->version, VHD_VERSION);
2109e79786marcel	be64enc(&footer->data_offset, data_offset);
2119e79786marcel	be32enc(&footer->timestamp, vhd_timestamp());
2129e79786marcel	be32enc(&footer->creator_tool, VHD_CREATOR_TOOL);
2139e79786marcel	be32enc(&footer->creator_version, VHD_CREATOR_VERSION);
2149e79786marcel	be32enc(&footer->creator_os, VHD_CREATOR_OS);
2159e79786marcel	be64enc(&footer->original_size, image_size);
2169e79786marcel	be64enc(&footer->current_size, image_size);
2177577833marcel	vhd_geometry(image_size, &footer->geometry);
2187577833marcel	be16enc(&footer->geometry.cylinders, footer->geometry.cylinders);
2199e79786marcel	be32enc(&footer->disk_type, disk_type);
2209e79786marcel	mkimg_uuid(&id);
221a31300amarcel	mkimg_uuid_enc(&footer->id, &id);
2229e79786marcel	be32enc(&footer->checksum, vhd_checksum(footer, sizeof(*footer)));
2239e79786marcel}
2249e79786marcel
2259e79786marcel/*
2269e79786marcel * PART 2: Dynamic VHD support
2279e79786marcel *
2289e79786marcel * Notes:
2299e79786marcel * o   File layout:
2309e79786marcel *	copy of disk footer
2319e79786marcel *	dynamic disk header
2329e79786marcel *	block allocation table (BAT)
2339e79786marcel *	data blocks
2349e79786marcel *	disk footer
2359e79786marcel */
2369e79786marcel
2379e79786marcelstruct vhd_dyn_header {
2389e79786marcel	uint64_t	cookie;
239a74e992marcel#define	VHD_HEADER_COOKIE	0x6378737061727365ULL
2409e79786marcel	uint64_t	data_offset;
2419e79786marcel	uint64_t	table_offset;
2429e79786marcel	uint32_t	version;
2439e79786marcel	uint32_t	max_entries;
2449e79786marcel	uint32_t	block_size;
2459e79786marcel	uint32_t	checksum;
246a31300amarcel	mkimg_uuid_t	parent_id;
2479e79786marcel	uint32_t	parent_timestamp;
2489e79786marcel	char		_reserved1[4];
2499e79786marcel	uint16_t	parent_name[256];	/* UTF-16 */
2509e79786marcel	struct {
2519e79786marcel		uint32_t	code;
2529e79786marcel		uint32_t	data_space;
2539e79786marcel		uint32_t	data_length;
2549e79786marcel		uint32_t	_reserved;
2559e79786marcel		uint64_t	data_offset;
2569e79786marcel	} parent_locator[8];
2579e79786marcel	char		_reserved2[256];
2589e79786marcel};
259b85c677marcel#if __has_extension(c_static_assert)
2609e79786marcel_Static_assert(sizeof(struct vhd_dyn_header) == VHD_SECTOR_SIZE * 2,
2619e79786marcel    "Wrong size for header");
262b85c677marcel#endif
2639e79786marcel
2649e79786marcelstatic int
2657577833marcelvhd_dyn_resize(lba_t imgsz)
2667577833marcel{
2677577833marcel	uint64_t imagesz;
2687577833marcel
269c071cf5marcel	imagesz = vhd_resize(imgsz * secsz);
2707577833marcel	return (image_set_size(imagesz / secsz));
2717577833marcel}
2727577833marcel
2737577833marcelstatic int
2749e79786marcelvhd_dyn_write(int fd)
2752f77485marcel{
2769cadcaemarcel	struct vhd_footer footer;
2779cadcaemarcel	struct vhd_dyn_header header;
278c071cf5marcel	uint64_t imgsz, rawsz;
279ca1d592marcel	lba_t blk, blkcnt, nblks;
2805709040marcel	uint32_t *bat;
2815709040marcel	void *bitmap;
2825709040marcel	size_t batsz;
2835709040marcel	uint32_t sector;
284e9602a1marcel	int bat_entries, error, entry;
2859cadcaemarcel
286c071cf5marcel	rawsz = image_get_size() * secsz;
287c071cf5marcel	imgsz = (rawsz + VHD_BLOCK_SIZE - 1) & ~(VHD_BLOCK_SIZE - 1);
2889cadcaemarcel
289c071cf5marcel	vhd_make_footer(&footer, rawsz, VHD_DISK_TYPE_DYNAMIC, sizeof(footer));
2907c000c1marcel	if (sparse_write(fd, &footer, sizeof(footer)) < 0)
2919cadcaemarcel		return (errno);
2922f77485marcel
293c071cf5marcel	bat_entries = imgsz / VHD_BLOCK_SIZE;
2949cadcaemarcel	memset(&header, 0, sizeof(header));
2957c000c1marcel	be64enc(&header.cookie, VHD_HEADER_COOKIE);
2967c000c1marcel	be64enc(&header.data_offset, ~0ULL);
2977c000c1marcel	be64enc(&header.table_offset, sizeof(footer) + sizeof(header));
2987c000c1marcel	be32enc(&header.version, VHD_VERSION);
2995709040marcel	be32enc(&header.max_entries, bat_entries);
3007c000c1marcel	be32enc(&header.block_size, VHD_BLOCK_SIZE);
3017c000c1marcel	be32enc(&header.checksum, vhd_checksum(&header, sizeof(header)));
3027c000c1marcel	if (sparse_write(fd, &header, sizeof(header)) < 0)
3037c000c1marcel		return (errno);
3047c000c1marcel
3055709040marcel	batsz = bat_entries * sizeof(uint32_t);
3065709040marcel	batsz = (batsz + VHD_SECTOR_SIZE - 1) & ~(VHD_SECTOR_SIZE - 1);
3075709040marcel	bat = malloc(batsz);
3085709040marcel	if (bat == NULL)
3095709040marcel		return (errno);
3105709040marcel	memset(bat, 0xff, batsz);
311ca1d592marcel	blkcnt = VHD_BLOCK_SIZE / secsz;
3125709040marcel	sector = (sizeof(footer) + sizeof(header) + batsz) / VHD_SECTOR_SIZE;
3135709040marcel	for (entry = 0; entry < bat_entries; entry++) {
314ca1d592marcel		blk = entry * blkcnt;
315ca1d592marcel		if (image_data(blk, blkcnt)) {
316ca1d592marcel			be32enc(&bat[entry], sector);
317ca1d592marcel			sector += (VHD_BLOCK_SIZE / VHD_SECTOR_SIZE) + 1;
318ca1d592marcel		}
3195709040marcel	}
3205709040marcel	if (sparse_write(fd, bat, batsz) < 0) {
3215709040marcel		free(bat);
3225709040marcel		return (errno);
3235709040marcel	}
3245709040marcel	free(bat);
3255709040marcel
3265709040marcel	bitmap = malloc(VHD_SECTOR_SIZE);
3275709040marcel	if (bitmap == NULL)
3285709040marcel		return (errno);
3295709040marcel	memset(bitmap, 0xff, VHD_SECTOR_SIZE);
33019a7092marcel
33119a7092marcel	blk = 0;
332ca1d592marcel	blkcnt = VHD_BLOCK_SIZE / secsz;
3337da6d95delphij	error = 0;
334c071cf5marcel	nblks = rawsz / secsz;
33519a7092marcel	while (blk < nblks) {
336ca1d592marcel		if (!image_data(blk, blkcnt)) {
337ca1d592marcel			blk += blkcnt;
338ca1d592marcel			continue;
339ca1d592marcel		}
34019a7092marcel		if (sparse_write(fd, bitmap, VHD_SECTOR_SIZE) < 0) {
34119a7092marcel			error = errno;
34219a7092marcel			break;
34319a7092marcel		}
344c071cf5marcel		/* Handle partial last block */
345c071cf5marcel		if (blk + blkcnt > nblks)
346c071cf5marcel			blkcnt = nblks - blk;
347ca1d592marcel		error = image_copyout_region(fd, blk, blkcnt);
34819a7092marcel		if (error)
34919a7092marcel			break;
350ca1d592marcel		blk += blkcnt;
3515709040marcel	}
3525709040marcel	free(bitmap);
353c071cf5marcel	if (error)
354c071cf5marcel		return (error);
355c071cf5marcel	error = image_copyout_zeroes(fd, imgsz - rawsz);
356c071cf5marcel	if (error)
357e9602a1marcel		return (error);
358e9602a1marcel	if (sparse_write(fd, &footer, sizeof(footer)) < 0)
359e9602a1marcel		return (errno);
360e9602a1marcel
361e9602a1marcel	return (0);
3622f77485marcel}
3632f77485marcel
3649e79786marcelstatic struct mkimg_format vhd_dyn_format = {
3652f77485marcel	.name = "vhd",
3662f77485marcel	.description = "Virtual Hard Disk",
3677577833marcel	.resize = vhd_dyn_resize,
3689e79786marcel	.write = vhd_dyn_write,
3699e79786marcel};
3709e79786marcel
3719e79786marcelFORMAT_DEFINE(vhd_dyn_format);
3729e79786marcel
3739e79786marcel/*
3747577833marcel * PART 3: Fixed VHD
3759e79786marcel */
3769e79786marcel
3779e79786marcelstatic int
3787577833marcelvhd_fix_resize(lba_t imgsz)
3797577833marcel{
380c071cf5marcel	uint64_t imagesz;
3817577833marcel
382c071cf5marcel	imagesz = vhd_resize(imgsz * secsz);
383149795amarcel	/*
384149795amarcel	 * Azure demands that images are a whole number of megabytes.
385149795amarcel	 */
386149795amarcel	imagesz = (imagesz + 0xfffffULL) & ~0xfffffULL;
3877577833marcel	return (image_set_size(imagesz / secsz));
3887577833marcel}
3897577833marcel
3907577833marcelstatic int
3919e79786marcelvhd_fix_write(int fd)
3929e79786marcel{
3939e79786marcel	struct vhd_footer footer;
394c071cf5marcel	uint64_t imagesz;
3959e79786marcel	int error;
3969e79786marcel
3979e79786marcel	error = image_copyout(fd);
398c071cf5marcel	if (error)
399c071cf5marcel		return (error);
400c071cf5marcel
401c071cf5marcel	imagesz = image_get_size() * secsz;
402c071cf5marcel	vhd_make_footer(&footer, imagesz, VHD_DISK_TYPE_FIXED, ~0ULL);
403c071cf5marcel	error = (sparse_write(fd, &footer, sizeof(footer)) < 0) ? errno : 0;
4049e79786marcel	return (error);
4059e79786marcel}
4069e79786marcel
4079e79786marcelstatic struct mkimg_format vhd_fix_format = {
408c071cf5marcel	.name = "vhdf",
409c071cf5marcel	.description = "Fixed Virtual Hard Disk",
410c071cf5marcel	.resize = vhd_fix_resize,
411c071cf5marcel	.write = vhd_fix_write,
4122f77485marcel};
4132f77485marcel
4149e79786marcelFORMAT_DEFINE(vhd_fix_format);
415