root / qga / guest-agent-commands.c @ 4eb36d40
History | View | Annotate | Download (13.7 kB)
1 |
/*
|
---|---|
2 |
* QEMU Guest Agent commands
|
3 |
*
|
4 |
* Copyright IBM Corp. 2011
|
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 |
|
15 |
#if defined(__linux__)
|
16 |
#include <mntent.h> |
17 |
#include <linux/fs.h> |
18 |
|
19 |
#if defined(__linux__) && defined(FIFREEZE)
|
20 |
#define CONFIG_FSFREEZE
|
21 |
#endif
|
22 |
#endif
|
23 |
|
24 |
#include <sys/types.h> |
25 |
#include <sys/ioctl.h> |
26 |
#include "qga/guest-agent-core.h" |
27 |
#include "qga-qmp-commands.h" |
28 |
#include "qerror.h" |
29 |
#include "qemu-queue.h" |
30 |
|
31 |
static GAState *ga_state;
|
32 |
|
33 |
/* Note: in some situations, like with the fsfreeze, logging may be
|
34 |
* temporarilly disabled. if it is necessary that a command be able
|
35 |
* to log for accounting purposes, check ga_logging_enabled() beforehand,
|
36 |
* and use the QERR_QGA_LOGGING_DISABLED to generate an error
|
37 |
*/
|
38 |
static void slog(const char *fmt, ...) |
39 |
{ |
40 |
va_list ap; |
41 |
|
42 |
va_start(ap, fmt); |
43 |
g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap);
|
44 |
va_end(ap); |
45 |
} |
46 |
|
47 |
int64_t qmp_guest_sync(int64_t id, Error **errp) |
48 |
{ |
49 |
return id;
|
50 |
} |
51 |
|
52 |
void qmp_guest_ping(Error **err)
|
53 |
{ |
54 |
slog("guest-ping called");
|
55 |
} |
56 |
|
57 |
struct GuestAgentInfo *qmp_guest_info(Error **err)
|
58 |
{ |
59 |
GuestAgentInfo *info = qemu_mallocz(sizeof(GuestAgentInfo));
|
60 |
|
61 |
info->version = g_strdup(QGA_VERSION); |
62 |
|
63 |
return info;
|
64 |
} |
65 |
|
66 |
void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err) |
67 |
{ |
68 |
int ret;
|
69 |
const char *shutdown_flag; |
70 |
|
71 |
slog("guest-shutdown called, mode: %s", mode);
|
72 |
if (!has_mode || strcmp(mode, "powerdown") == 0) { |
73 |
shutdown_flag = "-P";
|
74 |
} else if (strcmp(mode, "halt") == 0) { |
75 |
shutdown_flag = "-H";
|
76 |
} else if (strcmp(mode, "reboot") == 0) { |
77 |
shutdown_flag = "-r";
|
78 |
} else {
|
79 |
error_set(err, QERR_INVALID_PARAMETER_VALUE, "mode",
|
80 |
"halt|powerdown|reboot");
|
81 |
return;
|
82 |
} |
83 |
|
84 |
ret = fork(); |
85 |
if (ret == 0) { |
86 |
/* child, start the shutdown */
|
87 |
setsid(); |
88 |
fclose(stdin); |
89 |
fclose(stdout); |
90 |
fclose(stderr); |
91 |
|
92 |
ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0", |
93 |
"hypervisor initiated shutdown", (char*)NULL); |
94 |
if (ret) {
|
95 |
slog("guest-shutdown failed: %s", strerror(errno));
|
96 |
} |
97 |
exit(!!ret); |
98 |
} else if (ret < 0) { |
99 |
error_set(err, QERR_UNDEFINED_ERROR); |
100 |
} |
101 |
} |
102 |
|
103 |
typedef struct GuestFileHandle { |
104 |
uint64_t id; |
105 |
FILE *fh; |
106 |
QTAILQ_ENTRY(GuestFileHandle) next; |
107 |
} GuestFileHandle; |
108 |
|
109 |
static struct { |
110 |
QTAILQ_HEAD(, GuestFileHandle) filehandles; |
111 |
} guest_file_state; |
112 |
|
113 |
static void guest_file_handle_add(FILE *fh) |
114 |
{ |
115 |
GuestFileHandle *gfh; |
116 |
|
117 |
gfh = qemu_mallocz(sizeof(GuestFileHandle));
|
118 |
gfh->id = fileno(fh); |
119 |
gfh->fh = fh; |
120 |
QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next); |
121 |
} |
122 |
|
123 |
static GuestFileHandle *guest_file_handle_find(int64_t id)
|
124 |
{ |
125 |
GuestFileHandle *gfh; |
126 |
|
127 |
QTAILQ_FOREACH(gfh, &guest_file_state.filehandles, next) |
128 |
{ |
129 |
if (gfh->id == id) {
|
130 |
return gfh;
|
131 |
} |
132 |
} |
133 |
|
134 |
return NULL; |
135 |
} |
136 |
|
137 |
int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, Error **err) |
138 |
{ |
139 |
FILE *fh; |
140 |
int fd;
|
141 |
int64_t ret = -1;
|
142 |
|
143 |
if (!has_mode) {
|
144 |
mode = "r";
|
145 |
} |
146 |
slog("guest-file-open called, filepath: %s, mode: %s", path, mode);
|
147 |
fh = fopen(path, mode); |
148 |
if (!fh) {
|
149 |
error_set(err, QERR_OPEN_FILE_FAILED, path); |
150 |
return -1; |
151 |
} |
152 |
|
153 |
/* set fd non-blocking to avoid common use cases (like reading from a
|
154 |
* named pipe) from hanging the agent
|
155 |
*/
|
156 |
fd = fileno(fh); |
157 |
ret = fcntl(fd, F_GETFL); |
158 |
ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK); |
159 |
if (ret == -1) { |
160 |
error_set(err, QERR_QGA_COMMAND_FAILED, "fcntl() failed");
|
161 |
fclose(fh); |
162 |
return -1; |
163 |
} |
164 |
|
165 |
guest_file_handle_add(fh); |
166 |
slog("guest-file-open, handle: %d", fd);
|
167 |
return fd;
|
168 |
} |
169 |
|
170 |
void qmp_guest_file_close(int64_t handle, Error **err)
|
171 |
{ |
172 |
GuestFileHandle *gfh = guest_file_handle_find(handle); |
173 |
int ret;
|
174 |
|
175 |
slog("guest-file-close called, handle: %ld", handle);
|
176 |
if (!gfh) {
|
177 |
error_set(err, QERR_FD_NOT_FOUND, "handle");
|
178 |
return;
|
179 |
} |
180 |
|
181 |
ret = fclose(gfh->fh); |
182 |
if (ret == -1) { |
183 |
error_set(err, QERR_QGA_COMMAND_FAILED, "fclose() failed");
|
184 |
return;
|
185 |
} |
186 |
|
187 |
QTAILQ_REMOVE(&guest_file_state.filehandles, gfh, next); |
188 |
qemu_free(gfh); |
189 |
} |
190 |
|
191 |
struct GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count, |
192 |
int64_t count, Error **err) |
193 |
{ |
194 |
GuestFileHandle *gfh = guest_file_handle_find(handle); |
195 |
GuestFileRead *read_data = NULL;
|
196 |
guchar *buf; |
197 |
FILE *fh; |
198 |
size_t read_count; |
199 |
|
200 |
if (!gfh) {
|
201 |
error_set(err, QERR_FD_NOT_FOUND, "handle");
|
202 |
return NULL; |
203 |
} |
204 |
|
205 |
if (!has_count) {
|
206 |
count = QGA_READ_COUNT_DEFAULT; |
207 |
} else if (count < 0) { |
208 |
error_set(err, QERR_INVALID_PARAMETER, "count");
|
209 |
return NULL; |
210 |
} |
211 |
|
212 |
fh = gfh->fh; |
213 |
buf = qemu_mallocz(count+1);
|
214 |
read_count = fread(buf, 1, count, fh);
|
215 |
if (ferror(fh)) {
|
216 |
slog("guest-file-read failed, handle: %ld", handle);
|
217 |
error_set(err, QERR_QGA_COMMAND_FAILED, "fread() failed");
|
218 |
} else {
|
219 |
buf[read_count] = 0;
|
220 |
read_data = qemu_mallocz(sizeof(GuestFileRead));
|
221 |
read_data->count = read_count; |
222 |
read_data->eof = feof(fh); |
223 |
if (read_count) {
|
224 |
read_data->buf_b64 = g_base64_encode(buf, read_count); |
225 |
} |
226 |
} |
227 |
qemu_free(buf); |
228 |
clearerr(fh); |
229 |
|
230 |
return read_data;
|
231 |
} |
232 |
|
233 |
GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64, |
234 |
bool has_count, int64_t count, Error **err)
|
235 |
{ |
236 |
GuestFileWrite *write_data = NULL;
|
237 |
guchar *buf; |
238 |
gsize buf_len; |
239 |
int write_count;
|
240 |
GuestFileHandle *gfh = guest_file_handle_find(handle); |
241 |
FILE *fh; |
242 |
|
243 |
if (!gfh) {
|
244 |
error_set(err, QERR_FD_NOT_FOUND, "handle");
|
245 |
return NULL; |
246 |
} |
247 |
|
248 |
fh = gfh->fh; |
249 |
buf = g_base64_decode(buf_b64, &buf_len); |
250 |
|
251 |
if (!has_count) {
|
252 |
count = buf_len; |
253 |
} else if (count < 0 || count > buf_len) { |
254 |
qemu_free(buf); |
255 |
error_set(err, QERR_INVALID_PARAMETER, "count");
|
256 |
return NULL; |
257 |
} |
258 |
|
259 |
write_count = fwrite(buf, 1, count, fh);
|
260 |
if (ferror(fh)) {
|
261 |
slog("guest-file-write failed, handle: %ld", handle);
|
262 |
error_set(err, QERR_QGA_COMMAND_FAILED, "fwrite() error");
|
263 |
} else {
|
264 |
write_data = qemu_mallocz(sizeof(GuestFileWrite));
|
265 |
write_data->count = write_count; |
266 |
write_data->eof = feof(fh); |
267 |
} |
268 |
qemu_free(buf); |
269 |
clearerr(fh); |
270 |
|
271 |
return write_data;
|
272 |
} |
273 |
|
274 |
struct GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset,
|
275 |
int64_t whence, Error **err) |
276 |
{ |
277 |
GuestFileHandle *gfh = guest_file_handle_find(handle); |
278 |
GuestFileSeek *seek_data = NULL;
|
279 |
FILE *fh; |
280 |
int ret;
|
281 |
|
282 |
if (!gfh) {
|
283 |
error_set(err, QERR_FD_NOT_FOUND, "handle");
|
284 |
return NULL; |
285 |
} |
286 |
|
287 |
fh = gfh->fh; |
288 |
ret = fseek(fh, offset, whence); |
289 |
if (ret == -1) { |
290 |
error_set(err, QERR_QGA_COMMAND_FAILED, strerror(errno)); |
291 |
} else {
|
292 |
seek_data = qemu_mallocz(sizeof(GuestFileRead));
|
293 |
seek_data->position = ftell(fh); |
294 |
seek_data->eof = feof(fh); |
295 |
} |
296 |
clearerr(fh); |
297 |
|
298 |
return seek_data;
|
299 |
} |
300 |
|
301 |
void qmp_guest_file_flush(int64_t handle, Error **err)
|
302 |
{ |
303 |
GuestFileHandle *gfh = guest_file_handle_find(handle); |
304 |
FILE *fh; |
305 |
int ret;
|
306 |
|
307 |
if (!gfh) {
|
308 |
error_set(err, QERR_FD_NOT_FOUND, "handle");
|
309 |
return;
|
310 |
} |
311 |
|
312 |
fh = gfh->fh; |
313 |
ret = fflush(fh); |
314 |
if (ret == EOF) { |
315 |
error_set(err, QERR_QGA_COMMAND_FAILED, strerror(errno)); |
316 |
} |
317 |
} |
318 |
|
319 |
static void guest_file_init(void) |
320 |
{ |
321 |
QTAILQ_INIT(&guest_file_state.filehandles); |
322 |
} |
323 |
|
324 |
#if defined(CONFIG_FSFREEZE)
|
325 |
static void disable_logging(void) |
326 |
{ |
327 |
ga_disable_logging(ga_state); |
328 |
} |
329 |
|
330 |
static void enable_logging(void) |
331 |
{ |
332 |
ga_enable_logging(ga_state); |
333 |
} |
334 |
|
335 |
typedef struct GuestFsfreezeMount { |
336 |
char *dirname;
|
337 |
char *devtype;
|
338 |
QTAILQ_ENTRY(GuestFsfreezeMount) next; |
339 |
} GuestFsfreezeMount; |
340 |
|
341 |
struct {
|
342 |
GuestFsfreezeStatus status; |
343 |
QTAILQ_HEAD(, GuestFsfreezeMount) mount_list; |
344 |
} guest_fsfreeze_state; |
345 |
|
346 |
/*
|
347 |
* Walk the mount table and build a list of local file systems
|
348 |
*/
|
349 |
static int guest_fsfreeze_build_mount_list(void) |
350 |
{ |
351 |
struct mntent *ment;
|
352 |
GuestFsfreezeMount *mount, *temp; |
353 |
char const *mtab = MOUNTED; |
354 |
FILE *fp; |
355 |
|
356 |
QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) { |
357 |
QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next); |
358 |
qemu_free(mount->dirname); |
359 |
qemu_free(mount->devtype); |
360 |
qemu_free(mount); |
361 |
} |
362 |
|
363 |
fp = setmntent(mtab, "r");
|
364 |
if (!fp) {
|
365 |
g_warning("fsfreeze: unable to read mtab");
|
366 |
return -1; |
367 |
} |
368 |
|
369 |
while ((ment = getmntent(fp))) {
|
370 |
/*
|
371 |
* An entry which device name doesn't start with a '/' is
|
372 |
* either a dummy file system or a network file system.
|
373 |
* Add special handling for smbfs and cifs as is done by
|
374 |
* coreutils as well.
|
375 |
*/
|
376 |
if ((ment->mnt_fsname[0] != '/') || |
377 |
(strcmp(ment->mnt_type, "smbfs") == 0) || |
378 |
(strcmp(ment->mnt_type, "cifs") == 0)) { |
379 |
continue;
|
380 |
} |
381 |
|
382 |
mount = qemu_mallocz(sizeof(GuestFsfreezeMount));
|
383 |
mount->dirname = qemu_strdup(ment->mnt_dir); |
384 |
mount->devtype = qemu_strdup(ment->mnt_type); |
385 |
|
386 |
QTAILQ_INSERT_TAIL(&guest_fsfreeze_state.mount_list, mount, next); |
387 |
} |
388 |
|
389 |
endmntent(fp); |
390 |
|
391 |
return 0; |
392 |
} |
393 |
|
394 |
/*
|
395 |
* Return status of freeze/thaw
|
396 |
*/
|
397 |
GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err) |
398 |
{ |
399 |
return guest_fsfreeze_state.status;
|
400 |
} |
401 |
|
402 |
/*
|
403 |
* Walk list of mounted file systems in the guest, and freeze the ones which
|
404 |
* are real local file systems.
|
405 |
*/
|
406 |
int64_t qmp_guest_fsfreeze_freeze(Error **err) |
407 |
{ |
408 |
int ret = 0, i = 0; |
409 |
struct GuestFsfreezeMount *mount, *temp;
|
410 |
int fd;
|
411 |
char err_msg[512]; |
412 |
|
413 |
slog("guest-fsfreeze called");
|
414 |
|
415 |
if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) {
|
416 |
return 0; |
417 |
} |
418 |
|
419 |
ret = guest_fsfreeze_build_mount_list(); |
420 |
if (ret < 0) { |
421 |
return ret;
|
422 |
} |
423 |
|
424 |
/* cannot risk guest agent blocking itself on a write in this state */
|
425 |
disable_logging(); |
426 |
|
427 |
QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) { |
428 |
fd = qemu_open(mount->dirname, O_RDONLY); |
429 |
if (fd == -1) { |
430 |
sprintf(err_msg, "failed to open %s, %s", mount->dirname, strerror(errno));
|
431 |
error_set(err, QERR_QGA_COMMAND_FAILED, err_msg); |
432 |
goto error;
|
433 |
} |
434 |
|
435 |
/* we try to cull filesytems we know won't work in advance, but other
|
436 |
* filesytems may not implement fsfreeze for less obvious reasons.
|
437 |
* these will report EOPNOTSUPP, so we simply ignore them. when
|
438 |
* thawing, these filesystems will return an EINVAL instead, due to
|
439 |
* not being in a frozen state. Other filesystem-specific
|
440 |
* errors may result in EINVAL, however, so the user should check the
|
441 |
* number * of filesystems returned here against those returned by the
|
442 |
* thaw operation to determine whether everything completed
|
443 |
* successfully
|
444 |
*/
|
445 |
ret = ioctl(fd, FIFREEZE); |
446 |
if (ret < 0 && errno != EOPNOTSUPP) { |
447 |
sprintf(err_msg, "failed to freeze %s, %s", mount->dirname, strerror(errno));
|
448 |
error_set(err, QERR_QGA_COMMAND_FAILED, err_msg); |
449 |
close(fd); |
450 |
goto error;
|
451 |
} |
452 |
close(fd); |
453 |
|
454 |
i++; |
455 |
} |
456 |
|
457 |
guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_FROZEN; |
458 |
return i;
|
459 |
|
460 |
error:
|
461 |
if (i > 0) { |
462 |
qmp_guest_fsfreeze_thaw(NULL);
|
463 |
} |
464 |
return 0; |
465 |
} |
466 |
|
467 |
/*
|
468 |
* Walk list of frozen file systems in the guest, and thaw them.
|
469 |
*/
|
470 |
int64_t qmp_guest_fsfreeze_thaw(Error **err) |
471 |
{ |
472 |
int ret;
|
473 |
GuestFsfreezeMount *mount, *temp; |
474 |
int fd, i = 0; |
475 |
bool has_error = false; |
476 |
|
477 |
QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) { |
478 |
fd = qemu_open(mount->dirname, O_RDONLY); |
479 |
if (fd == -1) { |
480 |
has_error = true;
|
481 |
continue;
|
482 |
} |
483 |
ret = ioctl(fd, FITHAW); |
484 |
if (ret < 0 && errno != EOPNOTSUPP && errno != EINVAL) { |
485 |
has_error = true;
|
486 |
close(fd); |
487 |
continue;
|
488 |
} |
489 |
close(fd); |
490 |
i++; |
491 |
} |
492 |
|
493 |
if (has_error) {
|
494 |
guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_ERROR; |
495 |
} else {
|
496 |
guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED; |
497 |
} |
498 |
enable_logging(); |
499 |
return i;
|
500 |
} |
501 |
|
502 |
static void guest_fsfreeze_init(void) |
503 |
{ |
504 |
guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED; |
505 |
QTAILQ_INIT(&guest_fsfreeze_state.mount_list); |
506 |
} |
507 |
|
508 |
static void guest_fsfreeze_cleanup(void) |
509 |
{ |
510 |
int64_t ret; |
511 |
Error *err = NULL;
|
512 |
|
513 |
if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) {
|
514 |
ret = qmp_guest_fsfreeze_thaw(&err); |
515 |
if (ret < 0 || err) { |
516 |
slog("failed to clean up frozen filesystems");
|
517 |
} |
518 |
} |
519 |
} |
520 |
#else
|
521 |
/*
|
522 |
* Return status of freeze/thaw
|
523 |
*/
|
524 |
GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err) |
525 |
{ |
526 |
error_set(err, QERR_UNSUPPORTED); |
527 |
|
528 |
return 0; |
529 |
} |
530 |
|
531 |
/*
|
532 |
* Walk list of mounted file systems in the guest, and freeze the ones which
|
533 |
* are real local file systems.
|
534 |
*/
|
535 |
int64_t qmp_guest_fsfreeze_freeze(Error **err) |
536 |
{ |
537 |
error_set(err, QERR_UNSUPPORTED); |
538 |
|
539 |
return 0; |
540 |
} |
541 |
|
542 |
/*
|
543 |
* Walk list of frozen file systems in the guest, and thaw them.
|
544 |
*/
|
545 |
int64_t qmp_guest_fsfreeze_thaw(Error **err) |
546 |
{ |
547 |
error_set(err, QERR_UNSUPPORTED); |
548 |
|
549 |
return 0; |
550 |
} |
551 |
#endif
|
552 |
|
553 |
/* register init/cleanup routines for stateful command groups */
|
554 |
void ga_command_state_init(GAState *s, GACommandState *cs)
|
555 |
{ |
556 |
ga_state = s; |
557 |
#if defined(CONFIG_FSFREEZE)
|
558 |
ga_command_state_add(cs, guest_fsfreeze_init, guest_fsfreeze_cleanup); |
559 |
#endif
|
560 |
ga_command_state_add(cs, guest_file_init, NULL);
|
561 |
} |