diff options
Diffstat (limited to 'qga/commands-posix.c')
-rw-r--r-- | qga/commands-posix.c | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 126127aae4..af785f5e5d 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -23,6 +23,7 @@ #include <sys/types.h> #include <sys/ioctl.h> +#include <sys/wait.h> #include "qga/guest-agent-core.h" #include "qga-qmp-commands.h" #include "qerror.h" @@ -30,6 +31,22 @@ static GAState *ga_state; +static void reopen_fd_to_null(int fd) +{ + int nullfd; + + nullfd = open("/dev/null", O_RDWR); + if (nullfd < 0) { + return; + } + + dup2(nullfd, fd); + + if (nullfd != fd) { + close(nullfd); + } +} + void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err) { int ret; @@ -517,6 +534,177 @@ int64_t qmp_guest_fsfreeze_thaw(Error **err) } #endif +#define LINUX_SYS_STATE_FILE "/sys/power/state" +#define SUSPEND_SUPPORTED 0 +#define SUSPEND_NOT_SUPPORTED 1 + +/** + * This function forks twice and the information about the mode support + * status is passed to the qemu-ga process via a pipe. + * + * This approach allows us to keep the way we reap terminated children + * in qemu-ga quite simple. + */ +static void bios_supports_mode(const char *pmutils_bin, const char *pmutils_arg, + const char *sysfile_str, Error **err) +{ + pid_t pid; + ssize_t ret; + char *pmutils_path; + int status, pipefds[2]; + + if (pipe(pipefds) < 0) { + error_set(err, QERR_UNDEFINED_ERROR); + return; + } + + pmutils_path = g_find_program_in_path(pmutils_bin); + + pid = fork(); + if (!pid) { + struct sigaction act; + + memset(&act, 0, sizeof(act)); + act.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &act, NULL); + + setsid(); + close(pipefds[0]); + reopen_fd_to_null(0); + reopen_fd_to_null(1); + reopen_fd_to_null(2); + + pid = fork(); + if (!pid) { + int fd; + char buf[32]; /* hopefully big enough */ + + if (pmutils_path) { + execle(pmutils_path, pmutils_bin, pmutils_arg, NULL, environ); + } + + /* + * If we get here either pm-utils is not installed or execle() has + * failed. Let's try the manual method if the caller wants it. + */ + + if (!sysfile_str) { + _exit(SUSPEND_NOT_SUPPORTED); + } + + fd = open(LINUX_SYS_STATE_FILE, O_RDONLY); + if (fd < 0) { + _exit(SUSPEND_NOT_SUPPORTED); + } + + ret = read(fd, buf, sizeof(buf)-1); + if (ret <= 0) { + _exit(SUSPEND_NOT_SUPPORTED); + } + buf[ret] = '\0'; + + if (strstr(buf, sysfile_str)) { + _exit(SUSPEND_SUPPORTED); + } + + _exit(SUSPEND_NOT_SUPPORTED); + } + + if (pid > 0) { + wait(&status); + } else { + status = SUSPEND_NOT_SUPPORTED; + } + + ret = write(pipefds[1], &status, sizeof(status)); + if (ret != sizeof(status)) { + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); + } + + close(pipefds[1]); + g_free(pmutils_path); + + if (pid < 0) { + error_set(err, QERR_UNDEFINED_ERROR); + goto out; + } + + ret = read(pipefds[0], &status, sizeof(status)); + if (ret == sizeof(status) && WIFEXITED(status) && + WEXITSTATUS(status) == SUSPEND_SUPPORTED) { + goto out; + } + + error_set(err, QERR_UNSUPPORTED); + +out: + close(pipefds[0]); +} + +static void guest_suspend(const char *pmutils_bin, const char *sysfile_str, + Error **err) +{ + pid_t pid; + char *pmutils_path; + + pmutils_path = g_find_program_in_path(pmutils_bin); + + pid = fork(); + if (pid == 0) { + /* child */ + int fd; + + setsid(); + reopen_fd_to_null(0); + reopen_fd_to_null(1); + reopen_fd_to_null(2); + + if (pmutils_path) { + execle(pmutils_path, pmutils_bin, NULL, environ); + } + + /* + * If we get here either pm-utils is not installed or execle() has + * failed. Let's try the manual method if the caller wants it. + */ + + if (!sysfile_str) { + _exit(EXIT_FAILURE); + } + + fd = open(LINUX_SYS_STATE_FILE, O_WRONLY); + if (fd < 0) { + _exit(EXIT_FAILURE); + } + + if (write(fd, sysfile_str, strlen(sysfile_str)) < 0) { + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); + } + + g_free(pmutils_path); + + if (pid < 0) { + error_set(err, QERR_UNDEFINED_ERROR); + return; + } +} + +void qmp_guest_suspend_disk(Error **err) +{ + bios_supports_mode("pm-is-supported", "--hibernate", "disk", err); + if (error_is_set(err)) { + return; + } + + guest_suspend("pm-hibernate", "disk", err); +} + /* register init/cleanup routines for stateful command groups */ void ga_command_state_init(GAState *s, GACommandState *cs) { |