From nobody Mon Feb 9 19:08:15 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=virtuozzo.com ARC-Seal: i=1; a=rsa-sha256; t=1620850261; cv=none; d=zohomail.com; s=zohoarc; b=dJUOYGShx5s3r91267PFAYxSl7xwvT6iVCsn4W7oyABfSRkoCLrxBuplEinFrh0rEKRwf4Um0Ncgeqv6yqBYw65uOlygabCIvYozqJ0+PajH9d3LdiJpZEoqVMn0aiQkBiSyJMtCAJSbzX81JdUKYqm5FpvkolEgFL0n27sjrqw= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1620850261; h=Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=V4Bs5zwojnUESj9T0xGfn8nbKeLSbNirWo1Or+zmo0g=; b=YDfY4WL/8TlevUylES1cUTD/U3IMdOQVu99bHoFVK6YE27ECK5grgPp6lXU1r3hqQ+8ClklUWwHwEcjqIM18fvKxv/jYUwa3FXElU6ZQzKmKQdrIpVorxGiRLUcqpSXUj9wr4MMTnnV0ZlfLKvM0ljYR+UXWeq2ys5Wi0z1Cn44= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) header.from= Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1620850261235970.226668177182; Wed, 12 May 2021 13:11:01 -0700 (PDT) Received: from localhost ([::1]:38778 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lgvC7-0005h5-Nh for importer@patchew.org; Wed, 12 May 2021 16:10:59 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:60232) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lguV6-0002A6-0a for qemu-devel@nongnu.org; Wed, 12 May 2021 15:26:32 -0400 Received: from relay.sw.ru ([185.231.240.75]:44742) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lguUz-0000ok-Ah for qemu-devel@nongnu.org; Wed, 12 May 2021 15:26:31 -0400 Received: from [192.168.15.22] (helo=andrey-MS-7B54.sw.ru) by relay.sw.ru with esmtp (Exim 4.94) (envelope-from ) id 1lguUt-002BHm-N1; Wed, 12 May 2021 22:26:19 +0300 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=virtuozzo.com; s=relay; h=MIME-Version:Message-Id:Date:Subject:From: Content-Type; bh=V4Bs5zwojnUESj9T0xGfn8nbKeLSbNirWo1Or+zmo0g=; b=D4V1UDh59nUJ enmDL2uhLhWNOLT2mQYK6ESC9uKHeL1rmVmbIqhFa7fDSETvsrQcMVgcCvefdgv+Ro3vF4issM1Fd WIObJcbfjwCTOS7cD5LrY9HBy1CSxPqjSG61TqMZn83EeoF1veGUE7fJrVGvEceBuYMNYMkdvPBxT VATwA=; From: Andrey Gruzdev To: qemu-devel@nongnu.org Cc: Den Lunev , Vladimir Sementsov-Ogievskiy , Eric Blake , Paolo Bonzini , Juan Quintela , "Dr . David Alan Gilbert" , Markus Armbruster , Peter Xu , David Hildenbrand , Andrey Gruzdev Subject: [RFC PATCH v1 1/7] migration/snapshot: Introduce qemu-snapshot tool Date: Wed, 12 May 2021 22:26:13 +0300 Message-Id: <20210512192619.537268-2-andrey.gruzdev@virtuozzo.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210512192619.537268-1-andrey.gruzdev@virtuozzo.com> References: <20210512192619.537268-1-andrey.gruzdev@virtuozzo.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=185.231.240.75; envelope-from=andrey.gruzdev@virtuozzo.com; helo=relay.sw.ru X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: pass (identity @virtuozzo.com) Content-Type: text/plain; charset="utf-8" Execution environment, command-line argument parsing, usage/version info et= c. Signed-off-by: Andrey Gruzdev --- include/qemu-snapshot.h | 59 ++++++ meson.build | 2 + qemu-snapshot-vm.c | 57 ++++++ qemu-snapshot.c | 439 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 557 insertions(+) create mode 100644 include/qemu-snapshot.h create mode 100644 qemu-snapshot-vm.c create mode 100644 qemu-snapshot.c diff --git a/include/qemu-snapshot.h b/include/qemu-snapshot.h new file mode 100644 index 0000000000..154e11e9a5 --- /dev/null +++ b/include/qemu-snapshot.h @@ -0,0 +1,59 @@ +/* + * QEMU External Snapshot Utility + * + * Copyright Virtuozzo GmbH, 2021 + * + * Authors: + * Andrey Gruzdev + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * later. See the COPYING file in the top-level directory. + */ + +#ifndef QEMU_SNAPSHOT_H +#define QEMU_SNAPSHOT_H + +/* Invalid offset */ +#define INVALID_OFFSET -1 +/* Maximum byte count for qemu_get_buffer_in_place() */ +#define INPLACE_READ_MAX (32768 - 4096) + +/* Backing cluster size */ +#define BDRV_CLUSTER_SIZE (1024 * 1024) + +/* Minimum supported target page size */ +#define PAGE_SIZE_MIN 4096 +/* + * Maximum supported target page size. The limit is caused by using + * QEMUFile and qemu_get_buffer_in_place() on migration channel. + * IO_BUF_SIZE is currently 32KB. + */ +#define PAGE_SIZE_MAX 16384 +/* RAM slice size for snapshot saving */ +#define SLICE_SIZE PAGE_SIZE_MAX +/* RAM slice size for snapshot revert */ +#define SLICE_SIZE_REVERT (16 * PAGE_SIZE_MAX) + +typedef struct StateSaveCtx { + BlockBackend *blk; /* Block backend */ +} StateSaveCtx; + +typedef struct StateLoadCtx { + BlockBackend *blk; /* Block backend */ +} StateLoadCtx; + +extern int64_t page_size; /* Page size */ +extern int64_t page_mask; /* Page mask */ +extern int page_bits; /* Page size bits */ +extern int64_t slice_size; /* RAM slice size */ +extern int64_t slice_mask; /* RAM slice mask */ +extern int slice_bits; /* RAM slice size bits */ + +void ram_init_state(void); +void ram_destroy_state(void); +StateSaveCtx *get_save_context(void); +StateLoadCtx *get_load_context(void); +int coroutine_fn save_state_main(StateSaveCtx *s); +int coroutine_fn load_state_main(StateLoadCtx *s); + +#endif /* QEMU_SNAPSHOT_H */ diff --git a/meson.build b/meson.build index 0b41ff4118..b851671914 100644 --- a/meson.build +++ b/meson.build @@ -2361,6 +2361,8 @@ if have_tools dependencies: [block, qemuutil], install: true) qemu_nbd =3D executable('qemu-nbd', files('qemu-nbd.c'), dependencies: [blockdev, qemuutil, gnutls], install: true) + qemu_snapshot =3D executable('qemu-snapshot', files('qemu-snapshot.c', '= qemu-snapshot-vm.c'), + dependencies: [blockdev, qemuutil, migration], install: tru= e) =20 subdir('storage-daemon') subdir('contrib/rdmacm-mux') diff --git a/qemu-snapshot-vm.c b/qemu-snapshot-vm.c new file mode 100644 index 0000000000..f7695e75c7 --- /dev/null +++ b/qemu-snapshot-vm.c @@ -0,0 +1,57 @@ +/* + * QEMU External Snapshot Utility + * + * Copyright Virtuozzo GmbH, 2021 + * + * Authors: + * Andrey Gruzdev + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * later. See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "sysemu/block-backend.h" +#include "qemu/coroutine.h" +#include "qemu/cutils.h" +#include "qemu/bitmap.h" +#include "qemu/error-report.h" +#include "io/channel-buffer.h" +#include "migration/qemu-file-channel.h" +#include "migration/qemu-file.h" +#include "migration/savevm.h" +#include "migration/ram.h" +#include "qemu-snapshot.h" + +/* RAM transfer context */ +typedef struct RAMCtx { + int64_t normal_pages; /* Total number of normal pages */ +} RAMCtx; + +static RAMCtx ram_ctx; + +int coroutine_fn save_state_main(StateSaveCtx *s) +{ + /* TODO: implement */ + return 0; +} + +int coroutine_fn load_state_main(StateLoadCtx *s) +{ + /* TODO: implement */ + return 0; +} + +/* Initialize snapshot RAM state */ +void ram_init_state(void) +{ + RAMCtx *ram =3D &ram_ctx; + + memset(ram, 0, sizeof(ram_ctx)); +} + +/* Destroy snapshot RAM state */ +void ram_destroy_state(void) +{ + /* TODO: implement */ +} diff --git a/qemu-snapshot.c b/qemu-snapshot.c new file mode 100644 index 0000000000..7ac4ef66c4 --- /dev/null +++ b/qemu-snapshot.c @@ -0,0 +1,439 @@ +/* + * QEMU External Snapshot Utility + * + * Copyright Virtuozzo GmbH, 2021 + * + * Authors: + * Andrey Gruzdev + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * later. See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include + +#include "qemu-common.h" +#include "qemu-version.h" +#include "qapi/error.h" +#include "qapi/qmp/qdict.h" +#include "sysemu/sysemu.h" +#include "sysemu/block-backend.h" +#include "sysemu/runstate.h" /* for qemu_system_killed() prototype */ +#include "qemu/cutils.h" +#include "qemu/coroutine.h" +#include "qemu/error-report.h" +#include "qemu/config-file.h" +#include "qemu/log.h" +#include "qemu/option_int.h" +#include "trace/control.h" +#include "io/channel-util.h" +#include "io/channel-buffer.h" +#include "migration/qemu-file-channel.h" +#include "migration/qemu-file.h" +#include "qemu-snapshot.h" + +int64_t page_size; +int64_t page_mask; +int page_bits; +int64_t slice_size; +int64_t slice_mask; +int slice_bits; + +static QemuOptsList snap_blk_optslist =3D { + .name =3D "blockdev", + .implied_opt_name =3D "file.filename", + .head =3D QTAILQ_HEAD_INITIALIZER(snap_blk_optslist.head), + .desc =3D { + { /*End of the list */ } + }, +}; + +static struct { + bool revert; /* Operation is snapshot revert */ + + int fd; /* Migration channel fd */ + int rp_fd; /* Return path fd (for postcopy) */ + + const char *blk_optstr; /* Command-line options for vmstate blockd= ev */ + QDict *blk_options; /* Blockdev options */ + int blk_flags; /* Blockdev flags */ + + bool postcopy; /* Use postcopy */ + int postcopy_percent; /* Start postcopy after % of normal pages = loaded */ +} params; + +static StateSaveCtx state_save_ctx; +static StateLoadCtx state_load_ctx; + +static enum { + RUNNING =3D 0, + TERMINATED +} state; + +#ifdef CONFIG_POSIX +void qemu_system_killed(int signum, pid_t pid) +{ +} +#endif /* CONFIG_POSIX */ + +StateSaveCtx *get_save_context(void) +{ + return &state_save_ctx; +} + +StateLoadCtx *get_load_context(void) +{ + return &state_load_ctx; +} + +static void init_save_context(void) +{ + memset(&state_save_ctx, 0, sizeof(state_save_ctx)); +} + +static void destroy_save_context(void) +{ + /* TODO: implement */ +} + +static void init_load_context(void) +{ + memset(&state_load_ctx, 0, sizeof(state_load_ctx)); +} + +static void destroy_load_context(void) +{ + /* TODO: implement */ +} + +static BlockBackend *image_open_opts(const char *optstr, QDict *options, i= nt flags) +{ + BlockBackend *blk; + Error *local_err =3D NULL; + + /* Open image and create block backend */ + blk =3D blk_new_open(NULL, NULL, options, flags, &local_err); + if (!blk) { + error_reportf_err(local_err, "Failed to open image '%s': ", optstr= ); + return NULL; + } + + blk_set_enable_write_cache(blk, true); + + return blk; +} + +/* Use BH to enter coroutine from the main loop */ +static void enter_co_bh(void *opaque) +{ + Coroutine *co =3D (Coroutine *) opaque; + qemu_coroutine_enter(co); +} + +static void coroutine_fn snapshot_save_co(void *opaque) +{ + StateSaveCtx *s =3D get_save_context(); + int res =3D -1; + + init_save_context(); + + /* Block backend */ + s->blk =3D image_open_opts(params.blk_optstr, params.blk_options, + params.blk_flags); + if (!s->blk) { + goto fail; + } + + res =3D save_state_main(s); + if (res) { + error_report("Failed to save snapshot: %s", strerror(-res)); + } + +fail: + destroy_save_context(); + state =3D TERMINATED; +} + +static void coroutine_fn snapshot_load_co(void *opaque) +{ + StateLoadCtx *s =3D get_load_context(); + int res =3D -1; + + init_load_context(); + + /* Block backend */ + s->blk =3D image_open_opts(params.blk_optstr, params.blk_options, + params.blk_flags); + if (!s->blk) { + goto fail; + } + + res =3D load_state_main(s); + if (res) { + error_report("Failed to load snapshot: %s", strerror(-res)); + } + +fail: + destroy_load_context(); + state =3D TERMINATED; +} + +static void usage(const char *name) +{ + printf( + "Usage: %s [options] \n" + "QEMU External Snapshot Utility\n" + "\n" + "'image-blockspec' is a block device specification for vmstate ima= ge\n" + "\n" + " -h, --help display this help and exit\n" + " -V, --version output version information and exit\n" + "\n" + "Options:\n" + " -T, --trace [[enable=3D]][,events=3D][,file=3D]\n" + " specify tracing options\n" + " -r, --revert revert to snapshot\n" + " --uri=3Dfd: specify migration fd\n" + " --page-size=3D specify target page size\n" + " --postcopy=3D<%%ram> switch to postcopy after %%ram loa= ded\n" + "\n" + QEMU_HELP_BOTTOM "\n", name); +} + +static void version(const char *name) +{ + printf( + "%s " QEMU_FULL_VERSION "\n" + "Written by Andrey Gruzdev.\n" + "\n" + QEMU_COPYRIGHT "\n" + "This is free software; see the source for copying conditions. Th= ere is NO\n" + "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULA= R PURPOSE.\n", + name); +} + +enum { + OPTION_PAGE_SIZE =3D 256, + OPTION_POSTCOPY, + OPTION_URI, +}; + +static void process_options(int argc, char *argv[]) +{ + static const char *s_opt =3D "rhVT:"; + static const struct option l_opt[] =3D { + { "page-size", required_argument, NULL, OPTION_PAGE_SIZE }, + { "postcopy", required_argument, NULL, OPTION_POSTCOPY }, + { "uri", required_argument, NULL, OPTION_URI }, + { "revert", no_argument, NULL, 'r' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "trace", required_argument, NULL, 'T' }, + { NULL, 0, NULL, 0 } + }; + + bool has_page_size =3D false; + bool has_uri =3D false; + + int64_t target_page_size =3D qemu_real_host_page_size; + int uri_fd =3D -1; + bool revert =3D false; + bool postcopy =3D false; + int postcopy_percent =3D 0; + const char *blk_optstr; + QemuOpts *blk_opts; + QDict *blk_options; + int c; + + while ((c =3D getopt_long(argc, argv, s_opt, l_opt, NULL)) !=3D -1) { + switch (c) { + case '?': + exit(EXIT_FAILURE); + + case 'h': + usage(argv[0]); + exit(EXIT_SUCCESS); + + case 'V': + version(argv[0]); + exit(EXIT_SUCCESS); + + case 'T': + trace_opt_parse(optarg); + break; + + case 'r': + if (revert) { + error_report("-r and --revert can only be specified on= ce"); + exit(EXIT_FAILURE); + } + revert =3D true; + =20 + break; + + case OPTION_POSTCOPY: + { + char *r; + + if (postcopy) { + error_report("--postcopy can only be specified once"); + exit(EXIT_FAILURE); + } + postcopy =3D true; + + postcopy_percent =3D strtol(optarg, &r, 10); + if (*r !=3D '\0' || postcopy_percent < 0 || postcopy_perce= nt > 100) { + error_report("Invalid argument to --postcopy"); + exit(EXIT_FAILURE); + } + + break; + } + + case OPTION_PAGE_SIZE: + { + char *r; + + if (has_page_size) { + error_report("--page-size can only be specified once"); + exit(EXIT_FAILURE); + } + has_page_size =3D true; + + target_page_size =3D strtol(optarg, &r, 0); + if (*r !=3D '\0' || (target_page_size & (target_page_size = - 1)) !=3D 0 || + target_page_size < PAGE_SIZE_MIN || + target_page_size > PAGE_SIZE_MAX) { + error_report("Invalid argument to --page-size"); + exit(EXIT_FAILURE); + } + + break; + } + + case OPTION_URI: + { + const char *p; + + if (has_uri) { + error_report("--uri can only be specified once"); + exit(EXIT_FAILURE); + } + has_uri =3D true; + + /* Only "--uri=3Dfd:" is currently supported */ + if (strstart(optarg, "fd:", &p)) { + char *r; + int fd; + + fd =3D strtol(p, &r,10); + if (*r !=3D '\0' || fd <=3D STDERR_FILENO) { + error_report("Invalid FD value"); + exit(EXIT_FAILURE); + } + + uri_fd =3D qemu_dup_flags(fd, O_CLOEXEC); + if (uri_fd < 0) { + error_report("Could not dup FD %d", fd); + exit(EXIT_FAILURE); + } + + /* Close original fd */ + close(fd); + } else { + error_report("Invalid argument to --uri"); + exit(EXIT_FAILURE); + } + + break; + } + + default: + g_assert_not_reached(); + } + } + + if ((argc - optind) !=3D 1) { + error_report("Invalid number of arguments"); + exit(EXIT_FAILURE); + } + + blk_optstr =3D argv[optind]; + + blk_opts =3D qemu_opts_parse_noisily(&snap_blk_optslist, blk_optstr, t= rue); + if (!blk_opts) { + exit(EXIT_FAILURE); + } + blk_options =3D qemu_opts_to_qdict(blk_opts, NULL); + qemu_opts_reset(&snap_blk_optslist); + + /* Enforced block layer options */ + qdict_put_str(blk_options, "driver", "qcow2"); + qdict_put_null(blk_options, "backing"); + qdict_put_str(blk_options, "overlap-check", "none"); + qdict_put_str(blk_options, "auto-read-only", "off"); + qdict_put_str(blk_options, "detect-zeroes", "off"); + qdict_put_str(blk_options, "lazy-refcounts", "on"); + qdict_put_str(blk_options, "file.auto-read-only", "off"); + qdict_put_str(blk_options, "file.detect-zeroes", "off"); + + params.revert =3D revert; + + if (uri_fd !=3D -1) { + params.fd =3D params.rp_fd =3D uri_fd; + } else { + params.fd =3D revert ? STDOUT_FILENO : STDIN_FILENO; + params.rp_fd =3D revert ? STDIN_FILENO : -1; + } + params.blk_optstr =3D blk_optstr; + params.blk_options =3D blk_options; + params.blk_flags =3D revert ? 0 : BDRV_O_RDWR; + params.postcopy =3D postcopy; + params.postcopy_percent =3D postcopy_percent; + + page_size =3D target_page_size; + page_mask =3D ~(page_size - 1); + page_bits =3D ctz64(page_size); + slice_size =3D revert ? SLICE_SIZE_REVERT : SLICE_SIZE; + slice_mask =3D ~(slice_size - 1); + slice_bits =3D ctz64(slice_size); +} + +int main(int argc, char **argv) +{ + Coroutine *co; + + os_setup_early_signal_handling(); + os_setup_signal_handling(); + error_init(argv[0]); + qemu_init_exec_dir(argv[0]); + module_call_init(MODULE_INIT_TRACE); + module_call_init(MODULE_INIT_QOM); + qemu_init_main_loop(&error_fatal); + bdrv_init(); + + qemu_add_opts(&qemu_trace_opts); + process_options(argc, argv); + + if (!trace_init_backends()) { + exit(EXIT_FAILURE); + } + trace_init_file(); + qemu_set_log(LOG_TRACE); + + ram_init_state(); + + if (params.revert) { + co =3D qemu_coroutine_create(snapshot_load_co, NULL); + } else { + co =3D qemu_coroutine_create(snapshot_save_co, NULL); + } + aio_bh_schedule_oneshot(qemu_get_aio_context(), enter_co_bh, co); + + do { + main_loop_wait(false); + } while (state !=3D TERMINATED); + + exit(EXIT_SUCCESS); +} --=20 2.27.0