diff options
Diffstat (limited to '')
-rw-r--r-- | agents/virt/server/virt-serial.c | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/agents/virt/server/virt-serial.c b/agents/virt/server/virt-serial.c new file mode 100644 index 0000000..6b369bc --- /dev/null +++ b/agents/virt/server/virt-serial.c @@ -0,0 +1,444 @@ +// #include <config.h> + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <errno.h> +#include <pthread.h> +#include <unistd.h> +#include <fcntl.h> + +#include <sys/types.h> +#include <sys/poll.h> +#include <libvirt/libvirt.h> + +#include <libxml/xmlreader.h> + +#include "simpleconfig.h" +#include "debug.h" + +#define DEBUG0(fmt) dbg_printf(5,"%s:%d :: " fmt "\n", \ + __func__, __LINE__) +#define DEBUG1(fmt, ...) dbg_printf(5, "%s:%d: " fmt "\n", \ + __func__, __LINE__, __VA_ARGS__) + +#include "serial.h" + +#define STREQ(a,b) (strcmp((a),(b)) == 0) + +static pthread_t event_tid = 0; +static int run = 0; + +/* Prototypes */ +const char *eventToString(int event); +int myDomainEventCallback1(virConnectPtr conn, virDomainPtr dom, + int event, int detail, void *opaque); + +void usage(const char *pname); + +struct domain_info { + virDomainPtr dom; + virDomainEventType event; +}; + +static int +is_in_directory(const char *dir, const char *pathspec) +{ + char *last_slash = NULL; + size_t dirlen, pathlen; + + if (!dir || !pathspec) + return 0; + + dirlen = strlen(dir); + pathlen = strlen(pathspec); + + /* + printf("dirlen = %d pathlen = %d\n", + dirlen, pathlen); + */ + + /* chop off trailing slashes */ + while (dirlen && dir[dirlen-1]=='/') + --dirlen; + + /* chop off leading slashes */ + while (dirlen && dir[0] == '/') { + ++dir; + --dirlen; + } + + /* chop off leading slashes */ + while (pathlen && pathspec[0] == '/') { + ++pathspec; + --pathlen; + } + + if (!dirlen || !pathlen) + return 0; + + if (pathlen <= dirlen) + return 0; + + last_slash = strrchr(pathspec, '/'); + + if (!last_slash) + return 0; + + while (*last_slash == '/' && last_slash > pathspec) + --last_slash; + + if (last_slash == pathspec) + return 0; + + pathlen = last_slash - pathspec + 1; + /*printf("real dirlen = %d real pathlen = %d\n", + dirlen, pathlen);*/ + if (pathlen != dirlen) + return 0; + + /* todo - intelligently skip multiple slashes mid-path */ + return !strncmp(dir, pathspec, dirlen); +} + + +static int +domainStarted(virDomainPtr mojaDomain, const char *path, int mode) +{ + char dom_uuid[42]; + char *xml; + xmlDocPtr doc; + xmlNodePtr cur, devices, child, serial; + xmlAttrPtr attr, attr_mode, attr_path; + + if (!mojaDomain) + return -1; + + virDomainGetUUIDString(mojaDomain, dom_uuid); + + xml = virDomainGetXMLDesc(mojaDomain, 0); + // printf("%s\n", xml); + // @todo: free mojaDomain + + // parseXML output + doc = xmlParseMemory(xml, strlen(xml)); + xmlFree(xml); + cur = xmlDocGetRootElement(doc); + + if (cur == NULL) { + fprintf(stderr, "Empty doc\n"); + xmlFreeDoc(doc); + return -1; + } + + if (xmlStrcmp(cur->name, (const xmlChar *) "domain")) { + fprintf(stderr, "no domain?\n"); + xmlFreeDoc(doc); + return -1; + } + + devices = cur->xmlChildrenNode; + for (devices = cur->xmlChildrenNode; devices != NULL; + devices = devices->next) { + if (xmlStrcmp(devices->name, (const xmlChar *) "devices")) { + continue; + } + + for (child = devices->xmlChildrenNode; child != NULL; + child = child->next) { + + if ((!mode && xmlStrcmp(child->name, (const xmlChar *) "serial")) || + (mode && xmlStrcmp(child->name, (const xmlChar *) "channel"))) { + continue; + } + + attr = xmlHasProp(child, (const xmlChar *)"type"); + if (attr == NULL) + continue; + + if (xmlStrcmp(attr->children->content, + (const xmlChar *) "unix")) { + continue; + } + + for (serial = child->xmlChildrenNode; serial != NULL; + serial = serial->next) { + if (xmlStrcmp(serial->name, + (const xmlChar *) "source")) { + continue; + } + + attr_mode = xmlHasProp(serial, (const xmlChar *)"mode"); + attr_path = xmlHasProp(serial, (const xmlChar *)"path"); + + if (!attr_path || !attr_mode) + continue; + + if (xmlStrcmp(attr_mode->children->content, + (const xmlChar *) "bind")) + continue; + + if (path && !is_in_directory(path, (const char *) + attr_path->children->content)) + continue; + + domain_sock_setup(dom_uuid, (const char *) + attr_path->children->content); + } + } + } + + xmlFreeDoc(doc); + return 0; +} + +static int +registerExisting(virConnectPtr vp, const char *path, int mode) +{ + int *d_ids = NULL; + int d_count, x; + virDomainPtr dom; + virDomainInfo d_info; + + errno = EINVAL; + if (!vp) + return -1; + + d_count = virConnectNumOfDomains(vp); + if (d_count <= 0) { + if (d_count == 0) { + /* Successful, but no domains running */ + errno = 0; + return 0; + } + goto out_fail; + } + + d_ids = malloc(sizeof (int) * d_count); + if (!d_ids) + goto out_fail; + + if (virConnectListDomains(vp, d_ids, d_count) < 0) + goto out_fail; + + /* Ok, we have the domain IDs - let's get their names and states */ + for (x = 0; x < d_count; x++) { + dom = virDomainLookupByID(vp, d_ids[x]); + if (!dom) { + /* XXX doom */ + goto out_fail; + } + + if (virDomainGetInfo(dom, &d_info) < 0) { + /* XXX no info for the domain?!! */ + virDomainFree(dom); + goto out_fail; + } + + if (d_info.state != VIR_DOMAIN_SHUTOFF && + d_info.state != VIR_DOMAIN_CRASHED) + domainStarted(dom, path, mode); + + virDomainFree(dom); + } + + out_fail: + free(d_ids); + return 0; +} + +static int +domainStopped(virDomainPtr mojaDomain) +{ + char dom_uuid[42]; + + if (!mojaDomain) + return -1; + + virDomainGetUUIDString(mojaDomain, dom_uuid); + domain_sock_close(dom_uuid); + + return 0; +} + + +struct event_args { + char *uri; + char *path; + int mode; + int wake_fd; +}; + +static void +connectClose(virConnectPtr conn ATTRIBUTE_UNUSED, + int reason, + void *opaque ATTRIBUTE_UNUSED) +{ + switch (reason) { + case VIR_CONNECT_CLOSE_REASON_ERROR: + dbg_printf(2, "Connection closed due to I/O error\n"); + break; + case VIR_CONNECT_CLOSE_REASON_EOF: + dbg_printf(2, "Connection closed due to end of file\n"); + break; + case VIR_CONNECT_CLOSE_REASON_KEEPALIVE: + dbg_printf(2, "Connection closed due to keepalive timeout\n"); + break; + case VIR_CONNECT_CLOSE_REASON_CLIENT: + dbg_printf(2, "Connection closed due to client request\n"); + break; + default: + dbg_printf(2, "Connection closed due to unknown reason\n"); + break; + }; + run = 0; +} + +int +myDomainEventCallback1(virConnectPtr conn, + virDomainPtr dom, int event, int detail, void *opaque) +{ + struct event_args *args = (struct event_args *)opaque; + + if (event == VIR_DOMAIN_EVENT_STARTED || + event == VIR_DOMAIN_EVENT_STOPPED) { + virDomainRef(dom); + if (event == VIR_DOMAIN_EVENT_STARTED) { + domainStarted(dom, args->path, args->mode); + virDomainFree(dom); + if (write(args->wake_fd, "x", 1) != 1) { + dbg_printf(3, "Unable to wake up thread\n"); + } + } else if (event == VIR_DOMAIN_EVENT_STOPPED) { + domainStopped(dom); + virDomainFree(dom); + } + } + + return 0; +} + + +static void * +event_thread(void *arg) +{ + struct event_args *args = (struct event_args *)arg; + virConnectPtr dconn = NULL; + int callback1ret = -1; + + dbg_printf(3, "Libvirt event listener starting\n"); + if (args->uri) + dbg_printf(3," * URI: %s\n", args->uri); + if (args->path) + dbg_printf(3," * Socket path: %s\n", args->path); + dbg_printf(3," * Mode: %s\n", args->mode ? "VMChannel" : "Serial"); + + if (virEventRegisterDefaultImpl() < 0) { + dbg_printf(1, "Failed to register default event impl\n"); + goto out; + } + + dconn = virConnectOpen(args->uri); + if (!dconn) { + dbg_printf(1, "Error connecting to libvirt\n"); + goto out; + } + + virConnectRegisterCloseCallback(dconn, connectClose, NULL, NULL); + + DEBUG0("Registering domain event cbs"); + + registerExisting(dconn, args->path, args->mode); + + callback1ret = + virConnectDomainEventRegister(dconn, myDomainEventCallback1, arg, NULL); + + if (callback1ret != -1) { + if (virConnectSetKeepAlive(dconn, 5, 5) < 0) { + dbg_printf(1, "Failed to start keepalive protocol\n"); + run = 0; + } + while (run) { + if (virEventRunDefaultImpl() < 0) { + dbg_printf(1, "RunDefaultImpl Failed\n"); + } + } + + DEBUG0("Deregistering event handlers"); + virConnectDomainEventDeregister(dconn, myDomainEventCallback1); + } + + DEBUG0("Closing connection"); + if (dconn && virConnectClose(dconn) < 0) { + dbg_printf(1, "error closing libvirt connection\n"); + } + +out: + free(args->uri); + free(args->path); + free(args); + return NULL; +} + + +int +start_event_listener(const char *uri, const char *path, int mode, int *wake_fd) +{ + struct event_args *args = NULL; + int wake_pipe[2]; + + virInitialize(); + + args = malloc(sizeof(*args)); + if (!args) + return -1; + memset(args, 0, sizeof(*args)); + + if (pipe2(wake_pipe, O_CLOEXEC) < 0) { + goto out_fail; + } + + if (uri) { + args->uri = strdup(uri); + if (args->uri == NULL) + goto out_fail; + } + + if (path) { + args->path = strdup(path); + if (args->path == NULL) + goto out_fail; + } + + args->mode = mode; + //args->p_tid = pthread_self(); + *wake_fd = wake_pipe[0]; + args->wake_fd = wake_pipe[1]; + + run = 1; + + return pthread_create(&event_tid, NULL, event_thread, args); + +out_fail: + free(args->uri); + free(args->path); + free(args); + return -1; +} + + +int +stop_event_listener(void) +{ + run = 0; + //pthread_cancel(event_tid); + pthread_join(event_tid, NULL); + event_tid = 0; + + return 0; +} + + |