diff options
Diffstat (limited to 'block-vpc.c')
-rw-r--r-- | block-vpc.c | 169 |
1 files changed, 169 insertions, 0 deletions
diff --git a/block-vpc.c b/block-vpc.c index 24ed6c76d2..f46ff8cd73 100644 --- a/block-vpc.c +++ b/block-vpc.c @@ -37,6 +37,9 @@ enum vhd_type { VHD_DIFFERENCING = 4, }; +// Seconds since Jan 1, 2000 0:00:00 (UTC) +#define VHD_TIMESTAMP_BASE 946684800 + // always big-endian struct vhd_footer { char creator[8]; // "conectix" @@ -127,6 +130,18 @@ typedef struct BDRVVPCState { #endif } BDRVVPCState; +static uint32_t vpc_checksum(uint8_t* buf, size_t size) +{ + uint32_t res = 0; + int i; + + for (i = 0; i < size; i++) + res += buf[i]; + + return ~res; +} + + static int vpc_probe(const uint8_t *buf, int buf_size, const char *filename) { if (buf_size >= 8 && !strncmp((char *)buf, "conectix", 8)) @@ -141,6 +156,7 @@ static int vpc_open(BlockDriverState *bs, const char *filename, int flags) struct vhd_footer* footer; struct vhd_dyndisk_header* dyndisk_header; uint8_t buf[HEADER_SIZE]; + uint32_t checksum; ret = bdrv_file_open(&s->hd, filename, flags); if (ret < 0) @@ -153,6 +169,12 @@ static int vpc_open(BlockDriverState *bs, const char *filename, int flags) if (strncmp(footer->creator, "conectix", 8)) goto fail; + checksum = be32_to_cpu(footer->checksum); + footer->checksum = 0; + if (vpc_checksum(s->footer_buf, HEADER_SIZE) != checksum) + fprintf(stderr, "block-vpc: The header checksum of '%s' is " + "incorrect.\n", filename); + // The visible size of a image in Virtual PC depends on the geometry // rather than on the size stored in the footer (the size in the footer // is too large usually) @@ -407,6 +429,152 @@ static int vpc_write(BlockDriverState *bs, int64_t sector_num, return 0; } + +/* + * Calculates the number of cylinders, heads and sectors per cylinder + * based on a given number of sectors. This is the algorithm described + * in the VHD specification. + * + * Note that the geometry doesn't always exactly match total_sectors but + * may round it down. + */ +static void calculate_geometry(int64_t total_sectors, uint16_t* cyls, + uint8_t* heads, uint8_t* secs_per_cyl) +{ + uint32_t cyls_times_heads; + + if (total_sectors > 65535 * 16 * 255) + total_sectors = 65535 * 16 * 255; + + if (total_sectors > 65535 * 16 * 63) { + *secs_per_cyl = 255; + *heads = 16; + cyls_times_heads = total_sectors / *secs_per_cyl; + } else { + *secs_per_cyl = 17; + cyls_times_heads = total_sectors / *secs_per_cyl; + *heads = (cyls_times_heads + 1023) / 1024; + + if (*heads < 4) + *heads = 4; + + if (cyls_times_heads >= (*heads * 1024) || *heads > 16) { + *secs_per_cyl = 31; + *heads = 16; + cyls_times_heads = total_sectors / *secs_per_cyl; + } + + if (cyls_times_heads >= (*heads * 1024)) { + *secs_per_cyl = 63; + *heads = 16; + cyls_times_heads = total_sectors / *secs_per_cyl; + } + } + + // Note: Rounding up deviates from the Virtual PC behaviour + // However, we need this to avoid truncating images in qemu-img convert + *cyls = (cyls_times_heads + *heads - 1) / *heads; +} + +static int vpc_create(const char *filename, int64_t total_sectors, + const char *backing_file, int flags) +{ + uint8_t buf[1024]; + struct vhd_footer* footer = (struct vhd_footer*) buf; + struct vhd_dyndisk_header* dyndisk_header = + (struct vhd_dyndisk_header*) buf; + int fd, i; + uint16_t cyls; + uint8_t heads; + uint8_t secs_per_cyl; + size_t block_size, num_bat_entries; + + if (backing_file != NULL) + return -ENOTSUP; + + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644); + if (fd < 0) + return -EIO; + + // Calculate matching total_size and geometry + calculate_geometry(total_sectors, &cyls, &heads, &secs_per_cyl); + total_sectors = (int64_t) cyls * heads * secs_per_cyl; + + // Prepare the Hard Disk Footer + memset(buf, 0, 1024); + + strncpy(footer->creator, "conectix", 8); + // TODO Check if "qemu" creator_app is ok for VPC + strncpy(footer->creator_app, "qemu", 4); + strncpy(footer->creator_os, "Wi2k", 4); + + footer->features = be32_to_cpu(0x02); + footer->version = be32_to_cpu(0x00010000); + footer->data_offset = be64_to_cpu(HEADER_SIZE); + footer->timestamp = be32_to_cpu(time(NULL) - VHD_TIMESTAMP_BASE); + + // Version of Virtual PC 2007 + footer->major = be16_to_cpu(0x0005); + footer->minor =be16_to_cpu(0x0003); + + footer->orig_size = be64_to_cpu(total_sectors * 512); + footer->size = be64_to_cpu(total_sectors * 512); + + footer->cyls = be16_to_cpu(cyls); + footer->heads = heads; + footer->secs_per_cyl = secs_per_cyl; + + footer->type = be32_to_cpu(VHD_DYNAMIC); + + // TODO uuid is missing + + footer->checksum = be32_to_cpu(vpc_checksum(buf, HEADER_SIZE)); + + // Write the footer (twice: at the beginning and at the end) + block_size = 0x200000; + num_bat_entries = (total_sectors + block_size / 512) / (block_size / 512); + + if (write(fd, buf, HEADER_SIZE) != HEADER_SIZE) + return -EIO; + + if (lseek(fd, 1536 + ((num_bat_entries * 4 + 511) & ~511), SEEK_SET) < 0) + return -EIO; + if (write(fd, buf, HEADER_SIZE) != HEADER_SIZE) + return -EIO; + + // Write the initial BAT + if (lseek(fd, 3 * 512, SEEK_SET) < 0) + return -EIO; + + memset(buf, 0xFF, 512); + for (i = 0; i < (num_bat_entries * 4 + 511) / 512; i++) + if (write(fd, buf, 512) != 512) + return -EIO; + + + // Prepare the Dynamic Disk Header + memset(buf, 0, 1024); + + strncpy(dyndisk_header->magic, "cxsparse", 8); + + dyndisk_header->data_offset = be64_to_cpu(0xFFFFFFFF); + dyndisk_header->table_offset = be64_to_cpu(3 * 512); + dyndisk_header->version = be32_to_cpu(0x00010000); + dyndisk_header->block_size = be32_to_cpu(block_size); + dyndisk_header->max_table_entries = be32_to_cpu(num_bat_entries); + + dyndisk_header->checksum = be32_to_cpu(vpc_checksum(buf, 1024)); + + // Write the header + if (lseek(fd, 512, SEEK_SET) < 0) + return -EIO; + if (write(fd, buf, 1024) != 1024) + return -EIO; + + close(fd); + return 0; +} + static void vpc_close(BlockDriverState *bs) { BDRVVPCState *s = bs->opaque; @@ -425,4 +593,5 @@ BlockDriver bdrv_vpc = { vpc_read, vpc_write, vpc_close, + vpc_create, }; |