* Block driver for the VMDK format
*
* Copyright (c) 2004 Fabrice Bellard
+ * Copyright (c) 2005 Filip Navara
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
#include "vl.h"
#include "block_int.h"
-/* XXX: this code is untested */
-/* XXX: add write support */
-
#define VMDK3_MAGIC (('C' << 24) | ('O' << 16) | ('W' << 8) | 'D')
#define VMDK4_MAGIC (('K' << 24) | ('D' << 16) | ('M' << 8) | 'V')
int64_t grain_offset;
char filler[1];
char check_bytes[4];
-} VMDK4Header;
+} __attribute__((packed)) VMDK4Header;
#define L2_CACHE_SIZE 16
typedef struct BDRVVmdkState {
int fd;
int64_t l1_table_offset;
+ int64_t l1_backup_table_offset;
uint32_t *l1_table;
+ uint32_t *l1_backup_table;
unsigned int l1_size;
uint32_t l1_entry_sectors;
uint32_t magic;
int l1_size;
- fd = open(filename, O_RDONLY | O_BINARY | O_LARGEFILE);
- if (fd < 0)
- return -1;
+ fd = open(filename, O_RDWR | O_BINARY | O_LARGEFILE);
+ if (fd < 0) {
+ fd = open(filename, O_RDONLY | O_BINARY | O_LARGEFILE);
+ if (fd < 0)
+ return -1;
+ bs->read_only = 1;
+ }
if (read(fd, &magic, sizeof(magic)) != sizeof(magic))
goto fail;
- magic = le32_to_cpu(magic);
-
+ magic = be32_to_cpu(magic);
if (magic == VMDK3_MAGIC) {
VMDK3Header header;
if (read(fd, &header, sizeof(header)) !=
s->l2_size = 1 << 9;
s->l1_size = 1 << 6;
bs->total_sectors = le32_to_cpu(header.disk_sectors);
- s->l1_table_offset = le32_to_cpu(header.l1dir_offset) * 512;
+ s->l1_table_offset = le32_to_cpu(header.l1dir_offset) << 9;
+ s->l1_backup_table_offset = 0;
s->l1_entry_sectors = s->l2_size * s->cluster_sectors;
} else if (magic == VMDK4_MAGIC) {
VMDK4Header header;
if (read(fd, &header, sizeof(header)) != sizeof(header))
goto fail;
- bs->total_sectors = le32_to_cpu(header.capacity);
- s->cluster_sectors = le32_to_cpu(header.granularity);
+ bs->total_sectors = le64_to_cpu(header.capacity);
+ s->cluster_sectors = le64_to_cpu(header.granularity);
s->l2_size = le32_to_cpu(header.num_gtes_per_gte);
s->l1_entry_sectors = s->l2_size * s->cluster_sectors;
if (s->l1_entry_sectors <= 0)
goto fail;
s->l1_size = (bs->total_sectors + s->l1_entry_sectors - 1)
/ s->l1_entry_sectors;
- s->l1_table_offset = le64_to_cpu(header.rgd_offset) * 512;
+ s->l1_table_offset = le64_to_cpu(header.rgd_offset) << 9;
+ s->l1_backup_table_offset = le64_to_cpu(header.gd_offset) << 9;
} else {
goto fail;
}
s->l1_table = qemu_malloc(l1_size);
if (!s->l1_table)
goto fail;
- if (read(s->fd, s->l1_table, l1_size) != l1_size)
+ if (lseek(fd, s->l1_table_offset, SEEK_SET) == -1)
+ goto fail;
+ if (read(fd, s->l1_table, l1_size) != l1_size)
goto fail;
for(i = 0; i < s->l1_size; i++) {
le32_to_cpus(&s->l1_table[i]);
}
+ if (s->l1_backup_table_offset) {
+ s->l1_backup_table = qemu_malloc(l1_size);
+ if (!s->l1_backup_table)
+ goto fail;
+ if (lseek(fd, s->l1_backup_table_offset, SEEK_SET) == -1)
+ goto fail;
+ if (read(fd, s->l1_backup_table, l1_size) != l1_size)
+ goto fail;
+ for(i = 0; i < s->l1_size; i++) {
+ le32_to_cpus(&s->l1_backup_table[i]);
+ }
+ }
+
s->l2_cache = qemu_malloc(s->l2_size * L2_CACHE_SIZE * sizeof(uint32_t));
if (!s->l2_cache)
goto fail;
s->fd = fd;
- /* XXX: currently only read only */
- bs->read_only = 1;
return 0;
fail:
+ qemu_free(s->l1_backup_table);
qemu_free(s->l1_table);
qemu_free(s->l2_cache);
close(fd);
}
static uint64_t get_cluster_offset(BlockDriverState *bs,
- uint64_t offset)
+ uint64_t offset, int allocate)
{
BDRVVmdkState *s = bs->opaque;
unsigned int l1_index, l2_offset, l2_index;
int min_index, i, j;
- uint32_t min_count, *l2_table;
+ uint32_t min_count, *l2_table, tmp;
uint64_t cluster_offset;
l1_index = (offset >> 9) / s->l1_entry_sectors;
l2_offset = s->l1_table[l1_index];
if (!l2_offset)
return 0;
-
for(i = 0; i < L2_CACHE_SIZE; i++) {
if (l2_offset == s->l2_cache_offsets[i]) {
/* increment the hit count */
found:
l2_index = ((offset >> 9) / s->cluster_sectors) % s->l2_size;
cluster_offset = le32_to_cpu(l2_table[l2_index]);
+ if (!cluster_offset) {
+ if (!allocate)
+ return 0;
+ cluster_offset = lseek(s->fd, 0, SEEK_END);
+ ftruncate(s->fd, cluster_offset + (s->cluster_sectors << 9));
+ cluster_offset >>= 9;
+ /* update L2 table */
+ tmp = cpu_to_le32(cluster_offset);
+ l2_table[l2_index] = tmp;
+ lseek(s->fd, ((int64_t)l2_offset * 512) + (l2_index * sizeof(tmp)), SEEK_SET);
+ if (write(s->fd, &tmp, sizeof(tmp)) != sizeof(tmp))
+ return 0;
+ /* update backup L2 table */
+ if (s->l1_backup_table_offset != 0) {
+ l2_offset = s->l1_backup_table[l1_index];
+ lseek(s->fd, ((int64_t)l2_offset * 512) + (l2_index * sizeof(tmp)), SEEK_SET);
+ if (write(s->fd, &tmp, sizeof(tmp)) != sizeof(tmp))
+ return 0;
+ }
+ }
cluster_offset <<= 9;
return cluster_offset;
}
int index_in_cluster, n;
uint64_t cluster_offset;
- cluster_offset = get_cluster_offset(bs, sector_num << 9);
+ cluster_offset = get_cluster_offset(bs, sector_num << 9, 0);
index_in_cluster = sector_num % s->cluster_sectors;
n = s->cluster_sectors - index_in_cluster;
if (n > nb_sectors)
uint64_t cluster_offset;
while (nb_sectors > 0) {
- cluster_offset = get_cluster_offset(bs, sector_num << 9);
+ cluster_offset = get_cluster_offset(bs, sector_num << 9, 0);
index_in_cluster = sector_num % s->cluster_sectors;
n = s->cluster_sectors - index_in_cluster;
if (n > nb_sectors)
if (!cluster_offset) {
memset(buf, 0, 512 * n);
} else {
- lseek64(s->fd, cluster_offset + index_in_cluster * 512, SEEK_SET);
+ lseek(s->fd, cluster_offset + index_in_cluster * 512, SEEK_SET);
ret = read(s->fd, buf, n * 512);
if (ret != n * 512)
return -1;
static int vmdk_write(BlockDriverState *bs, int64_t sector_num,
const uint8_t *buf, int nb_sectors)
{
- return -1;
+ BDRVVmdkState *s = bs->opaque;
+ int ret, index_in_cluster, n;
+ uint64_t cluster_offset;
+
+ while (nb_sectors > 0) {
+ index_in_cluster = sector_num & (s->cluster_sectors - 1);
+ n = s->cluster_sectors - index_in_cluster;
+ if (n > nb_sectors)
+ n = nb_sectors;
+ cluster_offset = get_cluster_offset(bs, sector_num << 9, 1);
+ if (!cluster_offset)
+ return -1;
+ lseek(s->fd, cluster_offset + index_in_cluster * 512, SEEK_SET);
+ ret = write(s->fd, buf, n * 512);
+ if (ret != n * 512)
+ return -1;
+ nb_sectors -= n;
+ sector_num += n;
+ buf += n * 512;
+ }
+ return 0;
+}
+
+static int vmdk_create(const char *filename, int64_t total_size,
+ const char *backing_file, int flags)
+{
+ int fd, i;
+ VMDK4Header header;
+ uint32_t tmp, magic, grains, gd_size, gt_size, gt_count;
+ char *desc_template =
+ "# Disk DescriptorFile\n"
+ "version=1\n"
+ "CID=%x\n"
+ "parentCID=ffffffff\n"
+ "createType=\"monolithicSparse\"\n"
+ "\n"
+ "# Extent description\n"
+ "RW %lu SPARSE \"%s\"\n"
+ "\n"
+ "# The Disk Data Base \n"
+ "#DDB\n"
+ "\n"
+ "ddb.virtualHWVersion = \"3\"\n"
+ "ddb.geometry.cylinders = \"%lu\"\n"
+ "ddb.geometry.heads = \"16\"\n"
+ "ddb.geometry.sectors = \"63\"\n"
+ "ddb.adapterType = \"ide\"\n";
+ char desc[1024];
+ const char *real_filename, *temp_str;
+
+ /* XXX: add support for backing file */
+
+ fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_LARGEFILE,
+ 0644);
+ if (fd < 0)
+ return -1;
+ magic = cpu_to_be32(VMDK4_MAGIC);
+ memset(&header, 0, sizeof(header));
+ header.version = cpu_to_le32(1);
+ header.flags = cpu_to_le32(3); /* ?? */
+ header.capacity = cpu_to_le64(total_size);
+ header.granularity = cpu_to_le64(128);
+ header.num_gtes_per_gte = cpu_to_le32(512);
+
+ grains = (total_size + header.granularity - 1) / header.granularity;
+ gt_size = ((header.num_gtes_per_gte * sizeof(uint32_t)) + 511) >> 9;
+ gt_count = (grains + header.num_gtes_per_gte - 1) / header.num_gtes_per_gte;
+ gd_size = (gt_count * sizeof(uint32_t) + 511) >> 9;
+
+ header.desc_offset = 1;
+ header.desc_size = 20;
+ header.rgd_offset = header.desc_offset + header.desc_size;
+ header.gd_offset = header.rgd_offset + gd_size + (gt_size * gt_count);
+ header.grain_offset =
+ ((header.gd_offset + gd_size + (gt_size * gt_count) +
+ header.granularity - 1) / header.granularity) *
+ header.granularity;
+
+ header.desc_offset = cpu_to_le64(header.desc_offset);
+ header.desc_size = cpu_to_le64(header.desc_size);
+ header.rgd_offset = cpu_to_le64(header.rgd_offset);
+ header.gd_offset = cpu_to_le64(header.gd_offset);
+ header.grain_offset = cpu_to_le64(header.grain_offset);
+
+ header.check_bytes[0] = 0xa;
+ header.check_bytes[1] = 0x20;
+ header.check_bytes[2] = 0xd;
+ header.check_bytes[3] = 0xa;
+
+ /* write all the data */
+ write(fd, &magic, sizeof(magic));
+ write(fd, &header, sizeof(header));
+
+ ftruncate(fd, header.grain_offset << 9);
+
+ /* write grain directory */
+ lseek(fd, le64_to_cpu(header.rgd_offset) << 9, SEEK_SET);
+ for (i = 0, tmp = header.rgd_offset + gd_size;
+ i < gt_count; i++, tmp += gt_size)
+ write(fd, &tmp, sizeof(tmp));
+
+ /* write backup grain directory */
+ lseek(fd, le64_to_cpu(header.gd_offset) << 9, SEEK_SET);
+ for (i = 0, tmp = header.gd_offset + gd_size;
+ i < gt_count; i++, tmp += gt_size)
+ write(fd, &tmp, sizeof(tmp));
+
+ /* compose the descriptor */
+ real_filename = filename;
+ if ((temp_str = strrchr(real_filename, '\\')) != NULL)
+ real_filename = temp_str + 1;
+ if ((temp_str = strrchr(real_filename, '/')) != NULL)
+ real_filename = temp_str + 1;
+ if ((temp_str = strrchr(real_filename, ':')) != NULL)
+ real_filename = temp_str + 1;
+ sprintf(desc, desc_template, time(NULL), (unsigned long)total_size,
+ real_filename, total_size / (63 * 16));
+
+ /* write the descriptor */
+ lseek(fd, le64_to_cpu(header.desc_offset) << 9, SEEK_SET);
+ write(fd, desc, strlen(desc));
+
+ close(fd);
+ return 0;
}
-static int vmdk_close(BlockDriverState *bs)
+static void vmdk_close(BlockDriverState *bs)
{
BDRVVmdkState *s = bs->opaque;
qemu_free(s->l1_table);
vmdk_read,
vmdk_write,
vmdk_close,
- NULL, /* no create yet */
+ vmdk_create,
vmdk_is_allocated,
};