/* * xemu User Interface * * Copyright (C) 2020-2022 Matt Borgerson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "xemu-snapshots.h" #include "xemu-settings.h" #include "xemu-xbe.h" #include #include #include "block/aio.h" #include "block/block_int.h" #include "block/qapi.h" #include "block/qdict.h" #include "migration/qemu-file.h" #include "migration/snapshot.h" #include "qapi/error.h" #include "qapi/qapi-commands-block.h" #include "sysemu/runstate.h" #include "qemu-common.h" #include "ui/console.h" #include "ui/input.h" static QEMUSnapshotInfo *xemu_snapshots_metadata = NULL; static XemuSnapshotData *xemu_snapshots_extra_data = NULL; static int xemu_snapshots_len = 0; static bool xemu_snapshots_dirty = true; const char **g_snapshot_shortcut_index_key_map[] = { &g_config.general.snapshots.shortcuts.f5, &g_config.general.snapshots.shortcuts.f6, &g_config.general.snapshots.shortcuts.f7, &g_config.general.snapshots.shortcuts.f8, }; static void xemu_snapshots_load_data(BlockDriverState *bs_ro, QEMUSnapshotInfo *info, XemuSnapshotData *data, Error **err) { data->disc_path = NULL; data->xbe_title_name = NULL; data->gl_thumbnail = 0; int res = bdrv_snapshot_load_tmp(bs_ro, info->id_str, info->name, err); if (res < 0) { return; } uint32_t header[3]; int64_t offset = 0; res = bdrv_load_vmstate(bs_ro, (uint8_t *)&header, offset, sizeof(header)); if (res != sizeof(header)) { return; } offset += res; if (be32_to_cpu(header[0]) != XEMU_SNAPSHOT_DATA_MAGIC || be32_to_cpu(header[1]) != XEMU_SNAPSHOT_DATA_VERSION) { return; } size_t size = be32_to_cpu(header[2]); uint8_t *buf = g_malloc(size); res = bdrv_load_vmstate(bs_ro, buf, offset, size); if (res != size) { g_free(buf); return; } assert(size >= 9); offset = 0; const size_t disc_path_size = be32_to_cpu(*(uint32_t *)&buf[offset]); offset += 4; if (disc_path_size) { data->disc_path = (char *)g_malloc(disc_path_size + 1); assert(size >= (offset + disc_path_size)); memcpy(data->disc_path, &buf[offset], disc_path_size); data->disc_path[disc_path_size] = 0; offset += disc_path_size; } assert(size >= (offset + 4)); const size_t xbe_title_name_size = buf[offset]; offset += 1; if (xbe_title_name_size) { data->xbe_title_name = (char *)g_malloc(xbe_title_name_size + 1); assert(size >= (offset + xbe_title_name_size)); memcpy(data->xbe_title_name, &buf[offset], xbe_title_name_size); data->xbe_title_name[xbe_title_name_size] = 0; offset += xbe_title_name_size; } const size_t thumbnail_size = be32_to_cpu(*(uint32_t *)&buf[offset]); offset += 4; if (thumbnail_size) { GLuint thumbnail; glGenTextures(1, &thumbnail); assert(size >= (offset + thumbnail_size)); if (xemu_snapshots_load_png_to_texture(thumbnail, &buf[offset], thumbnail_size)) { data->gl_thumbnail = thumbnail; } else { glDeleteTextures(1, &thumbnail); } offset += thumbnail_size; } g_free(buf); } static void xemu_snapshots_all_load_data(QEMUSnapshotInfo **info, XemuSnapshotData **data, int snapshots_len, Error **err) { BlockDriverState *bs_ro; QDict *opts = qdict_new(); assert(info && data); if (*data) { for (int i = 0; i < xemu_snapshots_len; ++i) { g_free((*data)[i].xbe_title_name); if ((*data)[i].gl_thumbnail) { glDeleteTextures(1, &((*data)[i].gl_thumbnail)); } } g_free(*data); } *data = (XemuSnapshotData *)g_malloc(sizeof(XemuSnapshotData) * snapshots_len); memset(*data, 0, sizeof(XemuSnapshotData) * snapshots_len); qdict_put_bool(opts, BDRV_OPT_READ_ONLY, true); bs_ro = bdrv_open(g_config.sys.files.hdd_path, NULL, opts, BDRV_O_RO_WRITE_SHARE | BDRV_O_AUTO_RDONLY, err); if (!bs_ro) { return; } for (int i = 0; i < snapshots_len; ++i) { xemu_snapshots_load_data(bs_ro, (*info) + i, (*data) + i, err); if (*err) { break; } } bdrv_flush(bs_ro); bdrv_drain(bs_ro); bdrv_unref(bs_ro); assert(bs_ro->refcnt == 0); if (!(*err)) xemu_snapshots_dirty = false; } int xemu_snapshots_list(QEMUSnapshotInfo **info, XemuSnapshotData **extra_data, Error **err) { BlockDriverState *bs; AioContext *aio_context; int snapshots_len; assert(err); if (!xemu_snapshots_dirty && xemu_snapshots_extra_data && xemu_snapshots_metadata) { goto done; } if (xemu_snapshots_metadata) g_free(xemu_snapshots_metadata); bs = bdrv_all_find_vmstate_bs(NULL, false, NULL, err); if (!bs) { return -1; } aio_context = bdrv_get_aio_context(bs); aio_context_acquire(aio_context); snapshots_len = bdrv_snapshot_list(bs, &xemu_snapshots_metadata); aio_context_release(aio_context); xemu_snapshots_all_load_data(&xemu_snapshots_metadata, &xemu_snapshots_extra_data, snapshots_len, err); if (*err) { return -1; } xemu_snapshots_len = snapshots_len; done: if (info) { *info = xemu_snapshots_metadata; } if (extra_data) { *extra_data = xemu_snapshots_extra_data; } return xemu_snapshots_len; } char *xemu_get_currently_loaded_disc_path(void) { char *file = NULL; BlockInfoList *block_list, *info; block_list = qmp_query_block(NULL); for (info = block_list; info; info = info->next) { if (strcmp("ide0-cd1", info->value->device)) { continue; } if (info->value->has_inserted) { BlockDeviceInfo *inserted = info->value->inserted; if (inserted->has_node_name) { file = g_strdup(inserted->file); } } } qapi_free_BlockInfoList(block_list); return file; } void xemu_snapshots_load(const char *vm_name, Error **err) { bool vm_running = runstate_is_running(); vm_stop(RUN_STATE_RESTORE_VM); if (load_snapshot(vm_name, NULL, false, NULL, err) && vm_running) { vm_start(); } } void xemu_snapshots_save(const char *vm_name, Error **err) { save_snapshot(vm_name, true, NULL, false, NULL, err); } void xemu_snapshots_delete(const char *vm_name, Error **err) { delete_snapshot(vm_name, false, NULL, err); } void xemu_snapshots_save_extra_data(QEMUFile *f) { char *path = xemu_get_currently_loaded_disc_path(); size_t path_size = path ? strlen(path) : 0; size_t xbe_title_name_size = 0; char *xbe_title_name = NULL; struct xbe *xbe_data = xemu_get_xbe_info(); if (xbe_data && xbe_data->cert) { glong items_written = 0; xbe_title_name = g_utf16_to_utf8(xbe_data->cert->m_title_name, 40, NULL, &items_written, NULL); if (xbe_title_name) { xbe_title_name_size = items_written; } } size_t thumbnail_size = 0; void *thumbnail_buf = xemu_snapshots_create_framebuffer_thumbnail_png(&thumbnail_size); qemu_put_be32(f, XEMU_SNAPSHOT_DATA_MAGIC); qemu_put_be32(f, XEMU_SNAPSHOT_DATA_VERSION); qemu_put_be32(f, 4 + path_size + 1 + xbe_title_name_size + 4 + thumbnail_size); qemu_put_be32(f, path_size); if (path_size) { qemu_put_buffer(f, (const uint8_t *)path, path_size); g_free(path); } qemu_put_byte(f, xbe_title_name_size); if (xbe_title_name_size) { qemu_put_buffer(f, (const uint8_t *)xbe_title_name, xbe_title_name_size); g_free(xbe_title_name); } qemu_put_be32(f, thumbnail_size); if (thumbnail_size) { qemu_put_buffer(f, (const uint8_t *)thumbnail_buf, thumbnail_size); g_free(thumbnail_buf); } xemu_snapshots_dirty = true; } bool xemu_snapshots_offset_extra_data(QEMUFile *f) { unsigned int v; uint32_t version; uint32_t size; v = qemu_get_be32(f); if (v != XEMU_SNAPSHOT_DATA_MAGIC) { qemu_file_skip(f, -4); return true; } version = qemu_get_be32(f); (void)version; /* qemu_file_skip only works if you aren't skipping past internal buffer limit. * Unfortunately, it's not usable here. */ size = qemu_get_be32(f); void *buf = g_malloc(size); qemu_get_buffer(f, buf, size); g_free(buf); return true; } void xemu_snapshots_mark_dirty(void) { xemu_snapshots_dirty = true; }