summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--qapi-schema-guest.json24
-rw-r--r--qemu-ga.c19
-rw-r--r--qga/commands-posix.c188
-rw-r--r--qga/commands-win32.c5
4 files changed, 235 insertions, 1 deletions
diff --git a/qapi-schema-guest.json b/qapi-schema-guest.json
index 706925dea6..f4e0e1d241 100644
--- a/qapi-schema-guest.json
+++ b/qapi-schema-guest.json
@@ -295,3 +295,27 @@
##
{ 'command': 'guest-fsfreeze-thaw',
'returns': 'int' }
+
+##
+# @guest-suspend-disk
+#
+# Suspend guest to disk.
+#
+# This command tries to execute the scripts provided by the pm-utils package.
+# If it's not available, the suspend operation will be performed by manually
+# writing to a sysfs file.
+#
+# For the best results it's strongly recommended to have the pm-utils
+# package installed in the guest.
+#
+# Returns: nothing on success
+# If suspend to disk is not supported, Unsupported
+#
+# Notes: o This is an asynchronous request. There's no guarantee a response
+# will be sent
+# o It's strongly recommended to issue the guest-sync command before
+# sending commands when the guest resumes
+#
+# Since: 1.1
+##
+{ 'command': 'guest-suspend-disk' }
diff --git a/qemu-ga.c b/qemu-ga.c
index ba355d8783..1c90e6ef0d 100644
--- a/qemu-ga.c
+++ b/qemu-ga.c
@@ -17,6 +17,7 @@
#include <getopt.h>
#ifndef _WIN32
#include <syslog.h>
+#include <sys/wait.h>
#endif
#include "json-streamer.h"
#include "json-parser.h"
@@ -73,9 +74,16 @@ static void quit_handler(int sig)
}
#ifndef _WIN32
+/* reap _all_ terminated children */
+static void child_handler(int sig)
+{
+ int status;
+ while (waitpid(-1, &status, WNOHANG) > 0) /* NOTHING */;
+}
+
static gboolean register_signal_handlers(void)
{
- struct sigaction sigact;
+ struct sigaction sigact, sigact_chld;
int ret;
memset(&sigact, 0, sizeof(struct sigaction));
@@ -91,6 +99,15 @@ static gboolean register_signal_handlers(void)
g_error("error configuring signal handler: %s", strerror(errno));
return false;
}
+
+ memset(&sigact_chld, 0, sizeof(struct sigaction));
+ sigact_chld.sa_handler = child_handler;
+ sigact_chld.sa_flags = SA_NOCLDSTOP;
+ ret = sigaction(SIGCHLD, &sigact_chld, NULL);
+ if (ret == -1) {
+ g_error("error configuring signal handler: %s", strerror(errno));
+ }
+
return true;
}
#endif
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)
{
diff --git a/qga/commands-win32.c b/qga/commands-win32.c
index 4aa0f0d1e4..c688476c89 100644
--- a/qga/commands-win32.c
+++ b/qga/commands-win32.c
@@ -124,6 +124,11 @@ int64_t qmp_guest_fsfreeze_thaw(Error **err)
return 0;
}
+void qmp_guest_suspend_disk(Error **err)
+{
+ error_set(err, QERR_UNSUPPORTED);
+}
+
/* register init/cleanup routines for stateful command groups */
void ga_command_state_init(GAState *s, GACommandState *cs)
{