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,6 +37,9 @@ enum vhd_type { | ||
| 37 | VHD_DIFFERENCING = 4, | 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 | // always big-endian | 43 | // always big-endian |
| 41 | struct vhd_footer { | 44 | struct vhd_footer { |
| 42 | char creator[8]; // "conectix" | 45 | char creator[8]; // "conectix" |
| @@ -127,6 +130,18 @@ typedef struct BDRVVPCState { | @@ -127,6 +130,18 @@ typedef struct BDRVVPCState { | ||
| 127 | #endif | 130 | #endif |
| 128 | } BDRVVPCState; | 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 | static int vpc_probe(const uint8_t *buf, int buf_size, const char *filename) | 145 | static int vpc_probe(const uint8_t *buf, int buf_size, const char *filename) |
| 131 | { | 146 | { |
| 132 | if (buf_size >= 8 && !strncmp((char *)buf, "conectix", 8)) | 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,6 +156,7 @@ static int vpc_open(BlockDriverState *bs, const char *filename, int flags) | ||
| 141 | struct vhd_footer* footer; | 156 | struct vhd_footer* footer; |
| 142 | struct vhd_dyndisk_header* dyndisk_header; | 157 | struct vhd_dyndisk_header* dyndisk_header; |
| 143 | uint8_t buf[HEADER_SIZE]; | 158 | uint8_t buf[HEADER_SIZE]; |
| 159 | + uint32_t checksum; | ||
| 144 | 160 | ||
| 145 | ret = bdrv_file_open(&s->hd, filename, flags); | 161 | ret = bdrv_file_open(&s->hd, filename, flags); |
| 146 | if (ret < 0) | 162 | if (ret < 0) |
| @@ -153,6 +169,12 @@ static int vpc_open(BlockDriverState *bs, const char *filename, int flags) | @@ -153,6 +169,12 @@ static int vpc_open(BlockDriverState *bs, const char *filename, int flags) | ||
| 153 | if (strncmp(footer->creator, "conectix", 8)) | 169 | if (strncmp(footer->creator, "conectix", 8)) |
| 154 | goto fail; | 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 | // The visible size of a image in Virtual PC depends on the geometry | 178 | // The visible size of a image in Virtual PC depends on the geometry |
| 157 | // rather than on the size stored in the footer (the size in the footer | 179 | // rather than on the size stored in the footer (the size in the footer |
| 158 | // is too large usually) | 180 | // is too large usually) |
| @@ -407,6 +429,152 @@ static int vpc_write(BlockDriverState *bs, int64_t sector_num, | @@ -407,6 +429,152 @@ static int vpc_write(BlockDriverState *bs, int64_t sector_num, | ||
| 407 | return 0; | 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 | static void vpc_close(BlockDriverState *bs) | 578 | static void vpc_close(BlockDriverState *bs) |
| 411 | { | 579 | { |
| 412 | BDRVVPCState *s = bs->opaque; | 580 | BDRVVPCState *s = bs->opaque; |
| @@ -425,4 +593,5 @@ BlockDriver bdrv_vpc = { | @@ -425,4 +593,5 @@ BlockDriver bdrv_vpc = { | ||
| 425 | vpc_read, | 593 | vpc_read, |
| 426 | vpc_write, | 594 | vpc_write, |
| 427 | vpc_close, | 595 | vpc_close, |
| 596 | + vpc_create, | ||
| 428 | }; | 597 | }; |