summary refs log tree commit diff stats
path: root/qga/commands-posix.c
diff options
context:
space:
mode:
Diffstat (limited to 'qga/commands-posix.c')
-rw-r--r--qga/commands-posix.c390
1 files changed, 388 insertions, 2 deletions
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