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
This commit is contained in:
aliguori 2009-01-26 20:27:06 +00:00
parent 15d35bc557
commit 57c7d9e579
1 changed files with 169 additions and 0 deletions

View File

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