/*- * Copyright (c) 2014, 2015 Marcel Moolenaar * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include "endian.h" #include "image.h" #include "format.h" #include "mkimg.h" #ifndef __has_extension #define __has_extension(x) 0 #endif /* * General notes: * o File is in network byte order. * o The timestamp is seconds since 1/1/2000 12:00:00 AM UTC * * This file is divided in 3 parts: * 1. Common definitions * 2. Dynamic VHD support * 3. Fixed VHD support */ /* * PART 1: Common definitions */ #define VHD_SECTOR_SIZE 512 #define VHD_BLOCK_SIZE (4096 * VHD_SECTOR_SIZE) /* 2MB blocks */ struct vhd_geom { uint16_t cylinders; uint8_t heads; uint8_t sectors; }; struct vhd_footer { uint64_t cookie; #define VHD_FOOTER_COOKIE 0x636f6e6563746978ULL uint32_t features; #define VHD_FEATURES_TEMPORARY 0x01 #define VHD_FEATURES_RESERVED 0x02 uint32_t version; #define VHD_VERSION 0x00010000 uint64_t data_offset; uint32_t timestamp; uint32_t creator_tool; #define VHD_CREATOR_TOOL 0x2a696d67 /* FreeBSD mkimg */ uint32_t creator_version; #define VHD_CREATOR_VERSION 0x00020000 uint32_t creator_os; #define VHD_CREATOR_OS 0x5769326b /* Wi2k */ uint64_t original_size; uint64_t current_size; struct vhd_geom geometry; uint32_t disk_type; #define VHD_DISK_TYPE_FIXED 2 #define VHD_DISK_TYPE_DYNAMIC 3 #define VHD_DISK_TYPE_DIFF 4 uint32_t checksum; mkimg_uuid_t id; uint8_t saved_state; uint8_t _reserved[427]; }; #if __has_extension(c_static_assert) _Static_assert(sizeof(struct vhd_footer) == VHD_SECTOR_SIZE, "Wrong size for footer"); #endif static uint32_t vhd_checksum(void *buf, size_t sz) { uint8_t *p = buf; uint32_t sum; size_t ofs; sum = 0; for (ofs = 0; ofs < sz; ofs++) sum += p[ofs]; return (~sum); } static void vhd_geometry(uint64_t image_size, struct vhd_geom *geom) { lba_t imgsz; long cth; imgsz = image_size / VHD_SECTOR_SIZE; /* Respect command line options if possible. */ if (nheads > 1 && nheads < 256 && nsecs > 1 && nsecs < 256 && ncyls < 65536) { geom->cylinders = (ncyls != 0) ? ncyls : imgsz / (nheads * nsecs); geom->heads = nheads; geom->sectors = nsecs; return; } if (imgsz > 65536 * 16 * 255) imgsz = 65536 * 16 * 255; if (imgsz >= 65535 * 16 * 63) { geom->cylinders = imgsz / (16 * 255); geom->heads = 16; geom->sectors = 255; return; } geom->sectors = 17; cth = imgsz / 17; geom->heads = (cth + 1023) / 1024; if (geom->heads < 4) geom->heads = 4; if (cth >= (geom->heads * 1024) || geom->heads > 16) { geom->heads = 16; geom->sectors = 31; cth = imgsz / 31; } if (cth >= (geom->heads * 1024)) { geom->heads = 16; geom->sectors = 63; cth = imgsz / 63; } geom->cylinders = cth / geom->heads; } static uint64_t vhd_resize(uint64_t origsz) { struct vhd_geom geom; uint64_t newsz; /* * Round the image size to the pre-determined geometry that * matches the image size. This circular dependency implies * that we need to loop to handle boundary conditions. * The first time, newsz equals origsz and the geometry will * typically yield a new size that's smaller. We keep adding * cylinder's worth of sectors to the new size until its * larger or equal or origsz. But during those iterations, * the geometry can change, so we need to account for that. */ newsz = origsz; while (1) { vhd_geometry(newsz, &geom); newsz = (int64_t)geom.cylinders * geom.heads * geom.sectors * VHD_SECTOR_SIZE; if (newsz >= origsz) break; newsz += geom.heads * geom.sectors * VHD_SECTOR_SIZE; } return (newsz); } static uint32_t vhd_timestamp(void) { time_t t; if (!unit_testing) { t = time(NULL); return (t - 0x386d4380); } return (0x01234567); } static void vhd_make_footer(struct vhd_footer *footer, uint64_t image_size, uint32_t disk_type, uint64_t data_offset) { mkimg_uuid_t id; memset(footer, 0, sizeof(*footer)); be64enc(&footer->cookie, VHD_FOOTER_COOKIE); be32enc(&footer->features, VHD_FEATURES_RESERVED); be32enc(&footer->version, VHD_VERSION); be64enc(&footer->data_offset, data_offset); be32enc(&footer->timestamp, vhd_timestamp()); be32enc(&footer->creator_tool, VHD_CREATOR_TOOL); be32enc(&footer->creator_version, VHD_CREATOR_VERSION); be32enc(&footer->creator_os, VHD_CREATOR_OS); be64enc(&footer->original_size, image_size); be64enc(&footer->current_size, image_size); vhd_geometry(image_size, &footer->geometry); be16enc(&footer->geometry.cylinders, footer->geometry.cylinders); be32enc(&footer->disk_type, disk_type); mkimg_uuid(&id); mkimg_uuid_enc(&footer->id, &id); be32enc(&footer->checksum, vhd_checksum(footer, sizeof(*footer))); } /* * PART 2: Dynamic VHD support * * Notes: * o File layout: * copy of disk footer * dynamic disk header * block allocation table (BAT) * data blocks * disk footer */ struct vhd_dyn_header { uint64_t cookie; #define VHD_HEADER_COOKIE 0x6378737061727365ULL uint64_t data_offset; uint64_t table_offset; uint32_t version; uint32_t max_entries; uint32_t block_size; uint32_t checksum; mkimg_uuid_t parent_id; uint32_t parent_timestamp; char _reserved1[4]; uint16_t parent_name[256]; /* UTF-16 */ struct { uint32_t code; uint32_t data_space; uint32_t data_length; uint32_t _reserved; uint64_t data_offset; } parent_locator[8]; char _reserved2[256]; }; #if __has_extension(c_static_assert) _Static_assert(sizeof(struct vhd_dyn_header) == VHD_SECTOR_SIZE * 2, "Wrong size for header"); #endif static int vhd_dyn_resize(lba_t imgsz) { uint64_t imagesz; imagesz = vhd_resize(imgsz * secsz); return (image_set_size(imagesz / secsz)); } static int vhd_dyn_write(int fd) { struct vhd_footer footer; struct vhd_dyn_header header; uint64_t imgsz, rawsz; lba_t blk, blkcnt, nblks; uint32_t *bat; void *bitmap; size_t batsz; uint32_t sector; int bat_entries, error, entry; rawsz = image_get_size() * secsz; imgsz = (rawsz + VHD_BLOCK_SIZE - 1) & ~(VHD_BLOCK_SIZE - 1); vhd_make_footer(&footer, rawsz, VHD_DISK_TYPE_DYNAMIC, sizeof(footer)); if (sparse_write(fd, &footer, sizeof(footer)) < 0) return (errno); bat_entries = imgsz / VHD_BLOCK_SIZE; memset(&header, 0, sizeof(header)); be64enc(&header.cookie, VHD_HEADER_COOKIE); be64enc(&header.data_offset, ~0ULL); be64enc(&header.table_offset, sizeof(footer) + sizeof(header)); be32enc(&header.version, VHD_VERSION); be32enc(&header.max_entries, bat_entries); be32enc(&header.block_size, VHD_BLOCK_SIZE); be32enc(&header.checksum, vhd_checksum(&header, sizeof(header))); if (sparse_write(fd, &header, sizeof(header)) < 0) return (errno); batsz = bat_entries * sizeof(uint32_t); batsz = (batsz + VHD_SECTOR_SIZE - 1) & ~(VHD_SECTOR_SIZE - 1); bat = malloc(batsz); if (bat == NULL) return (errno); memset(bat, 0xff, batsz); blkcnt = VHD_BLOCK_SIZE / secsz; sector = (sizeof(footer) + sizeof(header) + batsz) / VHD_SECTOR_SIZE; for (entry = 0; entry < bat_entries; entry++) { blk = entry * blkcnt; if (image_data(blk, blkcnt)) { be32enc(&bat[entry], sector); sector += (VHD_BLOCK_SIZE / VHD_SECTOR_SIZE) + 1; } } if (sparse_write(fd, bat, batsz) < 0) { free(bat); return (errno); } free(bat); bitmap = malloc(VHD_SECTOR_SIZE); if (bitmap == NULL) return (errno); memset(bitmap, 0xff, VHD_SECTOR_SIZE); blk = 0; blkcnt = VHD_BLOCK_SIZE / secsz; error = 0; nblks = rawsz / secsz; while (blk < nblks) { if (!image_data(blk, blkcnt)) { blk += blkcnt; continue; } if (sparse_write(fd, bitmap, VHD_SECTOR_SIZE) < 0) { error = errno; break; } /* Handle partial last block */ if (blk + blkcnt > nblks) blkcnt = nblks - blk; error = image_copyout_region(fd, blk, blkcnt); if (error) break; blk += blkcnt; } free(bitmap); if (error) return (error); error = image_copyout_zeroes(fd, imgsz - rawsz); if (error) return (error); if (sparse_write(fd, &footer, sizeof(footer)) < 0) return (errno); return (0); } static struct mkimg_format vhd_dyn_format = { .name = "vhd", .description = "Virtual Hard Disk", .resize = vhd_dyn_resize, .write = vhd_dyn_write, }; FORMAT_DEFINE(vhd_dyn_format); /* * PART 3: Fixed VHD */ static int vhd_fix_resize(lba_t imgsz) { uint64_t imagesz; imagesz = vhd_resize(imgsz * secsz); /* * Azure demands that images are a whole number of megabytes. */ imagesz = (imagesz + 0xfffffULL) & ~0xfffffULL; return (image_set_size(imagesz / secsz)); } static int vhd_fix_write(int fd) { struct vhd_footer footer; uint64_t imagesz; int error; error = image_copyout(fd); if (error) return (error); imagesz = image_get_size() * secsz; vhd_make_footer(&footer, imagesz, VHD_DISK_TYPE_FIXED, ~0ULL); error = (sparse_write(fd, &footer, sizeof(footer)) < 0) ? errno : 0; return (error); } static struct mkimg_format vhd_fix_format = { .name = "vhdf", .description = "Fixed Virtual Hard Disk", .resize = vhd_fix_resize, .write = vhd_fix_write, }; FORMAT_DEFINE(vhd_fix_format);