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 | }; |