]> git.proxmox.com Git - mirror_qemu.git/blame - qga/commands.c
qga: guest exec functionality
[mirror_qemu.git] / qga / commands.c
CommitLineData
42074a9d
MR
1/*
2 * QEMU Guest Agent common/cross-platform command implementations
3 *
4 * Copyright IBM Corp. 2012
5 *
6 * Authors:
7 * Michael Roth <mdroth@linux.vnet.ibm.com>
8 *
9 * This work is licensed under the terms of the GNU GPL, version 2 or later.
10 * See the COPYING file in the top-level directory.
11 */
12
13#include <glib.h>
14#include "qga/guest-agent-core.h"
15#include "qga-qmp-commands.h"
7b1b5d19 16#include "qapi/qmp/qerror.h"
42074a9d
MR
17
18/* Note: in some situations, like with the fsfreeze, logging may be
19 * temporarilly disabled. if it is necessary that a command be able
20 * to log for accounting purposes, check ga_logging_enabled() beforehand,
21 * and use the QERR_QGA_LOGGING_DISABLED to generate an error
22 */
23void slog(const gchar *fmt, ...)
24{
25 va_list ap;
26
27 va_start(ap, fmt);
28 g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap);
29 va_end(ap);
30}
31
3cf0bed8
MR
32int64_t qmp_guest_sync_delimited(int64_t id, Error **errp)
33{
34 ga_set_response_delimited(ga_state);
35 return id;
36}
37
42074a9d
MR
38int64_t qmp_guest_sync(int64_t id, Error **errp)
39{
40 return id;
41}
42
77dbc81b 43void qmp_guest_ping(Error **errp)
42074a9d
MR
44{
45 slog("guest-ping called");
46}
47
8dc4d915 48static void qmp_command_info(QmpCommand *cmd, void *opaque)
42074a9d 49{
8dc4d915 50 GuestAgentInfo *info = opaque;
42074a9d
MR
51 GuestAgentCommandInfo *cmd_info;
52 GuestAgentCommandInfoList *cmd_info_list;
42074a9d 53
f3a06403 54 cmd_info = g_new0(GuestAgentCommandInfo, 1);
8dc4d915
MW
55 cmd_info->name = g_strdup(qmp_command_name(cmd));
56 cmd_info->enabled = qmp_command_is_enabled(cmd);
0106dc4f 57 cmd_info->success_response = qmp_has_success_response(cmd);
42074a9d 58
f3a06403 59 cmd_info_list = g_new0(GuestAgentCommandInfoList, 1);
8dc4d915
MW
60 cmd_info_list->value = cmd_info;
61 cmd_info_list->next = info->supported_commands;
62 info->supported_commands = cmd_info_list;
63}
42074a9d 64
77dbc81b 65struct GuestAgentInfo *qmp_guest_info(Error **errp)
8dc4d915 66{
f3a06403 67 GuestAgentInfo *info = g_new0(GuestAgentInfo, 1);
42074a9d 68
8dc4d915
MW
69 info->version = g_strdup(QEMU_VERSION);
70 qmp_for_each_command(qmp_command_info, info);
42074a9d
MR
71 return info;
72}
d697e30c
YP
73
74struct GuestExecInfo {
75 GPid pid;
76 int64_t pid_numeric;
77 gint status;
78 bool finished;
79 QTAILQ_ENTRY(GuestExecInfo) next;
80};
81typedef struct GuestExecInfo GuestExecInfo;
82
83static struct {
84 QTAILQ_HEAD(, GuestExecInfo) processes;
85} guest_exec_state = {
86 .processes = QTAILQ_HEAD_INITIALIZER(guest_exec_state.processes),
87};
88
89static int64_t gpid_to_int64(GPid pid)
90{
91#ifdef G_OS_WIN32
92 return GetProcessId(pid);
93#else
94 return (int64_t)pid;
95#endif
96}
97
98static GuestExecInfo *guest_exec_info_add(GPid pid)
99{
100 GuestExecInfo *gei;
101
102 gei = g_new0(GuestExecInfo, 1);
103 gei->pid = pid;
104 gei->pid_numeric = gpid_to_int64(pid);
105 QTAILQ_INSERT_TAIL(&guest_exec_state.processes, gei, next);
106
107 return gei;
108}
109
110static GuestExecInfo *guest_exec_info_find(int64_t pid_numeric)
111{
112 GuestExecInfo *gei;
113
114 QTAILQ_FOREACH(gei, &guest_exec_state.processes, next) {
115 if (gei->pid_numeric == pid_numeric) {
116 return gei;
117 }
118 }
119
120 return NULL;
121}
122
123GuestExecStatus *qmp_guest_exec_status(int64_t pid, Error **err)
124{
125 GuestExecInfo *gei;
126 GuestExecStatus *ges;
127
128 slog("guest-exec-status called, pid: %u", (uint32_t)pid);
129
130 gei = guest_exec_info_find(pid);
131 if (gei == NULL) {
132 error_setg(err, QERR_INVALID_PARAMETER, "pid");
133 return NULL;
134 }
135
136 ges = g_new0(GuestExecStatus, 1);
137 ges->exited = gei->finished;
138
139 if (gei->finished) {
140 /* Glib has no portable way to parse exit status.
141 * On UNIX, we can get either exit code from normal termination
142 * or signal number.
143 * On Windows, it is either the same exit code or the exception
144 * value for an unhandled exception that caused the process
145 * to terminate.
146 * See MSDN for GetExitCodeProcess() and ntstatus.h for possible
147 * well-known codes, e.g. C0000005 ACCESS_DENIED - analog of SIGSEGV
148 * References:
149 * https://msdn.microsoft.com/en-us/library/windows/desktop/ms683189(v=vs.85).aspx
150 * https://msdn.microsoft.com/en-us/library/aa260331(v=vs.60).aspx
151 */
152#ifdef G_OS_WIN32
153 /* Additionally WIN32 does not provide any additional information
154 * on whetherthe child exited or terminated via signal.
155 * We use this simple range check to distingish application exit code
156 * (usually value less then 256) and unhandled exception code with
157 * ntstatus (always value greater then 0xC0000005). */
158 if ((uint32_t)gei->status < 0xC0000000U) {
159 ges->has_exitcode = true;
160 ges->exitcode = gei->status;
161 } else {
162 ges->has_signal = true;
163 ges->signal = gei->status;
164 }
165#else
166 if (WIFEXITED(gei->status)) {
167 ges->has_exitcode = true;
168 ges->exitcode = WEXITSTATUS(gei->status);
169 } else if (WIFSIGNALED(gei->status)) {
170 ges->has_signal = true;
171 ges->signal = WTERMSIG(gei->status);
172 }
173#endif
174 QTAILQ_REMOVE(&guest_exec_state.processes, gei, next);
175 g_free(gei);
176 }
177
178 return ges;
179}
180
181/* Get environment variables or arguments array for execve(). */
182static char **guest_exec_get_args(const strList *entry, bool log)
183{
184 const strList *it;
185 int count = 1, i = 0; /* reserve for NULL terminator */
186 char **args;
187 char *str; /* for logging array of arguments */
188 size_t str_size = 1;
189
190 for (it = entry; it != NULL; it = it->next) {
191 count++;
192 str_size += 1 + strlen(it->value);
193 }
194
195 str = g_malloc(str_size);
196 *str = 0;
197 args = g_malloc(count * sizeof(char *));
198 for (it = entry; it != NULL; it = it->next) {
199 args[i++] = it->value;
200 pstrcat(str, str_size, it->value);
201 if (it->next) {
202 pstrcat(str, str_size, " ");
203 }
204 }
205 args[i] = NULL;
206
207 if (log) {
208 slog("guest-exec called: \"%s\"", str);
209 }
210 g_free(str);
211
212 return args;
213}
214
215static void guest_exec_child_watch(GPid pid, gint status, gpointer data)
216{
217 GuestExecInfo *gei = (GuestExecInfo *)data;
218
219 g_debug("guest_exec_child_watch called, pid: %d, status: %u",
220 (int32_t)gpid_to_int64(pid), (uint32_t)status);
221
222 gei->status = status;
223 gei->finished = true;
224
225 g_spawn_close_pid(pid);
226}
227
228GuestExec *qmp_guest_exec(const char *path,
229 bool has_arg, strList *arg,
230 bool has_env, strList *env,
231 bool has_input_data, const char *input_data,
232 bool has_capture_output, bool capture_output,
233 Error **err)
234{
235 GPid pid;
236 GuestExec *ge = NULL;
237 GuestExecInfo *gei;
238 char **argv, **envp;
239 strList arglist;
240 gboolean ret;
241 GError *gerr = NULL;
242
243 arglist.value = (char *)path;
244 arglist.next = has_arg ? arg : NULL;
245
246 argv = guest_exec_get_args(&arglist, true);
247 envp = guest_exec_get_args(has_env ? env : NULL, false);
248
249 ret = g_spawn_async_with_pipes(NULL, argv, envp,
250 G_SPAWN_SEARCH_PATH |
251 G_SPAWN_DO_NOT_REAP_CHILD |
252 G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
253 NULL, NULL, &pid, NULL, NULL, NULL, &gerr);
254 if (!ret) {
255 error_setg(err, QERR_QGA_COMMAND_FAILED, gerr->message);
256 g_error_free(gerr);
257 goto done;
258 }
259
260 ge = g_new0(GuestExec, 1);
261 ge->pid = gpid_to_int64(pid);
262
263 gei = guest_exec_info_add(pid);
264 g_child_watch_add(pid, guest_exec_child_watch, gei);
265
266done:
267 g_free(argv);
268 g_free(envp);
269
270 return ge;
271}