summary refs log tree commit diff stats
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)
 {