// #include #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #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; }