From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 8E9E9C25B6B for ; Thu, 26 Oct 2023 08:24:42 +0000 (UTC) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1qvvfG-0008Su-V4; Thu, 26 Oct 2023 04:24:27 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1qvvfE-0008Sd-5a for qemu-devel@nongnu.org; Thu, 26 Oct 2023 04:24:24 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.133.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1qvvfC-0007Cu-0V for qemu-devel@nongnu.org; Thu, 26 Oct 2023 04:24:23 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1698308660; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=2sG2mpAELY+ByqgBtr74+SvNRWvupaZrHcYCeXVlTrQ=; b=NyeC0fJ+l3+0xR5gcobX6OHR49RJkjwTqNsG7RW65t2txXKLwryW+THGZjbKJBJWZm21WX q2+G+OwfPYOAy7VLRuNOlewt7YVzejrvHhv0cDAeJg/Z3ig61Xfo60A9mOU+NK+Tn1vntx NSgZ212unR3dvFI7jOKzrFGPy33Wotw= Received: from mail-lf1-f69.google.com (mail-lf1-f69.google.com [209.85.167.69]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-36-U0lHQYn8Pl6-luWTsJFHyQ-1; Thu, 26 Oct 2023 04:24:18 -0400 X-MC-Unique: U0lHQYn8Pl6-luWTsJFHyQ-1 Received: by mail-lf1-f69.google.com with SMTP id 2adb3069b0e04-507a3ae32b2so639575e87.2 for ; Thu, 26 Oct 2023 01:24:17 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1698308656; x=1698913456; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=2sG2mpAELY+ByqgBtr74+SvNRWvupaZrHcYCeXVlTrQ=; b=w/th15pRSQdHr6EDawbCOr1Gp1P2OvU7o9SFM+43F9HUVVxtzdtPgy9YlNxR7k8gI5 OsKxMKT7MBDSp850tsT7Q/R5MyCoQhyUw+fHLZYZXZVrNWNeLaOvgsqXa0nu+WESJH8K fAn1T0NFKlEgJ+4FhFCZM8tOQCtmQFgqG9wGE21qTEE/rZqcIm6kq4gv2hpuIMoP3YUq 0zftttzWeAzMpZch2edVbgOB7DDrFkk+x6CBtZuUzsZQkGo2FJ6yVLVcv9SZB6eD4foI Pj30TDG1GJFJegKQfnOZJtv2QfA5z/y9rNTzFCsll2u5kM2JG4yM+bmSZkntlPrxVX9f zZ8Q== X-Gm-Message-State: AOJu0YwkcDHvllFNEbhr8c0a4thmIDrXJMfR5jRyTQ/0IG5p1qmmPKLU jKPKcx9KuanPV0ofTfWXqWNQg82RJOFvqfhwucMvjBjufdbDneVb9d4qi5rISEuL6NJWGrknkcj nsjX7w3s0IDLGlxNJ54PdfhS9cI0+Vqk= X-Received: by 2002:a05:6512:108e:b0:508:1857:91f3 with SMTP id j14-20020a056512108e00b00508185791f3mr3816902lfg.62.1698308656398; Thu, 26 Oct 2023 01:24:16 -0700 (PDT) X-Google-Smtp-Source: AGHT+IE98A726VNMbsH3pY3samYhiSXiyLRrqPx1oSLqE+vu0fmdDvYk6pxEY99QP90zoyAxAtrYQ4NY98p+zFLouWc= X-Received: by 2002:a05:6512:108e:b0:508:1857:91f3 with SMTP id j14-20020a056512108e00b00508185791f3mr3816885lfg.62.1698308656027; Thu, 26 Oct 2023 01:24:16 -0700 (PDT) MIME-Version: 1.0 References: <20231025140058.113376-1-alexander.ivanov@virtuozzo.com> <20231025140058.113376-2-alexander.ivanov@virtuozzo.com> In-Reply-To: <20231025140058.113376-2-alexander.ivanov@virtuozzo.com> From: Konstantin Kostiuk Date: Thu, 26 Oct 2023 11:24:04 +0300 Message-ID: Subject: Re: [PATCH 1/6] qga: Add process termination functionality To: Alexander Ivanov Cc: qemu-devel@nongnu.org, den@virtuozzo.com, michael.roth@amd.com, marcandre.lureau@gmail.com Content-Type: multipart/alternative; boundary="00000000000029704706089a4ca9" Received-SPF: pass client-ip=170.10.133.124; envelope-from=kkostiuk@redhat.com; helo=us-smtp-delivery-124.mimecast.com 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, DKIMWL_WL_HIGH=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, HTML_MESSAGE=0.001, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, 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.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org --00000000000029704706089a4ca9 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Wed, Oct 25, 2023 at 5:01=E2=80=AFPM Alexander Ivanov < alexander.ivanov@virtuozzo.com> wrote: > We need to terminate processes executed with guest-exec command. Add > guest-exec-terminate command for process termination by PID. > > Signed-off-by: Alexander Ivanov > --- > qga/commands-common.h | 2 ++ > qga/commands-win32.c | 64 +++++++++++++++++++++++++++++++++++++++++++ > qga/commands.c | 34 +++++++++++++++++++++++ > qga/qapi-schema.json | 13 +++++++++ > 4 files changed, 113 insertions(+) > > diff --git a/qga/commands-common.h b/qga/commands-common.h > index 8c1c56aac9..34b9a22578 100644 > --- a/qga/commands-common.h > +++ b/qga/commands-common.h > @@ -80,4 +80,6 @@ GuestFileRead *guest_file_read_unsafe(GuestFileHandle > *gfh, > */ > char *qga_get_host_name(Error **errp); > > +int kill_process_tree(int64_t pid); > + > #endif > diff --git a/qga/commands-win32.c b/qga/commands-win32.c > index 697c65507c..5aa43a9ed7 100644 > --- a/qga/commands-win32.c > +++ b/qga/commands-win32.c > @@ -27,6 +27,7 @@ > #include > #include > #include > +#include > > #include "guest-agent-core.h" > #include "vss-win32.h" > @@ -2522,3 +2523,66 @@ GuestCpuStatsList *qmp_guest_get_cpustats(Error > **errp) > error_setg(errp, QERR_UNSUPPORTED); > return NULL; > } > + > +int kill_process_tree(int64_t pid) > +{ > + PROCESSENTRY32 proc_entry; > + HANDLE snapshot, process; > + GList *pid_entry, *pid_list =3D NULL; > + bool added, success; > + int res =3D 0; > + > + snapshot =3D CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); > + if (snapshot =3D=3D INVALID_HANDLE_VALUE) { > + return GetLastError(); > + } > + > + pid_list =3D g_list_append(pid_list, GUINT_TO_POINTER(pid)); > + > + proc_entry.dwSize =3D sizeof(PROCESSENTRY32); > + do { > + added =3D false; > + for (success =3D Process32First(snapshot, &proc_entry); > + success; success =3D Process32Next(snapshot, &proc_entry)) = { > + gpointer ppid_p, pid_p; > + ppid_p =3D GUINT_TO_POINTER(proc_entry.th32ParentProcessID); > + pid_p =3D GUINT_TO_POINTER(proc_entry.th32ProcessID); > + if (g_list_find(pid_list, ppid_p) && !g_list_find(pid_list, > pid_p)) { > + pid_list =3D g_list_append(pid_list, pid_p); > + added =3D true; > + } > + } > + } while (added); > + > + for (success =3D Process32First(snapshot, &proc_entry); > + success; success =3D Process32Next(snapshot, &proc_entry)) { > + if (g_list_find(pid_list, > GUINT_TO_POINTER(proc_entry.th32ProcessID))) { > + g_debug("killing pid=3D%u ppid=3D%u name=3D%s", > + (guint)proc_entry.th32ProcessID, > + (guint)proc_entry.th32ParentProcessID, > + proc_entry.szExeFile); > + } > + } > + > Why do we need to store these processes before termination? I understand that we need to enumerate all processes to find children but why we can't terminate it on the fly? > + CloseHandle(snapshot); > + > + for (pid_entry =3D pid_list; pid_entry; pid_entry =3D pid_entry->nex= t) { > + pid =3D GPOINTER_TO_UINT(pid_entry->data); > + process =3D OpenProcess(PROCESS_TERMINATE, FALSE, pid); > + if (process =3D=3D INVALID_HANDLE_VALUE) { > + if (!res) { > + res =3D GetLastError(); > + if (res =3D=3D ERROR_FILE_NOT_FOUND) { > + res =3D 0; > + } > + } > + continue; > + } > + TerminateProcess(process, 255); > + CloseHandle(process); > + } > + > + g_list_free(pid_list); > + > + return res; > +} > diff --git a/qga/commands.c b/qga/commands.c > index ce172edd2d..af8459c587 100644 > --- a/qga/commands.c > +++ b/qga/commands.c > @@ -529,6 +529,40 @@ done: > return ge; > } > > +void qmp_guest_exec_terminate(int64_t pid, Error **errp) > +{ > + GuestExecInfo *gei; > + > + slog("guest-exec-terminate called, pid: %u", (uint32_t)pid); > + > + gei =3D guest_exec_info_find(pid); > + if (gei =3D=3D NULL) { > + error_setg(errp, QERR_INVALID_PARAMETER, "pid"); > + return; > + } > + > + if (gei->finished) { > + return; > + } > + > +#ifdef G_OS_WIN32 > + char buf[32]; > + int res; > + > + res =3D kill_process_tree(pid); > + if (res !=3D 0) { > + snprintf(buf, sizeof(buf), "win32 err %d", res); > + error_setg(errp, QERR_QGA_COMMAND_FAILED, buf); > + } > +#else > + if (kill(pid, SIGKILL) < 0) { > + if (errno !=3D ESRCH) { > + error_setg(errp, QERR_QGA_COMMAND_FAILED, strerror(errno)); > + } > + } > +#endif > +} > + > /* Convert GuestFileWhence (either a raw integer or an enum value) into > * the guest's SEEK_ constants. */ > int ga_parse_whence(GuestFileWhence *whence, Error **errp) > diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json > index 876e2a8ea8..b39be4cdc2 100644 > --- a/qga/qapi-schema.json > +++ b/qga/qapi-schema.json > @@ -1326,6 +1326,19 @@ > '*input-data': 'str', '*capture-output': > 'GuestExecCaptureOutput' }, > 'returns': 'GuestExec' } > > +## > +# @guest-exec-terminate: > +# > +# Terminate process associated with PID retrieved via guest-exec. > +# > +# @pid: pid returned from guest-exec > +# > +# Returns: Nothing on success. > +# > +# Since: 8.2 > +## > +{ 'command': 'guest-exec-terminate', > + 'data': { 'pid': 'int' } } > > ## > # @GuestHostName: > -- > 2.34.1 > > --00000000000029704706089a4ca9 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable


=
On Wed, Oct 25, 2023 at 5:01=E2=80=AF= PM Alexander Ivanov <a= lexander.ivanov@virtuozzo.com> wrote:
We need to terminate processes executed with g= uest-exec command. Add
guest-exec-terminate command for process termination by PID.

Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
---
=C2=A0qga/commands-common.h |=C2=A0 2 ++
=C2=A0qga/commands-win32.c=C2=A0 | 64 +++++++++++++++++++++++++++++++++++++= ++++++
=C2=A0qga/commands.c=C2=A0 =C2=A0 =C2=A0 =C2=A0 | 34 ++++++++++++++++++++++= +
=C2=A0qga/qapi-schema.json=C2=A0 | 13 +++++++++
=C2=A04 files changed, 113 insertions(+)

diff --git a/qga/commands-common.h b/qga/commands-common.h
index 8c1c56aac9..34b9a22578 100644
--- a/qga/commands-common.h
+++ b/qga/commands-common.h
@@ -80,4 +80,6 @@ GuestFileRead *guest_file_read_unsafe(GuestFileHandle *gf= h,
=C2=A0 */
=C2=A0char *qga_get_host_name(Error **errp);

+int kill_process_tree(int64_t pid);
+
=C2=A0#endif
diff --git a/qga/commands-win32.c b/qga/commands-win32.c
index 697c65507c..5aa43a9ed7 100644
--- a/qga/commands-win32.c
+++ b/qga/commands-win32.c
@@ -27,6 +27,7 @@
=C2=A0#include <lm.h>
=C2=A0#include <wtsapi32.h>
=C2=A0#include <wininet.h>
+#include <tlhelp32.h>

=C2=A0#include "guest-agent-core.h"
=C2=A0#include "vss-win32.h"
@@ -2522,3 +2523,66 @@ GuestCpuStatsList *qmp_guest_get_cpustats(Error **er= rp)
=C2=A0 =C2=A0 =C2=A0error_setg(errp, QERR_UNSUPPORTED);
=C2=A0 =C2=A0 =C2=A0return NULL;
=C2=A0}
+
+int kill_process_tree(int64_t pid)
+{
+=C2=A0 =C2=A0 PROCESSENTRY32 proc_entry;
+=C2=A0 =C2=A0 HANDLE snapshot, process;
+=C2=A0 =C2=A0 GList *pid_entry, *pid_list =3D NULL;
+=C2=A0 =C2=A0 bool added, success;
+=C2=A0 =C2=A0 int res =3D 0;
+
+=C2=A0 =C2=A0 snapshot =3D CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)= ;
+=C2=A0 =C2=A0 if (snapshot =3D=3D INVALID_HANDLE_VALUE) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return GetLastError();
+=C2=A0 =C2=A0 }
+
+=C2=A0 =C2=A0 pid_list =3D g_list_append(pid_list, GUINT_TO_POINTER(pid));=
+
+=C2=A0 =C2=A0 proc_entry.dwSize =3D sizeof(PROCESSENTRY32);
+=C2=A0 =C2=A0 do {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 added =3D false;
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 for (success =3D Process32First(snapshot, &= ;proc_entry);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0success; success =3D Proce= ss32Next(snapshot, &proc_entry)) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 gpointer ppid_p, pid_p;
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 ppid_p =3D GUINT_TO_POINTER(proc= _entry.th32ParentProcessID);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 pid_p =3D GUINT_TO_POINTER(proc_= entry.th32ProcessID);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if (g_list_find(pid_list, ppid_p= ) && !g_list_find(pid_list, pid_p)) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 pid_list =3D g_lis= t_append(pid_list, pid_p);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 added =3D true; +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 }
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
+=C2=A0 =C2=A0 } while (added);
+
+=C2=A0 =C2=A0 for (success =3D Process32First(snapshot, &proc_entry);<= br> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0success; success =3D Process32Next(snaps= hot, &proc_entry)) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (g_list_find(pid_list, GUINT_TO_POINTER(pro= c_entry.th32ProcessID))) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 g_debug("killing pid=3D%u p= pid=3D%u name=3D%s",
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (guint)proc_entry.= th32ProcessID,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (guint)proc_entry.= th32ParentProcessID,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 proc_entry.szExeFi= le);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
+=C2=A0 =C2=A0 }
+


Why do we need to= store these processes before termination?
I understand that= we need to enumerate all processes to find children
but why we c= an't terminate it on the fly?
=C2=A0
+=C2=A0 =C2=A0 CloseHandle(snapshot);
+
+=C2=A0 =C2=A0 for (pid_entry =3D pid_list; pid_entry; pid_entry =3D pid_en= try->next) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 pid =3D GPOINTER_TO_UINT(pid_entry->data);<= br> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 process =3D OpenProcess(PROCESS_TERMINATE, FAL= SE, pid);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (process =3D=3D INVALID_HANDLE_VALUE) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if (!res) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 res =3D GetLastErr= or();
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if (res =3D=3D ERR= OR_FILE_NOT_FOUND) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 res = =3D 0;
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 }
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 }
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 continue;
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 TerminateProcess(process, 255);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 CloseHandle(process);
+=C2=A0 =C2=A0 }
+
+=C2=A0 =C2=A0 g_list_free(pid_list);
+
+=C2=A0 =C2=A0 return res;
+}
diff --git a/qga/commands.c b/qga/commands.c
index ce172edd2d..af8459c587 100644
--- a/qga/commands.c
+++ b/qga/commands.c
@@ -529,6 +529,40 @@ done:
=C2=A0 =C2=A0 =C2=A0return ge;
=C2=A0}

+void qmp_guest_exec_terminate(int64_t pid, Error **errp)
+{
+=C2=A0 =C2=A0 GuestExecInfo *gei;
+
+=C2=A0 =C2=A0 slog("guest-exec-terminate called, pid: %u", (uint= 32_t)pid);
+
+=C2=A0 =C2=A0 gei =3D guest_exec_info_find(pid);
+=C2=A0 =C2=A0 if (gei =3D=3D NULL) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 error_setg(errp, QERR_INVALID_PARAMETER, "= ;pid");
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return;
+=C2=A0 =C2=A0 }
+
+=C2=A0 =C2=A0 if (gei->finished) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return;
+=C2=A0 =C2=A0 }
+
+#ifdef G_OS_WIN32
+=C2=A0 =C2=A0 char buf[32];
+=C2=A0 =C2=A0 int res;
+
+=C2=A0 =C2=A0 res =3D kill_process_tree(pid);
+=C2=A0 =C2=A0 if (res !=3D 0) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 snprintf(buf, sizeof(buf), "win32 err %d&= quot;, res);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 error_setg(errp, QERR_QGA_COMMAND_FAILED, buf)= ;
+=C2=A0 =C2=A0 }
+#else
+=C2=A0 =C2=A0 if (kill(pid, SIGKILL) < 0) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (errno !=3D ESRCH) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 error_setg(errp, QERR_QGA_COMMAN= D_FAILED, strerror(errno));
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
+=C2=A0 =C2=A0 }
+#endif
+}
+
=C2=A0/* Convert GuestFileWhence (either a raw integer or an enum value) in= to
=C2=A0 * the guest's SEEK_ constants.=C2=A0 */
=C2=A0int ga_parse_whence(GuestFileWhence *whence, Error **errp)
diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
index 876e2a8ea8..b39be4cdc2 100644
--- a/qga/qapi-schema.json
+++ b/qga/qapi-schema.json
@@ -1326,6 +1326,19 @@
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 '*input-data= 9;: 'str', '*capture-output': 'GuestExecCaptureOutput&#= 39; },
=C2=A0 =C2=A0'returns': 'GuestExec' }

+##
+# @guest-exec-terminate:
+#
+# Terminate process associated with PID retrieved via guest-exec.
+#
+# @pid: pid returned from guest-exec
+#
+# Returns: Nothing on success.
+#
+# Since: 8.2
+##
+{ 'command': 'guest-exec-terminate',
+=C2=A0 'data':=C2=A0 =C2=A0 { 'pid': 'int' } }

=C2=A0##
=C2=A0# @GuestHostName:
--
2.34.1

--00000000000029704706089a4ca9--