multiple snapshot support
authorbellard <bellard@c046a42c-6fe2-441c-8c8c-71466251a162>
Sat, 5 Aug 2006 21:31:00 +0000 (21:31 +0000)
committerbellard <bellard@c046a42c-6fe2-441c-8c8c-71466251a162>
Sat, 5 Aug 2006 21:31:00 +0000 (21:31 +0000)
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2086 c046a42c-6fe2-441c-8c8c-71466251a162

block.c
block_int.h
monitor.c
qemu-img.c
vl.c
vl.h

diff --git a/block.c b/block.c
index 3cf8b7b..529dd4e 100644 (file)
--- a/block.c
+++ b/block.c
@@ -494,12 +494,10 @@ int bdrv_write(BlockDriverState *bs, int64_t sector_num,
     }
 }
 
-#if 0
 /* not necessary now */
 static int bdrv_pread_em(BlockDriverState *bs, int64_t offset, 
-                         void *buf1, int count1)
+                         uint8_t *buf, int count1)
 {
-    uint8_t *buf = buf1;
     uint8_t tmp_buf[SECTOR_SIZE];
     int len, nb_sectors, count;
     int64_t sector_num;
@@ -542,9 +540,8 @@ static int bdrv_pread_em(BlockDriverState *bs, int64_t offset,
 }
 
 static int bdrv_pwrite_em(BlockDriverState *bs, int64_t offset, 
-                          const void *buf1, int count1)
+                          const uint8_t *buf, int count1)
 {
-    const uint8_t *buf = buf1;
     uint8_t tmp_buf[SECTOR_SIZE];
     int len, nb_sectors, count;
     int64_t sector_num;
@@ -589,7 +586,6 @@ static int bdrv_pwrite_em(BlockDriverState *bs, int64_t offset,
     }
     return count1;
 }
-#endif
 
 /**
  * Read with byte offsets (needed only for file protocols) 
@@ -602,7 +598,7 @@ int bdrv_pread(BlockDriverState *bs, int64_t offset,
     if (!drv)
         return -ENOENT;
     if (!drv->bdrv_pread)
-        return -ENOTSUP;
+        return bdrv_pread_em(bs, offset, buf1, count1);
     return drv->bdrv_pread(bs, offset, buf1, count1);
 }
 
@@ -617,7 +613,7 @@ int bdrv_pwrite(BlockDriverState *bs, int64_t offset,
     if (!drv)
         return -ENOENT;
     if (!drv->bdrv_pwrite)
-        return -ENOTSUP;
+        return bdrv_pwrite_em(bs, offset, buf1, count1);
     return drv->bdrv_pwrite(bs, offset, buf1, count1);
 }
 
@@ -859,6 +855,137 @@ void bdrv_get_backing_filename(BlockDriverState *bs,
     }
 }
 
+int bdrv_write_compressed(BlockDriverState *bs, int64_t sector_num, 
+                          const uint8_t *buf, int nb_sectors)
+{
+    BlockDriver *drv = bs->drv;
+    if (!drv)
+        return -ENOENT;
+    if (!drv->bdrv_write_compressed)
+        return -ENOTSUP;
+    return drv->bdrv_write_compressed(bs, sector_num, buf, nb_sectors);
+}
+    
+int bdrv_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
+{
+    BlockDriver *drv = bs->drv;
+    if (!drv)
+        return -ENOENT;
+    if (!drv->bdrv_get_info)
+        return -ENOTSUP;
+    memset(bdi, 0, sizeof(*bdi));
+    return drv->bdrv_get_info(bs, bdi);
+}
+
+/**************************************************************/
+/* handling of snapshots */
+
+int bdrv_snapshot_create(BlockDriverState *bs, 
+                         QEMUSnapshotInfo *sn_info)
+{
+    BlockDriver *drv = bs->drv;
+    if (!drv)
+        return -ENOENT;
+    if (!drv->bdrv_snapshot_create)
+        return -ENOTSUP;
+    return drv->bdrv_snapshot_create(bs, sn_info);
+}
+
+int bdrv_snapshot_goto(BlockDriverState *bs, 
+                       const char *snapshot_id)
+{
+    BlockDriver *drv = bs->drv;
+    if (!drv)
+        return -ENOENT;
+    if (!drv->bdrv_snapshot_goto)
+        return -ENOTSUP;
+    return drv->bdrv_snapshot_goto(bs, snapshot_id);
+}
+
+int bdrv_snapshot_delete(BlockDriverState *bs, const char *snapshot_id)
+{
+    BlockDriver *drv = bs->drv;
+    if (!drv)
+        return -ENOENT;
+    if (!drv->bdrv_snapshot_delete)
+        return -ENOTSUP;
+    return drv->bdrv_snapshot_delete(bs, snapshot_id);
+}
+
+int bdrv_snapshot_list(BlockDriverState *bs, 
+                       QEMUSnapshotInfo **psn_info)
+{
+    BlockDriver *drv = bs->drv;
+    if (!drv)
+        return -ENOENT;
+    if (!drv->bdrv_snapshot_list)
+        return -ENOTSUP;
+    return drv->bdrv_snapshot_list(bs, psn_info);
+}
+
+#define NB_SUFFIXES 4
+
+char *get_human_readable_size(char *buf, int buf_size, int64_t size)
+{
+    static const char suffixes[NB_SUFFIXES] = "KMGT";
+    int64_t base;
+    int i;
+
+    if (size <= 999) {
+        snprintf(buf, buf_size, "%" PRId64, size);
+    } else {
+        base = 1024;
+        for(i = 0; i < NB_SUFFIXES; i++) {
+            if (size < (10 * base)) {
+                snprintf(buf, buf_size, "%0.1f%c", 
+                         (double)size / base,
+                         suffixes[i]);
+                break;
+            } else if (size < (1000 * base) || i == (NB_SUFFIXES - 1)) {
+                snprintf(buf, buf_size, "%" PRId64 "%c", 
+                         ((size + (base >> 1)) / base),
+                         suffixes[i]);
+                break;
+            }
+            base = base * 1024;
+        }
+    }
+    return buf;
+}
+
+char *bdrv_snapshot_dump(char *buf, int buf_size, QEMUSnapshotInfo *sn)
+{
+    char buf1[128], date_buf[128], clock_buf[128];
+    struct tm tm;
+    time_t ti;
+    int64_t secs;
+
+    if (!sn) {
+        snprintf(buf, buf_size, 
+                 "%-10s%-20s%7s%20s%15s", 
+                 "ID", "TAG", "VM SIZE", "DATE", "VM CLOCK");
+    } else {
+        ti = sn->date_sec;
+        localtime_r(&ti, &tm);
+        strftime(date_buf, sizeof(date_buf),
+                 "%Y-%m-%d %H:%M:%S", &tm);
+        secs = sn->vm_clock_nsec / 1000000000;
+        snprintf(clock_buf, sizeof(clock_buf),
+                 "%02d:%02d:%02d.%03d",
+                 (int)(secs / 3600),
+                 (int)((secs / 60) % 60),
+                 (int)(secs % 60), 
+                 (int)((sn->vm_clock_nsec / 1000000) % 1000));
+        snprintf(buf, buf_size,
+                 "%-10s%-20s%7s%20s%15s", 
+                 sn->id_str, sn->name,
+                 get_human_readable_size(buf1, sizeof(buf1), sn->vm_state_size),
+                 date_buf,
+                 clock_buf);
+    }
+    return buf;
+}
+
 
 /**************************************************************/
 /* async I/Os */
@@ -1108,4 +1235,5 @@ void bdrv_init(void)
     bdrv_register(&bdrv_bochs);
     bdrv_register(&bdrv_vpc);
     bdrv_register(&bdrv_vvfat);
+    bdrv_register(&bdrv_qcow2);
 }
index e40503e..b3bc5b3 100644 (file)
@@ -57,6 +57,17 @@ struct BlockDriver {
                        const uint8_t *buf, int count);
     int (*bdrv_truncate)(BlockDriverState *bs, int64_t offset);
     int64_t (*bdrv_getlength)(BlockDriverState *bs);
+    int (*bdrv_write_compressed)(BlockDriverState *bs, int64_t sector_num, 
+                                 const uint8_t *buf, int nb_sectors);
+
+    int (*bdrv_snapshot_create)(BlockDriverState *bs, 
+                                QEMUSnapshotInfo *sn_info);
+    int (*bdrv_snapshot_goto)(BlockDriverState *bs, 
+                              const char *snapshot_id);
+    int (*bdrv_snapshot_delete)(BlockDriverState *bs, const char *snapshot_id);
+    int (*bdrv_snapshot_list)(BlockDriverState *bs, 
+                              QEMUSnapshotInfo **psn_info);
+    int (*bdrv_get_info)(BlockDriverState *bs, BlockDriverInfo *bdi);
 
     struct BlockDriver *next;
 };
index b48e8cc..b40eac4 100644 (file)
--- a/monitor.c
+++ b/monitor.c
@@ -380,18 +380,6 @@ static void do_log(const char *items)
     cpu_set_log(mask);
 }
 
-static void do_savevm(const char *filename)
-{
-    if (qemu_savevm(filename) < 0)
-        term_printf("I/O error when saving VM to '%s'\n", filename);
-}
-
-static void do_loadvm(const char *filename)
-{
-    if (qemu_loadvm(filename) < 0) 
-        term_printf("I/O error when loading VM from '%s'\n", filename);
-}
-
 static void do_stop(void)
 {
     vm_stop(EXCP_INTERRUPT);
@@ -1155,10 +1143,12 @@ static term_cmd_t term_cmds[] = {
       "filename", "save screen into PPM image 'filename'" },
     { "log", "s", do_log,
       "item1[,...]", "activate logging of the specified items to '/tmp/qemu.log'" }, 
-    { "savevm", "F", do_savevm,
-      "filename", "save the whole virtual machine state to 'filename'" }, 
-    { "loadvm", "F", do_loadvm,
-      "filename", "restore the whole virtual machine state from 'filename'" }, 
+    { "savevm", "s?", do_savevm,
+      "tag|id", "save a VM snapshot. If no tag or id are provided, a new snapshot is created" }, 
+    { "loadvm", "s", do_loadvm,
+      "tag|id", "restore a VM snapshot from its tag or id" }, 
+    { "delvm", "s", do_delvm,
+      "tag|id", "delete a VM snapshot from its tag or id" }, 
     { "stop", "", do_stop, 
       "", "stop emulation", },
     { "c|cont", "", do_cont, 
@@ -1241,6 +1231,8 @@ static term_cmd_t info_cmds[] = {
       "", "show profiling information", },
     { "capture", "", do_info_capture,
       "show capture information" },
+    { "snapshots", "", do_info_snapshots,
+      "show the currently saved VM snapshots" },
     { NULL, NULL, },
 };
 
index 8b2acad..06687bc 100644 (file)
@@ -159,36 +159,6 @@ void help(void)
     exit(1);
 }
 
-
-#define NB_SUFFIXES 4
-
-static void get_human_readable_size(char *buf, int buf_size, int64_t size)
-{
-    static const char suffixes[NB_SUFFIXES] = "KMGT";
-    int64_t base;
-    int i;
-
-    if (size <= 999) {
-        snprintf(buf, buf_size, "%" PRId64, size);
-    } else {
-        base = 1024;
-        for(i = 0; i < NB_SUFFIXES; i++) {
-            if (size < (10 * base)) {
-                snprintf(buf, buf_size, "%0.1f%c", 
-                         (double)size / base,
-                         suffixes[i]);
-                break;
-            } else if (size < (1000 * base) || i == (NB_SUFFIXES - 1)) {
-                snprintf(buf, buf_size, "%" PRId64 "%c", 
-                         ((size + (base >> 1)) / base),
-                         suffixes[i]);
-                break;
-            }
-            base = base * 1024;
-        }
-    }
-}
-
 #if defined(WIN32)
 /* XXX: put correct support for win32 */
 static int read_password(char *buf, int buf_size)
@@ -486,6 +456,7 @@ static int img_convert(int argc, char **argv)
     int64_t total_sectors, nb_sectors, sector_num;
     uint8_t buf[IO_BUF_SIZE];
     const uint8_t *buf1;
+    BlockDriverInfo bdi;
 
     fmt = NULL;
     out_fmt = "raw";
@@ -525,9 +496,9 @@ static int img_convert(int argc, char **argv)
     drv = bdrv_find_format(out_fmt);
     if (!drv)
         error("Unknown file format '%s'", fmt);
-    if (compress && drv != &bdrv_qcow)
+    if (compress && drv != &bdrv_qcow && drv != &bdrv_qcow2)
         error("Compression not supported for this file format");
-    if (encrypt && drv != &bdrv_qcow)
+    if (encrypt && drv != &bdrv_qcow && drv != &bdrv_qcow2)
         error("Encryption not supported for this file format");
     if (compress && encrypt)
         error("Compression and encryption not supported at the same time");
@@ -544,7 +515,9 @@ static int img_convert(int argc, char **argv)
     out_bs = bdrv_new_open(out_filename, out_fmt);
 
     if (compress) {
-        cluster_size = qcow_get_cluster_size(out_bs);
+        if (bdrv_get_info(out_bs, &bdi) < 0)
+            error("could not get block driver info");
+        cluster_size = bdi.cluster_size;
         if (cluster_size <= 0 || cluster_size > IO_BUF_SIZE)
             error("invalid cluster size");
         cluster_sectors = cluster_size >> 9;
@@ -562,12 +535,15 @@ static int img_convert(int argc, char **argv)
             if (n < cluster_sectors)
                 memset(buf + n * 512, 0, cluster_size - n * 512);
             if (is_not_zero(buf, cluster_size)) {
-                if (qcow_compress_cluster(out_bs, sector_num, buf) != 0)
+                if (bdrv_write_compressed(out_bs, sector_num, buf, 
+                                          cluster_sectors) != 0)
                     error("error while compressing sector %" PRId64,
                           sector_num);
             }
             sector_num += n;
         }
+        /* signal EOF to align */
+        bdrv_write_compressed(out_bs, 0, NULL, 0);
     } else {
         sector_num = 0;
         for(;;) {
@@ -630,6 +606,24 @@ static int64_t get_allocated_file_size(const char *filename)
 }
 #endif
 
+static void dump_snapshots(BlockDriverState *bs)
+{
+    QEMUSnapshotInfo *sn_tab, *sn;
+    int nb_sns, i;
+    char buf[256];
+
+    nb_sns = bdrv_snapshot_list(bs, &sn_tab);
+    if (nb_sns <= 0)
+        return;
+    printf("Snapshot list:\n");
+    printf("%s\n", bdrv_snapshot_dump(buf, sizeof(buf), NULL));
+    for(i = 0; i < nb_sns; i++) {
+        sn = &sn_tab[i];
+        printf("%s\n", bdrv_snapshot_dump(buf, sizeof(buf), sn));
+    }
+    qemu_free(sn_tab);
+}
+
 static int img_info(int argc, char **argv)
 {
     int c;
@@ -640,6 +634,7 @@ static int img_info(int argc, char **argv)
     int64_t total_sectors, allocated_size;
     char backing_filename[1024];
     char backing_filename2[1024];
+    BlockDriverInfo bdi;
 
     fmt = NULL;
     for(;;) {
@@ -690,13 +685,19 @@ static int img_info(int argc, char **argv)
            dsize_buf);
     if (bdrv_is_encrypted(bs))
         printf("encrypted: yes\n");
+    if (bdrv_get_info(bs, &bdi) >= 0) {
+        if (bdi.cluster_size != 0) 
+            printf("cluster_size: %d\n", bdi.cluster_size);
+    }
     bdrv_get_backing_filename(bs, backing_filename, sizeof(backing_filename));
-    if (backing_filename[0] != '\0')
+    if (backing_filename[0] != '\0') {
         path_combine(backing_filename2, sizeof(backing_filename2),
                      filename, backing_filename);
         printf("backing file: %s (actual path: %s)\n", 
                backing_filename,
                backing_filename2);
+    }
+    dump_snapshots(bs);
     bdrv_delete(bs);
     return 0;
 }
diff --git a/vl.c b/vl.c
index b31b239..6daed96 100644 (file)
--- a/vl.c
+++ b/vl.c
@@ -113,7 +113,11 @@ char phys_ram_file[1024];
 void *ioport_opaque[MAX_IOPORTS];
 IOPortReadFunc *ioport_read_table[3][MAX_IOPORTS];
 IOPortWriteFunc *ioport_write_table[3][MAX_IOPORTS];
-BlockDriverState *bs_table[MAX_DISKS], *fd_table[MAX_FD];
+/* Note: bs_table[MAX_DISKS] is a dummy block driver if none available
+   to store the VM snapshots */
+BlockDriverState *bs_table[MAX_DISKS + 1], *fd_table[MAX_FD];
+/* point to the block driver where the snapshots are managed */
+BlockDriverState *bs_snapshots;
 int vga_ram_size;
 int bios_size;
 static DisplayState display_state;
@@ -4085,14 +4089,190 @@ void qemu_del_wait_object(HANDLE handle, WaitObjectFunc *func, void *opaque)
 /***********************************************************/
 /* savevm/loadvm support */
 
+#define IO_BUF_SIZE 32768
+
+struct QEMUFile {
+    FILE *outfile;
+    BlockDriverState *bs;
+    int is_file;
+    int is_writable;
+    int64_t base_offset;
+    int64_t buf_offset; /* start of buffer when writing, end of buffer
+                           when reading */
+    int buf_index;
+    int buf_size; /* 0 when writing */
+    uint8_t buf[IO_BUF_SIZE];
+};
+
+QEMUFile *qemu_fopen(const char *filename, const char *mode)
+{
+    QEMUFile *f;
+
+    f = qemu_mallocz(sizeof(QEMUFile));
+    if (!f)
+        return NULL;
+    if (!strcmp(mode, "wb")) {
+        f->is_writable = 1;
+    } else if (!strcmp(mode, "rb")) {
+        f->is_writable = 0;
+    } else {
+        goto fail;
+    }
+    f->outfile = fopen(filename, mode);
+    if (!f->outfile)
+        goto fail;
+    f->is_file = 1;
+    return f;
+ fail:
+    if (f->outfile)
+        fclose(f->outfile);
+    qemu_free(f);
+    return NULL;
+}
+
+QEMUFile *qemu_fopen_bdrv(BlockDriverState *bs, int64_t offset, int is_writable)
+{
+    QEMUFile *f;
+
+    f = qemu_mallocz(sizeof(QEMUFile));
+    if (!f)
+        return NULL;
+    f->is_file = 0;
+    f->bs = bs;
+    f->is_writable = is_writable;
+    f->base_offset = offset;
+    return f;
+}
+
+void qemu_fflush(QEMUFile *f)
+{
+    if (!f->is_writable)
+        return;
+    if (f->buf_index > 0) {
+        if (f->is_file) {
+            fseek(f->outfile, f->buf_offset, SEEK_SET);
+            fwrite(f->buf, 1, f->buf_index, f->outfile);
+        } else {
+            bdrv_pwrite(f->bs, f->base_offset + f->buf_offset, 
+                        f->buf, f->buf_index);
+        }
+        f->buf_offset += f->buf_index;
+        f->buf_index = 0;
+    }
+}
+
+static void qemu_fill_buffer(QEMUFile *f)
+{
+    int len;
+
+    if (f->is_writable)
+        return;
+    if (f->is_file) {
+        fseek(f->outfile, f->buf_offset, SEEK_SET);
+        len = fread(f->buf, 1, IO_BUF_SIZE, f->outfile);
+        if (len < 0)
+            len = 0;
+    } else {
+        len = bdrv_pread(f->bs, f->base_offset + f->buf_offset, 
+                         f->buf, IO_BUF_SIZE);
+        if (len < 0)
+            len = 0;
+    }
+    f->buf_index = 0;
+    f->buf_size = len;
+    f->buf_offset += len;
+}
+
+void qemu_fclose(QEMUFile *f)
+{
+    if (f->is_writable)
+        qemu_fflush(f);
+    if (f->is_file) {
+        fclose(f->outfile);
+    }
+    qemu_free(f);
+}
+
 void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, int size)
 {
-    fwrite(buf, 1, size, f);
+    int l;
+    while (size > 0) {
+        l = IO_BUF_SIZE - f->buf_index;
+        if (l > size)
+            l = size;
+        memcpy(f->buf + f->buf_index, buf, l);
+        f->buf_index += l;
+        buf += l;
+        size -= l;
+        if (f->buf_index >= IO_BUF_SIZE)
+            qemu_fflush(f);
+    }
 }
 
 void qemu_put_byte(QEMUFile *f, int v)
 {
-    fputc(v, f);
+    f->buf[f->buf_index++] = v;
+    if (f->buf_index >= IO_BUF_SIZE)
+        qemu_fflush(f);
+}
+
+int qemu_get_buffer(QEMUFile *f, uint8_t *buf, int size1)
+{
+    int size, l;
+
+    size = size1;
+    while (size > 0) {
+        l = f->buf_size - f->buf_index;
+        if (l == 0) {
+            qemu_fill_buffer(f);
+            l = f->buf_size - f->buf_index;
+            if (l == 0)
+                break;
+        }
+        if (l > size)
+            l = size;
+        memcpy(buf, f->buf + f->buf_index, l);
+        f->buf_index += l;
+        buf += l;
+        size -= l;
+    }
+    return size1 - size;
+}
+
+int qemu_get_byte(QEMUFile *f)
+{
+    if (f->buf_index >= f->buf_size) {
+        qemu_fill_buffer(f);
+        if (f->buf_index >= f->buf_size)
+            return 0;
+    }
+    return f->buf[f->buf_index++];
+}
+
+int64_t qemu_ftell(QEMUFile *f)
+{
+    return f->buf_offset - f->buf_size + f->buf_index;
+}
+
+int64_t qemu_fseek(QEMUFile *f, int64_t pos, int whence)
+{
+    if (whence == SEEK_SET) {
+        /* nothing to do */
+    } else if (whence == SEEK_CUR) {
+        pos += qemu_ftell(f);
+    } else {
+        /* SEEK_END not supported */
+        return -1;
+    }
+    if (f->is_writable) {
+        qemu_fflush(f);
+        f->buf_offset = pos;
+    } else {
+        f->buf_offset = pos;
+        f->buf_index = 0;
+        f->buf_size = 0;
+    }
+    return pos;
 }
 
 void qemu_put_be16(QEMUFile *f, unsigned int v)
@@ -4115,21 +4295,6 @@ void qemu_put_be64(QEMUFile *f, uint64_t v)
     qemu_put_be32(f, v);
 }
 
-int qemu_get_buffer(QEMUFile *f, uint8_t *buf, int size)
-{
-    return fread(buf, 1, size, f);
-}
-
-int qemu_get_byte(QEMUFile *f)
-{
-    int v;
-    v = fgetc(f);
-    if (v == EOF)
-        return 0;
-    else
-        return v;
-}
-
 unsigned int qemu_get_be16(QEMUFile *f)
 {
     unsigned int v;
@@ -4156,18 +4321,6 @@ uint64_t qemu_get_be64(QEMUFile *f)
     return v;
 }
 
-int64_t qemu_ftell(QEMUFile *f)
-{
-    return ftell(f);
-}
-
-int64_t qemu_fseek(QEMUFile *f, int64_t pos, int whence)
-{
-    if (fseek(f, pos, whence) < 0)
-        return -1;
-    return ftell(f);
-}
-
 typedef struct SaveStateEntry {
     char idstr[256];
     int instance_id;
@@ -4209,25 +4362,18 @@ int register_savevm(const char *idstr,
 }
 
 #define QEMU_VM_FILE_MAGIC   0x5145564d
-#define QEMU_VM_FILE_VERSION 0x00000001
+#define QEMU_VM_FILE_VERSION 0x00000002
 
-int qemu_savevm(const char *filename)
+int qemu_savevm_state(QEMUFile *f)
 {
     SaveStateEntry *se;
-    QEMUFile *f;
-    int len, len_pos, cur_pos, saved_vm_running, ret;
-
-    saved_vm_running = vm_running;
-    vm_stop(0);
-
-    f = fopen(filename, "wb");
-    if (!f) {
-        ret = -1;
-        goto the_end;
-    }
+    int len, ret;
+    int64_t cur_pos, len_pos, total_len_pos;
 
     qemu_put_be32(f, QEMU_VM_FILE_MAGIC);
     qemu_put_be32(f, QEMU_VM_FILE_VERSION);
+    total_len_pos = qemu_ftell(f);
+    qemu_put_be64(f, 0); /* total size */
 
     for(se = first_se; se != NULL; se = se->next) {
         /* ID string */
@@ -4239,24 +4385,24 @@ int qemu_savevm(const char *filename)
         qemu_put_be32(f, se->version_id);
 
         /* record size: filled later */
-        len_pos = ftell(f);
+        len_pos = qemu_ftell(f);
         qemu_put_be32(f, 0);
         
         se->save_state(f, se->opaque);
 
         /* fill record size */
-        cur_pos = ftell(f);
-        len = ftell(f) - len_pos - 4;
-        fseek(f, len_pos, SEEK_SET);
+        cur_pos = qemu_ftell(f);
+        len = cur_pos - len_pos - 4;
+        qemu_fseek(f, len_pos, SEEK_SET);
         qemu_put_be32(f, len);
-        fseek(f, cur_pos, SEEK_SET);
+        qemu_fseek(f, cur_pos, SEEK_SET);
     }
+    cur_pos = qemu_ftell(f);
+    qemu_fseek(f, total_len_pos, SEEK_SET);
+    qemu_put_be64(f, cur_pos - total_len_pos - 8);
+    qemu_fseek(f, cur_pos, SEEK_SET);
 
-    fclose(f);
     ret = 0;
- the_end:
-    if (saved_vm_running)
-        vm_start();
     return ret;
 }
 
@@ -4272,38 +4418,29 @@ static SaveStateEntry *find_se(const char *idstr, int instance_id)
     return NULL;
 }
 
-int qemu_loadvm(const char *filename)
+int qemu_loadvm_state(QEMUFile *f)
 {
     SaveStateEntry *se;
-    QEMUFile *f;
-    int len, cur_pos, ret, instance_id, record_len, version_id;
-    int saved_vm_running;
+    int len, ret, instance_id, record_len, version_id;
+    int64_t total_len, end_pos, cur_pos;
     unsigned int v;
     char idstr[256];
     
-    saved_vm_running = vm_running;
-    vm_stop(0);
-
-    f = fopen(filename, "rb");
-    if (!f) {
-        ret = -1;
-        goto the_end;
-    }
-
     v = qemu_get_be32(f);
     if (v != QEMU_VM_FILE_MAGIC)
         goto fail;
     v = qemu_get_be32(f);
     if (v != QEMU_VM_FILE_VERSION) {
     fail:
-        fclose(f);
         ret = -1;
         goto the_end;
     }
+    total_len = qemu_get_be64(f);
+    end_pos = total_len + qemu_ftell(f);
     for(;;) {
-        len = qemu_get_byte(f);
-        if (feof(f))
+        if (qemu_ftell(f) >= end_pos)
             break;
+        len = qemu_get_byte(f);
         qemu_get_buffer(f, idstr, len);
         idstr[len] = '\0';
         instance_id = qemu_get_be32(f);
@@ -4313,7 +4450,7 @@ int qemu_loadvm(const char *filename)
         printf("idstr=%s instance=0x%x version=%d len=%d\n", 
                idstr, instance_id, version_id, record_len);
 #endif
-        cur_pos = ftell(f);
+        cur_pos = qemu_ftell(f);
         se = find_se(idstr, instance_id);
         if (!se) {
             fprintf(stderr, "qemu: warning: instance 0x%x of device '%s' not present in current VM\n", 
@@ -4328,12 +4465,281 @@ int qemu_loadvm(const char *filename)
         /* always seek to exact end of record */
         qemu_fseek(f, cur_pos + record_len, SEEK_SET);
     }
-    fclose(f);
     ret = 0;
  the_end:
+    return ret;
+}
+
+/* device can contain snapshots */
+static int bdrv_can_snapshot(BlockDriverState *bs)
+{
+    return (bs &&
+            !bdrv_is_removable(bs) &&
+            !bdrv_is_read_only(bs));
+}
+
+/* device must be snapshots in order to have a reliable snapshot */
+static int bdrv_has_snapshot(BlockDriverState *bs)
+{
+    return (bs &&
+            !bdrv_is_removable(bs) &&
+            !bdrv_is_read_only(bs));
+}
+
+static BlockDriverState *get_bs_snapshots(void)
+{
+    BlockDriverState *bs;
+    int i;
+
+    if (bs_snapshots)
+        return bs_snapshots;
+    for(i = 0; i <= MAX_DISKS; i++) {
+        bs = bs_table[i];
+        if (bdrv_can_snapshot(bs))
+            goto ok;
+    }
+    return NULL;
+ ok:
+    bs_snapshots = bs;
+    return bs;
+}
+
+static int bdrv_snapshot_find(BlockDriverState *bs, QEMUSnapshotInfo *sn_info,
+                              const char *name)
+{
+    QEMUSnapshotInfo *sn_tab, *sn;
+    int nb_sns, i, ret;
+    
+    ret = -ENOENT;
+    nb_sns = bdrv_snapshot_list(bs, &sn_tab);
+    if (nb_sns < 0)
+        return ret;
+    for(i = 0; i < nb_sns; i++) {
+        sn = &sn_tab[i];
+        if (!strcmp(sn->id_str, name) || !strcmp(sn->name, name)) {
+            *sn_info = *sn;
+            ret = 0;
+            break;
+        }
+    }
+    qemu_free(sn_tab);
+    return ret;
+}
+
+void do_savevm(const char *name)
+{
+    BlockDriverState *bs, *bs1;
+    QEMUSnapshotInfo sn1, *sn = &sn1, old_sn1, *old_sn = &old_sn1;
+    int must_delete, ret, i;
+    BlockDriverInfo bdi1, *bdi = &bdi1;
+    QEMUFile *f;
+    int saved_vm_running;
+    struct timeval tv;
+
+    bs = get_bs_snapshots();
+    if (!bs) {
+        term_printf("No block device can accept snapshots\n");
+        return;
+    }
+
+    saved_vm_running = vm_running;
+    vm_stop(0);
+    
+    must_delete = 0;
+    if (name) {
+        ret = bdrv_snapshot_find(bs, old_sn, name);
+        if (ret >= 0) {
+            must_delete = 1;
+        }
+    }
+    memset(sn, 0, sizeof(*sn));
+    if (must_delete) {
+        pstrcpy(sn->name, sizeof(sn->name), old_sn->name);
+        pstrcpy(sn->id_str, sizeof(sn->id_str), old_sn->id_str);
+    } else {
+        if (name)
+            pstrcpy(sn->name, sizeof(sn->name), name);
+    }
+
+    /* fill auxiliary fields */
+    gettimeofday(&tv, NULL);
+    sn->date_sec = tv.tv_sec;
+    sn->date_nsec = tv.tv_usec * 1000;
+    sn->vm_clock_nsec = qemu_get_clock(vm_clock);
+    
+    if (bdrv_get_info(bs, bdi) < 0 || bdi->vm_state_offset <= 0) {
+        term_printf("Device %s does not support VM state snapshots\n",
+                    bdrv_get_device_name(bs));
+        goto the_end;
+    }
+    
+    /* save the VM state */
+    f = qemu_fopen_bdrv(bs, bdi->vm_state_offset, 1);
+    if (!f) {
+        term_printf("Could not open VM state file\n");
+        goto the_end;
+    }
+    ret = qemu_savevm_state(f);
+    sn->vm_state_size = qemu_ftell(f);
+    qemu_fclose(f);
+    if (ret < 0) {
+        term_printf("Error %d while writing VM\n", ret);
+        goto the_end;
+    }
+    
+    /* create the snapshots */
+
+    for(i = 0; i < MAX_DISKS; i++) {
+        bs1 = bs_table[i];
+        if (bdrv_has_snapshot(bs1)) {
+            if (must_delete) {
+                ret = bdrv_snapshot_delete(bs1, old_sn->id_str);
+                if (ret < 0) {
+                    term_printf("Error while deleting snapshot on '%s'\n",
+                                bdrv_get_device_name(bs1));
+                }
+            }
+            ret = bdrv_snapshot_create(bs1, sn);
+            if (ret < 0) {
+                term_printf("Error while creating snapshot on '%s'\n",
+                            bdrv_get_device_name(bs1));
+            }
+        }
+    }
+
+ the_end:
     if (saved_vm_running)
         vm_start();
-    return ret;
+}
+
+void do_loadvm(const char *name)
+{
+    BlockDriverState *bs, *bs1;
+    BlockDriverInfo bdi1, *bdi = &bdi1;
+    QEMUFile *f;
+    int i, ret;
+    int saved_vm_running;
+
+    bs = get_bs_snapshots();
+    if (!bs) {
+        term_printf("No block device supports snapshots\n");
+        return;
+    }
+    
+    saved_vm_running = vm_running;
+    vm_stop(0);
+
+    for(i = 0; i <= MAX_DISKS; i++) {
+        bs1 = bs_table[i];
+        if (bdrv_has_snapshot(bs1)) {
+            ret = bdrv_snapshot_goto(bs1, name);
+            if (ret < 0) {
+                if (bs != bs1)
+                    term_printf("Warning: ");
+                switch(ret) {
+                case -ENOTSUP:
+                    term_printf("Snapshots not supported on device '%s'\n",
+                                bdrv_get_device_name(bs1));
+                    break;
+                case -ENOENT:
+                    term_printf("Could not find snapshot '%s' on device '%s'\n",
+                                name, bdrv_get_device_name(bs1));
+                    break;
+                default:
+                    term_printf("Error %d while activating snapshot on '%s'\n",
+                                ret, bdrv_get_device_name(bs1));
+                    break;
+                }
+                /* fatal on snapshot block device */
+                if (bs == bs1)
+                    goto the_end;
+            }
+        }
+    }
+
+    if (bdrv_get_info(bs, bdi) < 0 || bdi->vm_state_offset <= 0) {
+        term_printf("Device %s does not support VM state snapshots\n",
+                    bdrv_get_device_name(bs));
+        return;
+    }
+    
+    /* restore the VM state */
+    f = qemu_fopen_bdrv(bs, bdi->vm_state_offset, 0);
+    if (!f) {
+        term_printf("Could not open VM state file\n");
+        goto the_end;
+    }
+    ret = qemu_loadvm_state(f);
+    qemu_fclose(f);
+    if (ret < 0) {
+        term_printf("Error %d while loading VM state\n", ret);
+    }
+ the_end:
+    if (saved_vm_running)
+        vm_start();
+}
+
+void do_delvm(const char *name)
+{
+    BlockDriverState *bs, *bs1;
+    int i, ret;
+
+    bs = get_bs_snapshots();
+    if (!bs) {
+        term_printf("No block device supports snapshots\n");
+        return;
+    }
+    
+    for(i = 0; i <= MAX_DISKS; i++) {
+        bs1 = bs_table[i];
+        if (bdrv_has_snapshot(bs1)) {
+            ret = bdrv_snapshot_delete(bs1, name);
+            if (ret < 0) {
+                if (ret == -ENOTSUP)
+                    term_printf("Snapshots not supported on device '%s'\n",
+                                bdrv_get_device_name(bs1));
+                else
+                    term_printf("Error %d while deleting snapshot on '%s'\n",
+                                ret, bdrv_get_device_name(bs1));
+            }
+        }
+    }
+}
+
+void do_info_snapshots(void)
+{
+    BlockDriverState *bs, *bs1;
+    QEMUSnapshotInfo *sn_tab, *sn;
+    int nb_sns, i;
+    char buf[256];
+
+    bs = get_bs_snapshots();
+    if (!bs) {
+        term_printf("No available block device supports snapshots\n");
+        return;
+    }
+    term_printf("Snapshot devices:");
+    for(i = 0; i <= MAX_DISKS; i++) {
+        bs1 = bs_table[i];
+        if (bdrv_has_snapshot(bs1)) {
+            if (bs == bs1)
+                term_printf(" %s", bdrv_get_device_name(bs1));
+        }
+    }
+    term_printf("\n");
+
+    nb_sns = bdrv_snapshot_list(bs, &sn_tab);
+    if (nb_sns < 0) {
+        term_printf("bdrv_snapshot_list: error %d\n", nb_sns);
+        return;
+    }
+    term_printf("Snapshot list (from %s):\n", bdrv_get_device_name(bs));
+    term_printf("%s\n", bdrv_snapshot_dump(buf, sizeof(buf), NULL));
+    for(i = 0; i < nb_sns; i++) {
+        sn = &sn_tab[i];
+        term_printf("%s\n", bdrv_snapshot_dump(buf, sizeof(buf), sn));
+    }
+    qemu_free(sn_tab);
 }
 
 /***********************************************************/
@@ -6284,7 +6690,7 @@ int main(int argc, char **argv)
     } else 
 #endif
     if (loadvm)
-        qemu_loadvm(loadvm);
+        do_loadvm(loadvm);
 
     {
         /* XXX: simplify init */
diff --git a/vl.h b/vl.h
index d653f46..f717424 100644 (file)
--- a/vl.h
+++ b/vl.h
@@ -396,8 +396,11 @@ void cpu_disable_ticks(void);
 
 /* VM Load/Save */
 
-typedef FILE QEMUFile;
+typedef struct QEMUFile QEMUFile;
 
+QEMUFile *qemu_fopen(const char *filename, const char *mode);
+void qemu_fflush(QEMUFile *f);
+void qemu_fclose(QEMUFile *f);
 void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, int size);
 void qemu_put_byte(QEMUFile *f, int v);
 void qemu_put_be16(QEMUFile *f, unsigned int v);
@@ -467,8 +470,6 @@ int64_t qemu_fseek(QEMUFile *f, int64_t pos, int whence);
 typedef void SaveStateHandler(QEMUFile *f, void *opaque);
 typedef int LoadStateHandler(QEMUFile *f, void *opaque, int version_id);
 
-int qemu_loadvm(const char *filename);
-int qemu_savevm(const char *filename);
 int register_savevm(const char *idstr, 
                     int instance_id, 
                     int version_id,
@@ -481,6 +482,11 @@ void qemu_put_timer(QEMUFile *f, QEMUTimer *ts);
 void cpu_save(QEMUFile *f, void *opaque);
 int cpu_load(QEMUFile *f, void *opaque, int version_id);
 
+void do_savevm(const char *name);
+void do_loadvm(const char *name);
+void do_delvm(const char *name);
+void do_info_snapshots(void);
+
 /* bottom halves */
 typedef struct QEMUBH QEMUBH;
 typedef void QEMUBHFunc(void *opaque);
@@ -504,6 +510,25 @@ extern BlockDriver bdrv_dmg;
 extern BlockDriver bdrv_bochs;
 extern BlockDriver bdrv_vpc;
 extern BlockDriver bdrv_vvfat;
+extern BlockDriver bdrv_qcow2;
+
+typedef struct BlockDriverInfo {
+    /* in bytes, 0 if irrelevant */
+    int cluster_size; 
+    /* offset at which the VM state can be saved (0 if not possible) */
+    int64_t vm_state_offset; 
+} BlockDriverInfo;
+
+typedef struct QEMUSnapshotInfo {
+    char id_str[128]; /* unique snapshot id */
+    /* the following fields are informative. They are not needed for
+       the consistency of the snapshot */
+    char name[256]; /* user choosen name */
+    uint32_t vm_state_size; /* VM state info size */
+    uint32_t date_sec; /* UTC date of the snapshot */
+    uint32_t date_nsec;
+    uint64_t vm_clock_nsec; /* VM clock relative to boot */
+} QEMUSnapshotInfo;
 
 #define BDRV_O_RDONLY      0x0000
 #define BDRV_O_RDWR        0x0002
@@ -594,13 +619,22 @@ int bdrv_set_key(BlockDriverState *bs, const char *key);
 void bdrv_iterate_format(void (*it)(void *opaque, const char *name), 
                          void *opaque);
 const char *bdrv_get_device_name(BlockDriverState *bs);
+int bdrv_write_compressed(BlockDriverState *bs, int64_t sector_num, 
+                          const uint8_t *buf, int nb_sectors);
+int bdrv_get_info(BlockDriverState *bs, BlockDriverInfo *bdi);
 
-int qcow_get_cluster_size(BlockDriverState *bs);
-int qcow_compress_cluster(BlockDriverState *bs, int64_t sector_num,
-                          const uint8_t *buf);
 void bdrv_get_backing_filename(BlockDriverState *bs, 
                                char *filename, int filename_size);
-
+int bdrv_snapshot_create(BlockDriverState *bs, 
+                         QEMUSnapshotInfo *sn_info);
+int bdrv_snapshot_goto(BlockDriverState *bs, 
+                       const char *snapshot_id);
+int bdrv_snapshot_delete(BlockDriverState *bs, const char *snapshot_id);
+int bdrv_snapshot_list(BlockDriverState *bs, 
+                       QEMUSnapshotInfo **psn_info);
+char *bdrv_snapshot_dump(char *buf, int buf_size, QEMUSnapshotInfo *sn);
+
+char *get_human_readable_size(char *buf, int buf_size, int64_t size);
 int path_is_absolute(const char *path);
 void path_combine(char *dest, int dest_size,
                   const char *base_path,
@@ -824,7 +858,7 @@ void vnc_display_init(DisplayState *ds, int display);
 /* ide.c */
 #define MAX_DISKS 4
 
-extern BlockDriverState *bs_table[MAX_DISKS];
+extern BlockDriverState *bs_table[MAX_DISKS + 1];
 
 void isa_ide_init(int iobase, int iobase2, int irq,
                   BlockDriverState *hd0, BlockDriverState *hd1);