1/*-
2 * Copyright (c) 2020 Oleksandr Tymoshenko <gonzo@FreeBSD.org>
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23 * SUCH DAMAGE.
24 */
25
26#include <sys/cdefs.h>
27__FBSDID("$FreeBSD$");
28
29#include <sys/errno.h>
30#include <stdlib.h>
31#include <string.h>
32#include <time.h>
33
34#include "endian.h"
35#include "image.h"
36#include "format.h"
37#include "mkimg.h"
38
39#define	PAYLOAD_BLOCK_SIZE	(16*1024*1024)
40#define	SIZE_64KB		(64*1024)
41#define	SIZE_1MB		(1024*1024)
42
43#define	META_IS_REQUIRED	(1 << 2)
44#define	META_IS_VIRTUAL_DISK	(1 << 1)
45
46#define	PAYLOAD_BLOCK_FULLY_PRESENT	6
47#define	SB_BLOCK_NOT_PRESENT		0
48#define	BAT_ENTRY(offset, flags)	(((offset) << 20) | (flags))
49
50/* Regions' UUIDs */
51#define VHDX_REGION_BAT_GUID	\
52        {0x2dc27766,0xf623,0x4200,0x9d,0x64,{0x11,0x5e,0x9b,0xfd,0x4a,0x08}}
53#define VHDX_REGION_METADATA_GUID	\
54        {0x8b7ca206,0x4790,0x4b9a,0xb8,0xfe,{0x57,0x5f,0x05,0x0f,0x88,0x6e}}
55static mkimg_uuid_t vhdx_bat_guid = VHDX_REGION_BAT_GUID;
56static mkimg_uuid_t vhdx_metadata_guid = VHDX_REGION_METADATA_GUID;
57
58/* Metadata UUIDs */
59#define VHDX_META_FILE_PARAMETERS	\
60        {0xcaa16737,0xfa36,0x4d43,0xb3,0xb6,{0x33,0xf0,0xaa,0x44,0xe7,0x6b}}
61#define VHDX_META_VDISK_SIZE	\
62        {0x2fa54224,0xcd1b,0x4876,0xb2,0x11,{0x5d,0xbe,0xd8,0x3b,0xf4,0xb8}}
63#define VHDX_META_VDISK_ID	\
64        {0xbeca12ab,0xb2e6,0x4523,0x93,0xef,{0xc3,0x09,0xe0,0x00,0xc7,0x46}}
65#define VHDX_META_LOGICAL_SSIZE	\
66        {0x8141bf1D,0xa96f,0x4709,0xba,0x47,{0xf2,0x33,0xa8,0xfa,0xab,0x5f}}
67#define VHDX_META_PHYS_SSIZE	\
68        {0xcda348c7,0x445d,0x4471,0x9c,0xc9,{0xe9,0x88,0x52,0x51,0xc5,0x56}}
69
70static mkimg_uuid_t vhdx_meta_file_parameters_guid = VHDX_META_FILE_PARAMETERS;
71static mkimg_uuid_t vhdx_meta_vdisk_size_guid = VHDX_META_VDISK_SIZE;
72static mkimg_uuid_t vhdx_meta_vdisk_id_guid = VHDX_META_VDISK_ID;
73static mkimg_uuid_t vhdx_meta_logical_ssize_guid = VHDX_META_LOGICAL_SSIZE;
74static mkimg_uuid_t vhdx_meta_phys_ssize_guid = VHDX_META_PHYS_SSIZE;
75
76struct vhdx_filetype_identifier {
77	uint64_t	signature;
78#define	VHDX_FILETYPE_ID_SIGNATURE	0x656C696678646876
79	uint8_t		creator[512];
80};
81
82struct vhdx_header {
83	uint32_t	signature;
84#define	VHDX_HEADER_SIGNATURE		0x64616568
85	uint32_t	checksum;
86	uint64_t	sequence_number;
87	mkimg_uuid_t	file_write_guid;
88	mkimg_uuid_t	data_write_guid;
89	mkimg_uuid_t	log_guid;
90	uint16_t	log_version;
91	uint16_t	version;
92	uint32_t	log_length;
93	uint64_t	log_offset;
94	uint8_t		_reserved[4016];
95};
96
97struct vhdx_region_table_header {
98	uint32_t	signature;
99#define	VHDX_REGION_TABLE_HEADER_SIGNATURE	0x69676572
100	uint32_t	checksum;
101	uint32_t	entry_count;
102	uint32_t	_reserved;
103};
104
105struct vhdx_region_table_entry {
106	mkimg_uuid_t	guid;
107	uint64_t	file_offset;
108	uint32_t	length;
109	uint32_t	required;
110};
111
112struct vhdx_metadata_table_header {
113	uint64_t	signature;
114#define	VHDX_METADATA_TABLE_HEADER_SIGNATURE		0x617461646174656D
115	uint16_t	_reserved;
116	uint16_t	entry_count;
117	uint8_t		_reserved2[20];
118};
119
120struct vhdx_metadata_table_entry {
121	mkimg_uuid_t	item_id;
122	uint32_t	offset;
123	uint32_t	length;
124	uint32_t	flags;
125	uint32_t	_reserved;
126};
127
128#define CRC32C(c, d) (c = (c>>8) ^ crc_c[(c^(d))&0xFF])
129
130static uint32_t crc_c[256] = {
131	0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4,
132	0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB,
133	0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B,
134	0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24,
135	0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B,
136	0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384,
137	0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54,
138	0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B,
139	0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A,
140	0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35,
141	0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5,
142	0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA,
143	0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45,
144	0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A,
145	0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A,
146	0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595,
147	0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48,
148	0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957,
149	0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687,
150	0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198,
151	0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927,
152	0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38,
153	0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8,
154	0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7,
155	0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096,
156	0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789,
157	0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859,
158	0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46,
159	0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9,
160	0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6,
161	0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36,
162	0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829,
163	0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C,
164	0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93,
165	0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043,
166	0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C,
167	0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3,
168	0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC,
169	0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C,
170	0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033,
171	0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652,
172	0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D,
173	0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D,
174	0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982,
175	0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D,
176	0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622,
177	0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2,
178	0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED,
179	0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530,
180	0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F,
181	0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF,
182	0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0,
183	0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F,
184	0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540,
185	0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90,
186	0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F,
187	0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE,
188	0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1,
189	0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321,
190	0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E,
191	0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81,
192	0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E,
193	0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E,
194	0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351
195};
196
197static uint32_t
198crc32c(const void *data, uint32_t len)
199{
200	uint32_t i, crc;
201	const uint8_t *buf = (const uint8_t *)data;
202
203	crc = ~0;
204	for (i = 0; i < len; i++)
205		CRC32C(crc, buf[i]);
206	crc = ~crc;
207	return crc;
208}
209
210static int
211vhdx_resize(lba_t imgsz)
212{
213	uint64_t imagesz;
214
215	imagesz = imgsz * secsz;
216	imagesz = (imagesz + PAYLOAD_BLOCK_SIZE - 1) & ~(PAYLOAD_BLOCK_SIZE - 1);
217	return (image_set_size(imagesz / secsz));
218}
219
220static int
221vhdx_write_and_pad(int fd, const void *data, size_t data_size, size_t align)
222{
223	size_t pad_size;
224
225	if (sparse_write(fd, data, data_size) < 0)
226		return (errno);
227
228	if (data_size % align == 0)
229		return (0);
230
231	pad_size = align - (data_size % align);
232	return  image_copyout_zeroes(fd, pad_size);
233}
234
235static int
236vhdx_write_headers(int fd)
237{
238	int error;
239	struct vhdx_header header;
240	uint32_t checksum;
241
242	/* Write header 1 */
243	memset(&header, 0, sizeof(header));
244	le32enc(&header.signature, VHDX_HEADER_SIGNATURE);
245	le32enc(&header.sequence_number, 0);
246	le16enc(&header.log_version, 0);
247	le16enc(&header.version, 1);
248	le32enc(&header.log_length, SIZE_1MB);
249	le64enc(&header.log_offset, SIZE_1MB);
250	checksum = crc32c(&header, sizeof(header));
251	le32enc(&header.checksum, checksum);
252	error = vhdx_write_and_pad(fd, &header, sizeof(header), SIZE_64KB);
253	if (error)
254		return (error);
255
256	/* Write header 2, and make it active */
257	le32enc(&header.sequence_number, 1);
258	header.checksum = 0;
259	checksum = crc32c(&header, sizeof(header));
260	le32enc(&header.checksum, checksum);
261	return vhdx_write_and_pad(fd, &header, sizeof(header), SIZE_64KB);
262}
263
264static int
265vhdx_write_region_tables(int fd)
266{
267	int error;
268	uint8_t *region_table;
269	struct vhdx_region_table_header header;
270	struct vhdx_region_table_entry entry;
271	uint32_t checksum;
272
273	region_table = malloc(SIZE_64KB);
274	if (region_table == NULL)
275		return errno;
276	memset(region_table, 0, SIZE_64KB);
277
278	memset(&header, 0, sizeof(header));
279	le32enc(&header.signature, VHDX_REGION_TABLE_HEADER_SIGNATURE);
280	le32enc(&header.entry_count, 2);
281	memcpy(region_table, &header, sizeof(header));
282
283	/* Metadata region entry */
284	mkimg_uuid_enc(&entry.guid, &vhdx_metadata_guid);
285	le64enc(&entry.file_offset, 2*SIZE_1MB);
286	le64enc(&entry.length, SIZE_1MB);
287	memcpy(region_table + sizeof(header),
288	    &entry, sizeof(entry));
289
290	/* BAT region entry */
291	mkimg_uuid_enc(&entry.guid, &vhdx_bat_guid);
292	le64enc(&entry.file_offset, 3*SIZE_1MB);
293	le64enc(&entry.length, SIZE_1MB);
294	memcpy(region_table + sizeof(header) + sizeof(entry),
295	    &entry, sizeof(entry));
296
297	checksum = crc32c(region_table, SIZE_64KB);
298	le32enc(region_table + 4, checksum);
299
300	/* Region Table 1 */
301	if (sparse_write(fd, region_table, SIZE_64KB) < 0) {
302		error = errno;
303		free(region_table);
304		return error;
305	}
306
307	/* Region Table 2 */
308	if (sparse_write(fd, region_table, SIZE_64KB) < 0) {
309		error = errno;
310		free(region_table);
311		return error;
312	}
313
314	free(region_table);
315	return (0);
316}
317
318static int
319vhdx_write_metadata(int fd, uint64_t image_size)
320{
321	int error;
322	uint8_t *metadata;
323	struct vhdx_metadata_table_header header;
324	struct vhdx_metadata_table_entry entry;
325	int header_ptr, data_ptr;
326	mkimg_uuid_t id;
327
328	metadata = malloc(SIZE_1MB);
329	if (metadata == NULL)
330		return errno;
331	memset(metadata, 0, SIZE_1MB);
332
333	memset(&header, 0, sizeof(header));
334
335	le64enc(&header.signature, VHDX_METADATA_TABLE_HEADER_SIGNATURE);
336	le16enc(&header.entry_count, 5);
337	memcpy(metadata, &header, sizeof(header));
338	header_ptr = sizeof(header);
339	data_ptr = SIZE_64KB;
340
341	/* File parameters */
342	mkimg_uuid_enc(&entry.item_id, &vhdx_meta_file_parameters_guid);
343	le32enc(&entry.offset, data_ptr);
344	le32enc(&entry.length, 8);
345	le32enc(&entry.flags, META_IS_REQUIRED);
346	memcpy(metadata + header_ptr, &entry, sizeof(entry));
347	header_ptr += sizeof(entry);
348	le32enc(metadata + data_ptr, PAYLOAD_BLOCK_SIZE);
349	data_ptr += 4;
350	le32enc(metadata + data_ptr, 0);
351	data_ptr += 4;
352
353	/* Virtual Disk Size */
354	mkimg_uuid_enc(&entry.item_id, &vhdx_meta_vdisk_size_guid);
355	le32enc(&entry.offset, data_ptr);
356	le32enc(&entry.length, 8);
357	le32enc(&entry.flags, META_IS_REQUIRED | META_IS_VIRTUAL_DISK);
358	memcpy(metadata + header_ptr, &entry, sizeof(entry));
359	header_ptr += sizeof(entry);
360	le64enc(metadata + data_ptr, image_size);
361	data_ptr += 8;
362
363	/* Virtual Disk ID */
364	mkimg_uuid_enc(&entry.item_id, &vhdx_meta_vdisk_id_guid);
365	le32enc(&entry.offset, data_ptr);
366	le32enc(&entry.length, 16);
367	le32enc(&entry.flags, META_IS_REQUIRED | META_IS_VIRTUAL_DISK);
368	memcpy(metadata + header_ptr, &entry, sizeof(entry));
369	header_ptr += sizeof(entry);
370	mkimg_uuid(&id);
371	mkimg_uuid_enc(metadata + data_ptr, &id);
372	data_ptr += 16;
373
374	/* Logical Sector Size*/
375	mkimg_uuid_enc(&entry.item_id, &vhdx_meta_logical_ssize_guid);
376	le32enc(&entry.offset, data_ptr);
377	le32enc(&entry.length, 4);
378	le32enc(&entry.flags, META_IS_REQUIRED | META_IS_VIRTUAL_DISK);
379	memcpy(metadata + header_ptr, &entry, sizeof(entry));
380	header_ptr += sizeof(entry);
381	le32enc(metadata + data_ptr, secsz);
382	data_ptr += 4;
383
384	/* Physical Sector Size*/
385	mkimg_uuid_enc(&entry.item_id, &vhdx_meta_phys_ssize_guid);
386	le32enc(&entry.offset, data_ptr);
387	le32enc(&entry.length, 4);
388	le32enc(&entry.flags, META_IS_REQUIRED | META_IS_VIRTUAL_DISK);
389	memcpy(metadata + header_ptr, &entry, sizeof(entry));
390	header_ptr += sizeof(entry);
391	le32enc(metadata + data_ptr, blksz);
392	data_ptr += 4;
393
394	if (sparse_write(fd, metadata, SIZE_1MB) < 0) {
395		error = errno;
396		free(metadata);
397		return error;
398	}
399
400	free(metadata);
401	return (0);
402}
403
404static int
405vhdx_write_bat(int fd, uint64_t image_size)
406{
407	int error;
408	uint8_t *bat;
409	int chunk_ratio;
410	uint64_t bat_size, data_block_count, total_bat_entries;
411	uint64_t idx, payload_offset, bat_ptr;
412
413	bat = malloc(SIZE_1MB);
414	if (bat == NULL)
415		return errno;
416	memset(bat, 0, SIZE_1MB);
417
418	chunk_ratio = ((1024*1024*8ULL) * secsz) / PAYLOAD_BLOCK_SIZE;
419	data_block_count = (image_size + PAYLOAD_BLOCK_SIZE - 1) / PAYLOAD_BLOCK_SIZE;
420	total_bat_entries = data_block_count + (data_block_count - 1)/chunk_ratio;
421	bat_size = total_bat_entries * 8;
422	/* round it up to 1Mb */
423	bat_size = (bat_size + SIZE_1MB - 1) & ~(SIZE_1MB - 1);
424
425	/*
426	 * Offset to the first payload block
427	 * 1Mb of header + 1Mb of log + 1Mb of metadata + XMb BAT
428	 */
429	payload_offset = 3 + (bat_size / SIZE_1MB);
430	bat_ptr = 0;
431	for (idx = 0; idx < data_block_count; idx++) {
432		le64enc(bat + bat_ptr,
433		    BAT_ENTRY(payload_offset, PAYLOAD_BLOCK_FULLY_PRESENT));
434		bat_ptr += 8;
435		payload_offset += (PAYLOAD_BLOCK_SIZE / SIZE_1MB);
436
437		/* Flush the BAT buffer if required */
438		if (bat_ptr == SIZE_1MB) {
439			if (sparse_write(fd, bat, SIZE_1MB) < 0) {
440				error = errno;
441				free(bat);
442				return error;
443			}
444			memset(bat, 0, SIZE_1MB);
445			bat_ptr = 0;
446		}
447
448		if (((idx + 1) % chunk_ratio) == 0 &&
449		    (idx != data_block_count - 1)) {
450			le64enc(bat + bat_ptr,
451			    BAT_ENTRY(0, SB_BLOCK_NOT_PRESENT));
452			bat_ptr += 8;
453
454			/* Flush the BAT buffer if required */
455			if (bat_ptr == SIZE_1MB) {
456				if (sparse_write(fd, bat, SIZE_1MB) < 0) {
457					error = errno;
458					free(bat);
459					return error;
460				}
461				memset(bat, 0, SIZE_1MB);
462				bat_ptr = 0;
463			}
464		}
465	}
466
467	if (bat_ptr != 0) {
468		if (sparse_write(fd, bat, SIZE_1MB) < 0) {
469			error = errno;
470			free(bat);
471			return error;
472		}
473	}
474
475	free(bat);
476	return (0);
477}
478
479static int
480vhdx_write(int fd)
481{
482	int error;
483	uint64_t imgsz, rawsz;
484	struct vhdx_filetype_identifier identifier;
485
486	rawsz = image_get_size() * secsz;
487	imgsz = (rawsz + PAYLOAD_BLOCK_SIZE - 1) & ~(PAYLOAD_BLOCK_SIZE - 1);
488
489	memset(&identifier, 0, sizeof(identifier));
490	le64enc(&identifier.signature, VHDX_FILETYPE_ID_SIGNATURE);
491	error = vhdx_write_and_pad(fd, &identifier, sizeof(identifier), SIZE_64KB);
492	if (error)
493		return (error);
494
495	error = vhdx_write_headers(fd);
496	if (error)
497		return (error);
498
499
500	error = vhdx_write_region_tables(fd);
501	if (error)
502		return (error);
503
504	/* Reserved area */
505	error = image_copyout_zeroes(fd, SIZE_1MB - 5*SIZE_64KB);
506
507	/* Log */
508	error = image_copyout_zeroes(fd, SIZE_1MB);
509	if (error)
510		return (error);
511
512	error = vhdx_write_metadata(fd, imgsz);
513	if (error)
514		return (error);
515
516	error = vhdx_write_bat(fd, imgsz);
517	if (error)
518		return (error);
519
520	error = image_copyout(fd);
521	if (error)
522		return (error);
523
524	return (0);
525}
526
527static struct mkimg_format vhdx_format = {
528	.name = "vhdx",
529	.description = "Virtual Hard Disk, version 2",
530	.resize = vhdx_resize,
531	.write = vhdx_write,
532};
533
534FORMAT_DEFINE(vhdx_format);
535