]>
Commit | Line | Data |
---|---|---|
e3d4d252 | 1 | /* |
42074a9d | 2 | * QEMU Guest Agent POSIX-specific command implementations |
e3d4d252 MR |
3 | * |
4 | * Copyright IBM Corp. 2011 | |
5 | * | |
6 | * Authors: | |
7 | * Michael Roth <mdroth@linux.vnet.ibm.com> | |
3424fc9f | 8 | * Michal Privoznik <mprivozn@redhat.com> |
e3d4d252 MR |
9 | * |
10 | * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
11 | * See the COPYING file in the top-level directory. | |
12 | */ | |
13 | ||
14 | #include <glib.h> | |
e72c3f2e MR |
15 | #include <sys/types.h> |
16 | #include <sys/ioctl.h> | |
2c02cbf6 | 17 | #include <sys/wait.h> |
e72c3f2e MR |
18 | #include "qga/guest-agent-core.h" |
19 | #include "qga-qmp-commands.h" | |
7b1b5d19 | 20 | #include "qapi/qmp/qerror.h" |
1de7afc9 PB |
21 | #include "qemu/queue.h" |
22 | #include "qemu/host-utils.h" | |
4eb36d40 | 23 | |
2c02cbf6 | 24 | #ifndef CONFIG_HAS_ENVIRON |
eecae147 AF |
25 | #ifdef __APPLE__ |
26 | #include <crt_externs.h> | |
27 | #define environ (*_NSGetEnviron()) | |
28 | #else | |
2c02cbf6 LC |
29 | extern char **environ; |
30 | #endif | |
eecae147 | 31 | #endif |
2c02cbf6 | 32 | |
4eb36d40 | 33 | #if defined(__linux__) |
e3d4d252 | 34 | #include <mntent.h> |
7006b9cf | 35 | #include <linux/fs.h> |
3424fc9f MP |
36 | #include <ifaddrs.h> |
37 | #include <arpa/inet.h> | |
38 | #include <sys/socket.h> | |
39 | #include <net/if.h> | |
e3d4d252 | 40 | |
eab5fd59 | 41 | #ifdef FIFREEZE |
e72c3f2e MR |
42 | #define CONFIG_FSFREEZE |
43 | #endif | |
eab5fd59 PB |
44 | #ifdef FITRIM |
45 | #define CONFIG_FSTRIM | |
46 | #endif | |
e72c3f2e MR |
47 | #endif |
48 | ||
e3d4d252 MR |
49 | void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err) |
50 | { | |
e3d4d252 | 51 | const char *shutdown_flag; |
d5dd3498 | 52 | pid_t rpid, pid; |
3674838c | 53 | int status; |
e3d4d252 MR |
54 | |
55 | slog("guest-shutdown called, mode: %s", mode); | |
56 | if (!has_mode || strcmp(mode, "powerdown") == 0) { | |
57 | shutdown_flag = "-P"; | |
58 | } else if (strcmp(mode, "halt") == 0) { | |
59 | shutdown_flag = "-H"; | |
60 | } else if (strcmp(mode, "reboot") == 0) { | |
61 | shutdown_flag = "-r"; | |
62 | } else { | |
63 | error_set(err, QERR_INVALID_PARAMETER_VALUE, "mode", | |
64 | "halt|powerdown|reboot"); | |
65 | return; | |
66 | } | |
67 | ||
d5dd3498 LC |
68 | pid = fork(); |
69 | if (pid == 0) { | |
e3d4d252 MR |
70 | /* child, start the shutdown */ |
71 | setsid(); | |
3674838c LC |
72 | reopen_fd_to_null(0); |
73 | reopen_fd_to_null(1); | |
74 | reopen_fd_to_null(2); | |
e3d4d252 | 75 | |
3674838c LC |
76 | execle("/sbin/shutdown", "shutdown", shutdown_flag, "+0", |
77 | "hypervisor initiated shutdown", (char*)NULL, environ); | |
78 | _exit(EXIT_FAILURE); | |
d5dd3498 LC |
79 | } else if (pid < 0) { |
80 | goto exit_err; | |
e3d4d252 | 81 | } |
d5dd3498 LC |
82 | |
83 | do { | |
84 | rpid = waitpid(pid, &status, 0); | |
85 | } while (rpid == -1 && errno == EINTR); | |
86 | if (rpid == pid && WIFEXITED(status) && !WEXITSTATUS(status)) { | |
87 | return; | |
88 | } | |
89 | ||
90 | exit_err: | |
91 | error_set(err, QERR_UNDEFINED_ERROR); | |
e3d4d252 MR |
92 | } |
93 | ||
94 | typedef struct GuestFileHandle { | |
95 | uint64_t id; | |
96 | FILE *fh; | |
97 | QTAILQ_ENTRY(GuestFileHandle) next; | |
98 | } GuestFileHandle; | |
99 | ||
100 | static struct { | |
101 | QTAILQ_HEAD(, GuestFileHandle) filehandles; | |
102 | } guest_file_state; | |
103 | ||
104 | static void guest_file_handle_add(FILE *fh) | |
105 | { | |
106 | GuestFileHandle *gfh; | |
107 | ||
7267c094 | 108 | gfh = g_malloc0(sizeof(GuestFileHandle)); |
e3d4d252 MR |
109 | gfh->id = fileno(fh); |
110 | gfh->fh = fh; | |
111 | QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next); | |
112 | } | |
113 | ||
a9de6d01 | 114 | static GuestFileHandle *guest_file_handle_find(int64_t id, Error **err) |
e3d4d252 MR |
115 | { |
116 | GuestFileHandle *gfh; | |
117 | ||
118 | QTAILQ_FOREACH(gfh, &guest_file_state.filehandles, next) | |
119 | { | |
120 | if (gfh->id == id) { | |
121 | return gfh; | |
122 | } | |
123 | } | |
124 | ||
a9de6d01 | 125 | error_setg(err, "handle '%" PRId64 "' has not been found", id); |
e3d4d252 MR |
126 | return NULL; |
127 | } | |
128 | ||
129 | int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, Error **err) | |
130 | { | |
131 | FILE *fh; | |
132 | int fd; | |
133 | int64_t ret = -1; | |
134 | ||
135 | if (!has_mode) { | |
136 | mode = "r"; | |
137 | } | |
138 | slog("guest-file-open called, filepath: %s, mode: %s", path, mode); | |
139 | fh = fopen(path, mode); | |
140 | if (!fh) { | |
141 | error_set(err, QERR_OPEN_FILE_FAILED, path); | |
142 | return -1; | |
143 | } | |
144 | ||
145 | /* set fd non-blocking to avoid common use cases (like reading from a | |
146 | * named pipe) from hanging the agent | |
147 | */ | |
148 | fd = fileno(fh); | |
149 | ret = fcntl(fd, F_GETFL); | |
150 | ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK); | |
151 | if (ret == -1) { | |
152 | error_set(err, QERR_QGA_COMMAND_FAILED, "fcntl() failed"); | |
153 | fclose(fh); | |
154 | return -1; | |
155 | } | |
156 | ||
157 | guest_file_handle_add(fh); | |
158 | slog("guest-file-open, handle: %d", fd); | |
159 | return fd; | |
160 | } | |
161 | ||
162 | void qmp_guest_file_close(int64_t handle, Error **err) | |
163 | { | |
a9de6d01 | 164 | GuestFileHandle *gfh = guest_file_handle_find(handle, err); |
e3d4d252 MR |
165 | int ret; |
166 | ||
167 | slog("guest-file-close called, handle: %ld", handle); | |
168 | if (!gfh) { | |
e3d4d252 MR |
169 | return; |
170 | } | |
171 | ||
172 | ret = fclose(gfh->fh); | |
173 | if (ret == -1) { | |
174 | error_set(err, QERR_QGA_COMMAND_FAILED, "fclose() failed"); | |
175 | return; | |
176 | } | |
177 | ||
178 | QTAILQ_REMOVE(&guest_file_state.filehandles, gfh, next); | |
7267c094 | 179 | g_free(gfh); |
e3d4d252 MR |
180 | } |
181 | ||
182 | struct GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count, | |
183 | int64_t count, Error **err) | |
184 | { | |
a9de6d01 | 185 | GuestFileHandle *gfh = guest_file_handle_find(handle, err); |
e3d4d252 MR |
186 | GuestFileRead *read_data = NULL; |
187 | guchar *buf; | |
188 | FILE *fh; | |
189 | size_t read_count; | |
190 | ||
191 | if (!gfh) { | |
e3d4d252 MR |
192 | return NULL; |
193 | } | |
194 | ||
195 | if (!has_count) { | |
196 | count = QGA_READ_COUNT_DEFAULT; | |
197 | } else if (count < 0) { | |
198 | error_set(err, QERR_INVALID_PARAMETER, "count"); | |
199 | return NULL; | |
200 | } | |
201 | ||
202 | fh = gfh->fh; | |
7267c094 | 203 | buf = g_malloc0(count+1); |
e3d4d252 MR |
204 | read_count = fread(buf, 1, count, fh); |
205 | if (ferror(fh)) { | |
206 | slog("guest-file-read failed, handle: %ld", handle); | |
207 | error_set(err, QERR_QGA_COMMAND_FAILED, "fread() failed"); | |
208 | } else { | |
209 | buf[read_count] = 0; | |
7267c094 | 210 | read_data = g_malloc0(sizeof(GuestFileRead)); |
e3d4d252 MR |
211 | read_data->count = read_count; |
212 | read_data->eof = feof(fh); | |
213 | if (read_count) { | |
214 | read_data->buf_b64 = g_base64_encode(buf, read_count); | |
215 | } | |
216 | } | |
7267c094 | 217 | g_free(buf); |
e3d4d252 MR |
218 | clearerr(fh); |
219 | ||
220 | return read_data; | |
221 | } | |
222 | ||
223 | GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64, | |
224 | bool has_count, int64_t count, Error **err) | |
225 | { | |
226 | GuestFileWrite *write_data = NULL; | |
227 | guchar *buf; | |
228 | gsize buf_len; | |
229 | int write_count; | |
a9de6d01 | 230 | GuestFileHandle *gfh = guest_file_handle_find(handle, err); |
e3d4d252 MR |
231 | FILE *fh; |
232 | ||
233 | if (!gfh) { | |
e3d4d252 MR |
234 | return NULL; |
235 | } | |
236 | ||
237 | fh = gfh->fh; | |
238 | buf = g_base64_decode(buf_b64, &buf_len); | |
239 | ||
240 | if (!has_count) { | |
241 | count = buf_len; | |
242 | } else if (count < 0 || count > buf_len) { | |
7267c094 | 243 | g_free(buf); |
e3d4d252 MR |
244 | error_set(err, QERR_INVALID_PARAMETER, "count"); |
245 | return NULL; | |
246 | } | |
247 | ||
248 | write_count = fwrite(buf, 1, count, fh); | |
249 | if (ferror(fh)) { | |
250 | slog("guest-file-write failed, handle: %ld", handle); | |
251 | error_set(err, QERR_QGA_COMMAND_FAILED, "fwrite() error"); | |
252 | } else { | |
7267c094 | 253 | write_data = g_malloc0(sizeof(GuestFileWrite)); |
e3d4d252 MR |
254 | write_data->count = write_count; |
255 | write_data->eof = feof(fh); | |
256 | } | |
7267c094 | 257 | g_free(buf); |
e3d4d252 MR |
258 | clearerr(fh); |
259 | ||
260 | return write_data; | |
261 | } | |
262 | ||
263 | struct GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset, | |
264 | int64_t whence, Error **err) | |
265 | { | |
a9de6d01 | 266 | GuestFileHandle *gfh = guest_file_handle_find(handle, err); |
e3d4d252 MR |
267 | GuestFileSeek *seek_data = NULL; |
268 | FILE *fh; | |
269 | int ret; | |
270 | ||
271 | if (!gfh) { | |
e3d4d252 MR |
272 | return NULL; |
273 | } | |
274 | ||
275 | fh = gfh->fh; | |
276 | ret = fseek(fh, offset, whence); | |
277 | if (ret == -1) { | |
278 | error_set(err, QERR_QGA_COMMAND_FAILED, strerror(errno)); | |
279 | } else { | |
7267c094 | 280 | seek_data = g_malloc0(sizeof(GuestFileRead)); |
e3d4d252 MR |
281 | seek_data->position = ftell(fh); |
282 | seek_data->eof = feof(fh); | |
283 | } | |
284 | clearerr(fh); | |
285 | ||
286 | return seek_data; | |
287 | } | |
288 | ||
289 | void qmp_guest_file_flush(int64_t handle, Error **err) | |
290 | { | |
a9de6d01 | 291 | GuestFileHandle *gfh = guest_file_handle_find(handle, err); |
e3d4d252 MR |
292 | FILE *fh; |
293 | int ret; | |
294 | ||
295 | if (!gfh) { | |
e3d4d252 MR |
296 | return; |
297 | } | |
298 | ||
299 | fh = gfh->fh; | |
300 | ret = fflush(fh); | |
301 | if (ret == EOF) { | |
302 | error_set(err, QERR_QGA_COMMAND_FAILED, strerror(errno)); | |
303 | } | |
304 | } | |
305 | ||
306 | static void guest_file_init(void) | |
307 | { | |
308 | QTAILQ_INIT(&guest_file_state.filehandles); | |
309 | } | |
310 | ||
e72c3f2e MR |
311 | /* linux-specific implementations. avoid this if at all possible. */ |
312 | #if defined(__linux__) | |
313 | ||
eab5fd59 | 314 | #if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM) |
af02203f | 315 | typedef struct FsMount { |
e3d4d252 MR |
316 | char *dirname; |
317 | char *devtype; | |
af02203f PB |
318 | QTAILQ_ENTRY(FsMount) next; |
319 | } FsMount; | |
e3d4d252 | 320 | |
af02203f | 321 | typedef QTAILQ_HEAD(, FsMount) FsMountList; |
9e8aded4 | 322 | |
af02203f | 323 | static void free_fs_mount_list(FsMountList *mounts) |
9e8aded4 | 324 | { |
af02203f | 325 | FsMount *mount, *temp; |
9e8aded4 MR |
326 | |
327 | if (!mounts) { | |
328 | return; | |
329 | } | |
330 | ||
331 | QTAILQ_FOREACH_SAFE(mount, mounts, next, temp) { | |
332 | QTAILQ_REMOVE(mounts, mount, next); | |
333 | g_free(mount->dirname); | |
334 | g_free(mount->devtype); | |
335 | g_free(mount); | |
336 | } | |
337 | } | |
338 | ||
e3d4d252 MR |
339 | /* |
340 | * Walk the mount table and build a list of local file systems | |
341 | */ | |
af02203f | 342 | static int build_fs_mount_list(FsMountList *mounts) |
e3d4d252 MR |
343 | { |
344 | struct mntent *ment; | |
af02203f | 345 | FsMount *mount; |
9e2fa418 | 346 | char const *mtab = "/proc/self/mounts"; |
e3d4d252 MR |
347 | FILE *fp; |
348 | ||
e3d4d252 MR |
349 | fp = setmntent(mtab, "r"); |
350 | if (!fp) { | |
351 | g_warning("fsfreeze: unable to read mtab"); | |
352 | return -1; | |
353 | } | |
354 | ||
355 | while ((ment = getmntent(fp))) { | |
356 | /* | |
357 | * An entry which device name doesn't start with a '/' is | |
358 | * either a dummy file system or a network file system. | |
359 | * Add special handling for smbfs and cifs as is done by | |
360 | * coreutils as well. | |
361 | */ | |
362 | if ((ment->mnt_fsname[0] != '/') || | |
363 | (strcmp(ment->mnt_type, "smbfs") == 0) || | |
364 | (strcmp(ment->mnt_type, "cifs") == 0)) { | |
365 | continue; | |
366 | } | |
367 | ||
af02203f | 368 | mount = g_malloc0(sizeof(FsMount)); |
7267c094 AL |
369 | mount->dirname = g_strdup(ment->mnt_dir); |
370 | mount->devtype = g_strdup(ment->mnt_type); | |
e3d4d252 | 371 | |
9e8aded4 | 372 | QTAILQ_INSERT_TAIL(mounts, mount, next); |
e3d4d252 MR |
373 | } |
374 | ||
375 | endmntent(fp); | |
376 | ||
377 | return 0; | |
378 | } | |
eab5fd59 PB |
379 | #endif |
380 | ||
381 | #if defined(CONFIG_FSFREEZE) | |
e3d4d252 MR |
382 | |
383 | /* | |
384 | * Return status of freeze/thaw | |
385 | */ | |
386 | GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err) | |
387 | { | |
f22d85e9 MR |
388 | if (ga_is_frozen(ga_state)) { |
389 | return GUEST_FSFREEZE_STATUS_FROZEN; | |
390 | } | |
391 | ||
392 | return GUEST_FSFREEZE_STATUS_THAWED; | |
e3d4d252 MR |
393 | } |
394 | ||
395 | /* | |
396 | * Walk list of mounted file systems in the guest, and freeze the ones which | |
397 | * are real local file systems. | |
398 | */ | |
399 | int64_t qmp_guest_fsfreeze_freeze(Error **err) | |
400 | { | |
401 | int ret = 0, i = 0; | |
af02203f PB |
402 | FsMountList mounts; |
403 | struct FsMount *mount; | |
e3d4d252 MR |
404 | int fd; |
405 | char err_msg[512]; | |
406 | ||
407 | slog("guest-fsfreeze called"); | |
408 | ||
9e8aded4 | 409 | QTAILQ_INIT(&mounts); |
af02203f | 410 | ret = build_fs_mount_list(&mounts); |
e3d4d252 MR |
411 | if (ret < 0) { |
412 | return ret; | |
413 | } | |
414 | ||
415 | /* cannot risk guest agent blocking itself on a write in this state */ | |
f22d85e9 | 416 | ga_set_frozen(ga_state); |
e3d4d252 | 417 | |
9e8aded4 | 418 | QTAILQ_FOREACH(mount, &mounts, next) { |
e3d4d252 MR |
419 | fd = qemu_open(mount->dirname, O_RDONLY); |
420 | if (fd == -1) { | |
9e8aded4 MR |
421 | sprintf(err_msg, "failed to open %s, %s", mount->dirname, |
422 | strerror(errno)); | |
e3d4d252 MR |
423 | error_set(err, QERR_QGA_COMMAND_FAILED, err_msg); |
424 | goto error; | |
425 | } | |
426 | ||
427 | /* we try to cull filesytems we know won't work in advance, but other | |
428 | * filesytems may not implement fsfreeze for less obvious reasons. | |
9e8aded4 MR |
429 | * these will report EOPNOTSUPP. we simply ignore these when tallying |
430 | * the number of frozen filesystems. | |
431 | * | |
432 | * any other error means a failure to freeze a filesystem we | |
433 | * expect to be freezable, so return an error in those cases | |
434 | * and return system to thawed state. | |
e3d4d252 MR |
435 | */ |
436 | ret = ioctl(fd, FIFREEZE); | |
9e8aded4 MR |
437 | if (ret == -1) { |
438 | if (errno != EOPNOTSUPP) { | |
439 | sprintf(err_msg, "failed to freeze %s, %s", | |
440 | mount->dirname, strerror(errno)); | |
441 | error_set(err, QERR_QGA_COMMAND_FAILED, err_msg); | |
442 | close(fd); | |
443 | goto error; | |
444 | } | |
445 | } else { | |
446 | i++; | |
e3d4d252 MR |
447 | } |
448 | close(fd); | |
e3d4d252 MR |
449 | } |
450 | ||
af02203f | 451 | free_fs_mount_list(&mounts); |
e3d4d252 MR |
452 | return i; |
453 | ||
454 | error: | |
af02203f | 455 | free_fs_mount_list(&mounts); |
9e8aded4 | 456 | qmp_guest_fsfreeze_thaw(NULL); |
e3d4d252 MR |
457 | return 0; |
458 | } | |
459 | ||
460 | /* | |
461 | * Walk list of frozen file systems in the guest, and thaw them. | |
462 | */ | |
463 | int64_t qmp_guest_fsfreeze_thaw(Error **err) | |
464 | { | |
465 | int ret; | |
af02203f PB |
466 | FsMountList mounts; |
467 | FsMount *mount; | |
9e8aded4 MR |
468 | int fd, i = 0, logged; |
469 | ||
470 | QTAILQ_INIT(&mounts); | |
af02203f | 471 | ret = build_fs_mount_list(&mounts); |
9e8aded4 MR |
472 | if (ret) { |
473 | error_set(err, QERR_QGA_COMMAND_FAILED, | |
474 | "failed to enumerate filesystems"); | |
475 | return 0; | |
476 | } | |
e3d4d252 | 477 | |
9e8aded4 MR |
478 | QTAILQ_FOREACH(mount, &mounts, next) { |
479 | logged = false; | |
e3d4d252 MR |
480 | fd = qemu_open(mount->dirname, O_RDONLY); |
481 | if (fd == -1) { | |
e3d4d252 MR |
482 | continue; |
483 | } | |
9e8aded4 MR |
484 | /* we have no way of knowing whether a filesystem was actually unfrozen |
485 | * as a result of a successful call to FITHAW, only that if an error | |
486 | * was returned the filesystem was *not* unfrozen by that particular | |
487 | * call. | |
488 | * | |
a31f0531 | 489 | * since multiple preceding FIFREEZEs require multiple calls to FITHAW |
9e8aded4 MR |
490 | * to unfreeze, continuing issuing FITHAW until an error is returned, |
491 | * in which case either the filesystem is in an unfreezable state, or, | |
492 | * more likely, it was thawed previously (and remains so afterward). | |
493 | * | |
494 | * also, since the most recent successful call is the one that did | |
495 | * the actual unfreeze, we can use this to provide an accurate count | |
496 | * of the number of filesystems unfrozen by guest-fsfreeze-thaw, which | |
497 | * may * be useful for determining whether a filesystem was unfrozen | |
498 | * during the freeze/thaw phase by a process other than qemu-ga. | |
499 | */ | |
500 | do { | |
501 | ret = ioctl(fd, FITHAW); | |
502 | if (ret == 0 && !logged) { | |
503 | i++; | |
504 | logged = true; | |
505 | } | |
506 | } while (ret == 0); | |
e3d4d252 | 507 | close(fd); |
e3d4d252 MR |
508 | } |
509 | ||
f22d85e9 | 510 | ga_unset_frozen(ga_state); |
af02203f | 511 | free_fs_mount_list(&mounts); |
e3d4d252 MR |
512 | return i; |
513 | } | |
514 | ||
e3d4d252 MR |
515 | static void guest_fsfreeze_cleanup(void) |
516 | { | |
517 | int64_t ret; | |
518 | Error *err = NULL; | |
519 | ||
f22d85e9 | 520 | if (ga_is_frozen(ga_state) == GUEST_FSFREEZE_STATUS_FROZEN) { |
e3d4d252 MR |
521 | ret = qmp_guest_fsfreeze_thaw(&err); |
522 | if (ret < 0 || err) { | |
523 | slog("failed to clean up frozen filesystems"); | |
524 | } | |
525 | } | |
526 | } | |
e72c3f2e | 527 | #endif /* CONFIG_FSFREEZE */ |
e3d4d252 | 528 | |
eab5fd59 PB |
529 | #if defined(CONFIG_FSTRIM) |
530 | /* | |
531 | * Walk list of mounted file systems in the guest, and trim them. | |
532 | */ | |
533 | void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **err) | |
534 | { | |
535 | int ret = 0; | |
536 | FsMountList mounts; | |
537 | struct FsMount *mount; | |
538 | int fd; | |
539 | char err_msg[512]; | |
540 | struct fstrim_range r = { | |
541 | .start = 0, | |
542 | .len = -1, | |
543 | .minlen = has_minimum ? minimum : 0, | |
544 | }; | |
545 | ||
546 | slog("guest-fstrim called"); | |
547 | ||
548 | QTAILQ_INIT(&mounts); | |
549 | ret = build_fs_mount_list(&mounts); | |
550 | if (ret < 0) { | |
551 | return; | |
552 | } | |
553 | ||
554 | QTAILQ_FOREACH(mount, &mounts, next) { | |
555 | fd = qemu_open(mount->dirname, O_RDONLY); | |
556 | if (fd == -1) { | |
557 | sprintf(err_msg, "failed to open %s, %s", mount->dirname, | |
558 | strerror(errno)); | |
559 | error_set(err, QERR_QGA_COMMAND_FAILED, err_msg); | |
560 | goto error; | |
561 | } | |
562 | ||
563 | /* We try to cull filesytems we know won't work in advance, but other | |
564 | * filesytems may not implement fstrim for less obvious reasons. These | |
565 | * will report EOPNOTSUPP; we simply ignore these errors. Any other | |
566 | * error means an unexpected error, so return it in those cases. In | |
567 | * some other cases ENOTTY will be reported (e.g. CD-ROMs). | |
568 | */ | |
569 | ret = ioctl(fd, FITRIM, &r); | |
570 | if (ret == -1) { | |
571 | if (errno != ENOTTY && errno != EOPNOTSUPP) { | |
572 | sprintf(err_msg, "failed to trim %s, %s", | |
573 | mount->dirname, strerror(errno)); | |
574 | error_set(err, QERR_QGA_COMMAND_FAILED, err_msg); | |
575 | close(fd); | |
576 | goto error; | |
577 | } | |
578 | } | |
579 | close(fd); | |
580 | } | |
581 | ||
582 | error: | |
583 | free_fs_mount_list(&mounts); | |
584 | } | |
585 | #endif /* CONFIG_FSTRIM */ | |
586 | ||
587 | ||
11d0f125 LC |
588 | #define LINUX_SYS_STATE_FILE "/sys/power/state" |
589 | #define SUSPEND_SUPPORTED 0 | |
590 | #define SUSPEND_NOT_SUPPORTED 1 | |
591 | ||
11d0f125 LC |
592 | static void bios_supports_mode(const char *pmutils_bin, const char *pmutils_arg, |
593 | const char *sysfile_str, Error **err) | |
594 | { | |
11d0f125 | 595 | char *pmutils_path; |
dc8764f0 LC |
596 | pid_t pid, rpid; |
597 | int status; | |
11d0f125 LC |
598 | |
599 | pmutils_path = g_find_program_in_path(pmutils_bin); | |
600 | ||
601 | pid = fork(); | |
602 | if (!pid) { | |
dc8764f0 LC |
603 | char buf[32]; /* hopefully big enough */ |
604 | ssize_t ret; | |
605 | int fd; | |
11d0f125 LC |
606 | |
607 | setsid(); | |
11d0f125 LC |
608 | reopen_fd_to_null(0); |
609 | reopen_fd_to_null(1); | |
610 | reopen_fd_to_null(2); | |
611 | ||
dc8764f0 LC |
612 | if (pmutils_path) { |
613 | execle(pmutils_path, pmutils_bin, pmutils_arg, NULL, environ); | |
614 | } | |
11d0f125 | 615 | |
dc8764f0 LC |
616 | /* |
617 | * If we get here either pm-utils is not installed or execle() has | |
618 | * failed. Let's try the manual method if the caller wants it. | |
619 | */ | |
11d0f125 | 620 | |
dc8764f0 LC |
621 | if (!sysfile_str) { |
622 | _exit(SUSPEND_NOT_SUPPORTED); | |
623 | } | |
11d0f125 | 624 | |
dc8764f0 LC |
625 | fd = open(LINUX_SYS_STATE_FILE, O_RDONLY); |
626 | if (fd < 0) { | |
11d0f125 LC |
627 | _exit(SUSPEND_NOT_SUPPORTED); |
628 | } | |
629 | ||
dc8764f0 LC |
630 | ret = read(fd, buf, sizeof(buf)-1); |
631 | if (ret <= 0) { | |
632 | _exit(SUSPEND_NOT_SUPPORTED); | |
11d0f125 | 633 | } |
dc8764f0 | 634 | buf[ret] = '\0'; |
11d0f125 | 635 | |
dc8764f0 LC |
636 | if (strstr(buf, sysfile_str)) { |
637 | _exit(SUSPEND_SUPPORTED); | |
11d0f125 LC |
638 | } |
639 | ||
dc8764f0 | 640 | _exit(SUSPEND_NOT_SUPPORTED); |
11d0f125 LC |
641 | } |
642 | ||
11d0f125 LC |
643 | g_free(pmutils_path); |
644 | ||
645 | if (pid < 0) { | |
dc8764f0 LC |
646 | goto undef_err; |
647 | } | |
648 | ||
649 | do { | |
650 | rpid = waitpid(pid, &status, 0); | |
651 | } while (rpid == -1 && errno == EINTR); | |
652 | if (rpid == pid && WIFEXITED(status)) { | |
653 | switch (WEXITSTATUS(status)) { | |
654 | case SUSPEND_SUPPORTED: | |
655 | return; | |
656 | case SUSPEND_NOT_SUPPORTED: | |
657 | error_set(err, QERR_UNSUPPORTED); | |
658 | return; | |
659 | default: | |
660 | goto undef_err; | |
661 | } | |
11d0f125 LC |
662 | } |
663 | ||
dc8764f0 LC |
664 | undef_err: |
665 | error_set(err, QERR_UNDEFINED_ERROR); | |
11d0f125 LC |
666 | } |
667 | ||
668 | static void guest_suspend(const char *pmutils_bin, const char *sysfile_str, | |
669 | Error **err) | |
670 | { | |
11d0f125 | 671 | char *pmutils_path; |
dc8764f0 LC |
672 | pid_t rpid, pid; |
673 | int status; | |
11d0f125 LC |
674 | |
675 | pmutils_path = g_find_program_in_path(pmutils_bin); | |
676 | ||
677 | pid = fork(); | |
678 | if (pid == 0) { | |
679 | /* child */ | |
680 | int fd; | |
681 | ||
682 | setsid(); | |
683 | reopen_fd_to_null(0); | |
684 | reopen_fd_to_null(1); | |
685 | reopen_fd_to_null(2); | |
686 | ||
687 | if (pmutils_path) { | |
688 | execle(pmutils_path, pmutils_bin, NULL, environ); | |
689 | } | |
690 | ||
691 | /* | |
692 | * If we get here either pm-utils is not installed or execle() has | |
693 | * failed. Let's try the manual method if the caller wants it. | |
694 | */ | |
695 | ||
696 | if (!sysfile_str) { | |
697 | _exit(EXIT_FAILURE); | |
698 | } | |
699 | ||
700 | fd = open(LINUX_SYS_STATE_FILE, O_WRONLY); | |
701 | if (fd < 0) { | |
702 | _exit(EXIT_FAILURE); | |
703 | } | |
704 | ||
705 | if (write(fd, sysfile_str, strlen(sysfile_str)) < 0) { | |
706 | _exit(EXIT_FAILURE); | |
707 | } | |
708 | ||
709 | _exit(EXIT_SUCCESS); | |
710 | } | |
711 | ||
712 | g_free(pmutils_path); | |
713 | ||
714 | if (pid < 0) { | |
dc8764f0 LC |
715 | goto exit_err; |
716 | } | |
717 | ||
718 | do { | |
719 | rpid = waitpid(pid, &status, 0); | |
720 | } while (rpid == -1 && errno == EINTR); | |
721 | if (rpid == pid && WIFEXITED(status) && !WEXITSTATUS(status)) { | |
11d0f125 LC |
722 | return; |
723 | } | |
dc8764f0 LC |
724 | |
725 | exit_err: | |
726 | error_set(err, QERR_UNDEFINED_ERROR); | |
11d0f125 LC |
727 | } |
728 | ||
729 | void qmp_guest_suspend_disk(Error **err) | |
730 | { | |
731 | bios_supports_mode("pm-is-supported", "--hibernate", "disk", err); | |
732 | if (error_is_set(err)) { | |
733 | return; | |
734 | } | |
735 | ||
736 | guest_suspend("pm-hibernate", "disk", err); | |
737 | } | |
738 | ||
fbf42210 LC |
739 | void qmp_guest_suspend_ram(Error **err) |
740 | { | |
741 | bios_supports_mode("pm-is-supported", "--suspend", "mem", err); | |
742 | if (error_is_set(err)) { | |
743 | return; | |
744 | } | |
745 | ||
746 | guest_suspend("pm-suspend", "mem", err); | |
747 | } | |
748 | ||
95f4f404 LC |
749 | void qmp_guest_suspend_hybrid(Error **err) |
750 | { | |
751 | bios_supports_mode("pm-is-supported", "--suspend-hybrid", NULL, err); | |
752 | if (error_is_set(err)) { | |
753 | return; | |
754 | } | |
755 | ||
756 | guest_suspend("pm-suspend-hybrid", NULL, err); | |
757 | } | |
758 | ||
3424fc9f MP |
759 | static GuestNetworkInterfaceList * |
760 | guest_find_interface(GuestNetworkInterfaceList *head, | |
761 | const char *name) | |
762 | { | |
763 | for (; head; head = head->next) { | |
764 | if (strcmp(head->value->name, name) == 0) { | |
765 | break; | |
766 | } | |
767 | } | |
768 | ||
769 | return head; | |
770 | } | |
771 | ||
772 | /* | |
773 | * Build information about guest interfaces | |
774 | */ | |
775 | GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp) | |
776 | { | |
777 | GuestNetworkInterfaceList *head = NULL, *cur_item = NULL; | |
778 | struct ifaddrs *ifap, *ifa; | |
779 | char err_msg[512]; | |
780 | ||
781 | if (getifaddrs(&ifap) < 0) { | |
782 | snprintf(err_msg, sizeof(err_msg), | |
783 | "getifaddrs failed: %s", strerror(errno)); | |
784 | error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg); | |
785 | goto error; | |
786 | } | |
787 | ||
788 | for (ifa = ifap; ifa; ifa = ifa->ifa_next) { | |
789 | GuestNetworkInterfaceList *info; | |
790 | GuestIpAddressList **address_list = NULL, *address_item = NULL; | |
791 | char addr4[INET_ADDRSTRLEN]; | |
792 | char addr6[INET6_ADDRSTRLEN]; | |
793 | int sock; | |
794 | struct ifreq ifr; | |
795 | unsigned char *mac_addr; | |
796 | void *p; | |
797 | ||
798 | g_debug("Processing %s interface", ifa->ifa_name); | |
799 | ||
800 | info = guest_find_interface(head, ifa->ifa_name); | |
801 | ||
802 | if (!info) { | |
803 | info = g_malloc0(sizeof(*info)); | |
804 | info->value = g_malloc0(sizeof(*info->value)); | |
805 | info->value->name = g_strdup(ifa->ifa_name); | |
806 | ||
807 | if (!cur_item) { | |
808 | head = cur_item = info; | |
809 | } else { | |
810 | cur_item->next = info; | |
811 | cur_item = info; | |
812 | } | |
813 | } | |
814 | ||
815 | if (!info->value->has_hardware_address && | |
816 | ifa->ifa_flags & SIOCGIFHWADDR) { | |
817 | /* we haven't obtained HW address yet */ | |
818 | sock = socket(PF_INET, SOCK_STREAM, 0); | |
819 | if (sock == -1) { | |
820 | snprintf(err_msg, sizeof(err_msg), | |
821 | "failed to create socket: %s", strerror(errno)); | |
822 | error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg); | |
823 | goto error; | |
824 | } | |
825 | ||
826 | memset(&ifr, 0, sizeof(ifr)); | |
1ab516ed | 827 | pstrcpy(ifr.ifr_name, IF_NAMESIZE, info->value->name); |
3424fc9f MP |
828 | if (ioctl(sock, SIOCGIFHWADDR, &ifr) == -1) { |
829 | snprintf(err_msg, sizeof(err_msg), | |
a31f0531 | 830 | "failed to get MAC address of %s: %s", |
3424fc9f MP |
831 | ifa->ifa_name, |
832 | strerror(errno)); | |
833 | error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg); | |
834 | goto error; | |
835 | } | |
836 | ||
837 | mac_addr = (unsigned char *) &ifr.ifr_hwaddr.sa_data; | |
838 | ||
839 | if (asprintf(&info->value->hardware_address, | |
840 | "%02x:%02x:%02x:%02x:%02x:%02x", | |
841 | (int) mac_addr[0], (int) mac_addr[1], | |
842 | (int) mac_addr[2], (int) mac_addr[3], | |
843 | (int) mac_addr[4], (int) mac_addr[5]) == -1) { | |
844 | snprintf(err_msg, sizeof(err_msg), | |
845 | "failed to format MAC: %s", strerror(errno)); | |
846 | error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg); | |
847 | goto error; | |
848 | } | |
849 | ||
850 | info->value->has_hardware_address = true; | |
851 | close(sock); | |
852 | } | |
853 | ||
854 | if (ifa->ifa_addr && | |
855 | ifa->ifa_addr->sa_family == AF_INET) { | |
856 | /* interface with IPv4 address */ | |
857 | address_item = g_malloc0(sizeof(*address_item)); | |
858 | address_item->value = g_malloc0(sizeof(*address_item->value)); | |
859 | p = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr; | |
860 | if (!inet_ntop(AF_INET, p, addr4, sizeof(addr4))) { | |
861 | snprintf(err_msg, sizeof(err_msg), | |
862 | "inet_ntop failed : %s", strerror(errno)); | |
863 | error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg); | |
864 | goto error; | |
865 | } | |
866 | ||
867 | address_item->value->ip_address = g_strdup(addr4); | |
868 | address_item->value->ip_address_type = GUEST_IP_ADDRESS_TYPE_IPV4; | |
869 | ||
870 | if (ifa->ifa_netmask) { | |
871 | /* Count the number of set bits in netmask. | |
872 | * This is safe as '1' and '0' cannot be shuffled in netmask. */ | |
873 | p = &((struct sockaddr_in *)ifa->ifa_netmask)->sin_addr; | |
874 | address_item->value->prefix = ctpop32(((uint32_t *) p)[0]); | |
875 | } | |
876 | } else if (ifa->ifa_addr && | |
877 | ifa->ifa_addr->sa_family == AF_INET6) { | |
878 | /* interface with IPv6 address */ | |
879 | address_item = g_malloc0(sizeof(*address_item)); | |
880 | address_item->value = g_malloc0(sizeof(*address_item->value)); | |
881 | p = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; | |
882 | if (!inet_ntop(AF_INET6, p, addr6, sizeof(addr6))) { | |
883 | snprintf(err_msg, sizeof(err_msg), | |
884 | "inet_ntop failed : %s", strerror(errno)); | |
885 | error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg); | |
886 | goto error; | |
887 | } | |
888 | ||
889 | address_item->value->ip_address = g_strdup(addr6); | |
890 | address_item->value->ip_address_type = GUEST_IP_ADDRESS_TYPE_IPV6; | |
891 | ||
892 | if (ifa->ifa_netmask) { | |
893 | /* Count the number of set bits in netmask. | |
894 | * This is safe as '1' and '0' cannot be shuffled in netmask. */ | |
895 | p = &((struct sockaddr_in6 *)ifa->ifa_netmask)->sin6_addr; | |
896 | address_item->value->prefix = | |
897 | ctpop32(((uint32_t *) p)[0]) + | |
898 | ctpop32(((uint32_t *) p)[1]) + | |
899 | ctpop32(((uint32_t *) p)[2]) + | |
900 | ctpop32(((uint32_t *) p)[3]); | |
901 | } | |
902 | } | |
903 | ||
904 | if (!address_item) { | |
905 | continue; | |
906 | } | |
907 | ||
908 | address_list = &info->value->ip_addresses; | |
909 | ||
910 | while (*address_list && (*address_list)->next) { | |
911 | address_list = &(*address_list)->next; | |
912 | } | |
913 | ||
914 | if (!*address_list) { | |
915 | *address_list = address_item; | |
916 | } else { | |
917 | (*address_list)->next = address_item; | |
918 | } | |
919 | ||
920 | info->value->has_ip_addresses = true; | |
921 | ||
922 | ||
923 | } | |
924 | ||
925 | freeifaddrs(ifap); | |
926 | return head; | |
927 | ||
928 | error: | |
929 | freeifaddrs(ifap); | |
930 | qapi_free_GuestNetworkInterfaceList(head); | |
931 | return NULL; | |
932 | } | |
933 | ||
e72c3f2e MR |
934 | #else /* defined(__linux__) */ |
935 | ||
d35d4cb5 | 936 | void qmp_guest_suspend_disk(Error **err) |
e72c3f2e MR |
937 | { |
938 | error_set(err, QERR_UNSUPPORTED); | |
e72c3f2e MR |
939 | } |
940 | ||
d35d4cb5 | 941 | void qmp_guest_suspend_ram(Error **err) |
e72c3f2e MR |
942 | { |
943 | error_set(err, QERR_UNSUPPORTED); | |
e72c3f2e MR |
944 | } |
945 | ||
d35d4cb5 | 946 | void qmp_guest_suspend_hybrid(Error **err) |
e72c3f2e MR |
947 | { |
948 | error_set(err, QERR_UNSUPPORTED); | |
e72c3f2e MR |
949 | } |
950 | ||
d35d4cb5 | 951 | GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp) |
e72c3f2e | 952 | { |
d35d4cb5 MR |
953 | error_set(errp, QERR_UNSUPPORTED); |
954 | return NULL; | |
e72c3f2e MR |
955 | } |
956 | ||
d35d4cb5 MR |
957 | #endif |
958 | ||
959 | #if !defined(CONFIG_FSFREEZE) | |
960 | ||
961 | GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err) | |
e72c3f2e MR |
962 | { |
963 | error_set(err, QERR_UNSUPPORTED); | |
d35d4cb5 MR |
964 | |
965 | return 0; | |
e72c3f2e MR |
966 | } |
967 | ||
d35d4cb5 | 968 | int64_t qmp_guest_fsfreeze_freeze(Error **err) |
e72c3f2e MR |
969 | { |
970 | error_set(err, QERR_UNSUPPORTED); | |
d35d4cb5 MR |
971 | |
972 | return 0; | |
e72c3f2e MR |
973 | } |
974 | ||
d35d4cb5 | 975 | int64_t qmp_guest_fsfreeze_thaw(Error **err) |
e72c3f2e | 976 | { |
d35d4cb5 MR |
977 | error_set(err, QERR_UNSUPPORTED); |
978 | ||
979 | return 0; | |
e72c3f2e | 980 | } |
eab5fd59 PB |
981 | #endif /* CONFIG_FSFREEZE */ |
982 | ||
983 | #if !defined(CONFIG_FSTRIM) | |
984 | void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **err) | |
985 | { | |
986 | error_set(err, QERR_UNSUPPORTED); | |
eab5fd59 | 987 | } |
e72c3f2e MR |
988 | #endif |
989 | ||
e3d4d252 MR |
990 | /* register init/cleanup routines for stateful command groups */ |
991 | void ga_command_state_init(GAState *s, GACommandState *cs) | |
992 | { | |
7006b9cf | 993 | #if defined(CONFIG_FSFREEZE) |
f22d85e9 | 994 | ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup); |
7006b9cf | 995 | #endif |
e3d4d252 MR |
996 | ga_command_state_add(cs, guest_file_init, NULL); |
997 | } |