vhd.c revision 284773
1/*- 2 * Copyright (c) 2014, 2015 Marcel Moolenaar 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 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. 13 * 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 24 * SUCH DAMAGE. 25 */ 26 27#include <sys/cdefs.h> 28__FBSDID("$FreeBSD: stable/10/usr.bin/mkimg/vhd.c 284773 2015-06-24 18:40:34Z marcel $"); 29 30#include <sys/types.h> 31#include <sys/endian.h> 32#include <sys/errno.h> 33#include <stdlib.h> 34#include <string.h> 35#include <time.h> 36#include <unistd.h> 37#include <uuid.h> 38 39#include "image.h" 40#include "format.h" 41#include "mkimg.h" 42 43#ifndef __has_extension 44#define __has_extension(x) 0 45#endif 46 47/* 48 * General notes: 49 * o File is in network byte order. 50 * o The timestamp is seconds since 1/1/2000 12:00:00 AM UTC 51 * 52 * This file is divided in 3 parts: 53 * 1. Common definitions 54 * 2. Dynamic VHD support 55 * 3. Fixed VHD support 56 */ 57 58/* 59 * PART 1: Common definitions 60 */ 61 62#define VHD_SECTOR_SIZE 512 63#define VHD_BLOCK_SIZE (4096 * VHD_SECTOR_SIZE) /* 2MB blocks */ 64 65struct vhd_geom { 66 uint16_t cylinders; 67 uint8_t heads; 68 uint8_t sectors; 69}; 70 71struct vhd_footer { 72 uint64_t cookie; 73#define VHD_FOOTER_COOKIE 0x636f6e6563746978 74 uint32_t features; 75#define VHD_FEATURES_TEMPORARY 0x01 76#define VHD_FEATURES_RESERVED 0x02 77 uint32_t version; 78#define VHD_VERSION 0x00010000 79 uint64_t data_offset; 80 uint32_t timestamp; 81 uint32_t creator_tool; 82#define VHD_CREATOR_TOOL 0x2a696d67 /* FreeBSD mkimg */ 83 uint32_t creator_version; 84#define VHD_CREATOR_VERSION 0x00020000 85 uint32_t creator_os; 86#define VHD_CREATOR_OS 0x5769326b /* Wi2k */ 87 uint64_t original_size; 88 uint64_t current_size; 89 struct vhd_geom geometry; 90 uint32_t disk_type; 91#define VHD_DISK_TYPE_FIXED 2 92#define VHD_DISK_TYPE_DYNAMIC 3 93#define VHD_DISK_TYPE_DIFF 4 94 uint32_t checksum; 95 uuid_t id; 96 uint8_t saved_state; 97 uint8_t _reserved[427]; 98}; 99#if __has_extension(c_static_assert) 100_Static_assert(sizeof(struct vhd_footer) == VHD_SECTOR_SIZE, 101 "Wrong size for footer"); 102#endif 103 104static uint32_t 105vhd_checksum(void *buf, size_t sz) 106{ 107 uint8_t *p = buf; 108 uint32_t sum; 109 size_t ofs; 110 111 sum = 0; 112 for (ofs = 0; ofs < sz; ofs++) 113 sum += p[ofs]; 114 return (~sum); 115} 116 117static void 118vhd_geometry(uint64_t image_size, struct vhd_geom *geom) 119{ 120 lba_t imgsz; 121 long cth; 122 123 imgsz = image_size / VHD_SECTOR_SIZE; 124 125 /* Respect command line options if possible. */ 126 if (nheads > 1 && nheads < 256 && 127 nsecs > 1 && nsecs < 256 && 128 ncyls < 65536) { 129 geom->cylinders = (ncyls != 0) ? ncyls : 130 imgsz / (nheads * nsecs); 131 geom->heads = nheads; 132 geom->sectors = nsecs; 133 return; 134 } 135 136 if (imgsz > 65536 * 16 * 255) 137 imgsz = 65536 * 16 * 255; 138 if (imgsz >= 65535 * 16 * 63) { 139 geom->cylinders = imgsz / (16 * 255); 140 geom->heads = 16; 141 geom->sectors = 255; 142 return; 143 } 144 geom->sectors = 17; 145 cth = imgsz / 17; 146 geom->heads = (cth + 1023) / 1024; 147 if (geom->heads < 4) 148 geom->heads = 4; 149 if (cth >= (geom->heads * 1024) || geom->heads > 16) { 150 geom->heads = 16; 151 geom->sectors = 31; 152 cth = imgsz / 31; 153 } 154 if (cth >= (geom->heads * 1024)) { 155 geom->heads = 16; 156 geom->sectors = 63; 157 cth = imgsz / 63; 158 } 159 geom->cylinders = cth / geom->heads; 160} 161 162static uint32_t 163vhd_timestamp(void) 164{ 165 time_t t; 166 167 if (!unit_testing) { 168 t = time(NULL); 169 return (t - 0x386d4380); 170 } 171 172 return (0x01234567); 173} 174 175static void 176vhd_uuid_enc(void *buf, const uuid_t *uuid) 177{ 178 uint8_t *p = buf; 179 int i; 180 181 be32enc(p, uuid->time_low); 182 be16enc(p + 4, uuid->time_mid); 183 be16enc(p + 6, uuid->time_hi_and_version); 184 p[8] = uuid->clock_seq_hi_and_reserved; 185 p[9] = uuid->clock_seq_low; 186 for (i = 0; i < _UUID_NODE_LEN; i++) 187 p[10 + i] = uuid->node[i]; 188} 189 190static void 191vhd_make_footer(struct vhd_footer *footer, uint64_t image_size, 192 uint32_t disk_type, uint64_t data_offset) 193{ 194 uuid_t id; 195 196 memset(footer, 0, sizeof(*footer)); 197 be64enc(&footer->cookie, VHD_FOOTER_COOKIE); 198 be32enc(&footer->features, VHD_FEATURES_RESERVED); 199 be32enc(&footer->version, VHD_VERSION); 200 be64enc(&footer->data_offset, data_offset); 201 be32enc(&footer->timestamp, vhd_timestamp()); 202 be32enc(&footer->creator_tool, VHD_CREATOR_TOOL); 203 be32enc(&footer->creator_version, VHD_CREATOR_VERSION); 204 be32enc(&footer->creator_os, VHD_CREATOR_OS); 205 be64enc(&footer->original_size, image_size); 206 be64enc(&footer->current_size, image_size); 207 vhd_geometry(image_size, &footer->geometry); 208 be16enc(&footer->geometry.cylinders, footer->geometry.cylinders); 209 be32enc(&footer->disk_type, disk_type); 210 mkimg_uuid(&id); 211 vhd_uuid_enc(&footer->id, &id); 212 be32enc(&footer->checksum, vhd_checksum(footer, sizeof(*footer))); 213} 214 215/* 216 * PART 2: Dynamic VHD support 217 * 218 * Notes: 219 * o File layout: 220 * copy of disk footer 221 * dynamic disk header 222 * block allocation table (BAT) 223 * data blocks 224 * disk footer 225 */ 226 227struct vhd_dyn_header { 228 uint64_t cookie; 229#define VHD_HEADER_COOKIE 0x6378737061727365 230 uint64_t data_offset; 231 uint64_t table_offset; 232 uint32_t version; 233 uint32_t max_entries; 234 uint32_t block_size; 235 uint32_t checksum; 236 uuid_t parent_id; 237 uint32_t parent_timestamp; 238 char _reserved1[4]; 239 uint16_t parent_name[256]; /* UTF-16 */ 240 struct { 241 uint32_t code; 242 uint32_t data_space; 243 uint32_t data_length; 244 uint32_t _reserved; 245 uint64_t data_offset; 246 } parent_locator[8]; 247 char _reserved2[256]; 248}; 249#if __has_extension(c_static_assert) 250_Static_assert(sizeof(struct vhd_dyn_header) == VHD_SECTOR_SIZE * 2, 251 "Wrong size for header"); 252#endif 253 254static int 255vhd_dyn_resize(lba_t imgsz) 256{ 257 uint64_t imagesz; 258 259 imagesz = imgsz * secsz; 260 imagesz = (imagesz + VHD_BLOCK_SIZE - 1) & ~(VHD_BLOCK_SIZE - 1); 261 return (image_set_size(imagesz / secsz)); 262} 263 264static int 265vhd_dyn_write(int fd) 266{ 267 struct vhd_footer footer; 268 struct vhd_dyn_header header; 269 uint64_t imgsz; 270 lba_t blk, blkcnt, nblks; 271 uint32_t *bat; 272 void *bitmap; 273 size_t batsz; 274 uint32_t sector; 275 int bat_entries, error, entry; 276 277 imgsz = image_get_size() * secsz; 278 bat_entries = imgsz / VHD_BLOCK_SIZE; 279 280 vhd_make_footer(&footer, imgsz, VHD_DISK_TYPE_DYNAMIC, sizeof(footer)); 281 if (sparse_write(fd, &footer, sizeof(footer)) < 0) 282 return (errno); 283 284 memset(&header, 0, sizeof(header)); 285 be64enc(&header.cookie, VHD_HEADER_COOKIE); 286 be64enc(&header.data_offset, ~0ULL); 287 be64enc(&header.table_offset, sizeof(footer) + sizeof(header)); 288 be32enc(&header.version, VHD_VERSION); 289 be32enc(&header.max_entries, bat_entries); 290 be32enc(&header.block_size, VHD_BLOCK_SIZE); 291 be32enc(&header.checksum, vhd_checksum(&header, sizeof(header))); 292 if (sparse_write(fd, &header, sizeof(header)) < 0) 293 return (errno); 294 295 batsz = bat_entries * sizeof(uint32_t); 296 batsz = (batsz + VHD_SECTOR_SIZE - 1) & ~(VHD_SECTOR_SIZE - 1); 297 bat = malloc(batsz); 298 if (bat == NULL) 299 return (errno); 300 memset(bat, 0xff, batsz); 301 blkcnt = VHD_BLOCK_SIZE / secsz; 302 sector = (sizeof(footer) + sizeof(header) + batsz) / VHD_SECTOR_SIZE; 303 for (entry = 0; entry < bat_entries; entry++) { 304 blk = entry * blkcnt; 305 if (image_data(blk, blkcnt)) { 306 be32enc(&bat[entry], sector); 307 sector += (VHD_BLOCK_SIZE / VHD_SECTOR_SIZE) + 1; 308 } 309 } 310 if (sparse_write(fd, bat, batsz) < 0) { 311 free(bat); 312 return (errno); 313 } 314 free(bat); 315 316 bitmap = malloc(VHD_SECTOR_SIZE); 317 if (bitmap == NULL) 318 return (errno); 319 memset(bitmap, 0xff, VHD_SECTOR_SIZE); 320 321 blk = 0; 322 blkcnt = VHD_BLOCK_SIZE / secsz; 323 error = 0; 324 nblks = image_get_size(); 325 while (blk < nblks) { 326 if (!image_data(blk, blkcnt)) { 327 blk += blkcnt; 328 continue; 329 } 330 if (sparse_write(fd, bitmap, VHD_SECTOR_SIZE) < 0) { 331 error = errno; 332 break; 333 } 334 error = image_copyout_region(fd, blk, blkcnt); 335 if (error) 336 break; 337 blk += blkcnt; 338 } 339 free(bitmap); 340 if (blk != nblks) 341 return (error); 342 343 if (sparse_write(fd, &footer, sizeof(footer)) < 0) 344 return (errno); 345 346 return (0); 347} 348 349static struct mkimg_format vhd_dyn_format = { 350 .name = "vhd", 351 .description = "Virtual Hard Disk", 352 .resize = vhd_dyn_resize, 353 .write = vhd_dyn_write, 354}; 355 356FORMAT_DEFINE(vhd_dyn_format); 357 358/* 359 * PART 3: Fixed VHD 360 */ 361 362static int 363vhd_fix_resize(lba_t imgsz) 364{ 365 struct vhd_geom geom; 366 int64_t imagesz; 367 368 /* 369 * Round the image size to the pre-determined geometry that 370 * matches the image size. This circular dependency implies 371 * that we need to loop to handle boundary conditions. 372 */ 373 imgsz *= secsz; 374 imagesz = imgsz; 375 while (1) { 376 vhd_geometry(imagesz, &geom); 377 imagesz = (int64_t)geom.cylinders * geom.heads * 378 geom.sectors * VHD_SECTOR_SIZE; 379 if (imagesz >= imgsz) 380 break; 381 imagesz += geom.heads * geom.sectors * VHD_SECTOR_SIZE; 382 } 383 /* 384 * Azure demands that images are a whole number of megabytes. 385 */ 386 imagesz = (imagesz + 0xfffffULL) & ~0xfffffULL; 387 return (image_set_size(imagesz / secsz)); 388} 389 390static int 391vhd_fix_write(int fd) 392{ 393 struct vhd_footer footer; 394 uint64_t imgsz; 395 int error; 396 397 error = image_copyout(fd); 398 if (!error) { 399 imgsz = image_get_size() * secsz; 400 vhd_make_footer(&footer, imgsz, VHD_DISK_TYPE_FIXED, ~0ULL); 401 if (sparse_write(fd, &footer, sizeof(footer)) < 0) 402 error = errno; 403 } 404 return (error); 405} 406 407static struct mkimg_format vhd_fix_format = { 408 .name = "vhdf", 409 .description = "Fixed Virtual Hard Disk", 410 .resize = vhd_fix_resize, 411 .write = vhd_fix_write, 412}; 413 414FORMAT_DEFINE(vhd_fix_format); 415