Commit cb72b758 authored by Anthony Liguori's avatar Anthony Liguori
Browse files

Merge remote-tracking branch 'mdroth/qga-pull-3-12-2012' into staging

* mdroth/qga-pull-3-12-2012:
  qemu-ga: add guest-sync-delimited
  qemu-ga: add guest-network-get-interfaces command
  qemu-ga: add win32 guest-suspend-ram command
  qemu-ga: add win32 guest-suspend-disk command.
  qemu-ga: add guest-suspend-hybrid
  qemu-ga: add guest-suspend-ram
  qemu-ga: add guest-suspend-disk
parents fb23ae6e 3cf0bed8
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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=""
+181 −1
Original line number Diff line number Diff line
# *-*- 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
@@ -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'] }
+40 −6
Original line number Diff line number Diff line
@@ -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);
    status = ga_channel_write_all(s->channel, buf, strlen(buf));
    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(response_qstr);
    if (status != G_IO_STATUS_NORMAL) {
        return -EIO;
    }
+388 −2
Original line number Diff line number Diff line
@@ -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
+156 −11

File changed.

Preview size limit exceeded, changes collapsed.

Loading