int qcow2_snapshot_delete(BlockDriverState *bs, const char *snapshot_id) { BDRVQcowState *s = bs->opaque; QCowSnapshot *sn; int snapshot_index, ret; snapshot_index = find_snapshot_by_id_or_name(bs, snapshot_id); if (snapshot_index < 0) return -ENOENT; sn = &s->snapshots[snapshot_index]; ret = qcow2_update_snapshot_refcount(bs, sn->l1_table_offset, sn->l1_size, -1); if (ret < 0) return ret; /* must update the copied flag on the current cluster offsets */ ret = qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, 0); if (ret < 0) return ret; qcow2_free_clusters(bs, sn->l1_table_offset, sn->l1_size * sizeof(uint64_t)); g_free(sn->id_str); g_free(sn->name); memmove(sn, sn + 1, (s->nb_snapshots - snapshot_index - 1) * sizeof(*sn)); s->nb_snapshots--; ret = qcow2_write_snapshots(bs); if (ret < 0) { /* XXX: restore snapshot if error ? */ return ret; } #ifdef DEBUG_ALLOC qcow2_check_refcounts(bs); #endif return 0; }
int qcow2_snapshot_delete(BlockDriverState *bs, const char *snapshot_id) { BDRVQcowState *s = bs->opaque; QCowSnapshot sn; int snapshot_index, ret; /* Search the snapshot */ snapshot_index = find_snapshot_by_id_or_name(bs, snapshot_id); if (snapshot_index < 0) { return -ENOENT; } sn = s->snapshots[snapshot_index]; /* Remove it from the snapshot list */ memmove(s->snapshots + snapshot_index, s->snapshots + snapshot_index + 1, (s->nb_snapshots - snapshot_index - 1) * sizeof(sn)); s->nb_snapshots--; ret = qcow2_write_snapshots(bs); if (ret < 0) { return ret; } /* * The snapshot is now unused, clean up. If we fail after this point, we * won't recover but just leak clusters. */ g_free(sn.id_str); g_free(sn.name); /* * Now decrease the refcounts of clusters referenced by the snapshot and * free the L1 table. */ ret = qcow2_update_snapshot_refcount(bs, sn.l1_table_offset, sn.l1_size, -1); if (ret < 0) { return ret; } qcow2_free_clusters(bs, sn.l1_table_offset, sn.l1_size * sizeof(uint64_t), QCOW2_DISCARD_SNAPSHOT); /* must update the copied flag on the current cluster offsets */ ret = qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, 0); if (ret < 0) { return ret; } #ifdef DEBUG_ALLOC { BdrvCheckResult result = {0}; qcow2_check_refcounts(bs, &result, 0); } #endif return 0; }
/* copy the snapshot 'snapshot_name' into the current disk image */ int qcow2_snapshot_goto(BlockDriverState *bs, const char *snapshot_id) { BDRVQcowState *s = bs->opaque; QCowSnapshot *sn; int i, snapshot_index; int cur_l1_bytes, sn_l1_bytes; snapshot_index = find_snapshot_by_id_or_name(bs, snapshot_id); if (snapshot_index < 0) return -ENOENT; sn = &s->snapshots[snapshot_index]; if (qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, -1) < 0) goto fail; if (qcow2_grow_l1_table(bs, sn->l1_size, true) < 0) goto fail; cur_l1_bytes = s->l1_size * sizeof(uint64_t); sn_l1_bytes = sn->l1_size * sizeof(uint64_t); if (cur_l1_bytes > sn_l1_bytes) { memset(s->l1_table + sn->l1_size, 0, cur_l1_bytes - sn_l1_bytes); } /* copy the snapshot l1 table to the current l1 table */ if (bdrv_pread(bs->file, sn->l1_table_offset, s->l1_table, sn_l1_bytes) < 0) goto fail; if (bdrv_pwrite_sync(bs->file, s->l1_table_offset, s->l1_table, cur_l1_bytes) < 0) goto fail; for(i = 0; i < s->l1_size; i++) { be64_to_cpus(&s->l1_table[i]); } if (qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, 1) < 0) goto fail; #ifdef DEBUG_ALLOC qcow2_check_refcounts(bs); #endif return 0; fail: return -EIO; }
/* copy the snapshot 'snapshot_name' into the current disk image */ int qcow2_snapshot_goto(BlockDriverState *bs, const char *snapshot_id) { BDRVQcowState *s = bs->opaque; QCowSnapshot *sn; int i, snapshot_index, l1_size2; snapshot_index = find_snapshot_by_id_or_name(bs, snapshot_id); if (snapshot_index < 0) return -ENOENT; sn = &s->snapshots[snapshot_index]; if (!bs->read_only && qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, -1) < 0) goto fail; if (qcow2_grow_l1_table(bs, sn->l1_size) < 0) goto fail; s->l1_size = sn->l1_size; l1_size2 = s->l1_size * sizeof(uint64_t); /* copy the snapshot l1 table to the current l1 table */ if (bdrv_pread(s->hd, sn->l1_table_offset, s->l1_table, l1_size2) != l1_size2) goto fail; if (bs->read_only) s->l1_table_offset = sn->l1_table_offset; else if (bdrv_pwrite(s->hd, s->l1_table_offset, s->l1_table, l1_size2) != l1_size2) goto fail; for(i = 0;i < s->l1_size; i++) { be64_to_cpus(&s->l1_table[i]); } if (!bs->read_only && qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, 1) < 0) goto fail; #ifdef DEBUG_ALLOC qcow2_check_refcounts(bs); #endif return 0; fail: return -EIO; }
/* if no id is provided, a new one is constructed */ int qcow2_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info) { BDRVQcowState *s = bs->opaque; QCowSnapshot *snapshots1, sn1, *sn = &sn1; int i, ret; uint64_t *l1_table = NULL; memset(sn, 0, sizeof(*sn)); if (sn_info->id_str[0] == '\0') { /* compute a new id */ find_new_snapshot_id(bs, sn_info->id_str, sizeof(sn_info->id_str)); } /* check that the ID is unique */ if (find_snapshot_by_id(bs, sn_info->id_str) >= 0) return -ENOENT; sn->id_str = qemu_strdup(sn_info->id_str); if (!sn->id_str) goto fail; sn->name = qemu_strdup(sn_info->name); if (!sn->name) goto fail; sn->vm_state_size = sn_info->vm_state_size; sn->date_sec = sn_info->date_sec; sn->date_nsec = sn_info->date_nsec; sn->vm_clock_nsec = sn_info->vm_clock_nsec; ret = qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, 1); if (ret < 0) goto fail; /* create the L1 table of the snapshot */ sn->l1_table_offset = qcow2_alloc_clusters(bs, s->l1_size * sizeof(uint64_t)); sn->l1_size = s->l1_size; l1_table = qemu_malloc(s->l1_size * sizeof(uint64_t)); for(i = 0; i < s->l1_size; i++) { l1_table[i] = cpu_to_be64(s->l1_table[i]); } if (bdrv_pwrite(s->hd, sn->l1_table_offset, l1_table, s->l1_size * sizeof(uint64_t)) != (s->l1_size * sizeof(uint64_t))) goto fail; qemu_free(l1_table); l1_table = NULL; snapshots1 = qemu_malloc((s->nb_snapshots + 1) * sizeof(QCowSnapshot)); if (s->snapshots) { memcpy(snapshots1, s->snapshots, s->nb_snapshots * sizeof(QCowSnapshot)); qemu_free(s->snapshots); } s->snapshots = snapshots1; s->snapshots[s->nb_snapshots++] = *sn; if (qcow_write_snapshots(bs) < 0) goto fail; #ifdef DEBUG_ALLOC qcow2_check_refcounts(bs); #endif return 0; fail: qemu_free(sn->name); qemu_free(l1_table); return -1; }
/* copy the snapshot 'snapshot_name' into the current disk image */ int qcow2_snapshot_goto(BlockDriverState *bs, const char *snapshot_id) { BDRVQcowState *s = bs->opaque; QCowSnapshot *sn; int i, snapshot_index; int cur_l1_bytes, sn_l1_bytes; int ret; uint64_t *sn_l1_table = NULL; /* Search the snapshot */ snapshot_index = find_snapshot_by_id_or_name(bs, snapshot_id); if (snapshot_index < 0) { return -ENOENT; } sn = &s->snapshots[snapshot_index]; if (sn->disk_size != bs->total_sectors * BDRV_SECTOR_SIZE) { error_report("qcow2: Loading snapshots with different disk " "size is not implemented"); ret = -ENOTSUP; goto fail; } /* * Make sure that the current L1 table is big enough to contain the whole * L1 table of the snapshot. If the snapshot L1 table is smaller, the * current one must be padded with zeros. */ ret = qcow2_grow_l1_table(bs, sn->l1_size, true); if (ret < 0) { goto fail; } cur_l1_bytes = s->l1_size * sizeof(uint64_t); sn_l1_bytes = sn->l1_size * sizeof(uint64_t); /* * Copy the snapshot L1 table to the current L1 table. * * Before overwriting the old current L1 table on disk, make sure to * increase all refcounts for the clusters referenced by the new one. * Decrease the refcount referenced by the old one only when the L1 * table is overwritten. */ sn_l1_table = g_malloc0(cur_l1_bytes); ret = bdrv_pread(bs->file, sn->l1_table_offset, sn_l1_table, sn_l1_bytes); if (ret < 0) { goto fail; } ret = qcow2_update_snapshot_refcount(bs, sn->l1_table_offset, sn->l1_size, 1); if (ret < 0) { goto fail; } ret = qcow2_pre_write_overlap_check(bs, QCOW2_OL_DEFAULT & ~QCOW2_OL_ACTIVE_L1, s->l1_table_offset, cur_l1_bytes); if (ret < 0) { goto fail; } ret = bdrv_pwrite_sync(bs->file, s->l1_table_offset, sn_l1_table, cur_l1_bytes); if (ret < 0) { goto fail; } /* * Decrease refcount of clusters of current L1 table. * * At this point, the in-memory s->l1_table points to the old L1 table, * whereas on disk we already have the new one. * * qcow2_update_snapshot_refcount special cases the current L1 table to use * the in-memory data instead of really using the offset to load a new one, * which is why this works. */ ret = qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, -1); /* * Now update the in-memory L1 table to be in sync with the on-disk one. We * need to do this even if updating refcounts failed. */ for(i = 0; i < s->l1_size; i++) { s->l1_table[i] = be64_to_cpu(sn_l1_table[i]); } if (ret < 0) { goto fail; } g_free(sn_l1_table); sn_l1_table = NULL; /* * Update QCOW_OFLAG_COPIED in the active L1 table (it may have changed * when we decreased the refcount of the old snapshot. */ ret = qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, 0); if (ret < 0) { goto fail; } #ifdef DEBUG_ALLOC { BdrvCheckResult result = {0}; qcow2_check_refcounts(bs, &result, 0); } #endif return 0; fail: g_free(sn_l1_table); return ret; }
/* if no id is provided, a new one is constructed */ int qcow2_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info) { BDRVQcowState *s = bs->opaque; QCowSnapshot *new_snapshot_list = NULL; QCowSnapshot *old_snapshot_list = NULL; QCowSnapshot sn1, *sn = &sn1; int i, ret; uint64_t *l1_table = NULL; int64_t l1_table_offset; memset(sn, 0, sizeof(*sn)); /* Generate an ID if it wasn't passed */ if (sn_info->id_str[0] == '\0') { find_new_snapshot_id(bs, sn_info->id_str, sizeof(sn_info->id_str)); } /* Check that the ID is unique */ if (find_snapshot_by_id(bs, sn_info->id_str) >= 0) { return -EEXIST; } /* Populate sn with passed data */ sn->id_str = g_strdup(sn_info->id_str); sn->name = g_strdup(sn_info->name); sn->disk_size = bs->total_sectors * BDRV_SECTOR_SIZE; sn->vm_state_size = sn_info->vm_state_size; sn->date_sec = sn_info->date_sec; sn->date_nsec = sn_info->date_nsec; sn->vm_clock_nsec = sn_info->vm_clock_nsec; /* Allocate the L1 table of the snapshot and copy the current one there. */ l1_table_offset = qcow2_alloc_clusters(bs, s->l1_size * sizeof(uint64_t)); if (l1_table_offset < 0) { ret = l1_table_offset; goto fail; } sn->l1_table_offset = l1_table_offset; sn->l1_size = s->l1_size; l1_table = g_malloc(s->l1_size * sizeof(uint64_t)); for(i = 0; i < s->l1_size; i++) { l1_table[i] = cpu_to_be64(s->l1_table[i]); } ret = qcow2_pre_write_overlap_check(bs, QCOW2_OL_DEFAULT, sn->l1_table_offset, s->l1_size * sizeof(uint64_t)); if (ret < 0) { goto fail; } ret = bdrv_pwrite(bs->file, sn->l1_table_offset, l1_table, s->l1_size * sizeof(uint64_t)); if (ret < 0) { goto fail; } g_free(l1_table); l1_table = NULL; /* * Increase the refcounts of all clusters and make sure everything is * stable on disk before updating the snapshot table to contain a pointer * to the new L1 table. */ ret = qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, 1); if (ret < 0) { goto fail; } /* Append the new snapshot to the snapshot list */ new_snapshot_list = g_malloc((s->nb_snapshots + 1) * sizeof(QCowSnapshot)); if (s->snapshots) { memcpy(new_snapshot_list, s->snapshots, s->nb_snapshots * sizeof(QCowSnapshot)); old_snapshot_list = s->snapshots; } s->snapshots = new_snapshot_list; s->snapshots[s->nb_snapshots++] = *sn; ret = qcow2_write_snapshots(bs); if (ret < 0) { g_free(s->snapshots); s->snapshots = old_snapshot_list; goto fail; } g_free(old_snapshot_list); #ifdef DEBUG_ALLOC { BdrvCheckResult result = {0}; qcow2_check_refcounts(bs, &result, 0); } #endif return 0; fail: g_free(sn->id_str); g_free(sn->name); g_free(l1_table); return ret; }
/* if no id is provided, a new one is constructed */ int qcow2_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info) { BDRVQcow2State *s = bs->opaque; QCowSnapshot *new_snapshot_list = NULL; QCowSnapshot *old_snapshot_list = NULL; QCowSnapshot sn1, *sn = &sn1; int i, ret; uint64_t *l1_table = NULL; int64_t l1_table_offset; if (s->nb_snapshots >= QCOW_MAX_SNAPSHOTS) { return -EFBIG; } memset(sn, 0, sizeof(*sn)); /* Generate an ID */ find_new_snapshot_id(bs, sn_info->id_str, sizeof(sn_info->id_str)); /* Check that the ID is unique */ if (find_snapshot_by_id_and_name(bs, sn_info->id_str, NULL) >= 0) { return -EEXIST; } /* Populate sn with passed data */ sn->id_str = g_strdup(sn_info->id_str); sn->name = g_strdup(sn_info->name); sn->disk_size = bs->total_sectors * BDRV_SECTOR_SIZE; sn->vm_state_size = sn_info->vm_state_size; sn->date_sec = sn_info->date_sec; sn->date_nsec = sn_info->date_nsec; sn->vm_clock_nsec = sn_info->vm_clock_nsec; /* Allocate the L1 table of the snapshot and copy the current one there. */ l1_table_offset = qcow2_alloc_clusters(bs, s->l1_size * sizeof(uint64_t)); if (l1_table_offset < 0) { ret = l1_table_offset; goto fail; } sn->l1_table_offset = l1_table_offset; sn->l1_size = s->l1_size; l1_table = g_try_new(uint64_t, s->l1_size); if (s->l1_size && l1_table == NULL) { ret = -ENOMEM; goto fail; } for(i = 0; i < s->l1_size; i++) { l1_table[i] = cpu_to_be64(s->l1_table[i]); } ret = qcow2_pre_write_overlap_check(bs, 0, sn->l1_table_offset, s->l1_size * sizeof(uint64_t)); if (ret < 0) { goto fail; } ret = bdrv_pwrite(bs->file->bs, sn->l1_table_offset, l1_table, s->l1_size * sizeof(uint64_t)); if (ret < 0) { goto fail; } g_free(l1_table); l1_table = NULL; /* * Increase the refcounts of all clusters and make sure everything is * stable on disk before updating the snapshot table to contain a pointer * to the new L1 table. */ ret = qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, 1); if (ret < 0) { goto fail; } /* Append the new snapshot to the snapshot list */ new_snapshot_list = g_new(QCowSnapshot, s->nb_snapshots + 1); if (s->snapshots) { memcpy(new_snapshot_list, s->snapshots, s->nb_snapshots * sizeof(QCowSnapshot)); old_snapshot_list = s->snapshots; } s->snapshots = new_snapshot_list; s->snapshots[s->nb_snapshots++] = *sn; ret = qcow2_write_snapshots(bs); if (ret < 0) { g_free(s->snapshots); s->snapshots = old_snapshot_list; s->nb_snapshots--; goto fail; } g_free(old_snapshot_list); /* The VM state isn't needed any more in the active L1 table; in fact, it * hurts by causing expensive COW for the next snapshot. */ qcow2_discard_clusters(bs, qcow2_vm_state_offset(s), align_offset(sn->vm_state_size, s->cluster_size) >> BDRV_SECTOR_BITS, QCOW2_DISCARD_NEVER, false); #ifdef DEBUG_ALLOC { BdrvCheckResult result = {0}; qcow2_check_refcounts(bs, &result, 0); } #endif return 0; fail: g_free(sn->id_str); g_free(sn->name); g_free(l1_table); return ret; }