]>
Commit | Line | Data |
---|---|---|
e3d4d252 MR |
1 | /* |
2 | * QEMU Guest Agent commands | |
3 | * | |
4 | * Copyright IBM Corp. 2011 | |
5 | * | |
6 | * Authors: | |
7 | * Michael Roth <[email protected]> | |
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 | ||
7006b9cf AL |
13 | #if defined(__linux__) |
14 | #define CONFIG_FSFREEZE | |
15 | #endif | |
16 | ||
e3d4d252 | 17 | #include <glib.h> |
7006b9cf | 18 | #if defined(CONFIG_FSFREEZE) |
e3d4d252 | 19 | #include <mntent.h> |
7006b9cf AL |
20 | #include <linux/fs.h> |
21 | #endif | |
e3d4d252 MR |
22 | #include <sys/types.h> |
23 | #include <sys/ioctl.h> | |
e3d4d252 MR |
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 | ||
e3d4d252 MR |
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 | ||
7006b9cf AL |
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 | ||
e3d4d252 MR |
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 | } | |
7006b9cf AL |
518 | #else |
519 | /* | |
520 | * Return status of freeze/thaw | |
521 | */ | |
522 | GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err) | |
523 | { | |
524 | error_set(err, QERR_COMMAND_NOT_FOUND, "guest_fsfreeze_status"); | |
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_COMMAND_NOT_FOUND, "guest_fsfreeze_freeze"); | |
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_COMMAND_NOT_FOUND, "guest_fsfreeze_thaw"); | |
546 | ||
547 | return 0; | |
548 | } | |
549 | #endif | |
e3d4d252 MR |
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; | |
7006b9cf | 555 | #if defined(CONFIG_FSFREEZE) |
e3d4d252 | 556 | ga_command_state_add(cs, guest_fsfreeze_init, guest_fsfreeze_cleanup); |
7006b9cf | 557 | #endif |
e3d4d252 MR |
558 | ga_command_state_add(cs, guest_file_init, NULL); |
559 | } |