├── Makefile ├── README ├── test ├── win7dyn.vhd.bz2 └── win7fixed.vhd.bz2 └── vhdtool.c /Makefile: -------------------------------------------------------------------------------- 1 | CC := gcc 2 | CFLAGS := -O2 -Wall -Wextra -Wno-missing-field-initializers -Wno-unused-parameter -g2 3 | LDFLAGS := -luuid 4 | 5 | all: vhdtool 6 | 7 | vhdtool: vhdtool.o 8 | $(CC) $^ $(LDFLAGS) -o $@ 9 | 10 | vhdtool.o: vhdtool.c 11 | 12 | clean: 13 | rm -f vhdtool vhdtool.o 14 | 15 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | VHDtool 2 | ========== 3 | 4 | A tool to examine and manipulate VHD images. Initially 5 | meant as a way to test dynamic VHD support in my 6 | Linux kernel loop VHD parser support. Images mount 7 | in Win7. 8 | 9 | Why would you use this instead of qemu-img? VHDtool 10 | lets you tweak more parameters and create funky 11 | VHDs (and will support differencing disks soon too!). 12 | 13 | One more reason is that qemu-img actually produces 14 | corrupt images that don't fit the VHD spec. Unsure 15 | if they event mount on Win7. 16 | 17 | Stay tuned. 18 | 19 | Building 20 | ======== 21 | 22 | Builds on recent Linux (i.e. not RHEL5, *sigh*), and 23 | needs libuuid (you probably have it, but look for 24 | libuuid-devel or some similar package). 25 | 26 | To build: 27 | 28 | $ make 29 | 30 | Examples 31 | ======== 32 | 33 | $ ./vhdtool 34 | ./vhdtool [-s size] [-b block_size] [-c] [-t type] create|convert ... 35 | 36 | $ ./vhdtool create 37 | ./vhdtool -s size [-b block_size] [-c] [-t type] create vhd-file-name 38 | command 'create' failed 39 | 40 | $ ./vhdtool convert 41 | ./vhdtool [-b block_size] [-c] [-t type] convert source-file-name dest-file-name 42 | command 'convert' failed 43 | 44 | Generate a 512-byte fixed VHD: 45 | 46 | $ vhdtool -s 512 create test.vhd 47 | 48 | Generate a 50 sector fixed VHD: 49 | 50 | $ vhdtool -s 512s create test.vhd 51 | 52 | Generate a 12mb dynamic VHD: 53 | 54 | $ vhdtool -s 12m -t dyn create test.vhd 55 | 56 | Generate a 12mb dynamic VHD with non-default (2mb) block size: 57 | 58 | $ vhdtool -s 12m -b 4m create test.vhd 59 | 60 | Convert a raw image to a VHD 61 | 62 | $ vhdtool convert test.raw test.vhd 63 | 64 | Valid size modifiers are: 65 | b/B/*nothing* -> bytes 66 | k/K -> kilobytes 67 | m/M -> megabytes 68 | g/G -> gigabytes 69 | t/T -> terabytes 70 | s/S -> sectors 71 | 72 | Special Considerations 73 | ====================== 74 | 75 | You need to pass the '-c' option if you are creating images < 127GiB 76 | in size and the following apply to you: 77 | 78 | 1) You are converting a RAW disk imag and will be using it with the 79 | emulated IDE controller in Hyper-V. 80 | 81 | 2) You are creating a new VHD and are expecting to use it both with 82 | the emulated IDE controller and the virtual SCSI adapter in 83 | Hyper-V. 84 | 85 | The '-c' option, will ensure that the disk size implied by C/H/S 86 | calculations will never be less than the desired image size, with 87 | the side effect of making the image a little larger. 88 | 89 | Contact Info 90 | ============ 91 | 92 | Andrei Warkentin (andrey.warkentin@gmail.com, andreiw@vmware.com, andreiw@msalumni.com) -------------------------------------------------------------------------------- /test/win7dyn.vhd.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreiw/vhdtool/cafd9bf2d28b2aa5bfd1a413e31641615ea2df61/test/win7dyn.vhd.bz2 -------------------------------------------------------------------------------- /test/win7fixed.vhd.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreiw/vhdtool/cafd9bf2d28b2aa5bfd1a413e31641615ea2df61/test/win7fixed.vhd.bz2 -------------------------------------------------------------------------------- /vhdtool.c: -------------------------------------------------------------------------------- 1 | /* 2 | VHD manipulation tool. 3 | 4 | Copyright (C) 2011 Andrei Warkentin 5 | 6 | This module is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This module is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this module; if not, write to the Free Software 18 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | */ 20 | 21 | #define _GNU_SOURCE 22 | #define _FILE_OFFSET_BITS 64 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #define COOKIE(x) (*(uint64_t *) x) 38 | #define COOKIE32(x) (*(uint32_t *) x) 39 | #define FOOTER_FEAT_RSVD (2) 40 | #define VHD_VERSION_1 (0x00010000UL) 41 | #define VHD_VMAJ_MASK (0xFFFF0000UL) 42 | #define VHD_VMIN_MASK (0x0000FFFFUL) 43 | #define DYN_VERSION_1 (0x00010000UL) 44 | #define DYN_VMAJ_MASK (0xFFFF0000UL) 45 | #define DYN_VMIN_MASK (0x0000FFFFUL) 46 | #define FOOTER_DOFF_FIXED (0xFFFFFFFFFFFFFFFFULL) 47 | #define DYN_DOFF_DYN (0xFFFFFFFFFFFFFFFFULL) 48 | #define SECONDS_OFFSET 946684800 49 | #define FOOTER_TYPE_FIXED (2) 50 | #define FOOTER_TYPE_DYN (3) 51 | #define FOOTER_TYPE_DIFF (4) 52 | #define SEC_SHIFT (9) 53 | #define SEC_SZ (1 << SEC_SHIFT) 54 | #define SEC_MASK (SEC_SZ - 1) 55 | #define round_up(what, on) ((((what) + (on) - 1) / (on)) * (on)) 56 | #define DYN_BLOCK_SZ 0x200000 57 | #define BAT_ENTRY_EMPTY 0xFFFFFFFF 58 | 59 | /* All fields Big-Endian */ 60 | struct vhd_id 61 | { 62 | uint32_t f1; 63 | uint16_t f2; 64 | uint16_t f3; 65 | uint8_t f4[8]; 66 | }; 67 | 68 | /* All fields Big-Endian */ 69 | struct vhd_chs 70 | { 71 | uint16_t c; 72 | uint8_t h; 73 | uint8_t s; 74 | }; 75 | 76 | /* All fields Big-Endian */ 77 | struct vhd_footer 78 | { 79 | uint64_t cookie; 80 | uint32_t features; 81 | uint32_t file_format_ver; 82 | uint64_t data_offset; 83 | uint32_t time_stamp; 84 | uint32_t creator_app; 85 | uint32_t creator_ver; 86 | uint32_t creator_os; 87 | uint64_t original_size; 88 | uint64_t current_size; 89 | struct vhd_chs disk_geometry; 90 | uint32_t disk_type; 91 | uint32_t checksum; 92 | struct vhd_id vhd_id; 93 | uint8_t saved_state; 94 | uint8_t reserved[427]; 95 | }; 96 | 97 | /* All fields Big-Endian */ 98 | struct vhd_ploc 99 | { 100 | uint32_t code; 101 | uint32_t sectors; 102 | uint32_t length; 103 | uint32_t reserved; 104 | uint64_t offset; 105 | }; 106 | 107 | /* All fields Big-Endian */ 108 | struct vhd_dyn 109 | { 110 | uint64_t cookie; 111 | uint64_t data_offset; 112 | uint64_t table_offset; 113 | uint32_t header_version; 114 | uint32_t max_tab_entries; 115 | uint32_t block_size; 116 | uint32_t checksum; 117 | struct vhd_id parent; 118 | uint32_t parent_time_stamp; 119 | uint32_t reserved0; 120 | uint8_t parent_utf16[512]; 121 | struct vhd_ploc pe[8]; 122 | uint8_t reserved1[256]; 123 | }; 124 | 125 | typedef uint32_t vhd_batent; 126 | 127 | struct vhd; 128 | typedef int (*op_read_t)(struct vhd *, void *, off64_t, size_t); 129 | typedef int (*op_write_t)(struct vhd *, void *, off64_t, size_t); 130 | 131 | struct vhd 132 | { 133 | struct vhd_footer footer; 134 | struct vhd_dyn dyn; 135 | char uuid_str[37]; 136 | char *name; 137 | off64_t size; 138 | off64_t offset; 139 | int fd; 140 | off64_t file_size; 141 | uint32_t type; 142 | #define OPEN_RAW_OK (1 << 1) 143 | #define OPEN_RW (1 << 2) 144 | #define OPEN_CREAT (1 << 3) 145 | #define COMPAT_SIZE (1 << 4) 146 | unsigned flags; 147 | op_read_t read; 148 | op_write_t write; 149 | }; 150 | 151 | int vhd_read(struct vhd *vhd, 152 | void *buf, 153 | size_t size) 154 | { 155 | if (lseek64(vhd->fd, vhd->offset, SEEK_SET) != vhd->offset) { 156 | fprintf(stderr, "Error: couldn't seek '%s': %s\n", 157 | vhd->name, strerror(errno)); 158 | return -1; 159 | } 160 | 161 | if (read(vhd->fd, buf, size) != (int) size) { 162 | fprintf(stderr, "Error: couldn't read from '%s': %s\n", 163 | vhd->name, strerror(errno)); 164 | return -1; 165 | } 166 | 167 | vhd->offset += size; 168 | return 0; 169 | } 170 | 171 | int vhd_write(struct vhd *vhd, 172 | void *buf, 173 | size_t size) 174 | { 175 | if (lseek64(vhd->fd, vhd->offset, SEEK_SET) != vhd->offset) { 176 | fprintf(stderr, "Error: couldn't seek '%s': %s\n", 177 | vhd->name, strerror(errno)); 178 | return -1; 179 | } 180 | 181 | if (write(vhd->fd, buf, size) != (int) size) { 182 | fprintf(stderr, "Error: couldn't read from '%s': %s\n", 183 | vhd->name, strerror(errno)); 184 | return -1; 185 | } 186 | 187 | vhd->offset += size; 188 | return 0; 189 | } 190 | 191 | int op_raw_read(struct vhd *vhd, void *buf, off64_t offset, size_t size) 192 | { 193 | if (offset > vhd->size || 194 | (off64_t) (offset + size) > vhd->size) { 195 | fprintf(stderr, "Error: out-of-bound read from '%s'\n", 196 | vhd->name); 197 | return -1; 198 | } 199 | 200 | vhd->offset = offset; 201 | return vhd_read(vhd, buf, size); 202 | } 203 | 204 | int op_raw_write(struct vhd *vhd, void *buf, off64_t offset, size_t size) 205 | { 206 | if (offset > vhd->size || 207 | (off64_t) (offset + size) > vhd->size) { 208 | fprintf(stderr, "Error: out-of-bound read from '%s'\n", 209 | vhd->name); 210 | return -1; 211 | } 212 | 213 | vhd->offset = offset; 214 | return vhd_write(vhd, buf, size); 215 | } 216 | 217 | int vhd_verify(struct vhd *vhd) 218 | { 219 | uint32_t type; 220 | 221 | if (vhd->footer.cookie != COOKIE("conectix")) { 222 | return -1; 223 | } 224 | 225 | type = be32toh(vhd->footer.disk_type); 226 | if (type != FOOTER_TYPE_FIXED) { 227 | return -1; 228 | } 229 | 230 | vhd->read = op_raw_read; 231 | vhd->write = op_raw_write; 232 | vhd->size = vhd->file_size - sizeof(vhd->footer); 233 | 234 | /* TBD: More checks and dynamic disks. */ 235 | return 0; 236 | } 237 | 238 | int vhd_open(struct vhd *vhd, 239 | char *name, 240 | unsigned flags) 241 | { 242 | struct stat stat; 243 | memset(vhd, 0, sizeof(*vhd)); 244 | vhd->flags = flags; 245 | vhd->name = name; 246 | 247 | if (flags & OPEN_CREAT) { 248 | vhd->fd = open(vhd->name, O_CREAT | O_EXCL | O_RDWR, 249 | 0644); 250 | } else { 251 | vhd->fd = open(vhd->name, flags & OPEN_RW ? 252 | O_RDWR : O_RDONLY); 253 | } 254 | if (vhd->fd == -1) { 255 | fprintf(stderr, "Error: couldn't open '%s': %s\n", 256 | vhd->name, 257 | strerror(errno)); 258 | return -1; 259 | } 260 | 261 | if (flags & OPEN_CREAT) { 262 | return 0; 263 | } 264 | 265 | if (fstat(vhd->fd, &stat) == -1) { 266 | fprintf(stderr, "Error: couldn't stat '%s': %s\n", 267 | vhd->name, strerror(errno)); 268 | return -1; 269 | } 270 | 271 | vhd->file_size = stat.st_size; 272 | vhd->offset = vhd->file_size - sizeof(vhd->footer); 273 | if (vhd_read(vhd, &vhd->footer, sizeof(vhd->footer)) == -1) { 274 | return -1; 275 | } 276 | 277 | if (vhd_verify(vhd) == -1) { 278 | if (flags & OPEN_RAW_OK) { 279 | fprintf(stderr, 280 | "Warning: '%s' treated as raw image\n", 281 | vhd->name); 282 | vhd->read = op_raw_read; 283 | vhd->write = op_raw_write; 284 | vhd->size = vhd->file_size; 285 | return 0; 286 | } 287 | return -1; 288 | } 289 | 290 | return 0; 291 | } 292 | 293 | int vhd_close(struct vhd *vhd, int status) 294 | { 295 | if (vhd->fd != -1) { 296 | if (!status) { 297 | if (fsync(vhd->fd)) { 298 | perror("couldn't flush VHD data"); 299 | return -1; 300 | } 301 | 302 | if (close(vhd->fd)) { 303 | perror("couldn't close VHD file"); 304 | return -1; 305 | } 306 | } else { 307 | if (vhd->flags & OPEN_CREAT) { 308 | if (unlink(vhd->name)) { 309 | perror("couldn't clean up VHD file"); 310 | return -1; 311 | } 312 | } 313 | } 314 | } 315 | 316 | return 0; 317 | } 318 | 319 | uint32_t vhd_checksum(uint8_t *data, size_t size) 320 | { 321 | uint32_t csum = 0; 322 | while (size--) { 323 | csum += *data++; 324 | } 325 | return ~csum; 326 | } 327 | 328 | /* Returns the minimum, which cannot be zero. */ 329 | unsigned min_nz(unsigned a, unsigned b) 330 | { 331 | if (a < b && a != 0) { 332 | return a; 333 | } else { 334 | return b; 335 | } 336 | } 337 | 338 | int vhd_chs(struct vhd *vhd) 339 | { 340 | uint64_t cyl_x_heads; 341 | struct vhd_chs chs; 342 | uint64_t new_sectors; 343 | uint64_t sectors; 344 | off64_t original_size = vhd->size; 345 | 346 | again: 347 | sectors = vhd->size >> 9; 348 | 349 | /* 350 | * Blame AndrewN for this one... All this logic is from 351 | * the VHD specification. 352 | */ 353 | if (sectors > 65535 * 16 * 255) { 354 | 355 | /* ~127GiB */ 356 | sectors = 65535 * 16 * 255; 357 | } 358 | if (sectors >= 65535 * 16 * 63) { 359 | chs.s = 255; 360 | chs.h = 16; 361 | cyl_x_heads = sectors / chs.s; 362 | } else { 363 | chs.s = 17; 364 | cyl_x_heads = sectors / chs.s; 365 | chs.h = (cyl_x_heads + 1023) >> 10; 366 | if (chs.h < 4) 367 | chs.h = 4; 368 | 369 | if (cyl_x_heads >= (uint64_t) (chs.h << 10) || 370 | chs.h > 16) { 371 | chs.s = 31; 372 | chs.h = 16; 373 | cyl_x_heads = sectors / chs.s; 374 | } 375 | if (cyl_x_heads >= (uint64_t) (chs.h << 10)) { 376 | chs.s = 63; 377 | chs.h = 16; 378 | cyl_x_heads = sectors / chs.s; 379 | } 380 | } 381 | chs.c = cyl_x_heads / chs.h; 382 | vhd->footer.disk_geometry.c = htobe16(chs.c); 383 | vhd->footer.disk_geometry.h = chs.h; 384 | vhd->footer.disk_geometry.s = chs.s; 385 | 386 | if (sectors < 65535 * 16 * 255) { 387 | 388 | /* 389 | * All of this nonsense matters only for disks < 127GB. 390 | */ 391 | new_sectors = chs.c * chs.h * chs.s; 392 | if (new_sectors != sectors) { 393 | if (original_size == vhd->size) { 394 | 395 | /* Only show warning once. */ 396 | fprintf(stderr, 397 | "Warning: C(%u)H(%u)S(%u)-derived" 398 | " total sector count (%lu) does" 399 | " not match actual (%lu)%s.\n", 400 | chs.c, chs.h, chs.s, 401 | new_sectors, sectors, 402 | vhd->flags & COMPAT_SIZE ? 403 | " and will be recomputed" : 404 | ""); 405 | } 406 | 407 | if (vhd->flags & COMPAT_SIZE) { 408 | vhd->size = round_up(vhd->size + 1, 409 | min_nz(min_nz(chs.c, chs.h), 410 | chs.s) << 9); 411 | goto again; 412 | } 413 | 414 | fprintf(stderr, "Warning: You may have problems" 415 | " with Hyper-V if converting raw disks" 416 | " to VHD, or if moving VHDs from ATA to" 417 | " SCSI.\n"); 418 | } 419 | } 420 | 421 | if (original_size != vhd->size) { 422 | fprintf(stderr, "Warning: increased VHD size from" 423 | " %ju to %ju bytes\n", original_size, 424 | vhd->size); 425 | } 426 | 427 | return 0; 428 | } 429 | 430 | int vhd_footer(struct vhd *vhd, 431 | uint64_t data_offset) 432 | { 433 | if (vhd->size >> 9 << 9 != vhd->size) { 434 | fprintf(stderr, 435 | "Error: size must be in units " 436 | "of 512-byte sectors\n"); 437 | return -1; 438 | } 439 | 440 | if (vhd_chs(vhd) == -1) { 441 | fprintf(stderr, "Error: size is too small\n"); 442 | return -1; 443 | } 444 | 445 | vhd->footer.cookie = COOKIE("conectix"); 446 | vhd->footer.features = htobe32(FOOTER_FEAT_RSVD); 447 | vhd->footer.data_offset = htobe64(data_offset); 448 | vhd->footer.file_format_ver = htobe32(VHD_VERSION_1); 449 | vhd->footer.time_stamp = htobe32(time(NULL) + SECONDS_OFFSET); 450 | vhd->footer.creator_app = COOKIE32("vhdt"); 451 | vhd->footer.creator_ver = htobe32(0x1); 452 | vhd->footer.creator_os = COOKIE32("Lnux"); 453 | vhd->footer.original_size = htobe64(vhd->size); 454 | vhd->footer.current_size = htobe64(vhd->size); 455 | vhd->footer.disk_type = htobe32(vhd->type); 456 | uuid_generate((uint8_t *) &vhd->footer.vhd_id); 457 | uuid_unparse((uint8_t *) &vhd->footer.vhd_id, vhd->uuid_str); 458 | vhd->footer.vhd_id.f1 = htobe32(vhd->footer.vhd_id.f1); 459 | vhd->footer.vhd_id.f2 = htobe16(vhd->footer.vhd_id.f2); 460 | vhd->footer.vhd_id.f3 = htobe16(vhd->footer.vhd_id.f3); 461 | vhd->footer.checksum = vhd_checksum((uint8_t *) &vhd->footer, 462 | sizeof(vhd->footer)); 463 | vhd->footer.checksum = htobe32(vhd->footer.checksum); 464 | 465 | return 0; 466 | } 467 | 468 | int vhd_dyn(struct vhd *vhd, uint32_t block_size) 469 | { 470 | vhd->dyn.cookie = COOKIE("cxsparse"); 471 | vhd->dyn.data_offset = htobe64(DYN_DOFF_DYN); 472 | vhd->dyn.table_offset = htobe64(vhd->offset + sizeof(vhd->dyn)); 473 | vhd->dyn.header_version = htobe32(DYN_VERSION_1); 474 | 475 | if (block_size >> 9 << 9 != block_size) { 476 | fprintf(stderr, "Error: block size must be in units " 477 | "of 512-byte sectors\n"); 478 | return -1; 479 | } 480 | 481 | vhd->dyn.block_size = htobe32(block_size); 482 | vhd->dyn.max_tab_entries = vhd->size / block_size; 483 | if (!vhd->dyn.max_tab_entries) { 484 | fprintf(stderr, "Error: block size can't be larger " 485 | "than the VHD\n"); 486 | return -1; 487 | } 488 | if ((off64_t)vhd->dyn.max_tab_entries * block_size != vhd->size) { 489 | fprintf(stderr, 490 | "Error: VHD size not multiple of block size\n"); 491 | return -1; 492 | } 493 | vhd->dyn.max_tab_entries = htobe32(vhd->dyn.max_tab_entries); 494 | vhd->dyn.checksum = vhd_checksum((uint8_t *) &vhd->dyn, 495 | sizeof(vhd->dyn)); 496 | vhd->dyn.checksum = htobe32(vhd->dyn.checksum); 497 | return 0; 498 | } 499 | 500 | int vhd_create(struct vhd *vhd, 501 | off64_t block_size) 502 | { 503 | int status; 504 | 505 | if (!block_size) 506 | block_size = DYN_BLOCK_SZ; 507 | 508 | if ((status = vhd_footer(vhd, 509 | vhd->type == FOOTER_TYPE_FIXED ? 510 | FOOTER_DOFF_FIXED : 511 | sizeof(vhd->footer)))) { 512 | goto done; 513 | } 514 | 515 | printf("Creating %s VHD %s (%ju bytes)\n", 516 | vhd->type == FOOTER_TYPE_FIXED ? "fixed" : "dynamic", 517 | vhd->uuid_str, vhd->size); 518 | 519 | if (vhd->type == FOOTER_TYPE_FIXED) { 520 | vhd->offset = vhd->size; 521 | 522 | vhd->read = op_raw_read; 523 | vhd->write = op_raw_write; 524 | 525 | if ((status = vhd_write(vhd, &vhd->footer, sizeof(vhd->footer)))) 526 | goto done; 527 | } else { 528 | size_t bat_entries; 529 | vhd->offset = 0; 530 | vhd_batent empty = BAT_ENTRY_EMPTY; 531 | 532 | if ((status = vhd_write(vhd, &vhd->footer, sizeof(vhd->footer)))) 533 | goto done; 534 | if ((status = vhd_dyn(vhd, block_size))) 535 | goto done; 536 | if ((status = vhd_write(vhd, &vhd->dyn, sizeof(vhd->dyn)))) 537 | goto done; 538 | 539 | bat_entries = vhd->size / block_size; 540 | while (bat_entries--) 541 | if ((status = vhd_write(vhd, &empty, sizeof(empty)))) 542 | goto done; 543 | vhd->offset = round_up(vhd->offset, 512); 544 | if ((status = vhd_write(vhd, &vhd->footer, sizeof(vhd->footer)))) 545 | goto done; 546 | } 547 | done: 548 | return status; 549 | } 550 | 551 | int vhd_copy(struct vhd *src, struct vhd *dst) 552 | { 553 | int status; 554 | off64_t pos; 555 | uint8_t buf[SEC_SZ]; 556 | 557 | printf("Copying contents from '%s' to '%s'\n", 558 | src->name, dst->name); 559 | for (pos = 0; pos < src->size; pos += sizeof(buf)) { 560 | status = src->read(src, &buf, pos, sizeof(buf)); 561 | if (status == -1) { 562 | return -1; 563 | } 564 | status = dst->write(dst, &buf, pos, sizeof(buf)); 565 | if (status == -1) { 566 | return -1; 567 | } 568 | } 569 | 570 | return 0; 571 | } 572 | 573 | int vhd_cmd_convert(int optind, 574 | int argc, 575 | char **argv, 576 | bool size_compat, 577 | uint32_t vhd_type, 578 | off64_t block_size) 579 | { 580 | struct vhd src; 581 | struct vhd dest; 582 | int status; 583 | 584 | if (optind != (argc - 2)) { 585 | fprintf(stderr, "Usage: %s [-b block_size] [-c] [-t type] convert source-file-name dest-file-name\n", 586 | argv[0]); 587 | return -1; 588 | } 589 | 590 | if (!vhd_type) { 591 | if (block_size) 592 | vhd_type = FOOTER_TYPE_DYN; 593 | else 594 | vhd_type = FOOTER_TYPE_FIXED; 595 | } 596 | 597 | if (vhd_open(&src, argv[optind], 598 | OPEN_RAW_OK) == -1) { 599 | return -1; 600 | } 601 | 602 | if (vhd_open(&dest, argv[optind+1], 603 | OPEN_RW | OPEN_CREAT | 604 | (size_compat ? COMPAT_SIZE : 0)) == -1) { 605 | return -1; 606 | } 607 | 608 | dest.type = vhd_type; 609 | dest.size = src.size; 610 | 611 | status = vhd_create(&dest, block_size); 612 | if (status == -1) { 613 | goto done; 614 | } 615 | 616 | status = vhd_copy(&src, &dest); 617 | done: 618 | if (vhd_close(&dest, status) == -1) 619 | status = -1; 620 | if (vhd_close(&src, status) == -1) 621 | status = -1; 622 | return status; 623 | } 624 | 625 | int vhd_cmd_create(int optind, 626 | int argc, 627 | char **argv, 628 | bool size_compat, 629 | off64_t vhd_size, 630 | uint32_t vhd_type, 631 | off64_t block_size) 632 | { 633 | int status; 634 | struct vhd vhd; 635 | 636 | if (optind != (argc - 1) || !vhd_size) { 637 | fprintf(stderr, "Usage: %s -s size [-b block_size] [-c] [-t type] create vhd-file-name\n", 638 | argv[0]); 639 | return -1; 640 | } 641 | 642 | if (!vhd_size) { 643 | fprintf(stderr, "Error: missing VHD size parameter\n"); 644 | return -1; 645 | } 646 | 647 | if (!vhd_type) { 648 | if (block_size) 649 | vhd_type = FOOTER_TYPE_DYN; 650 | else 651 | vhd_type = FOOTER_TYPE_FIXED; 652 | } 653 | 654 | if (vhd_open(&vhd, argv[optind], 655 | OPEN_RW | OPEN_CREAT | 656 | (size_compat ? COMPAT_SIZE : 0))) { 657 | return -1; 658 | } 659 | 660 | vhd.type = vhd_type; 661 | vhd.size = vhd_size; 662 | 663 | status = vhd_create(&vhd, block_size); 664 | 665 | if (vhd_close(&vhd, status)) { 666 | return -1; 667 | } 668 | return status; 669 | } 670 | 671 | int main(int argc, char **argv) 672 | { 673 | int status; 674 | off64_t vhd_size = 0; 675 | uint32_t vhd_type = 0; 676 | off64_t block_size = 0; 677 | bool do_help = false; 678 | bool do_compat = false; 679 | 680 | while (1) { 681 | int c; 682 | opterr = 0; 683 | c = getopt(argc, argv, "cb:s:t:"); 684 | if (c == -1) 685 | break; 686 | else if (c == '?') { 687 | do_help = true; 688 | break; 689 | } 690 | 691 | switch (c) { 692 | case 'c': 693 | do_compat = true; 694 | break; 695 | case 'b': 696 | case 's': 697 | { 698 | off64_t *size; 699 | char type = 0; 700 | 701 | if (c == 'b') 702 | size = &block_size; 703 | else 704 | size = &vhd_size; 705 | 706 | /* Handle VHD size. */ 707 | sscanf (optarg, "%ju%c", size, &type); 708 | switch (type) { 709 | case 't': 710 | case 'T': 711 | *size <<= 10; 712 | case 'g': 713 | case 'G': 714 | *size <<= 10; 715 | case 'm': 716 | case 'M': 717 | *size <<= 10; 718 | case 'k': 719 | case 'K': 720 | *size <<= 10; 721 | case '\0': 722 | case 'b': 723 | case 'B': 724 | break; 725 | case 's': 726 | case 'S': 727 | *size <<= 9; 728 | break; 729 | default: 730 | fprintf(stderr, 731 | "Error: %s size modifer '%c' not one of [BKMGTS]\n", 732 | size == &block_size ? "block" : "VHD", 733 | type); 734 | return -1; 735 | } 736 | break; 737 | } 738 | case 't': 739 | { 740 | static const char fixed_str[] = "fixed"; 741 | static const char dynamic_str[] = "dynamic"; 742 | if (strstr(fixed_str, optarg) == fixed_str) 743 | vhd_type = FOOTER_TYPE_FIXED; 744 | else if (strstr(dynamic_str, optarg) == dynamic_str) 745 | vhd_type = FOOTER_TYPE_DYN; 746 | else { 747 | fprintf(stderr, "Error: Disk type not one of 'fixed' or 'dynamic'\n"); 748 | return -1; 749 | } 750 | break; 751 | } 752 | } 753 | } 754 | 755 | if (do_help || optind == argc) { 756 | fprintf(stderr, "Usage: %s [-s size] [-b block_size] [-c] [-t type] create|convert ...\n", 757 | argv[0]); 758 | return -1; 759 | }; 760 | 761 | /* First optind will be a command selector. */ 762 | if (strcmp(argv[optind], "create") == 0) { 763 | status = vhd_cmd_create(optind + 1, 764 | argc, argv, 765 | do_compat, 766 | vhd_size, 767 | vhd_type, block_size); 768 | } else if (strcmp(argv[optind], "convert") == 0) { 769 | status = vhd_cmd_convert(optind + 1, 770 | argc, argv, 771 | do_compat, 772 | vhd_type, 773 | block_size); 774 | } else { 775 | fprintf(stderr, "Error: unknown command '%s'\n", argv[optind]); 776 | return -1; 777 | } 778 | 779 | if (status == -1) { 780 | fprintf(stderr, "Error: command '%s' failed\n", argv[optind]); 781 | } 782 | 783 | return status; 784 | } 785 | --------------------------------------------------------------------------------