913e54c544
it does not generate nor capable to answer to keepalive requests, which causes libvirt connection to disconnect every 30 seconds (interval*timeout in libvirtd.conf). Furthermore, it does not clean up filehandlers and leaves hanging sockets. Also, if other thread opens its own connection to libvirt (i.e. checkpoint.c), event function in virt-serial.c just updates event listener file handler with a wrong one, what causes checkpoint.c malfunctions, fence_virtd hangs and so on. This patch uses default event listener implementation from libvirt and resolves theese problems.
441 lines
8.8 KiB
C
441 lines
8.8 KiB
C
// #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;
|
|
};
|
|
|
|
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);
|
|
write(args->wake_fd, "x", 1);
|
|
} 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;
|
|
}
|
|
|
|
|