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