From 11d0f1255bd5651f628280dc96c4ce9d63ae9236 Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Tue, 28 Feb 2012 11:03:03 -0300 Subject: qemu-ga: add guest-suspend-disk As the command name implies, this command suspends the guest to disk. The suspend operation is implemented by two functions: bios_supports_mode() and guest_suspend(). Both functions are generic enough to be used by other suspend modes (introduced by next commits). Both functions will try to use the scripts provided by the pm-utils package if it's available. If it's not available, a manual method, which consists of directly writing to '/sys/power/state', will be used. To reap terminated children, a new signal handler is installed in the parent to catch SIGCHLD signals and a non-blocking call to waitpid() is done to collect their exit statuses. The statuses, however, are discarded. 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 resulting 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. The approach explained above avoids that complexity. Signed-off-by: Luiz Capitulino --- qga/commands-posix.c | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) (limited to 'qga/commands-posix.c') 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 #include +#include #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) { -- cgit 1.4.1 From fbf42210c19ec4315e409b7f9f0bfa46c7dfb921 Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Tue, 28 Feb 2012 11:03:04 -0300 Subject: qemu-ga: add guest-suspend-ram Signed-off-by: Luiz Capitulino --- qapi-schema-guest.json | 28 ++++++++++++++++++++++++++++ qga/commands-posix.c | 10 ++++++++++ qga/commands-win32.c | 5 +++++ 3 files changed, 43 insertions(+) (limited to 'qga/commands-posix.c') diff --git a/qapi-schema-guest.json b/qapi-schema-guest.json index f4e0e1d241..b1023110d7 100644 --- a/qapi-schema-guest.json +++ b/qapi-schema-guest.json @@ -319,3 +319,31 @@ # 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' } diff --git a/qga/commands-posix.c b/qga/commands-posix.c index af785f5e5d..134c130d08 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -705,6 +705,16 @@ void qmp_guest_suspend_disk(Error **err) 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); +} + /* 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 c688476c89..b19a63ccf6 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -129,6 +129,11 @@ void qmp_guest_suspend_disk(Error **err) error_set(err, QERR_UNSUPPORTED); } +void qmp_guest_suspend_ram(Error **err) +{ + error_set(err, QERR_UNSUPPORTED); +} + /* register init/cleanup routines for stateful command groups */ void ga_command_state_init(GAState *s, GACommandState *cs) { -- cgit 1.4.1 From 95f4f404e108f8c6b937ddf2ba973f3799b51fb5 Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Tue, 28 Feb 2012 11:03:05 -0300 Subject: qemu-ga: add guest-suspend-hybrid Signed-off-by: Luiz Capitulino --- qapi-schema-guest.json | 23 +++++++++++++++++++++++ qga/commands-posix.c | 10 ++++++++++ qga/commands-win32.c | 5 +++++ 3 files changed, 38 insertions(+) (limited to 'qga/commands-posix.c') diff --git a/qapi-schema-guest.json b/qapi-schema-guest.json index b1023110d7..6a755529c7 100644 --- a/qapi-schema-guest.json +++ b/qapi-schema-guest.json @@ -347,3 +347,26 @@ # 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' } diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 134c130d08..79571bf8cc 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -715,6 +715,16 @@ void qmp_guest_suspend_ram(Error **err) 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); +} + /* 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 b19a63ccf6..7ef185f587 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -134,6 +134,11 @@ void qmp_guest_suspend_ram(Error **err) error_set(err, QERR_UNSUPPORTED); } +void qmp_guest_suspend_hybrid(Error **err) +{ + error_set(err, QERR_UNSUPPORTED); +} + /* register init/cleanup routines for stateful command groups */ void ga_command_state_init(GAState *s, GACommandState *cs) { -- cgit 1.4.1 From 3424fc9f16a1e7d1c48eb6d605eb0ca63e199ec2 Mon Sep 17 00:00:00 2001 From: Michal Privoznik Date: Wed, 29 Feb 2012 17:02:23 +0100 Subject: qemu-ga: add guest-network-get-interfaces command This command returns an array of: [ifname, hwaddr, [ipaddr, ipaddr_family, prefix] ] for each interface in the system. Currently, only IPv4 and IPv6 are supported. Signed-off-by: Michal Privoznik --- qapi-schema-guest.json | 59 ++++++++++++++++ qga/commands-posix.c | 181 +++++++++++++++++++++++++++++++++++++++++++++++++ qga/commands-win32.c | 6 ++ 3 files changed, 246 insertions(+) (limited to 'qga/commands-posix.c') diff --git a/qapi-schema-guest.json b/qapi-schema-guest.json index 6a755529c7..12b5d4fdef 100644 --- a/qapi-schema-guest.json +++ b/qapi-schema-guest.json @@ -370,3 +370,62 @@ # 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/qga/commands-posix.c b/qga/commands-posix.c index 79571bf8cc..5b77fa9eee 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -5,6 +5,7 @@ * * Authors: * Michael Roth + * Michal Privoznik * * 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,11 +24,16 @@ #include #include +#include +#include +#include +#include #include #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; @@ -725,6 +731,181 @@ void qmp_guest_suspend_hybrid(Error **err) 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) { diff --git a/qga/commands-win32.c b/qga/commands-win32.c index b7600ed89a..eb8d1405d3 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -263,6 +263,12 @@ 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) { -- cgit 1.4.1 From 3cf0bed8369267184e5dc5b58882811519d67437 Mon Sep 17 00:00:00 2001 From: Michael Roth Date: Tue, 7 Feb 2012 13:56:48 -0600 Subject: qemu-ga: add guest-sync-delimited guest-sync leaves it as an exercise to the user as to how to reliably obtain the response to guest-sync if the client had previously read in a partial response (due qemu-ga previously being restarted mid-"sentence" due to reboot, forced restart, etc). qemu-ga handles this situation on its end by having a client precede their guest-sync request with a 0xFF byte (invalid UTF-8), which qemu-ga/QEMU JSON parsers will treat as a flush event. Thus we can reliably flush the qemu-ga parser state in preparation for receiving the guest-sync request. guest-sync-delimited provides the same functionality for a client: when a guest-sync-delimited is issued, qemu-ga will precede it's response with a 0xFF byte that the client can use as an indicator to flush its buffer/parser state in preparation for reliably receiving the guest-sync-delimited response. It is also useful as an optimization for clients, since, after issuing a guest-sync-delimited, clients can safely discard all stale data read from the channel until the 0xFF is found. More information available on the wiki: http://wiki.qemu.org/Features/QAPI/GuestAgent#QEMU_Guest_Agent_Protocol Signed-off-by: Michael Roth --- qapi-schema-guest.json | 48 +++++++++++++++++++++++++++++++++++++++++++++++- qemu-ga.c | 27 ++++++++++++++++++++++----- qga/commands-posix.c | 3 --- qga/commands.c | 6 ++++++ qga/guest-agent-core.h | 2 ++ 5 files changed, 77 insertions(+), 9 deletions(-) (limited to 'qga/commands-posix.c') diff --git a/qapi-schema-guest.json b/qapi-schema-guest.json index 12b5d4fdef..cf18876c57 100644 --- a/qapi-schema-guest.json +++ b/qapi-schema-guest.json @@ -1,5 +1,40 @@ # *-*- 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: # @@ -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 diff --git a/qemu-ga.c b/qemu-ga.c index 1c90e6ef0d..d6f786e50d 100644 --- a/qemu-ga.c +++ b/qemu-ga.c @@ -41,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; @@ -54,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, @@ -198,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) { @@ -254,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); @@ -264,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 5b77fa9eee..7b2be2f936 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -35,8 +35,6 @@ #include "qemu-queue.h" #include "host-utils.h" -static GAState *ga_state; - static void reopen_fd_to_null(int fd) { int nullfd; @@ -909,7 +907,6 @@ error: /* 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.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); -- cgit 1.4.1