>From b22afc3464ec124d131ab21fc4270ef575c7b47d Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Fri, 18 Dec 2009 15:30:03 +0000 Subject: [PATCH 1/2] VMDK4: Parse the VMDK descriptor explicitly, improve handling of CIDs The VMDK monolithic file format[1] contains an embedded text descriptor. This is at a variable location in the file, pointed to by fields in the header. An example of this embedded descriptor might be: # Disk DescriptorFile version=1 CID=12345678 parentCID=87654321 createType="monolithicSparse" # Extent description RW 16777216 SPARSE "generated-stream.vmdk" The current code assumes the descriptor is at offset 0x200 with a fixed size, and doesn't really parse the descriptor properly eg when reading and writing the CID and parentCID fields. This patch contains code to properly locate and parse the embedded descriptor in monolithic files (qemu doesn't support "split" VMDK files at all). Also this modifies the code which updates the CID fields so it doesn't rewrite the whole of the descriptor. [1] The VMDK 1.1 (ie. "VMDK4") format is partially described here: http://www.vmware.com/app/vmdk/?src=vmdk [PDF link] Signed-off-by: Richard Jones --- block/vmdk.c | 174 +++++++++++++++++++++++++++++++++++++++++++++++---------- 1 files changed, 143 insertions(+), 31 deletions(-) diff --git a/block/vmdk.c b/block/vmdk.c index 4e48622..0f59ea5 100644 --- a/block/vmdk.c +++ b/block/vmdk.c @@ -77,6 +77,10 @@ typedef struct BDRVVmdkState { unsigned int cluster_sectors; uint32_t parent_cid; int is_parent; + + /* VMDK4 fields */ + int64_t cid_offset; + int64_t parent_cid_offset; } BDRVVmdkState; typedef struct VmdkMetaData { @@ -112,57 +116,160 @@ static int vmdk_probe(const uint8_t *buf, int buf_size, const char *filename) #define CHECK_CID 1 #define SECTOR_SIZE 512 -#define DESC_SIZE 20*SECTOR_SIZE // 20 sectors of 512 bytes each #define HEADER_SIZE 512 // first sector of 512 bytes +/* Read and parse the VMDK4 descriptor. + * + * So-called "monolithic" VMDK4 files (which are the only ones we can + * currently read) have a text descriptor section embedded within the + * file at an offset described in the header. + * + * The other format for VMDK4 files ("split") has a toplevel .vmdk + * file which is the plain text descriptor, and that points to other + * files that contain the disk data. We can't read this sort of file + * yet. + */ +#define MAX_DESC_SIZE 100*512 + +#define VMDK4_ACCESS_RW 1 +#define VMDK4_ACCESS_RDONLY 2 +#define VMDK4_ACCESS_NOACCESS 3 + +static int vmdk4_parse_desc_extent(BlockDriverState *bs, VMDK4Header *header, + int access_type, char *p) +{ + /* We should probably check that the monolithic file + * has only one extent. + */ + return 0; +} + +static int vmdk4_parse_desc_assignment(BlockDriverState *bs, + VMDK4Header *header, char *p, size_t len, + int64_t file_offset) +{ + BDRVVmdkState *s = bs->opaque; + + if (strncmp(p, "version=", 8) == 0 && strcmp(p, "version=1") != 0) + fprintf (stderr, + "warning: vmdk4_parse_desc: descriptor version != 1 (%s)\n", + p); + + if (!strncmp(p, "CID=", 4)) + s->cid_offset = file_offset + 4; + + if (!strncmp(p, "parentCID=", 10)) + s->parent_cid_offset = file_offset + 10; + + return 0; +} + +static int vmdk4_parse_desc(BlockDriverState *bs, VMDK4Header *header) +{ + BDRVVmdkState *s = bs->opaque; + int64_t desc_offset, desc_size; + char desc[MAX_DESC_SIZE]; + char *p, *pnext; + size_t len; + + s->cid_offset = 0; + s->parent_cid_offset = 0; + + /* Stored in the header are the offset/size in sectors. */ + desc_size = le64_to_cpu(header->desc_size) * SECTOR_SIZE; + desc_offset = le64_to_cpu(header->desc_offset) * SECTOR_SIZE; + + if (desc_size > MAX_DESC_SIZE) { + fprintf (stderr, + "vmdk4_parse_desc: descriptor size > maximum size " + "(%" PRIi64 " > %d)\n", desc_size, MAX_DESC_SIZE); + return -1; + } + + if (bdrv_pread(s->hd, desc_offset, (uint8_t *)desc, desc_size) != desc_size) { + fprintf (stderr, + "vmdk4_parse_desc: could not read descriptor from " + "%" PRIi64 " size %" PRIi64 "\n", + desc_offset, desc_size); + return -1; + } + + if (!strncmp(desc, "# Disk DescriptorFile", 22)) { + fprintf (stderr, "vmdk4_parse_desc: unrecognized descriptor"); + return -1; + } + + /* Parse the descriptor line by line. */ + for (p = desc; p && *p; p = pnext) { + pnext = strchr (p, '\n'); + if (pnext != NULL) { + *pnext = '\0'; + pnext++; + } + + while (*p && isspace (*p)) + p++; + while ((len = strlen (p)) > 0 && isspace (p[len-1])) + p[len-1] = '\0'; + + if (!*p) + continue; + if (p[0] == '#') + continue; + + /* Ignore any lines that we can't parse. */ + if (!strncmp(p, "RW ", 3)) + vmdk4_parse_desc_extent (bs, header, VMDK4_ACCESS_RW, p + 3); + else if (!strncmp(p, "RDONLY ", 7)) + vmdk4_parse_desc_extent (bs, header, VMDK4_ACCESS_RDONLY, p + 7); + else if (!strncmp(p, "NOACCESS ", 9)) + vmdk4_parse_desc_extent (bs, header, VMDK4_ACCESS_NOACCESS, p + 9); + else /* else expecting name = value */ + vmdk4_parse_desc_assignment (bs, header, p, len, + p-desc+desc_offset); + } + + return 0; +} + static uint32_t vmdk_read_cid(BlockDriverState *bs, int parent) { BDRVVmdkState *s = bs->opaque; - char desc[DESC_SIZE]; + int64_t offset; + char str[9]; uint32_t cid; - const char *p_name, *cid_str; - size_t cid_str_size; - /* the descriptor offset = 0x200 */ - if (bdrv_pread(s->hd, 0x200, desc, DESC_SIZE) != DESC_SIZE) - return 0; + if (parent) + offset = s->parent_cid_offset; + else + offset = s->cid_offset; - if (parent) { - cid_str = "parentCID"; - cid_str_size = sizeof("parentCID"); - } else { - cid_str = "CID"; - cid_str_size = sizeof("CID"); - } + if (offset == 0) + return 0; - if ((p_name = strstr(desc,cid_str)) != NULL) { - p_name += cid_str_size; - sscanf(p_name,"%x",&cid); - } + if (bdrv_pread(s->hd, offset, str, 8) != 8) + return 0; + str[8] = '\0'; + sscanf(str, "%x", &cid); return cid; } static int vmdk_write_cid(BlockDriverState *bs, uint32_t cid) { BDRVVmdkState *s = bs->opaque; - char desc[DESC_SIZE], tmp_desc[DESC_SIZE]; - char *p_name, *tmp_str; + int64_t offset; + char str[9]; - /* the descriptor offset = 0x200 */ - if (bdrv_pread(s->hd, 0x200, desc, DESC_SIZE) != DESC_SIZE) - return -1; + offset = s->cid_offset; + if (offset == 0) + return 0; - tmp_str = strstr(desc,"parentCID"); - pstrcpy(tmp_desc, sizeof(tmp_desc), tmp_str); - if ((p_name = strstr(desc,"CID")) != NULL) { - p_name += sizeof("CID"); - snprintf(p_name, sizeof(desc) - (p_name - desc), "%x\n", cid); - pstrcat(desc, sizeof(desc), tmp_desc); - } + snprintf(str, sizeof str, "%08x", cid); - if (bdrv_pwrite(s->hd, 0x200, desc, DESC_SIZE) != DESC_SIZE) + if (bdrv_pwrite(s->hd, offset, str, 8) != 8) return -1; + return 0; } @@ -184,6 +291,8 @@ static int vmdk_is_cid_valid(BlockDriverState *bs) return 1; } +#define DESC_SIZE 20*SECTOR_SIZE + static int vmdk_snapshot_create(const char *filename, const char *backing_file) { int snp_fd, p_fd; @@ -410,6 +519,9 @@ static int vmdk_open(BlockDriverState *bs, const char *filename, int flags) s->l1_table_offset = le64_to_cpu(header.rgd_offset) << 9; s->l1_backup_table_offset = le64_to_cpu(header.gd_offset) << 9; + if (vmdk4_parse_desc(bs, &header) == -1) + goto fail; + if (parent_open) s->is_parent = 1; else -- 1.6.5.2