]>
Commit | Line | Data |
---|---|---|
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 | */ | |
23 | void 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 |
32 | int64_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 |
38 | int64_t qmp_guest_sync(int64_t id, Error **errp) |
39 | { | |
40 | return id; | |
41 | } | |
42 | ||
77dbc81b | 43 | void qmp_guest_ping(Error **errp) |
42074a9d MR |
44 | { |
45 | slog("guest-ping called"); | |
46 | } | |
47 | ||
8dc4d915 | 48 | static 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 | 65 | struct 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 | |
74 | struct GuestExecInfo { | |
75 | GPid pid; | |
76 | int64_t pid_numeric; | |
77 | gint status; | |
78 | bool finished; | |
79 | QTAILQ_ENTRY(GuestExecInfo) next; | |
80 | }; | |
81 | typedef struct GuestExecInfo GuestExecInfo; | |
82 | ||
83 | static struct { | |
84 | QTAILQ_HEAD(, GuestExecInfo) processes; | |
85 | } guest_exec_state = { | |
86 | .processes = QTAILQ_HEAD_INITIALIZER(guest_exec_state.processes), | |
87 | }; | |
88 | ||
89 | static 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 | ||
98 | static 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 | ||
110 | static 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 | ||
123 | GuestExecStatus *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(). */ | |
182 | static 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 | ||
215 | static 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 | ||
228 | GuestExec *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 | ||
266 | done: | |
267 | g_free(argv); | |
268 | g_free(envp); | |
269 | ||
270 | return ge; | |
271 | } |