Commit 57c7d9e57983d25ddf2ef995493366b9e15d32b4
1 parent
15d35bc5
block-vpc: Create images (Kevin Wolf)
Add an implementation to create VHD images. Signed-off-by: Kevin Wolf <kwolf@suse.de> Signed-off-by: Anthony Liguori <aliguori@us.ibm.com> git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@6459 c046a42c-6fe2-441c-8c8c-71466251a162
Showing
1 changed file
with
169 additions
and
0 deletions
block-vpc.c
| ... | ... | @@ -37,6 +37,9 @@ enum vhd_type { |
| 37 | 37 | VHD_DIFFERENCING = 4, |
| 38 | 38 | }; |
| 39 | 39 | |
| 40 | +// Seconds since Jan 1, 2000 0:00:00 (UTC) | |
| 41 | +#define VHD_TIMESTAMP_BASE 946684800 | |
| 42 | + | |
| 40 | 43 | // always big-endian |
| 41 | 44 | struct vhd_footer { |
| 42 | 45 | char creator[8]; // "conectix" |
| ... | ... | @@ -127,6 +130,18 @@ typedef struct BDRVVPCState { |
| 127 | 130 | #endif |
| 128 | 131 | } BDRVVPCState; |
| 129 | 132 | |
| 133 | +static uint32_t vpc_checksum(uint8_t* buf, size_t size) | |
| 134 | +{ | |
| 135 | + uint32_t res = 0; | |
| 136 | + int i; | |
| 137 | + | |
| 138 | + for (i = 0; i < size; i++) | |
| 139 | + res += buf[i]; | |
| 140 | + | |
| 141 | + return ~res; | |
| 142 | +} | |
| 143 | + | |
| 144 | + | |
| 130 | 145 | static int vpc_probe(const uint8_t *buf, int buf_size, const char *filename) |
| 131 | 146 | { |
| 132 | 147 | if (buf_size >= 8 && !strncmp((char *)buf, "conectix", 8)) |
| ... | ... | @@ -141,6 +156,7 @@ static int vpc_open(BlockDriverState *bs, const char *filename, int flags) |
| 141 | 156 | struct vhd_footer* footer; |
| 142 | 157 | struct vhd_dyndisk_header* dyndisk_header; |
| 143 | 158 | uint8_t buf[HEADER_SIZE]; |
| 159 | + uint32_t checksum; | |
| 144 | 160 | |
| 145 | 161 | ret = bdrv_file_open(&s->hd, filename, flags); |
| 146 | 162 | if (ret < 0) |
| ... | ... | @@ -153,6 +169,12 @@ static int vpc_open(BlockDriverState *bs, const char *filename, int flags) |
| 153 | 169 | if (strncmp(footer->creator, "conectix", 8)) |
| 154 | 170 | goto fail; |
| 155 | 171 | |
| 172 | + checksum = be32_to_cpu(footer->checksum); | |
| 173 | + footer->checksum = 0; | |
| 174 | + if (vpc_checksum(s->footer_buf, HEADER_SIZE) != checksum) | |
| 175 | + fprintf(stderr, "block-vpc: The header checksum of '%s' is " | |
| 176 | + "incorrect.\n", filename); | |
| 177 | + | |
| 156 | 178 | // The visible size of a image in Virtual PC depends on the geometry |
| 157 | 179 | // rather than on the size stored in the footer (the size in the footer |
| 158 | 180 | // is too large usually) |
| ... | ... | @@ -407,6 +429,152 @@ static int vpc_write(BlockDriverState *bs, int64_t sector_num, |
| 407 | 429 | return 0; |
| 408 | 430 | } |
| 409 | 431 | |
| 432 | + | |
| 433 | +/* | |
| 434 | + * Calculates the number of cylinders, heads and sectors per cylinder | |
| 435 | + * based on a given number of sectors. This is the algorithm described | |
| 436 | + * in the VHD specification. | |
| 437 | + * | |
| 438 | + * Note that the geometry doesn't always exactly match total_sectors but | |
| 439 | + * may round it down. | |
| 440 | + */ | |
| 441 | +static void calculate_geometry(int64_t total_sectors, uint16_t* cyls, | |
| 442 | + uint8_t* heads, uint8_t* secs_per_cyl) | |
| 443 | +{ | |
| 444 | + uint32_t cyls_times_heads; | |
| 445 | + | |
| 446 | + if (total_sectors > 65535 * 16 * 255) | |
| 447 | + total_sectors = 65535 * 16 * 255; | |
| 448 | + | |
| 449 | + if (total_sectors > 65535 * 16 * 63) { | |
| 450 | + *secs_per_cyl = 255; | |
| 451 | + *heads = 16; | |
| 452 | + cyls_times_heads = total_sectors / *secs_per_cyl; | |
| 453 | + } else { | |
| 454 | + *secs_per_cyl = 17; | |
| 455 | + cyls_times_heads = total_sectors / *secs_per_cyl; | |
| 456 | + *heads = (cyls_times_heads + 1023) / 1024; | |
| 457 | + | |
| 458 | + if (*heads < 4) | |
| 459 | + *heads = 4; | |
| 460 | + | |
| 461 | + if (cyls_times_heads >= (*heads * 1024) || *heads > 16) { | |
| 462 | + *secs_per_cyl = 31; | |
| 463 | + *heads = 16; | |
| 464 | + cyls_times_heads = total_sectors / *secs_per_cyl; | |
| 465 | + } | |
| 466 | + | |
| 467 | + if (cyls_times_heads >= (*heads * 1024)) { | |
| 468 | + *secs_per_cyl = 63; | |
| 469 | + *heads = 16; | |
| 470 | + cyls_times_heads = total_sectors / *secs_per_cyl; | |
| 471 | + } | |
| 472 | + } | |
| 473 | + | |
| 474 | + // Note: Rounding up deviates from the Virtual PC behaviour | |
| 475 | + // However, we need this to avoid truncating images in qemu-img convert | |
| 476 | + *cyls = (cyls_times_heads + *heads - 1) / *heads; | |
| 477 | +} | |
| 478 | + | |
| 479 | +static int vpc_create(const char *filename, int64_t total_sectors, | |
| 480 | + const char *backing_file, int flags) | |
| 481 | +{ | |
| 482 | + uint8_t buf[1024]; | |
| 483 | + struct vhd_footer* footer = (struct vhd_footer*) buf; | |
| 484 | + struct vhd_dyndisk_header* dyndisk_header = | |
| 485 | + (struct vhd_dyndisk_header*) buf; | |
| 486 | + int fd, i; | |
| 487 | + uint16_t cyls; | |
| 488 | + uint8_t heads; | |
| 489 | + uint8_t secs_per_cyl; | |
| 490 | + size_t block_size, num_bat_entries; | |
| 491 | + | |
| 492 | + if (backing_file != NULL) | |
| 493 | + return -ENOTSUP; | |
| 494 | + | |
| 495 | + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644); | |
| 496 | + if (fd < 0) | |
| 497 | + return -EIO; | |
| 498 | + | |
| 499 | + // Calculate matching total_size and geometry | |
| 500 | + calculate_geometry(total_sectors, &cyls, &heads, &secs_per_cyl); | |
| 501 | + total_sectors = (int64_t) cyls * heads * secs_per_cyl; | |
| 502 | + | |
| 503 | + // Prepare the Hard Disk Footer | |
| 504 | + memset(buf, 0, 1024); | |
| 505 | + | |
| 506 | + strncpy(footer->creator, "conectix", 8); | |
| 507 | + // TODO Check if "qemu" creator_app is ok for VPC | |
| 508 | + strncpy(footer->creator_app, "qemu", 4); | |
| 509 | + strncpy(footer->creator_os, "Wi2k", 4); | |
| 510 | + | |
| 511 | + footer->features = be32_to_cpu(0x02); | |
| 512 | + footer->version = be32_to_cpu(0x00010000); | |
| 513 | + footer->data_offset = be64_to_cpu(HEADER_SIZE); | |
| 514 | + footer->timestamp = be32_to_cpu(time(NULL) - VHD_TIMESTAMP_BASE); | |
| 515 | + | |
| 516 | + // Version of Virtual PC 2007 | |
| 517 | + footer->major = be16_to_cpu(0x0005); | |
| 518 | + footer->minor =be16_to_cpu(0x0003); | |
| 519 | + | |
| 520 | + footer->orig_size = be64_to_cpu(total_sectors * 512); | |
| 521 | + footer->size = be64_to_cpu(total_sectors * 512); | |
| 522 | + | |
| 523 | + footer->cyls = be16_to_cpu(cyls); | |
| 524 | + footer->heads = heads; | |
| 525 | + footer->secs_per_cyl = secs_per_cyl; | |
| 526 | + | |
| 527 | + footer->type = be32_to_cpu(VHD_DYNAMIC); | |
| 528 | + | |
| 529 | + // TODO uuid is missing | |
| 530 | + | |
| 531 | + footer->checksum = be32_to_cpu(vpc_checksum(buf, HEADER_SIZE)); | |
| 532 | + | |
| 533 | + // Write the footer (twice: at the beginning and at the end) | |
| 534 | + block_size = 0x200000; | |
| 535 | + num_bat_entries = (total_sectors + block_size / 512) / (block_size / 512); | |
| 536 | + | |
| 537 | + if (write(fd, buf, HEADER_SIZE) != HEADER_SIZE) | |
| 538 | + return -EIO; | |
| 539 | + | |
| 540 | + if (lseek(fd, 1536 + ((num_bat_entries * 4 + 511) & ~511), SEEK_SET) < 0) | |
| 541 | + return -EIO; | |
| 542 | + if (write(fd, buf, HEADER_SIZE) != HEADER_SIZE) | |
| 543 | + return -EIO; | |
| 544 | + | |
| 545 | + // Write the initial BAT | |
| 546 | + if (lseek(fd, 3 * 512, SEEK_SET) < 0) | |
| 547 | + return -EIO; | |
| 548 | + | |
| 549 | + memset(buf, 0xFF, 512); | |
| 550 | + for (i = 0; i < (num_bat_entries * 4 + 511) / 512; i++) | |
| 551 | + if (write(fd, buf, 512) != 512) | |
| 552 | + return -EIO; | |
| 553 | + | |
| 554 | + | |
| 555 | + // Prepare the Dynamic Disk Header | |
| 556 | + memset(buf, 0, 1024); | |
| 557 | + | |
| 558 | + strncpy(dyndisk_header->magic, "cxsparse", 8); | |
| 559 | + | |
| 560 | + dyndisk_header->data_offset = be64_to_cpu(0xFFFFFFFF); | |
| 561 | + dyndisk_header->table_offset = be64_to_cpu(3 * 512); | |
| 562 | + dyndisk_header->version = be32_to_cpu(0x00010000); | |
| 563 | + dyndisk_header->block_size = be32_to_cpu(block_size); | |
| 564 | + dyndisk_header->max_table_entries = be32_to_cpu(num_bat_entries); | |
| 565 | + | |
| 566 | + dyndisk_header->checksum = be32_to_cpu(vpc_checksum(buf, 1024)); | |
| 567 | + | |
| 568 | + // Write the header | |
| 569 | + if (lseek(fd, 512, SEEK_SET) < 0) | |
| 570 | + return -EIO; | |
| 571 | + if (write(fd, buf, 1024) != 1024) | |
| 572 | + return -EIO; | |
| 573 | + | |
| 574 | + close(fd); | |
| 575 | + return 0; | |
| 576 | +} | |
| 577 | + | |
| 410 | 578 | static void vpc_close(BlockDriverState *bs) |
| 411 | 579 | { |
| 412 | 580 | BDRVVPCState *s = bs->opaque; |
| ... | ... | @@ -425,4 +593,5 @@ BlockDriver bdrv_vpc = { |
| 425 | 593 | vpc_read, |
| 426 | 594 | vpc_write, |
| 427 | 595 | vpc_close, |
| 596 | + vpc_create, | |
| 428 | 597 | }; | ... | ... |