aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xconfigure2
-rw-r--r--qapi-schema-guest.json182
-rw-r--r--qemu-ga.c46
-rw-r--r--qga/commands-posix.c390
-rw-r--r--qga/commands-win32.c167
-rw-r--r--qga/commands.c6
-rw-r--r--qga/guest-agent-core.h2
7 files changed, 774 insertions, 21 deletions
diff --git a/configure b/configure
index ddb3e398fa..d7631ed247 100755
--- a/configure
+++ b/configure
@@ -526,7 +526,7 @@ EOF
bindir="\${prefix}"
sysconfdir="\${prefix}"
confsuffix=""
- libs_qga="-lws2_32 -lwinmm $lib_qga"
+ libs_qga="-lws2_32 -lwinmm -lpowrprof $lib_qga"
fi
werror=""
diff --git a/qapi-schema-guest.json b/qapi-schema-guest.json
index 706925dea6..cf18876c57 100644
--- a/qapi-schema-guest.json
+++ b/qapi-schema-guest.json
@@ -1,6 +1,41 @@
# *-*- Mode: Python -*-*
##
+#
+# Echo back a unique integer value, and prepend to response a
+# leading sentinel byte (0xFF) the client can check scan for.
+#
+# This is used by clients talking to the guest agent over the
+# wire to ensure the stream is in sync and doesn't contain stale
+# data from previous client. It must be issued upon initial
+# connection, and after any client-side timeouts (including
+# timeouts on receiving a response to this command).
+#
+# After issuing this request, all guest agent responses should be
+# ignored until the response containing the unique integer value
+# the client passed in is returned. Receival of the 0xFF sentinel
+# byte must be handled as an indication that the client's
+# lexer/tokenizer/parser state should be flushed/reset in
+# preparation for reliably receiving the subsequent response. As
+# an optimization, clients may opt to ignore all data until a
+# sentinel value is receiving to avoid unecessary processing of
+# stale data.
+#
+# Similarly, clients should also precede this *request*
+# with a 0xFF byte to make sure the guest agent flushes any
+# partially read JSON data from a previous client connection.
+#
+# @id: randomly generated 64-bit integer
+#
+# Returns: The unique integer id passed in by the client
+#
+# Since: 1.1
+# ##
+{ 'command': 'guest-sync-delimited'
+ 'data': { 'id': 'int' },
+ 'returns': 'int' }
+
+##
# @guest-sync:
#
# Echo back a unique integer value
@@ -13,8 +48,19 @@
# partially-delivered JSON text in such a way that this response
# can be obtained.
#
+# In cases where a partial stale response was previously
+# received by the client, this cannot always be done reliably.
+# One particular scenario being if qemu-ga responses are fed
+# character-by-character into a JSON parser. In these situations,
+# using guest-sync-delimited may be optimal.
+#
+# For clients that fetch responses line by line and convert them
+# to JSON objects, guest-sync should be sufficient, but note that
+# in cases where the channel is dirty some attempts at parsing the
+# response may result in a parser error.
+#
# Such clients should also precede this command
-# with a 0xFF byte to make such the guest agent flushes any
+# with a 0xFF byte to make sure the guest agent flushes any
# partially read JSON data from a previous session.
#
# @id: randomly generated 64-bit integer
@@ -295,3 +341,137 @@
##
{ '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' }
+
+##
+# @guest-suspend-ram
+#
+# Suspend guest to ram.
+#
+# 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.
+#
+# IMPORTANT: guest-suspend-ram requires QEMU to support the 'system_wakeup'
+# command. Thus, it's *required* to query QEMU for the presence of the
+# 'system_wakeup' command before issuing guest-suspend-ram.
+#
+# Returns: nothing on success
+# If suspend to ram 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-ram' }
+
+##
+# @guest-suspend-hybrid
+#
+# Save guest state to disk and suspend to ram.
+#
+# This command requires the pm-utils package to be installed in the guest.
+#
+# IMPORTANT: guest-suspend-hybrid requires QEMU to support the 'system_wakeup'
+# command. Thus, it's *required* to query QEMU for the presence of the
+# 'system_wakeup' command before issuing guest-suspend-hybrid.
+#
+# Returns: nothing on success
+# If hybrid suspend 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-hybrid' }
+
+##
+# @GuestIpAddressType:
+#
+# An enumeration of supported IP address types
+#
+# @ipv4: IP version 4
+#
+# @ipv6: IP version 6
+#
+# Since: 1.1
+##
+{ 'enum': 'GuestIpAddressType',
+ 'data': [ 'ipv4', 'ipv6' ] }
+
+##
+# @GuestIpAddress:
+#
+# @ip-address: IP address
+#
+# @ip-address-type: Type of @ip-address (e.g. ipv4, ipv6)
+#
+# @prefix: Network prefix length of @ip-address
+#
+# Since: 1.1
+##
+{ 'type': 'GuestIpAddress',
+ 'data': {'ip-address': 'str',
+ 'ip-address-type': 'GuestIpAddressType',
+ 'prefix': 'int'} }
+
+##
+# @GuestNetworkInterface:
+#
+# @name: The name of interface for which info are being delivered
+#
+# @hardware-address: Hardware address of @name
+#
+# @ip-addresses: List of addresses assigned to @name
+#
+# Since: 1.1
+##
+{ 'type': 'GuestNetworkInterface',
+ 'data': {'name': 'str',
+ '*hardware-address': 'str',
+ '*ip-addresses': ['GuestIpAddress'] } }
+
+##
+# @guest-network-get-interfaces:
+#
+# Get list of guest IP addresses, MAC addresses
+# and netmasks.
+#
+# Returns: List of GuestNetworkInfo on success.
+#
+# Since: 1.1
+##
+{ 'command': 'guest-network-get-interfaces',
+ 'returns': ['GuestNetworkInterface'] }
diff --git a/qemu-ga.c b/qemu-ga.c
index ba355d8783..d6f786e50d 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"
@@ -40,6 +41,7 @@
#define QGA_VIRTIO_PATH_DEFAULT "\\\\.\\Global\\org.qemu.guest_agent.0"
#endif
#define QGA_PIDFILE_DEFAULT "/var/run/qemu-ga.pid"
+#define QGA_SENTINEL_BYTE 0xFF
struct GAState {
JSONMessageParser parser;
@@ -53,9 +55,10 @@ struct GAState {
#ifdef _WIN32
GAService service;
#endif
+ bool delimit_response;
};
-static struct GAState *ga_state;
+struct GAState *ga_state;
#ifdef _WIN32
DWORD WINAPI service_ctrl_handler(DWORD ctrl, DWORD type, LPVOID data,
@@ -73,9 +76,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 +101,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
@@ -181,6 +200,11 @@ static void ga_log(const gchar *domain, GLogLevelFlags level,
}
}
+void ga_set_response_delimited(GAState *s)
+{
+ s->delimit_response = true;
+}
+
#ifndef _WIN32
static void become_daemon(const char *pidfile)
{
@@ -237,7 +261,7 @@ fail:
static int send_response(GAState *s, QObject *payload)
{
const char *buf;
- QString *payload_qstr;
+ QString *payload_qstr, *response_qstr;
GIOStatus status;
g_assert(payload && s->channel);
@@ -247,10 +271,20 @@ static int send_response(GAState *s, QObject *payload)
return -EINVAL;
}
- qstring_append_chr(payload_qstr, '\n');
- buf = qstring_get_str(payload_qstr);
+ if (s->delimit_response) {
+ s->delimit_response = false;
+ response_qstr = qstring_new();
+ qstring_append_chr(response_qstr, QGA_SENTINEL_BYTE);
+ qstring_append(response_qstr, qstring_get_str(payload_qstr));
+ QDECREF(payload_qstr);
+ } else {
+ response_qstr = payload_qstr;
+ }
+
+ qstring_append_chr(response_qstr, '\n');
+ buf = qstring_get_str(response_qstr);
status = ga_channel_write_all(s->channel, buf, strlen(buf));
- QDECREF(payload_qstr);
+ QDECREF(response_qstr);
if (status != G_IO_STATUS_NORMAL) {
return -EIO;
}
diff --git a/qga/commands-posix.c b/qga/commands-posix.c
index 126127aae4..7b2be2f936 100644
--- a/qga/commands-posix.c
+++ b/qga/commands-posix.c
@@ -5,6 +5,7 @@
*
* Authors:
* Michael Roth <mdroth@linux.vnet.ibm.com>
+ * Michal Privoznik <mprivozn@redhat.com>
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
@@ -23,12 +24,32 @@
#include <sys/types.h>
#include <sys/ioctl.h>
+#include <ifaddrs.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <sys/wait.h>
#include "qga/guest-agent-core.h"
#include "qga-qmp-commands.h"
#include "qerror.h"
#include "qemu-queue.h"
+#include "host-utils.h"
-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)
{
@@ -517,10 +538,375 @@ 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);
+}
+
+void qmp_guest_suspend_ram(Error **err)
+{
+ bios_supports_mode("pm-is-supported", "--suspend", "mem", err);
+ if (error_is_set(err)) {
+ return;
+ }
+
+ guest_suspend("pm-suspend", "mem", err);
+}
+
+void qmp_guest_suspend_hybrid(Error **err)
+{
+ bios_supports_mode("pm-is-supported", "--suspend-hybrid", NULL, err);
+ if (error_is_set(err)) {
+ return;
+ }
+
+ guest_suspend("pm-suspend-hybrid", NULL, err);
+}
+
+static GuestNetworkInterfaceList *
+guest_find_interface(GuestNetworkInterfaceList *head,
+ const char *name)
+{
+ for (; head; head = head->next) {
+ if (strcmp(head->value->name, name) == 0) {
+ break;
+ }
+ }
+
+ return head;
+}
+
+/*
+ * Build information about guest interfaces
+ */
+GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
+{
+ GuestNetworkInterfaceList *head = NULL, *cur_item = NULL;
+ struct ifaddrs *ifap, *ifa;
+ char err_msg[512];
+
+ if (getifaddrs(&ifap) < 0) {
+ snprintf(err_msg, sizeof(err_msg),
+ "getifaddrs failed: %s", strerror(errno));
+ error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg);
+ goto error;
+ }
+
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ GuestNetworkInterfaceList *info;
+ GuestIpAddressList **address_list = NULL, *address_item = NULL;
+ char addr4[INET_ADDRSTRLEN];
+ char addr6[INET6_ADDRSTRLEN];
+ int sock;
+ struct ifreq ifr;
+ unsigned char *mac_addr;
+ void *p;
+
+ g_debug("Processing %s interface", ifa->ifa_name);
+
+ info = guest_find_interface(head, ifa->ifa_name);
+
+ if (!info) {
+ info = g_malloc0(sizeof(*info));
+ info->value = g_malloc0(sizeof(*info->value));
+ info->value->name = g_strdup(ifa->ifa_name);
+
+ if (!cur_item) {
+ head = cur_item = info;
+ } else {
+ cur_item->next = info;
+ cur_item = info;
+ }
+ }
+
+ if (!info->value->has_hardware_address &&
+ ifa->ifa_flags & SIOCGIFHWADDR) {
+ /* we haven't obtained HW address yet */
+ sock = socket(PF_INET, SOCK_STREAM, 0);
+ if (sock == -1) {
+ snprintf(err_msg, sizeof(err_msg),
+ "failed to create socket: %s", strerror(errno));
+ error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg);
+ goto error;
+ }
+
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, info->value->name, IF_NAMESIZE);
+ if (ioctl(sock, SIOCGIFHWADDR, &ifr) == -1) {
+ snprintf(err_msg, sizeof(err_msg),
+ "failed to get MAC addres of %s: %s",
+ ifa->ifa_name,
+ strerror(errno));
+ error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg);
+ goto error;
+ }
+
+ mac_addr = (unsigned char *) &ifr.ifr_hwaddr.sa_data;
+
+ if (asprintf(&info->value->hardware_address,
+ "%02x:%02x:%02x:%02x:%02x:%02x",
+ (int) mac_addr[0], (int) mac_addr[1],
+ (int) mac_addr[2], (int) mac_addr[3],
+ (int) mac_addr[4], (int) mac_addr[5]) == -1) {
+ snprintf(err_msg, sizeof(err_msg),
+ "failed to format MAC: %s", strerror(errno));
+ error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg);
+ goto error;
+ }
+
+ info->value->has_hardware_address = true;
+ close(sock);
+ }
+
+ if (ifa->ifa_addr &&
+ ifa->ifa_addr->sa_family == AF_INET) {
+ /* interface with IPv4 address */
+ address_item = g_malloc0(sizeof(*address_item));
+ address_item->value = g_malloc0(sizeof(*address_item->value));
+ p = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
+ if (!inet_ntop(AF_INET, p, addr4, sizeof(addr4))) {
+ snprintf(err_msg, sizeof(err_msg),
+ "inet_ntop failed : %s", strerror(errno));
+ error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg);
+ goto error;
+ }
+
+ address_item->value->ip_address = g_strdup(addr4);
+ address_item->value->ip_address_type = GUEST_IP_ADDRESS_TYPE_IPV4;
+
+ if (ifa->ifa_netmask) {
+ /* Count the number of set bits in netmask.
+ * This is safe as '1' and '0' cannot be shuffled in netmask. */
+ p = &((struct sockaddr_in *)ifa->ifa_netmask)->sin_addr;
+ address_item->value->prefix = ctpop32(((uint32_t *) p)[0]);
+ }
+ } else if (ifa->ifa_addr &&
+ ifa->ifa_addr->sa_family == AF_INET6) {
+ /* interface with IPv6 address */
+ address_item = g_malloc0(sizeof(*address_item));
+ address_item->value = g_malloc0(sizeof(*address_item->value));
+ p = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
+ if (!inet_ntop(AF_INET6, p, addr6, sizeof(addr6))) {
+ snprintf(err_msg, sizeof(err_msg),
+ "inet_ntop failed : %s", strerror(errno));
+ error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg);
+ goto error;
+ }
+
+ address_item->value->ip_address = g_strdup(addr6);
+ address_item->value->ip_address_type = GUEST_IP_ADDRESS_TYPE_IPV6;
+
+ if (ifa->ifa_netmask) {
+ /* Count the number of set bits in netmask.
+ * This is safe as '1' and '0' cannot be shuffled in netmask. */
+ p = &((struct sockaddr_in6 *)ifa->ifa_netmask)->sin6_addr;
+ address_item->value->prefix =
+ ctpop32(((uint32_t *) p)[0]) +
+ ctpop32(((uint32_t *) p)[1]) +
+ ctpop32(((uint32_t *) p)[2]) +
+ ctpop32(((uint32_t *) p)[3]);
+ }
+ }
+
+ if (!address_item) {
+ continue;
+ }
+
+ address_list = &info->value->ip_addresses;
+
+ while (*address_list && (*address_list)->next) {
+ address_list = &(*address_list)->next;
+ }
+
+ if (!*address_list) {
+ *address_list = address_item;
+ } else {
+ (*address_list)->next = address_item;
+ }
+
+ info->value->has_ip_addresses = true;
+
+
+ }
+
+ freeifaddrs(ifap);
+ return head;
+
+error:
+ freeifaddrs(ifap);
+ qapi_free_GuestNetworkInterfaceList(head);
+ return NULL;
+}
+
/* register init/cleanup routines for stateful command groups */
void ga_command_state_init(GAState *s, GACommandState *cs)
{
- ga_state = s;
#if defined(CONFIG_FSFREEZE)
ga_command_state_add(cs, guest_fsfreeze_init, guest_fsfreeze_cleanup);
#endif
diff --git a/qga/commands-win32.c b/qga/commands-win32.c
index 4aa0f0d1e4..eb8d1405d3 100644
--- a/qga/commands-win32.c
+++ b/qga/commands-win32.c
@@ -5,12 +5,15 @@
*
* Authors:
* Michael Roth <mdroth@linux.vnet.ibm.com>
+ * Gal Hammer <ghammer@redhat.com>
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include <glib.h>
+#include <wtypes.h>
+#include <powrprof.h>
#include "qga/guest-agent-core.h"
#include "qga-qmp-commands.h"
#include "qerror.h"
@@ -19,10 +22,63 @@
#define SHTDN_REASON_FLAG_PLANNED 0x80000000
#endif
-void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err)
+static void acquire_privilege(const char *name, Error **err)
{
HANDLE token;
TOKEN_PRIVILEGES priv;
+ Error *local_err = NULL;
+
+ if (error_is_set(err)) {
+ return;
+ }
+
+ if (OpenProcessToken(GetCurrentProcess(),
+ TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &token))
+ {
+ if (!LookupPrivilegeValue(NULL, name, &priv.Privileges[0].Luid)) {
+ error_set(&local_err, QERR_QGA_COMMAND_FAILED,
+ "no luid for requested privilege");
+ goto out;
+ }
+
+ priv.PrivilegeCount = 1;
+ priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+
+ if (!AdjustTokenPrivileges(token, FALSE, &priv, 0, NULL, 0)) {
+ error_set(&local_err, QERR_QGA_COMMAND_FAILED,
+ "unable to acquire requested privilege");
+ goto out;
+ }
+
+ CloseHandle(token);
+ } else {
+ error_set(&local_err, QERR_QGA_COMMAND_FAILED,
+ "failed to open privilege token");
+ }
+
+out:
+ if (local_err) {
+ error_propagate(err, local_err);
+ }
+}
+
+static void execute_async(DWORD WINAPI (*func)(LPVOID), LPVOID opaque, Error **err)
+{
+ Error *local_err = NULL;
+
+ if (error_is_set(err)) {
+ return;
+ }
+ HANDLE thread = CreateThread(NULL, 0, func, opaque, 0, NULL);
+ if (!thread) {
+ error_set(&local_err, QERR_QGA_COMMAND_FAILED,
+ "failed to dispatch asynchronous command");
+ error_propagate(err, local_err);
+ }
+}
+
+void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err)
+{
UINT shutdown_flag = EWX_FORCE;
slog("guest-shutdown called, mode: %s", mode);
@@ -41,16 +97,9 @@ void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err)
/* Request a shutdown privilege, but try to shut down the system
anyway. */
- if (OpenProcessToken(GetCurrentProcess(),
- TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &token))
- {
- LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,
- &priv.Privileges[0].Luid);
-
- priv.PrivilegeCount = 1;
- priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
-
- AdjustTokenPrivileges(token, FALSE, &priv, 0, NULL, 0);
+ acquire_privilege(SE_SHUTDOWN_NAME, err);
+ if (error_is_set(err)) {
+ return;
}
if (!ExitWindowsEx(shutdown_flag, SHTDN_REASON_FLAG_PLANNED)) {
@@ -124,6 +173,102 @@ int64_t qmp_guest_fsfreeze_thaw(Error **err)
return 0;
}
+typedef enum {
+ GUEST_SUSPEND_MODE_DISK,
+ GUEST_SUSPEND_MODE_RAM
+} GuestSuspendMode;
+
+static void check_suspend_mode(GuestSuspendMode mode, Error **err)
+{
+ SYSTEM_POWER_CAPABILITIES sys_pwr_caps;
+ Error *local_err = NULL;
+
+ if (error_is_set(err)) {
+ return;
+ }
+ ZeroMemory(&sys_pwr_caps, sizeof(sys_pwr_caps));
+ if (!GetPwrCapabilities(&sys_pwr_caps)) {
+ error_set(&local_err, QERR_QGA_COMMAND_FAILED,
+ "failed to determine guest suspend capabilities");
+ goto out;
+ }
+
+ switch (mode) {
+ case GUEST_SUSPEND_MODE_DISK:
+ if (!sys_pwr_caps.SystemS4) {
+ error_set(&local_err, QERR_QGA_COMMAND_FAILED,
+ "suspend-to-disk not supported by OS");
+ }
+ break;
+ case GUEST_SUSPEND_MODE_RAM:
+ if (!sys_pwr_caps.SystemS3) {
+ error_set(&local_err, QERR_QGA_COMMAND_FAILED,
+ "suspend-to-ram not supported by OS");
+ }
+ break;
+ default:
+ error_set(&local_err, QERR_INVALID_PARAMETER_VALUE, "mode",
+ "GuestSuspendMode");
+ }
+
+out:
+ if (local_err) {
+ error_propagate(err, local_err);
+ }
+}
+
+static DWORD WINAPI do_suspend(LPVOID opaque)
+{
+ GuestSuspendMode *mode = opaque;
+ DWORD ret = 0;
+
+ if (!SetSuspendState(*mode == GUEST_SUSPEND_MODE_DISK, TRUE, TRUE)) {
+ slog("failed to suspend guest, %s", GetLastError());
+ ret = -1;
+ }
+ g_free(mode);
+ return ret;
+}
+
+void qmp_guest_suspend_disk(Error **err)
+{
+ GuestSuspendMode *mode = g_malloc(sizeof(GuestSuspendMode));
+
+ *mode = GUEST_SUSPEND_MODE_DISK;
+ check_suspend_mode(*mode, err);
+ acquire_privilege(SE_SHUTDOWN_NAME, err);
+ execute_async(do_suspend, mode, err);
+
+ if (error_is_set(err)) {
+ g_free(mode);
+ }
+}
+
+void qmp_guest_suspend_ram(Error **err)
+{
+ GuestSuspendMode *mode = g_malloc(sizeof(GuestSuspendMode));
+
+ *mode = GUEST_SUSPEND_MODE_RAM;
+ check_suspend_mode(*mode, err);
+ acquire_privilege(SE_SHUTDOWN_NAME, err);
+ execute_async(do_suspend, mode, err);
+
+ if (error_is_set(err)) {
+ g_free(mode);
+ }
+}
+
+void qmp_guest_suspend_hybrid(Error **err)
+{
+ error_set(err, QERR_UNSUPPORTED);
+}
+
+GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **err)
+{
+ error_set(err, QERR_UNSUPPORTED);
+ return NULL;
+}
+
/* register init/cleanup routines for stateful command groups */
void ga_command_state_init(GAState *s, GACommandState *cs)
{
diff --git a/qga/commands.c b/qga/commands.c
index b27407d5d7..5bcceaae34 100644
--- a/qga/commands.c
+++ b/qga/commands.c
@@ -29,6 +29,12 @@ void slog(const gchar *fmt, ...)
va_end(ap);
}
+int64_t qmp_guest_sync_delimited(int64_t id, Error **errp)
+{
+ ga_set_response_delimited(ga_state);
+ return id;
+}
+
int64_t qmp_guest_sync(int64_t id, Error **errp)
{
return id;
diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h
index b5dfa5bbda..304525d3c2 100644
--- a/qga/guest-agent-core.h
+++ b/qga/guest-agent-core.h
@@ -18,6 +18,7 @@
typedef struct GAState GAState;
typedef struct GACommandState GACommandState;
+extern GAState *ga_state;
void ga_command_state_init(GAState *s, GACommandState *cs);
void ga_command_state_add(GACommandState *cs,
@@ -30,3 +31,4 @@ bool ga_logging_enabled(GAState *s);
void ga_disable_logging(GAState *s);
void ga_enable_logging(GAState *s);
void slog(const gchar *fmt, ...);
+void ga_set_response_delimited(GAState *s);