[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH 2/2] qemu-ga: Add the guest-suspend command
From: |
Luiz Capitulino |
Subject: |
[Qemu-devel] [PATCH 2/2] qemu-ga: Add the guest-suspend command |
Date: |
Fri, 13 Jan 2012 17:15:22 -0200 |
The guest-suspend command supports three modes:
o hibernate (suspend to disk)
o sleep (suspend to ram)
o hybrid (save RAM contents to disk, but suspend instead of
powering off)
Before trying to suspend, the command queries the guest in order
to know whether a given mode is supported. The sleep and hybrid
modes are only supported in QEMU 1.1 and later though, because
QEMU would adverstise broken S3 support in previous versions.
The guest-suspend command will use the scripts provided by the
pm-utils package if they are available. If they aren't, a manual
process which directly writes to the "/sys/power/state" file will
be tried.
To reap terminated children, a new signal handler is installed to
catch SIGCHLD signals and a non-blocking call to waitpid() is done
to collect their exit statuses.
The approach used to query the guest for suspend support deserves
some explanation. It's implemented by bios_supports_mode() and shown
below:
qemu-ga
|
create pipe
|
fork()
-----------------
| |
| |
| fork()
| --------------------------
| | |
| | |
| | exec('pm-is-supported')
| |
| wait()
| write exit status to pipe
| exit
|
read pipe
This might look complex, but the final code is quite simple. The
purpose of that approach is to allow qemu-ga to reap its children
(semi-)automatically from its SIGCHLD handler.
Implementing this the obvious way, that's, doing the exec() call
from the first child process, would force us to introduce a more
complex way to reap qemu-ga's children. Like registering PIDs to
be reaped and having a way to wait for them when returning their
exit status to qemu-ga is necessary.
Signed-off-by: Luiz Capitulino <address@hidden>
---
qapi-schema-guest.json | 29 ++++++
qemu-ga.c | 18 ++++-
qga/guest-agent-commands.c | 212 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 258 insertions(+), 1 deletions(-)
diff --git a/qapi-schema-guest.json b/qapi-schema-guest.json
index 5f8a18d..6a0605b 100644
--- a/qapi-schema-guest.json
+++ b/qapi-schema-guest.json
@@ -219,3 +219,32 @@
##
{ 'command': 'guest-fsfreeze-thaw',
'returns': 'int' }
+
+##
+# @guest-suspend
+#
+# Suspend guest execution by entering ACPI power state S3 or S4.
+#
+# This command tries to execute the scripts provided by the pm-utils
+# package. If they are not available, it will perform the suspend
+# operation 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.
+#
+# @mode: 'hibernate' RAM content is saved to the disk and the guest is
+# powered off (this corresponds to ACPI S4)
+# 'sleep' execution is suspended but the RAM retains its contents
+# (this corresponds to ACPI S3)
+# 'hybrid' RAM content is saved to the disk but the guest is
+# suspended instead of powering off
+#
+# Notes: o This is an asynchronous request. There's no guarantee a response
+# will be sent.
+# o Errors will be logged to guest's syslog.
+# o It's strongly recommended to issue the guest-sync command before
+# sending commands when the guest resumes.
+#
+# Since: 1.1
+##
+{ 'command': 'guest-suspend', 'data': { 'mode': 'str' } }
diff --git a/qemu-ga.c b/qemu-ga.c
index 647df82..f531084 100644
--- a/qemu-ga.c
+++ b/qemu-ga.c
@@ -17,6 +17,7 @@
#include <getopt.h>
#include <termios.h>
#include <syslog.h>
+#include <sys/wait.h>
#include "qemu_socket.h"
#include "json-streamer.h"
#include "json-parser.h"
@@ -59,9 +60,16 @@ static void quit_handler(int sig)
}
}
+/* reap _all_ terminated children */
+static void child_handler(int sig)
+{
+ int status;
+ while (waitpid(-1, &status, WNOHANG) > 0) /* NOTHING */;
+}
+
static void register_signal_handlers(void)
{
- struct sigaction sigact;
+ struct sigaction sigact, sigact_chld;
int ret;
memset(&sigact, 0, sizeof(struct sigaction));
@@ -76,6 +84,14 @@ static void register_signal_handlers(void)
if (ret == -1) {
g_error("error configuring signal handler: %s", strerror(errno));
}
+
+ 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));
+ }
}
static void usage(const char *cmd)
diff --git a/qga/guest-agent-commands.c b/qga/guest-agent-commands.c
index a09c8ca..963270c 100644
--- a/qga/guest-agent-commands.c
+++ b/qga/guest-agent-commands.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"
@@ -44,6 +45,21 @@ static void slog(const char *fmt, ...)
va_end(ap);
}
+static int reopen_fd_to_null(int fd)
+{
+ int ret, nullfd;
+
+ nullfd = open("/dev/null", O_RDWR);
+ if (nullfd < 0) {
+ return -1;
+ }
+
+ ret = dup2(nullfd, fd);
+ close(nullfd);
+
+ return ret;
+}
+
int64_t qmp_guest_sync(int64_t id, Error **errp)
{
return id;
@@ -574,6 +590,202 @@ int64_t qmp_guest_fsfreeze_thaw(Error **err)
}
#endif
+#define LINUX_SYS_STATE_FILE "/sys/power/state"
+#define SUS_MODE_SUPPORTED 0
+#define SUS_MODE_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 bool bios_supports_mode(const char *mode, Error **err)
+{
+ pid_t pid;
+ ssize_t ret;
+ int status, pipefds[2];
+
+ if (pipe(pipefds) < 0) {
+ error_set(err, QERR_UNDEFINED_ERROR);
+ return false;
+ }
+
+ 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) {
+ char buf[32];
+ FILE *sysfile;
+ const char *arg;
+ const char *pmutils_bin = "pm-is-supported";
+
+ if (strcmp(mode, "hibernate") == 0) {
+ arg = "--hibernate";
+ } else if (strcmp(mode, "sleep") == 0) {
+ arg = "--suspend";
+ } else if (strcmp(mode, "hybrid") == 0) {
+ arg = "--suspend-hybrid";
+ } else {
+ _exit(SUS_MODE_NOT_SUPPORTED);
+ }
+
+ execlp(pmutils_bin, pmutils_bin, arg, NULL);
+
+ /*
+ * If we get here execlp() has failed. Let's try the manual
+ * approach if mode is not hybrid (as it's only suported by
+ * pm-utils)
+ */
+
+ if (strcmp(mode, "hybrid") == 0) {
+ _exit(SUS_MODE_NOT_SUPPORTED);
+ }
+
+ sysfile = fopen(LINUX_SYS_STATE_FILE, "r");
+ if (!sysfile) {
+ _exit(SUS_MODE_NOT_SUPPORTED);
+ }
+
+ if (!fgets(buf, sizeof(buf), sysfile)) {
+ _exit(SUS_MODE_NOT_SUPPORTED);
+ }
+
+ if (strcmp(mode, "hibernate") == 0 && strstr(buf, "disk")) {
+ _exit(SUS_MODE_SUPPORTED);
+ } else if (strcmp(mode, "sleep") == 0 && strstr(buf, "mem")) {
+ _exit(SUS_MODE_SUPPORTED);
+ }
+
+ _exit(SUS_MODE_NOT_SUPPORTED);
+ }
+
+ if (pid > 0) {
+ wait(&status);
+ } else {
+ status = SUS_MODE_NOT_SUPPORTED;
+ }
+
+ ret = write(pipefds[1], &status, sizeof(status));
+ if (ret != sizeof(status)) {
+ _exit(1);
+ }
+
+ _exit(0);
+ }
+
+ ret = read(pipefds[0], &status, sizeof(status));
+ close(pipefds[0]);
+ close(pipefds[1]);
+
+ if (ret == sizeof(status) && WIFEXITED(status) &&
+ WEXITSTATUS(status) == SUS_MODE_SUPPORTED) {
+ return true;
+ }
+
+ return false;
+}
+
+static bool host_supports_mode(const char *mode)
+{
+ if (strcmp(mode, "hibernate")) {
+ /* sleep & hybrid are only supported in qemu 1.1.0 and above */
+ return ga_has_support_level(1, 1, 0);
+ }
+ return true;
+}
+
+void qmp_guest_suspend(const char *mode, Error **err)
+{
+ pid_t pid;
+ const char *pmutils_bin;
+ Error *local_err = NULL;
+
+ if (strcmp(mode, "hibernate") == 0) {
+ pmutils_bin = "pm-hibernate";
+ } else if (strcmp(mode, "sleep") == 0) {
+ pmutils_bin = "pm-suspend";
+ } else if (strcmp(mode, "hybrid") == 0) {
+ pmutils_bin = "pm-suspend-hybrid";
+ } else {
+ error_set(err, QERR_INVALID_PARAMETER, "mode");
+ return;
+ }
+
+ if (!host_supports_mode(mode)) {
+ error_set(err, QERR_UNSUPPORTED);
+ return;
+ }
+
+ if (!bios_supports_mode(mode, &local_err)) {
+ if (error_is_set(&local_err)) {
+ error_propagate(err, local_err);
+ } else {
+ error_set(err, QERR_UNSUPPORTED);
+ }
+ return;
+ }
+
+ pid = fork();
+ if (pid == 0) {
+ /* child */
+ int fd;
+ const char *cmd;
+
+ setsid();
+ reopen_fd_to_null(0);
+ reopen_fd_to_null(1);
+ reopen_fd_to_null(2);
+
+ execlp(pmutils_bin, pmutils_bin, NULL);
+
+ /*
+ * If we get here execlp() has failed. Let's try the manual
+ * approach if mode is not hybrid (as it's only suported by
+ * pm-utils)
+ */
+
+ slog("could not execute %s: %s\n", pmutils_bin, strerror(errno));
+ if (strcmp(mode, "hybrid") == 0) {
+ _exit(1);
+ }
+
+ slog("trying to suspend using the manual method...\n");
+
+ fd = open(LINUX_SYS_STATE_FILE, O_WRONLY);
+ if (fd < 0) {
+ slog("can't open file %s: %s\n", LINUX_SYS_STATE_FILE,
+ strerror(errno));
+ _exit(1);
+ }
+
+ cmd = strcmp(mode, "sleep") == 0 ? "mem" : "disk";
+ if (write(fd, cmd, strlen(cmd)) < 0) {
+ slog("can't write to %s: %s\n", LINUX_SYS_STATE_FILE,
+ strerror(errno));
+ _exit(1);
+ }
+
+ _exit(0);
+ } else if (pid < 0) {
+ error_set(err, QERR_UNDEFINED_ERROR);
+ return;
+ }
+}
+
/* register init/cleanup routines for stateful command groups */
void ga_command_state_init(GAState *s, GACommandState *cs)
{
--
1.7.9.rc0.dirty
- [Qemu-devel] [PATCH v5 0/2]: qemu-ga: Add the guest-suspend command, Luiz Capitulino, 2012/01/13
- [Qemu-devel] [PATCH 2/2] qemu-ga: Add the guest-suspend command,
Luiz Capitulino <=
- Re: [Qemu-devel] [PATCH 2/2] qemu-ga: Add the guest-suspend command, Eric Blake, 2012/01/13
- Re: [Qemu-devel] [PATCH 2/2] qemu-ga: Add the guest-suspend command, Luiz Capitulino, 2012/01/16
- Re: [Qemu-devel] [PATCH 2/2] qemu-ga: Add the guest-suspend command, Luiz Capitulino, 2012/01/16
- Re: [Qemu-devel] [PATCH 2/2] qemu-ga: Add the guest-suspend command, Daniel P. Berrange, 2012/01/16
- Re: [Qemu-devel] [PATCH 2/2] qemu-ga: Add the guest-suspend command, Luiz Capitulino, 2012/01/16
- Re: [Qemu-devel] [PATCH 2/2] qemu-ga: Add the guest-suspend command, Luiz Capitulino, 2012/01/16
- Re: [Qemu-devel] [PATCH 2/2] qemu-ga: Add the guest-suspend command, Michael Roth, 2012/01/16
- Re: [Qemu-devel] [PATCH 2/2] qemu-ga: Add the guest-suspend command, Daniel P. Berrange, 2012/01/16
- Re: [Qemu-devel] [PATCH 2/2] qemu-ga: Add the guest-suspend command, Michael Roth, 2012/01/16
- Re: [Qemu-devel] [PATCH 2/2] qemu-ga: Add the guest-suspend command, Jamie Lokier, 2012/01/17