|
@@ -286,6 +286,271 @@ int qmp_guest_fsfreeze_do_thaw(Error **errp)
|
|
|
}
|
|
|
#endif /* CONFIG_FSFREEZE */
|
|
|
|
|
|
+
|
|
|
+#define LINUX_SYS_STATE_FILE "/sys/power/state"
|
|
|
+#define SUSPEND_SUPPORTED 0
|
|
|
+#define SUSPEND_NOT_SUPPORTED 1
|
|
|
+
|
|
|
+typedef enum {
|
|
|
+ SUSPEND_MODE_DISK = 0,
|
|
|
+ SUSPEND_MODE_RAM = 1,
|
|
|
+ SUSPEND_MODE_HYBRID = 2,
|
|
|
+} SuspendMode;
|
|
|
+
|
|
|
+/*
|
|
|
+ * Executes a command in a child process using g_spawn_sync,
|
|
|
+ * returning an int >= 0 representing the exit status of the
|
|
|
+ * process.
|
|
|
+ *
|
|
|
+ * If the program wasn't found in path, returns -1.
|
|
|
+ *
|
|
|
+ * If a problem happened when creating the child process,
|
|
|
+ * returns -1 and errp is set.
|
|
|
+ */
|
|
|
+static int run_process_child(const char *command[], Error **errp)
|
|
|
+{
|
|
|
+ int exit_status, spawn_flag;
|
|
|
+ GError *g_err = NULL;
|
|
|
+ bool success;
|
|
|
+
|
|
|
+ spawn_flag = G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL |
|
|
|
+ G_SPAWN_STDERR_TO_DEV_NULL;
|
|
|
+
|
|
|
+ success = g_spawn_sync(NULL, (char **)command, NULL, spawn_flag,
|
|
|
+ NULL, NULL, NULL, NULL,
|
|
|
+ &exit_status, &g_err);
|
|
|
+
|
|
|
+ if (success) {
|
|
|
+ return WEXITSTATUS(exit_status);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (g_err && (g_err->code != G_SPAWN_ERROR_NOENT)) {
|
|
|
+ error_setg(errp, "failed to create child process, error '%s'",
|
|
|
+ g_err->message);
|
|
|
+ }
|
|
|
+
|
|
|
+ g_error_free(g_err);
|
|
|
+ return -1;
|
|
|
+}
|
|
|
+
|
|
|
+static bool systemd_supports_mode(SuspendMode mode, Error **errp)
|
|
|
+{
|
|
|
+ const char *systemctl_args[3] = {"systemd-hibernate", "systemd-suspend",
|
|
|
+ "systemd-hybrid-sleep"};
|
|
|
+ const char *cmd[4] = {"systemctl", "status", systemctl_args[mode], NULL};
|
|
|
+ int status;
|
|
|
+
|
|
|
+ status = run_process_child(cmd, errp);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * systemctl status uses LSB return codes so we can expect
|
|
|
+ * status > 0 and be ok. To assert if the guest has support
|
|
|
+ * for the selected suspend mode, status should be < 4. 4 is
|
|
|
+ * the code for unknown service status, the return value when
|
|
|
+ * the service does not exist. A common value is status = 3
|
|
|
+ * (program is not running).
|
|
|
+ */
|
|
|
+ if (status > 0 && status < 4) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+static void systemd_suspend(SuspendMode mode, Error **errp)
|
|
|
+{
|
|
|
+ Error *local_err = NULL;
|
|
|
+ const char *systemctl_args[3] = {"hibernate", "suspend", "hybrid-sleep"};
|
|
|
+ const char *cmd[3] = {"systemctl", systemctl_args[mode], NULL};
|
|
|
+ int status;
|
|
|
+
|
|
|
+ status = run_process_child(cmd, &local_err);
|
|
|
+
|
|
|
+ if (status == 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((status == -1) && !local_err) {
|
|
|
+ error_setg(errp, "the helper program 'systemctl %s' was not found",
|
|
|
+ systemctl_args[mode]);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (local_err) {
|
|
|
+ error_propagate(errp, local_err);
|
|
|
+ } else {
|
|
|
+ error_setg(errp, "the helper program 'systemctl %s' returned an "
|
|
|
+ "unexpected exit status code (%d)",
|
|
|
+ systemctl_args[mode], status);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static bool pmutils_supports_mode(SuspendMode mode, Error **errp)
|
|
|
+{
|
|
|
+ Error *local_err = NULL;
|
|
|
+ const char *pmutils_args[3] = {"--hibernate", "--suspend",
|
|
|
+ "--suspend-hybrid"};
|
|
|
+ const char *cmd[3] = {"pm-is-supported", pmutils_args[mode], NULL};
|
|
|
+ int status;
|
|
|
+
|
|
|
+ status = run_process_child(cmd, &local_err);
|
|
|
+
|
|
|
+ if (status == SUSPEND_SUPPORTED) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((status == -1) && !local_err) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (local_err) {
|
|
|
+ error_propagate(errp, local_err);
|
|
|
+ } else {
|
|
|
+ error_setg(errp,
|
|
|
+ "the helper program '%s' returned an unexpected exit"
|
|
|
+ " status code (%d)", "pm-is-supported", status);
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+static void pmutils_suspend(SuspendMode mode, Error **errp)
|
|
|
+{
|
|
|
+ Error *local_err = NULL;
|
|
|
+ const char *pmutils_binaries[3] = {"pm-hibernate", "pm-suspend",
|
|
|
+ "pm-suspend-hybrid"};
|
|
|
+ const char *cmd[2] = {pmutils_binaries[mode], NULL};
|
|
|
+ int status;
|
|
|
+
|
|
|
+ status = run_process_child(cmd, &local_err);
|
|
|
+
|
|
|
+ if (status == 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((status == -1) && !local_err) {
|
|
|
+ error_setg(errp, "the helper program '%s' was not found",
|
|
|
+ pmutils_binaries[mode]);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (local_err) {
|
|
|
+ error_propagate(errp, local_err);
|
|
|
+ } else {
|
|
|
+ error_setg(errp,
|
|
|
+ "the helper program '%s' returned an unexpected exit"
|
|
|
+ " status code (%d)", pmutils_binaries[mode], status);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static bool linux_sys_state_supports_mode(SuspendMode mode, Error **errp)
|
|
|
+{
|
|
|
+ const char *sysfile_strs[3] = {"disk", "mem", NULL};
|
|
|
+ const char *sysfile_str = sysfile_strs[mode];
|
|
|
+ char buf[32]; /* hopefully big enough */
|
|
|
+ int fd;
|
|
|
+ ssize_t ret;
|
|
|
+
|
|
|
+ if (!sysfile_str) {
|
|
|
+ error_setg(errp, "unknown guest suspend mode");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ fd = open(LINUX_SYS_STATE_FILE, O_RDONLY);
|
|
|
+ if (fd < 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = read(fd, buf, sizeof(buf) - 1);
|
|
|
+ close(fd);
|
|
|
+ if (ret <= 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ buf[ret] = '\0';
|
|
|
+
|
|
|
+ if (strstr(buf, sysfile_str)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+static void linux_sys_state_suspend(SuspendMode mode, Error **errp)
|
|
|
+{
|
|
|
+ g_autoptr(GError) local_gerr = NULL;
|
|
|
+ const char *sysfile_strs[3] = {"disk", "mem", NULL};
|
|
|
+ const char *sysfile_str = sysfile_strs[mode];
|
|
|
+
|
|
|
+ if (!sysfile_str) {
|
|
|
+ error_setg(errp, "unknown guest suspend mode");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!g_file_set_contents(LINUX_SYS_STATE_FILE, sysfile_str,
|
|
|
+ -1, &local_gerr)) {
|
|
|
+ error_setg(errp, "suspend: cannot write to '%s': %s",
|
|
|
+ LINUX_SYS_STATE_FILE, local_gerr->message);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void guest_suspend(SuspendMode mode, Error **errp)
|
|
|
+{
|
|
|
+ Error *local_err = NULL;
|
|
|
+ bool mode_supported = false;
|
|
|
+
|
|
|
+ if (systemd_supports_mode(mode, &local_err)) {
|
|
|
+ mode_supported = true;
|
|
|
+ systemd_suspend(mode, &local_err);
|
|
|
+
|
|
|
+ if (!local_err) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ error_free(local_err);
|
|
|
+ local_err = NULL;
|
|
|
+
|
|
|
+ if (pmutils_supports_mode(mode, &local_err)) {
|
|
|
+ mode_supported = true;
|
|
|
+ pmutils_suspend(mode, &local_err);
|
|
|
+
|
|
|
+ if (!local_err) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ error_free(local_err);
|
|
|
+ local_err = NULL;
|
|
|
+
|
|
|
+ if (linux_sys_state_supports_mode(mode, &local_err)) {
|
|
|
+ mode_supported = true;
|
|
|
+ linux_sys_state_suspend(mode, &local_err);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!mode_supported) {
|
|
|
+ error_free(local_err);
|
|
|
+ error_setg(errp,
|
|
|
+ "the requested suspend mode is not supported by the guest");
|
|
|
+ } else {
|
|
|
+ error_propagate(errp, local_err);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void qmp_guest_suspend_disk(Error **errp)
|
|
|
+{
|
|
|
+ guest_suspend(SUSPEND_MODE_DISK, errp);
|
|
|
+}
|
|
|
+
|
|
|
+void qmp_guest_suspend_ram(Error **errp)
|
|
|
+{
|
|
|
+ guest_suspend(SUSPEND_MODE_RAM, errp);
|
|
|
+}
|
|
|
+
|
|
|
+void qmp_guest_suspend_hybrid(Error **errp)
|
|
|
+{
|
|
|
+ guest_suspend(SUSPEND_MODE_HYBRID, errp);
|
|
|
+}
|
|
|
+
|
|
|
/* Transfer online/offline status between @vcpu and the guest system.
|
|
|
*
|
|
|
* On input either @errp or *@errp must be NULL.
|