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