From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 722841FF14C for ; Fri, 12 Jun 2026 14:30:22 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 5415C1284F; Fri, 12 Jun 2026 14:30:22 +0200 (CEST) From: Jakob Klocker To: pve-devel@lists.proxmox.com Subject: [PATCH qemu-server 1/2] fix #5032: qemu: sync guest time on resume and snapshot of saved state Date: Fri, 12 Jun 2026 14:29:41 +0200 Message-ID: <20260612122942.181958-2-j.klocker@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260612122942.181958-1-j.klocker@proxmox.com> References: <20260612122942.181958-1-j.klocker@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.027 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods RDNS_NONE 0.793 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record Message-ID-Hash: STAQYRVXXSASBZEOTGFJGM5TWKM2ODRC X-Message-ID-Hash: STAQYRVXXSASBZEOTGFJGM5TWKM2ODRC X-MailFrom: root@dev.proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Jakob Klocker X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: When a VM is resumed from a saved state (hibernation or snapshot with RAM), the guest clock may be stale. A time skew can occur when creating a snapshot of a running VM. Add a new agent option `set-time-on-resume` to automatically synchronize the guest time with the host using the QEMU Guest Agent. Trigger time synchronization: - after restoring a VM from a saved state or snapshot with RAM - after taking a snapshot This ensures consistent guest time after rollback, restore, or snapshot operations when the guest OS clock does not automatically correct itself. Link: https://bugzilla.proxmox.com/show_bug.cgi?id=5032 Signed-off-by: Jakob Klocker --- src/PVE/QemuConfig.pm | 7 ++++++ src/PVE/QemuServer.pm | 10 ++++++++ src/PVE/QemuServer/Agent.pm | 47 +++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/src/PVE/QemuConfig.pm b/src/PVE/QemuConfig.pm index 80a3999e..1950c328 100644 --- a/src/PVE/QemuConfig.pm +++ b/src/PVE/QemuConfig.pm @@ -383,6 +383,13 @@ sub __snapshot_create_vol_snapshots_hook { next; } } + if ($snap->{vmstate}) { + my $conf = $class->load_config($vmid); + if (PVE::QemuServer::Agent::should_set_time_on_resume($conf->{agent})) { + eval { PVE::QemuServer::Agent::guest_set_time($vmid); 1 } + or warn "could not sync guest time after snapshot - $@"; + } + } } } } diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm index 239b0cab..99bdd9f5 100644 --- a/src/PVE/QemuServer.pm +++ b/src/PVE/QemuServer.pm @@ -5983,6 +5983,16 @@ sub vm_start_nolock { ); } + my $from_saved_state = $resume || ($statefile && !$migratedfrom); + + if ( + $from_saved_state + && PVE::QemuServer::Agent::should_set_time_on_resume($conf->{agent}) + ) { + eval { PVE::QemuServer::Agent::guest_set_time($vmid); 1 } + or warn "could not sync guest time after snapshot - $@"; + } + return $res; } diff --git a/src/PVE/QemuServer/Agent.pm b/src/PVE/QemuServer/Agent.pm index be6df443..be8bbae6 100644 --- a/src/PVE/QemuServer/Agent.pm +++ b/src/PVE/QemuServer/Agent.pm @@ -4,6 +4,7 @@ use v5.36; use JSON; use MIME::Base64 qw(decode_base64 encode_base64); +use Time::HiRes (); use PVE::JSONSchema; @@ -18,6 +19,8 @@ our @EXPORT_OK = qw( get_qga_key parse_guest_agent qga_check_running + should_set_time_on_resume + guest_set_time ); our $agent_fmt = { @@ -52,6 +55,21 @@ our $agent_fmt = { optional => 1, default => 1, }, + 'set-time-on-resume' => { + description => "Update the guest clock through QGA after resuming from" + . " hibernation or rolling back to a snapshot with RAM.", + verbose_description => + "Whether to issue the guest-set-time QEMU guest agent command after the VM" + . " resumes with a restored RAM state, that is, when waking from hibernation" + . " or after rolling back to a snapshot that includes RAM. In these cases the" + . " guest's clock still reflects the time the state was saved. With this" + . " option enabled, the clock is synchronized to the host's current time," + . " provided the QEMU Guest Agent option is enabled in the guest's" + . " configuration and the agent is running inside of the guest.", + type => 'boolean', + optional => 1, + default => 1, + }, type => { description => "Select the agent type", type => 'string', @@ -332,4 +350,33 @@ sub guest_fs_freeze_applicable($agent_str, $vmid, $logfunc = undef) { return 1; } +=head3 should_set_time_on_resume + +Returns whether the guest's clock should be synchronized to the host's via the QEMU Guest Agent +when the VM is resumed from saved state. Does B check whether the agent is actually running. + +=cut + +sub should_set_time_on_resume($agent_str) { + my $agent = parse_guest_agent($agent_str); + return 0 if !$agent->{enabled}; + return $agent->{'set-time-on-resume'} // 1; +} + +=head3 guest_set_time + +Sets the guest's clock via the QEMU Guest Agent's C command. If C<$time_ns> +(nanoseconds since the UNIX epoch, UTC) is not given, the current host time is used. Passing +an explicit time is required because the agent's argument-less form reads the guest's RTC, +which may itself be stale after a vmstate snapshot or resume. + +=cut + +sub guest_set_time($vmid, $time_ns = undef) { + $time_ns //= int(Time::HiRes::time() * 1_000_000_000); + my $res = PVE::QemuServer::Monitor::mon_cmd($vmid, 'guest-set-time', time => $time_ns); + check_agent_error($res, "unable to set guest time"); + return; +} + 1; -- 2.47.3