vhd.c revision 269177
1/*- 2 * Copyright (c) 2014 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 269177 2014-07-28 02:07:16Z 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_footer { 66 uint64_t cookie; 67#define VHD_FOOTER_COOKIE 0x636f6e6563746978 68 uint32_t features; 69#define VHD_FEATURES_TEMPORARY 0x01 70#define VHD_FEATURES_RESERVED 0x02 71 uint32_t version; 72#define VHD_VERSION 0x00010000 73 uint64_t data_offset; 74 uint32_t timestamp; 75 uint32_t creator_tool; 76#define VHD_CREATOR_TOOL 0x2a696d67 /* FreeBSD mkimg */ 77 uint32_t creator_version; 78#define VHD_CREATOR_VERSION 0x00010000 79 uint32_t creator_os; 80#define VHD_CREATOR_OS 0x46425344 81 uint64_t original_size; 82 uint64_t current_size; 83 uint16_t cylinders; 84 uint8_t heads; 85 uint8_t sectors; 86 uint32_t disk_type; 87#define VHD_DISK_TYPE_FIXED 2 88#define VHD_DISK_TYPE_DYNAMIC 3 89#define VHD_DISK_TYPE_DIFF 4 90 uint32_t checksum; 91 uuid_t id; 92 uint8_t saved_state; 93 uint8_t _reserved[427]; 94}; 95#if __has_extension(c_static_assert) 96_Static_assert(sizeof(struct vhd_footer) == VHD_SECTOR_SIZE, 97 "Wrong size for footer"); 98#endif 99 100static uint32_t 101vhd_checksum(void *buf, size_t sz) 102{ 103 uint8_t *p = buf; 104 uint32_t sum; 105 size_t ofs; 106 107 sum = 0; 108 for (ofs = 0; ofs < sz; ofs++) 109 sum += p[ofs]; 110 return (~sum); 111} 112 113static void 114vhd_geometry(struct vhd_footer *footer, uint64_t image_size) 115{ 116 lba_t imgsz; 117 long cth; 118 119 /* Respect command line options if possible. */ 120 if (nheads > 1 && nheads < 256 && 121 nsecs > 1 && nsecs < 256 && 122 ncyls < 65536) { 123 be16enc(&footer->cylinders, ncyls); 124 footer->heads = nheads; 125 footer->sectors = nsecs; 126 return; 127 } 128 129 imgsz = image_size / VHD_SECTOR_SIZE; 130 if (imgsz > 65536 * 16 * 255) 131 imgsz = 65536 * 16 * 255; 132 if (imgsz >= 65535 * 16 * 63) { 133 be16enc(&footer->cylinders, imgsz / (16 * 255)); 134 footer->heads = 16; 135 footer->sectors = 255; 136 return; 137 } 138 footer->sectors = 17; 139 cth = imgsz / 17; 140 footer->heads = (cth + 1023) / 1024; 141 if (footer->heads < 4) 142 footer->heads = 4; 143 if (cth >= (footer->heads * 1024) || footer->heads > 16) { 144 footer->heads = 16; 145 footer->sectors = 31; 146 cth = imgsz / 31; 147 } 148 if (cth >= (footer->heads * 1024)) { 149 footer->heads = 16; 150 footer->sectors = 63; 151 cth = imgsz / 63; 152 } 153 be16enc(&footer->cylinders, cth / footer->heads); 154} 155 156static uint32_t 157vhd_timestamp(void) 158{ 159 time_t t; 160 161 if (!unit_testing) { 162 t = time(NULL); 163 return (t - 0x386d4380); 164 } 165 166 return (0x01234567); 167} 168 169static void 170vhd_uuid_enc(void *buf, const uuid_t *uuid) 171{ 172 uint8_t *p = buf; 173 int i; 174 175 be32enc(p, uuid->time_low); 176 be16enc(p + 4, uuid->time_mid); 177 be16enc(p + 6, uuid->time_hi_and_version); 178 p[8] = uuid->clock_seq_hi_and_reserved; 179 p[9] = uuid->clock_seq_low; 180 for (i = 0; i < _UUID_NODE_LEN; i++) 181 p[10 + i] = uuid->node[i]; 182} 183 184static void 185vhd_make_footer(struct vhd_footer *footer, uint64_t image_size, 186 uint32_t disk_type, uint64_t data_offset) 187{ 188 uuid_t id; 189 190 memset(footer, 0, sizeof(*footer)); 191 be64enc(&footer->cookie, VHD_FOOTER_COOKIE); 192 be32enc(&footer->features, VHD_FEATURES_RESERVED); 193 be32enc(&footer->version, VHD_VERSION); 194 be64enc(&footer->data_offset, data_offset); 195 be32enc(&footer->timestamp, vhd_timestamp()); 196 be32enc(&footer->creator_tool, VHD_CREATOR_TOOL); 197 be32enc(&footer->creator_version, VHD_CREATOR_VERSION); 198 be32enc(&footer->creator_os, VHD_CREATOR_OS); 199 be64enc(&footer->original_size, image_size); 200 be64enc(&footer->current_size, image_size); 201 vhd_geometry(footer, image_size); 202 be32enc(&footer->disk_type, disk_type); 203 mkimg_uuid(&id); 204 vhd_uuid_enc(&footer->id, &id); 205 be32enc(&footer->checksum, vhd_checksum(footer, sizeof(*footer))); 206} 207 208/* 209 * We round the image size to 2MB for both the dynamic and 210 * fixed VHD formats. For dynamic VHD, this is needed to 211 * have the image size be a multiple of the grain size. For 212 * fixed VHD this is not really needed, but makes sure that 213 * it's easy to convert from fixed VHD to dynamic VHD. 214 */ 215static int 216vhd_resize(lba_t imgsz) 217{ 218 uint64_t imagesz; 219 220 imagesz = imgsz * secsz; 221 imagesz = (imagesz + VHD_BLOCK_SIZE - 1) & ~(VHD_BLOCK_SIZE - 1); 222 return (image_set_size(imagesz / secsz)); 223} 224 225/* 226 * PART 2: Dynamic VHD support 227 * 228 * Notes: 229 * o File layout: 230 * copy of disk footer 231 * dynamic disk header 232 * block allocation table (BAT) 233 * data blocks 234 * disk footer 235 */ 236 237struct vhd_dyn_header { 238 uint64_t cookie; 239#define VHD_HEADER_COOKIE 0x6378737061727365 240 uint64_t data_offset; 241 uint64_t table_offset; 242 uint32_t version; 243 uint32_t max_entries; 244 uint32_t block_size; 245 uint32_t checksum; 246 uuid_t parent_id; 247 uint32_t parent_timestamp; 248 char _reserved1[4]; 249 uint16_t parent_name[256]; /* UTF-16 */ 250 struct { 251 uint32_t code; 252 uint32_t data_space; 253 uint32_t data_length; 254 uint32_t _reserved; 255 uint64_t data_offset; 256 } parent_locator[8]; 257 char _reserved2[256]; 258}; 259#if __has_extension(c_static_assert) 260_Static_assert(sizeof(struct vhd_dyn_header) == VHD_SECTOR_SIZE * 2, 261 "Wrong size for header"); 262#endif 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 nblks = image_get_size(); 324 while (blk < nblks) { 325 if (!image_data(blk, blkcnt)) { 326 blk += blkcnt; 327 continue; 328 } 329 if (sparse_write(fd, bitmap, VHD_SECTOR_SIZE) < 0) { 330 error = errno; 331 break; 332 } 333 error = image_copyout_region(fd, blk, blkcnt); 334 if (error) 335 break; 336 blk += blkcnt; 337 } 338 free(bitmap); 339 if (blk != nblks) 340 return (error); 341 342 if (sparse_write(fd, &footer, sizeof(footer)) < 0) 343 return (errno); 344 345 return (0); 346} 347 348static struct mkimg_format vhd_dyn_format = { 349 .name = "vhd", 350 .description = "Virtual Hard Disk", 351 .resize = vhd_resize, 352 .write = vhd_dyn_write, 353}; 354 355FORMAT_DEFINE(vhd_dyn_format); 356 357/* 358 * PART 2: Fixed VHD 359 */ 360 361static int 362vhd_fix_write(int fd) 363{ 364 struct vhd_footer footer; 365 uint64_t imgsz; 366 int error; 367 368 error = image_copyout(fd); 369 if (!error) { 370 imgsz = image_get_size() * secsz; 371 vhd_make_footer(&footer, imgsz, VHD_DISK_TYPE_FIXED, ~0ULL); 372 if (sparse_write(fd, &footer, sizeof(footer)) < 0) 373 error = errno; 374 } 375 return (error); 376} 377 378static struct mkimg_format vhd_fix_format = { 379 .name = "vhdf", 380 .description = "Fixed Virtual Hard Disk", 381 .resize = vhd_resize, 382 .write = vhd_fix_write, 383}; 384 385FORMAT_DEFINE(vhd_fix_format); 386