separate alias_addr (10.0.2.2) from our_addr (Ed Swierk)
[qemu] / block-vmdk.c
index d1414e4..fc87be3 100644 (file)
@@ -2,6 +2,7 @@
  * 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
@@ -24,9 +25,6 @@
 #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')
 
@@ -56,14 +54,16 @@ typedef struct {
     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;
 
@@ -96,13 +96,16 @@ static int vmdk_open(BlockDriverState *bs, const char *filename)
     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)) != 
@@ -112,22 +115,24 @@ static int vmdk_open(BlockDriverState *bs, const char *filename)
         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;
     }
@@ -136,20 +141,34 @@ static int vmdk_open(BlockDriverState *bs, const char *filename)
     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);
@@ -157,12 +176,12 @@ static int vmdk_open(BlockDriverState *bs, const char *filename)
 }
 
 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;
@@ -171,7 +190,6 @@ static uint64_t get_cluster_offset(BlockDriverState *bs,
     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 */
@@ -203,6 +221,26 @@ static uint64_t get_cluster_offset(BlockDriverState *bs,
  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;
 }
@@ -214,7 +252,7 @@ static int vmdk_is_allocated(BlockDriverState *bs, int64_t sector_num,
     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)
@@ -231,7 +269,7 @@ static int vmdk_read(BlockDriverState *bs, int64_t sector_num,
     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)
@@ -239,7 +277,7 @@ static int vmdk_read(BlockDriverState *bs, int64_t sector_num,
         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;
@@ -254,10 +292,133 @@ static int vmdk_read(BlockDriverState *bs, int64_t sector_num,
 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);
@@ -273,6 +434,6 @@ BlockDriver bdrv_vmdk = {
     vmdk_read,
     vmdk_write,
     vmdk_close,
-    NULL, /* no create yet */
+    vmdk_create,
     vmdk_is_allocated,
 };