diff options
Diffstat (limited to 'qga/commands-posix.c')
-rw-r--r-- | qga/commands-posix.c | 390 |
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 |