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