2 * Copyright (c) 2014, 2015 Marcel Moolenaar
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
30 #include <sys/errno.h>
42 #ifndef __has_extension
43 #define __has_extension(x) 0
48 * o File is in network byte order.
49 * o The timestamp is seconds since 1/1/2000 12:00:00 AM UTC
51 * This file is divided in 3 parts:
52 * 1. Common definitions
53 * 2. Dynamic VHD support
54 * 3. Fixed VHD support
58 * PART 1: Common definitions
61 #define VHD_SECTOR_SIZE 512
62 #define VHD_BLOCK_SIZE (4096 * VHD_SECTOR_SIZE) /* 2MB blocks */
72 #define VHD_FOOTER_COOKIE 0x636f6e6563746978ULL
74 #define VHD_FEATURES_TEMPORARY 0x01
75 #define VHD_FEATURES_RESERVED 0x02
77 #define VHD_VERSION 0x00010000
80 uint32_t creator_tool;
81 #define VHD_CREATOR_TOOL 0x2a696d67 /* FreeBSD mkimg */
82 uint32_t creator_version;
83 #define VHD_CREATOR_VERSION 0x00020000
85 #define VHD_CREATOR_OS 0x5769326b /* Wi2k */
86 uint64_t original_size;
87 uint64_t current_size;
88 struct vhd_geom geometry;
90 #define VHD_DISK_TYPE_FIXED 2
91 #define VHD_DISK_TYPE_DYNAMIC 3
92 #define VHD_DISK_TYPE_DIFF 4
96 uint8_t _reserved[427];
98 #if __has_extension(c_static_assert)
99 _Static_assert(sizeof(struct vhd_footer) == VHD_SECTOR_SIZE,
100 "Wrong size for footer");
104 vhd_checksum(void *buf, size_t sz)
111 for (ofs = 0; ofs < sz; ofs++)
117 vhd_geometry(uint64_t image_size, struct vhd_geom *geom)
122 imgsz = image_size / VHD_SECTOR_SIZE;
124 /* Respect command line options if possible. */
125 if (nheads > 1 && nheads < 256 &&
126 nsecs > 1 && nsecs < 256 &&
128 geom->cylinders = (ncyls != 0) ? ncyls :
129 imgsz / (nheads * nsecs);
130 geom->heads = nheads;
131 geom->sectors = nsecs;
135 if (imgsz > 65536 * 16 * 255)
136 imgsz = 65536 * 16 * 255;
137 if (imgsz >= 65535 * 16 * 63) {
138 geom->cylinders = imgsz / (16 * 255);
145 geom->heads = (cth + 1023) / 1024;
148 if (cth >= (geom->heads * 1024) || geom->heads > 16) {
153 if (cth >= (geom->heads * 1024)) {
158 geom->cylinders = cth / geom->heads;
162 vhd_resize(uint64_t origsz)
164 struct vhd_geom geom;
168 * Round the image size to the pre-determined geometry that
169 * matches the image size. This circular dependency implies
170 * that we need to loop to handle boundary conditions.
171 * The first time, newsz equals origsz and the geometry will
172 * typically yield a new size that's smaller. We keep adding
173 * cylinder's worth of sectors to the new size until its
174 * larger or equal or origsz. But during those iterations,
175 * the geometry can change, so we need to account for that.
179 vhd_geometry(newsz, &geom);
180 newsz = (int64_t)geom.cylinders * geom.heads *
181 geom.sectors * VHD_SECTOR_SIZE;
184 newsz += geom.heads * geom.sectors * VHD_SECTOR_SIZE;
196 return (t - 0x386d4380);
203 vhd_make_footer(struct vhd_footer *footer, uint64_t image_size,
204 uint32_t disk_type, uint64_t data_offset)
208 memset(footer, 0, sizeof(*footer));
209 be64enc(&footer->cookie, VHD_FOOTER_COOKIE);
210 be32enc(&footer->features, VHD_FEATURES_RESERVED);
211 be32enc(&footer->version, VHD_VERSION);
212 be64enc(&footer->data_offset, data_offset);
213 be32enc(&footer->timestamp, vhd_timestamp());
214 be32enc(&footer->creator_tool, VHD_CREATOR_TOOL);
215 be32enc(&footer->creator_version, VHD_CREATOR_VERSION);
216 be32enc(&footer->creator_os, VHD_CREATOR_OS);
217 be64enc(&footer->original_size, image_size);
218 be64enc(&footer->current_size, image_size);
219 vhd_geometry(image_size, &footer->geometry);
220 be16enc(&footer->geometry.cylinders, footer->geometry.cylinders);
221 be32enc(&footer->disk_type, disk_type);
223 mkimg_uuid_enc(&footer->id, &id);
224 be32enc(&footer->checksum, vhd_checksum(footer, sizeof(*footer)));
228 * PART 2: Dynamic VHD support
232 * copy of disk footer
233 * dynamic disk header
234 * block allocation table (BAT)
239 struct vhd_dyn_header {
241 #define VHD_HEADER_COOKIE 0x6378737061727365ULL
242 uint64_t data_offset;
243 uint64_t table_offset;
245 uint32_t max_entries;
248 mkimg_uuid_t parent_id;
249 uint32_t parent_timestamp;
251 uint16_t parent_name[256]; /* UTF-16 */
255 uint32_t data_length;
257 uint64_t data_offset;
259 char _reserved2[256];
261 #if __has_extension(c_static_assert)
262 _Static_assert(sizeof(struct vhd_dyn_header) == VHD_SECTOR_SIZE * 2,
263 "Wrong size for header");
267 vhd_dyn_resize(lba_t imgsz)
271 imagesz = vhd_resize(imgsz * secsz);
272 return (image_set_size(imagesz / secsz));
276 vhd_dyn_write(int fd)
278 struct vhd_footer footer;
279 struct vhd_dyn_header header;
280 uint64_t imgsz, rawsz;
281 lba_t blk, blkcnt, nblks;
286 int bat_entries, error, entry;
288 rawsz = image_get_size() * secsz;
289 imgsz = (rawsz + VHD_BLOCK_SIZE - 1) & ~(VHD_BLOCK_SIZE - 1);
291 vhd_make_footer(&footer, rawsz, VHD_DISK_TYPE_DYNAMIC, sizeof(footer));
292 if (sparse_write(fd, &footer, sizeof(footer)) < 0)
295 bat_entries = imgsz / VHD_BLOCK_SIZE;
296 memset(&header, 0, sizeof(header));
297 be64enc(&header.cookie, VHD_HEADER_COOKIE);
298 be64enc(&header.data_offset, ~0ULL);
299 be64enc(&header.table_offset, sizeof(footer) + sizeof(header));
300 be32enc(&header.version, VHD_VERSION);
301 be32enc(&header.max_entries, bat_entries);
302 be32enc(&header.block_size, VHD_BLOCK_SIZE);
303 be32enc(&header.checksum, vhd_checksum(&header, sizeof(header)));
304 if (sparse_write(fd, &header, sizeof(header)) < 0)
307 batsz = bat_entries * sizeof(uint32_t);
308 batsz = (batsz + VHD_SECTOR_SIZE - 1) & ~(VHD_SECTOR_SIZE - 1);
312 memset(bat, 0xff, batsz);
313 blkcnt = VHD_BLOCK_SIZE / secsz;
314 sector = (sizeof(footer) + sizeof(header) + batsz) / VHD_SECTOR_SIZE;
315 for (entry = 0; entry < bat_entries; entry++) {
316 blk = entry * blkcnt;
317 if (image_data(blk, blkcnt)) {
318 be32enc(&bat[entry], sector);
319 sector += (VHD_BLOCK_SIZE / VHD_SECTOR_SIZE) + 1;
322 if (sparse_write(fd, bat, batsz) < 0) {
328 bitmap = malloc(VHD_SECTOR_SIZE);
331 memset(bitmap, 0xff, VHD_SECTOR_SIZE);
334 blkcnt = VHD_BLOCK_SIZE / secsz;
336 nblks = rawsz / secsz;
337 while (blk < nblks) {
338 if (!image_data(blk, blkcnt)) {
342 if (sparse_write(fd, bitmap, VHD_SECTOR_SIZE) < 0) {
346 /* Handle partial last block */
347 if (blk + blkcnt > nblks)
348 blkcnt = nblks - blk;
349 error = image_copyout_region(fd, blk, blkcnt);
357 error = image_copyout_zeroes(fd, imgsz - rawsz);
360 if (sparse_write(fd, &footer, sizeof(footer)) < 0)
366 static struct mkimg_format vhd_dyn_format = {
368 .description = "Virtual Hard Disk",
369 .resize = vhd_dyn_resize,
370 .write = vhd_dyn_write,
373 FORMAT_DEFINE(vhd_dyn_format);
380 vhd_fix_resize(lba_t imgsz)
384 imagesz = vhd_resize(imgsz * secsz);
386 * Azure demands that images are a whole number of megabytes.
388 imagesz = (imagesz + 0xfffffULL) & ~0xfffffULL;
389 return (image_set_size(imagesz / secsz));
393 vhd_fix_write(int fd)
395 struct vhd_footer footer;
399 error = image_copyout(fd);
403 imagesz = image_get_size() * secsz;
404 vhd_make_footer(&footer, imagesz, VHD_DISK_TYPE_FIXED, ~0ULL);
405 error = (sparse_write(fd, &footer, sizeof(footer)) < 0) ? errno : 0;
409 static struct mkimg_format vhd_fix_format = {
411 .description = "Fixed Virtual Hard Disk",
412 .resize = vhd_fix_resize,
413 .write = vhd_fix_write,
416 FORMAT_DEFINE(vhd_fix_format);