aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGerd Hoffmann <kraxel@redhat.com>2015-06-08 16:02:51 +0200
committerGerd Hoffmann <kraxel@redhat.com>2015-06-08 16:02:51 +0200
commitf62d03779bc8869ef80ab4e7deb7f0c813608cae (patch)
tree3ac12b201e2a79982c98121dc46e5164f3322f17
parent32d07c00381c6c9ea74ac7cfa325bd52a5328af6 (diff)
downloadvconsole-f62d03779bc8869ef80ab4e7deb7f0c813608cae.tar.gz
[wip] mdns announce for guest vnc servers
-rw-r--r--GNUmakefile14
-rw-r--r--list.h169
-rw-r--r--mdns-publish.c409
-rw-r--r--mdns-publish.h28
-rw-r--r--vpublish.c268
-rw-r--r--vpublish.h0
6 files changed, 883 insertions, 5 deletions
diff --git a/GNUmakefile b/GNUmakefile
index 2996ee9..e89806c 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -4,12 +4,13 @@ include mk/Variables.mk
# add our flags + libs
CFLAGS += -DVERSION='"$(VERSION)"' -DLIB='"$(LIB)"'
+CFLAGS += -Wno-pointer-sign
# valgrind options
VFLAGS := --leak-check=full --show-possibly-lost=no
# build
-TARGETS := vconsole
+TARGETS := vconsole vpublish
# default target
all: build
@@ -33,18 +34,20 @@ endef
ifeq ($(HAVE_GTK3)-$(HAVE_VTE3),yes-yes)
CFLAGS += -Wno-deprecated-declarations
wanted := $(HAVE_GLIB)-$(HAVE_GTHREAD)-$(HAVE_GTK3)-$(HAVE_VTE3)-$(HAVE_LIBVIRT)
-pkglst := glib-2.0 gthread-2.0 gtk+-3.0 vte-2.90 libvirt
+vconsole : pkglst := glib-2.0 gthread-2.0 gtk+-3.0 vte-2.90 libvirt
+vpublish : pkglst := glib-2.0 gthread-2.0 libvirt libxml-2.0 avahi-client avahi-glib
else
CFLAGS += -DGTK_DISABLE_SINGLE_INCLUDES
CFLAGS += -DGTK_DISABLE_DEPRECATED
CFLAGS += -DGSEAL_ENABLE
wanted := $(HAVE_GLIB)-$(HAVE_GTHREAD)-$(HAVE_GTK2)-$(HAVE_VTE2)-$(HAVE_LIBVIRT)
-pkglst := glib-2.0 gthread-2.0 gtk+-2.0 vte libvirt
+vconsole : pkglst := glib-2.0 gthread-2.0 gtk+-2.0 vte libvirt
+vpublish : pkglst := glib-2.0 gthread-2.0 libvirt libxml-2.0 avahi-client avahi-glib
endif
CFLAGS += -Wno-strict-prototypes
-CFLAGS += $(shell test "$(pkglst)" != "" && pkg-config --cflags $(pkglst))
-LDLIBS += $(shell test "$(pkglst)" != "" && pkg-config --libs $(pkglst))
+CFLAGS += $(shell pkg-config --cflags $(pkglst))
+LDLIBS += $(shell pkg-config --libs $(pkglst))
# desktop files
DESKTOP := $(wildcard $(patsubst %,%.desktop,$(TARGETS)))
@@ -81,6 +84,7 @@ realclean distclean: clean
#############################################
vconsole: vconsole.o connect.o domain.o libvirt-glib-event.o
+vpublish: vpublish.o mdns-publish.o libvirt-glib-event.o
include mk/Compile.mk
include mk/Maintainer.mk
diff --git a/list.h b/list.h
new file mode 100644
index 0000000..6072f68
--- /dev/null
+++ b/list.h
@@ -0,0 +1,169 @@
+#ifndef _LIST_H
+#define _LIST_H 1
+
+/*
+ * Simple doubly linked list implementation.
+ * -- shameless stolen from the linux kernel sources
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+ struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+ struct list_head name = LIST_HEAD_INIT(name)
+
+#define INIT_LIST_HEAD(ptr) do { \
+ (ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+/*
+ * Insert a item entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static __inline__ void __list_add(struct list_head * item,
+ struct list_head * prev,
+ struct list_head * next)
+{
+ next->prev = item;
+ item->next = next;
+ item->prev = prev;
+ prev->next = item;
+}
+
+/**
+ * list_add - add a item entry
+ * @item: item entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a item entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static __inline__ void list_add(struct list_head *item, struct list_head *head)
+{
+ __list_add(item, head, head->next);
+}
+
+/**
+ * list_add_tail - add a item entry
+ * @item: item entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a item entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static __inline__ void list_add_tail(struct list_head *item, struct list_head *head)
+{
+ __list_add(item, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static __inline__ void __list_del(struct list_head * prev,
+ struct list_head * next)
+{
+ next->prev = prev;
+ prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty on entry does not return true after this, the entry is in an undefined state.
+ */
+static __inline__ void list_del(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.
+ */
+static __inline__ void list_del_init(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+ INIT_LIST_HEAD(entry);
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static __inline__ int list_empty(struct list_head *head)
+{
+ return head->next == head;
+}
+
+/**
+ * list_splice - join two lists
+ * @list: the item list to add.
+ * @head: the place to add it in the first list.
+ */
+static __inline__ void list_splice(struct list_head *list, struct list_head *head)
+{
+ struct list_head *first = list->next;
+
+ if (first != list) {
+ struct list_head *last = list->prev;
+ struct list_head *at = head->next;
+
+ first->prev = head;
+ head->next = first;
+
+ last->next = at;
+ at->prev = last;
+ }
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr: the &struct list_head pointer.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+ ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
+
+/**
+ * list_for_each - iterate over a list
+ * @pos: the &struct list_head to use as a loop counter.
+ * @head: the head for your list.
+ */
+#define list_for_each(pos, head) \
+ for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_safe - iterate over a list safe against removal of list entry
+ * @pos: the &struct list_head to use as a loop counter.
+ * @n: another &struct list_head to use as temporary storage
+ * @head: the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+ for (pos = (head)->next, n = pos->next; pos != (head); \
+ pos = n, n = pos->next)
+
+/**
+ * list_for_each_prev - iterate over a list in reverse order
+ * @pos: the &struct list_head to use as a loop counter.
+ * @head: the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+ for (pos = (head)->prev; pos != (head); pos = pos->prev)
+
+#endif /* _LIST_H */
diff --git a/mdns-publish.c b/mdns-publish.c
new file mode 100644
index 0000000..868a7b9
--- /dev/null
+++ b/mdns-publish.c
@@ -0,0 +1,409 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/signal.h>
+
+#include <avahi-client/client.h>
+#include <avahi-client/publish.h>
+
+#include <avahi-common/alternative.h>
+#include <avahi-common/thread-watch.h>
+#include <avahi-common/malloc.h>
+#include <avahi-common/error.h>
+
+#include "list.h"
+#include "mdns-publish.h"
+
+/* --------------------------------------------------------------------- */
+
+struct mdns_pub {
+ int have_tty;
+ int have_syslog;
+ int debug;
+
+ AvahiThreadedPoll *thread_poll;
+ AvahiClient *client;
+
+ struct list_head entries;
+};
+
+struct mdns_pub_entry {
+ struct list_head next;
+
+ char *name;
+ const char *service;
+ int port;
+ char *txt[4];
+
+ struct mdns_pub *mdns;
+ AvahiEntryGroup *group;
+};
+
+char *mdns_pub_appname;
+int mdns_pub_termsig;
+int mdns_pub_appquit;
+
+/* ------------------------------------------------------------------ */
+
+static char *group_state_name[] = {
+ [ AVAHI_ENTRY_GROUP_UNCOMMITED ] = "uncommited",
+ [ AVAHI_ENTRY_GROUP_REGISTERING ] = "registering",
+ [ AVAHI_ENTRY_GROUP_ESTABLISHED ] = "established",
+ [ AVAHI_ENTRY_GROUP_COLLISION ] = "collision",
+ [ AVAHI_ENTRY_GROUP_FAILURE ] = "failure",
+};
+
+static char *client_state_name[] = {
+ [ AVAHI_CLIENT_S_REGISTERING ] = "server registering",
+ [ AVAHI_CLIENT_S_RUNNING ] = "server running",
+ [ AVAHI_CLIENT_S_COLLISION ] = "server collision",
+ [ AVAHI_CLIENT_FAILURE ] = "failure",
+ [ AVAHI_CLIENT_CONNECTING ] = "connecting",
+};
+
+static void update_services(AvahiClient *c, struct mdns_pub *mdns);
+
+static void entry_group_callback(AvahiEntryGroup *g,
+ AvahiEntryGroupState state,
+ void *userdata)
+{
+ struct mdns_pub_entry *entry = userdata;
+ char *n;
+
+ mdns_log_printf(entry->mdns, LOG_DEBUG, "%s: %s: state %d [%s]\n", __FUNCTION__,
+ entry->name, state, group_state_name[state]);
+
+ switch (state) {
+ case AVAHI_ENTRY_GROUP_COLLISION:
+ n = avahi_alternative_service_name(entry->name);
+ mdns_log_printf(entry->mdns, LOG_NOTICE,
+ "service name collision, renaming '%s' to '%s'\n",
+ entry->name, n);
+ avahi_free(entry->name);
+ entry->name = n;
+ avahi_entry_group_reset(entry->group);
+ update_services(avahi_entry_group_get_client(g), entry->mdns);
+ break;
+ case AVAHI_ENTRY_GROUP_FAILURE:
+ mdns_pub_appquit++;
+ break;
+ default:
+ break;
+ }
+}
+
+static void update_services(AvahiClient *c, struct mdns_pub *mdns)
+{
+ struct list_head *item;
+ struct mdns_pub_entry *entry;
+ AvahiEntryGroupState state;
+ int ret;
+
+ if (AVAHI_CLIENT_S_RUNNING != avahi_client_get_state(c))
+ return;
+
+ list_for_each(item, &mdns->entries) {
+ entry = list_entry(item, struct mdns_pub_entry, next);
+
+ /* If this is the first time we're called, let's create a new entry group */
+ if (!entry->group) {
+ entry->group = avahi_entry_group_new(c, entry_group_callback, entry);
+ if (!entry->group) {
+ mdns_log_printf(mdns, LOG_ERR, "avahi_entry_group_new() failed: %s\n",
+ avahi_strerror(avahi_client_errno(c)));
+ goto fail;
+ }
+ }
+
+ /* something to do ? */
+ state = avahi_entry_group_get_state(entry->group);
+ mdns_log_printf(mdns, LOG_DEBUG, "%s: %s: %d [%s]\n", __FUNCTION__,
+ entry->name, state, group_state_name[state]);
+ if (AVAHI_ENTRY_GROUP_UNCOMMITED != state)
+ continue;
+
+ /* Add the service */
+ ret = avahi_entry_group_add_service(entry->group,
+ AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0,
+ entry->name, entry->service,
+ NULL, NULL,
+ entry->port,
+ entry->txt[0], entry->txt[1],
+ entry->txt[2], entry->txt[3],
+ NULL);
+ if (ret < 0) {
+ mdns_log_printf(mdns, LOG_ERR, "failed to add '%s' service: %s\n",
+ entry->name, avahi_strerror(ret));
+ goto fail;
+ }
+
+ /* Tell the server to register the service */
+ ret = avahi_entry_group_commit(entry->group);
+ if (ret < 0) {
+ mdns_log_printf(mdns, LOG_ERR, "failed to commit entry_group: %s\n",
+ avahi_strerror(ret));
+ goto fail;
+ }
+ }
+ return;
+
+fail:
+ mdns_pub_appquit++;
+}
+
+static void reset_services(struct mdns_pub *mdns, int free_groups)
+{
+ struct list_head *item;
+ struct mdns_pub_entry *entry;
+
+ list_for_each(item, &mdns->entries) {
+ entry = list_entry(item, struct mdns_pub_entry, next);
+ avahi_entry_group_reset(entry->group);
+ if (!free_groups)
+ continue;
+ avahi_entry_group_free(entry->group);
+ entry->group = NULL;
+ }
+}
+
+static void client_callback(AvahiClient *c,
+ AvahiClientState state,
+ void * userdata)
+{
+ struct mdns_pub *mdns = userdata;
+ int error;
+
+ mdns_log_printf(mdns, LOG_DEBUG, "%s: state %d [%s]\n", __FUNCTION__,
+ state, client_state_name[state]);
+
+ switch (state) {
+ case AVAHI_CLIENT_CONNECTING:
+ mdns_log_printf(mdns, LOG_NOTICE,
+ "avahi daemon not running (yet), I'll keep trying ...\n");
+ break;
+ case AVAHI_CLIENT_S_RUNNING:
+ update_services(c, mdns);
+ break;
+ case AVAHI_CLIENT_S_COLLISION:
+ reset_services(mdns, 0);
+ break;
+ case AVAHI_CLIENT_FAILURE:
+ switch (avahi_client_errno(c)) {
+ case AVAHI_ERR_DISCONNECTED:
+ reset_services(mdns, 1);
+ avahi_client_free(c);
+
+ mdns_log_printf(mdns, LOG_NOTICE, "disconnected from avahi daemon, reconnecting ...\n");
+ mdns->client = avahi_client_new(avahi_threaded_poll_get(mdns->thread_poll),
+ AVAHI_CLIENT_NO_FAIL,
+ client_callback, mdns, &error);
+ if (!mdns->client) {
+ mdns_log_printf(mdns, LOG_ERR, "failed to create client: %s\n",
+ avahi_strerror(error));
+ goto fail;
+ }
+ break;
+ default:
+ mdns_log_printf(mdns, LOG_ERR, "client failure: %s\n",
+ avahi_strerror(avahi_client_errno(c)));
+ goto fail;
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return;
+
+ fail:
+ mdns_pub_appquit++;
+}
+
+/* ------------------------------------------------------------------ */
+
+struct mdns_pub *mdns_pub_init(int debug)
+{
+ struct mdns_pub *mdns;
+ int error;
+
+ mdns = avahi_malloc(sizeof(*mdns));
+ if (NULL == mdns) {
+ fprintf(stderr, "%s: out of memory\n", mdns_pub_appname);
+ goto fail;
+ }
+ memset(mdns, 0, sizeof(*mdns));
+ INIT_LIST_HEAD(&mdns->entries);
+ mdns->debug = debug;
+ mdns->have_tty = isatty(2);
+
+ openlog(mdns_pub_appname, 0, LOG_LOCAL0);
+ mdns->have_syslog = 1;
+
+ mdns->thread_poll = avahi_threaded_poll_new();
+ if (!mdns->thread_poll) {
+ mdns_log_printf(mdns, LOG_ERR, "failed to create simple poll object\n");
+ goto fail;
+ }
+
+ mdns->client = avahi_client_new(avahi_threaded_poll_get(mdns->thread_poll),
+ AVAHI_CLIENT_NO_FAIL,
+ client_callback, mdns, &error);
+ if (!mdns->client) {
+ mdns_log_printf(mdns, LOG_ERR, "failed to create client: %s\n", avahi_strerror(error));
+ goto fail;
+ }
+ return mdns;
+
+ fail:
+ mdns_pub_fini(mdns);
+ return NULL;
+}
+
+int mdns_pub_start(struct mdns_pub *mdns)
+{
+ return avahi_threaded_poll_stop(mdns->thread_poll);
+}
+
+int mdns_pub_stop(struct mdns_pub *mdns)
+{
+ return avahi_threaded_poll_stop(mdns->thread_poll);
+}
+
+void mdns_pub_fini(struct mdns_pub *mdns)
+{
+ if (!mdns)
+ return;
+ mdns_pub_del_all(mdns);
+ if (mdns->client)
+ avahi_client_free(mdns->client);
+ if (mdns->thread_poll)
+ avahi_threaded_poll_free(mdns->thread_poll);
+ avahi_free(mdns);
+}
+
+/* --------------------------------------------------------------------- */
+
+struct mdns_pub_entry *mdns_pub_add(struct mdns_pub *mdns,
+ const char *name,
+ const char *service,
+ int port,
+ ...)
+{
+ struct mdns_pub_entry *entry;
+ va_list args;
+ char *txt;
+ int i;
+
+ entry = avahi_malloc(sizeof(*entry));
+ if (NULL == entry)
+ return NULL;
+ memset(entry, 0, sizeof(*entry));
+
+ entry->name = avahi_strdup(name);
+ entry->service = service;
+ entry->port = port;
+ entry->mdns = mdns;
+
+ va_start(args, port);
+ for (i = 0; i < sizeof(entry->txt)/sizeof(entry->txt[0]); i++) {
+ txt = va_arg(args,char*);
+ if (NULL == txt)
+ break;
+ entry->txt[i] = txt;
+ }
+ va_end(args);
+
+ list_add_tail(&entry->next, &mdns->entries);
+ if (mdns->client)
+ update_services(mdns->client, mdns);
+
+ mdns_log_printf(entry->mdns, LOG_DEBUG, "%s: %s\n", __FUNCTION__, entry->name);
+ return entry;
+}
+
+void mdns_pub_del(struct mdns_pub_entry *entry)
+{
+ mdns_log_printf(entry->mdns, LOG_DEBUG, "%s: %s\n", __FUNCTION__, entry->name);
+ if (entry->group) {
+ avahi_entry_group_reset(entry->group);
+ avahi_entry_group_free(entry->group);
+ entry->group = NULL;
+ }
+ avahi_free(entry->name);
+ list_del(&entry->next);
+ avahi_free(entry);
+}
+
+void mdns_pub_del_all(struct mdns_pub *mdns)
+{
+ struct mdns_pub_entry *entry;
+
+ while (!list_empty(&mdns->entries)) {
+ entry = list_entry(mdns->entries.next, struct mdns_pub_entry, next);
+ mdns_pub_del(entry);
+ }
+}
+
+/* --------------------------------------------------------------------- */
+
+int mdns_log_printf(struct mdns_pub *mdns, int priority,
+ char *fmt, ...)
+{
+ va_list args;
+ char msgbuf[1024];
+ int rc;
+
+ va_start(args, fmt);
+ rc = vsnprintf(msgbuf, sizeof(msgbuf), fmt, args);
+ va_end(args);
+
+ if (!mdns || mdns->have_tty)
+ fprintf(stderr, "%s: %s", mdns_pub_appname, msgbuf);
+ if (mdns && mdns->have_syslog)
+ syslog(priority, "%s", msgbuf);
+ return rc;
+}
+
+int mdns_daemonize(void)
+{
+ switch (fork()) {
+ case -1:
+ mdns_log_printf(NULL, LOG_ERR, "fork: %s", strerror(errno));
+ return -1;
+ case 0:
+ /* child */
+ close(0); close(1); close(2);
+ setsid();
+ open("/dev/null", O_RDWR); dup(0); dup(0);
+ return 0;
+ default:
+ /* parent */
+ exit(0);
+ }
+}
+
+static void catchsig(int signal)
+{
+ mdns_pub_termsig = signal;
+ mdns_pub_appquit = 1;
+}
+
+void mdns_sigsetup(struct mdns_pub *mdns)
+{
+ struct sigaction act,old;
+
+ memset(&act,0,sizeof(act));
+ sigemptyset(&act.sa_mask);
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE,&act,&old);
+ act.sa_handler = catchsig;
+ sigaction(SIGHUP,&act,&old);
+ sigaction(SIGUSR1,&act,&old);
+ sigaction(SIGTERM,&act,&old);
+ if (mdns->debug)
+ sigaction(SIGINT,&act,&old);
+}
diff --git a/mdns-publish.h b/mdns-publish.h
new file mode 100644
index 0000000..82fbe41
--- /dev/null
+++ b/mdns-publish.h
@@ -0,0 +1,28 @@
+struct mdns_pub;
+struct mdns_pub_entry;
+
+extern char *mdns_pub_appname;
+extern int mdns_pub_termsig;
+extern int mdns_pub_appquit;
+
+/* initialization and cleanup */
+struct mdns_pub *mdns_pub_init(int debug);
+int mdns_pub_start(struct mdns_pub *mdns);
+int mdns_pub_stop(struct mdns_pub *mdns);
+void mdns_pub_fini(struct mdns_pub *mdns);
+
+/* add and remove services */
+struct mdns_pub_entry *mdns_pub_add(struct mdns_pub *mdns,
+ const char *name,
+ const char *service,
+ int port,
+ ...);
+void mdns_pub_del(struct mdns_pub_entry *entry);
+void mdns_pub_del_all(struct mdns_pub *mdns);
+
+/* misc helper functions */
+int __attribute__ ((format (printf, 3, 0)))
+mdns_log_printf(struct mdns_pub *mdns, int priority,
+ char *fmt, ...);
+int mdns_daemonize(void);
+void mdns_sigsetup(struct mdns_pub *mdns);
diff --git a/vpublish.c b/vpublish.c
new file mode 100644
index 0000000..5a4b547
--- /dev/null
+++ b/vpublish.c
@@ -0,0 +1,268 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <inttypes.h>
+
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <glib.h>
+
+#include <libvirt/libvirt.h>
+#include <libvirt/virterror.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+
+#include "mdns-publish.h"
+#include "libvirt-glib-event.h"
+
+#define APPNAME "vpublish"
+
+/* ------------------------------------------------------------------ */
+
+int debug = 1;
+
+/* ------------------------------------------------------------------ */
+
+typedef struct display display;
+struct display {
+ struct mdns_pub_entry *entry;
+ display *next;
+};
+
+static display *domains;
+static struct mdns_pub *mdns;
+
+static void display_add_vnc(virDomainPtr d, xmlChar *port)
+{
+ const char *name = virDomainGetName(d);
+ display *dpy = g_new0(display, 1);
+
+ dpy->entry = mdns_pub_add(mdns, name, "_rfb._tcp", atoi(port), NULL);
+
+ dpy->next = domains;
+ domains = dpy;
+}
+
+/* ------------------------------------------------------------------ */
+
+static void domain_check(virConnectPtr c, virDomainPtr d)
+{
+ static const unsigned char *xpath_spice =
+ "//domain//graphics[@type='spice']";
+ static const unsigned char *xpath_vnc =
+ "//domain//graphics[@type='vnc']";
+ const char *name = virDomainGetName(d);
+ xmlXPathContextPtr ctx;
+ xmlXPathObjectPtr obj;
+ xmlNodePtr cur;
+ xmlDocPtr xml;
+ char *domain;
+ int i;
+
+ if (debug)
+ fprintf(stderr, "%s: %s [enter]\n", __func__, name);
+
+ domain = virDomainGetXMLDesc(d, 0);
+ if (!domain) {
+ if (debug)
+ fprintf(stderr, "%s: %s virDomainGetXMLDesc failure\n", __func__, name);
+ return;
+ }
+
+ xml = xmlReadMemory(domain, strlen(domain), NULL, NULL, 0);
+ if (!xml) {
+ if (debug)
+ fprintf(stderr, "%s: %s xmlReadMemory failure\n", __func__, name);
+ goto err_domain;
+ }
+
+ ctx = xmlXPathNewContext(xml);
+
+ obj = xmlXPathEvalExpression(xpath_spice, ctx);
+ if (obj && obj->nodesetval && obj->nodesetval->nodeNr) {
+ fprintf(stderr, "%s: %s, %d spice nodes\n", __func__, name,
+ obj->nodesetval->nodeNr);
+ for (i = 0; i < obj->nodesetval->nodeNr; i++) {
+ cur = obj->nodesetval->nodeTab[i];
+ /* TODO */
+ }
+ }
+ xmlXPathFreeObject(obj);
+
+ obj = xmlXPathEvalExpression(xpath_vnc, ctx);
+ if (obj && obj->nodesetval && obj->nodesetval->nodeNr) {
+ fprintf(stderr, "%s: %s, %d vnc nodes\n", __func__, name,
+ obj->nodesetval->nodeNr);
+ for (i = 0; i < obj->nodesetval->nodeNr; i++) {
+ cur = obj->nodesetval->nodeTab[i];
+ fprintf(stderr, " %d: %s:%s\n", i + 1,
+ xmlGetProp(cur, "listen"),
+ xmlGetProp(cur, "port"));
+ display_add_vnc(d, xmlGetProp(cur, "port"));
+ }
+ }
+ xmlXPathFreeObject(obj);
+
+ xmlXPathFreeContext(ctx);
+ xmlFreeDoc(xml);
+
+ if (debug)
+ fprintf(stderr, "%s: %s [done]\n", __func__, name);
+ return;
+
+err_domain:
+ free(domain);
+ return;
+}
+
+static void domain_update(virConnectPtr c, virDomainPtr d, virDomainEventType event)
+{
+ const char *name = virDomainGetName(d);
+
+ /* handle events */
+ switch (event) {
+ case VIR_DOMAIN_EVENT_UNDEFINED:
+ if (debug)
+ fprintf(stderr, "%s: %s: undefined\n", __func__, name);
+ break;
+ case VIR_DOMAIN_EVENT_STARTED:
+ if (debug)
+ fprintf(stderr, "%s: %s: started\n", __func__, name);
+ domain_check(c, d);
+ break;
+ case VIR_DOMAIN_EVENT_STOPPED:
+ if (debug)
+ fprintf(stderr, "%s: %s: stopped\n", __func__, name);
+ break;
+ default:
+ if (debug)
+ fprintf(stderr, "%s: %s: Oops, unknown (default catch)\n",
+ __func__, name);
+ break;
+ }
+}
+
+/* ------------------------------------------------------------------ */
+
+static int connect_domain_event(virConnectPtr c, virDomainPtr d,
+ int event, int detail, void *opaque)
+{
+ if (debug)
+ fprintf(stderr, "%s: %s, event %d\n", __func__,
+ virDomainGetName(d), event);
+ domain_update(c, d, event);
+ return 0;
+}
+
+static void connect_list(virConnectPtr c)
+{
+ virDomainPtr d;
+ int i, n;
+ int *active;
+
+ n = virConnectNumOfDomains(c);
+ active = malloc(sizeof(int) * n);
+ n = virConnectListDomains(c, active, n);
+ for (i = 0; i < n; i++) {
+ d = virDomainLookupByID(c, active[i]);
+ domain_check(c, d);
+ virDomainFree(d);
+ }
+ free(active);
+}
+
+static void connect_init(const char *uri)
+{
+ virConnectPtr c;
+
+ c = virConnectOpen(uri);
+ if (c == NULL) {
+ fprintf(stderr, "Failed to open connection to %s\n", uri);
+ exit(1);
+ }
+ if (debug)
+ fprintf(stderr, "%s: connected to %s\n", __func__, uri);
+
+ virConnectDomainEventRegister(c, connect_domain_event,
+ NULL, NULL);
+ connect_list(c);
+}
+
+/* ------------------------------------------------------------------ */
+
+static void usage(FILE *fp)
+{
+ fprintf(fp,
+ "This is a virtual machine display publisher.\n"
+ "It'll announce vnc screens via mdns (aka zeroconf/bonjour).\n"
+ "\n"
+ "usage: %s [ options ]\n"
+ "options:\n"
+ " -h Print this text.\n"
+ " -d Enable debugging.\n"
+ " -c <uri> Connect to libvirt.\n"
+ "\n"
+ "-- \n"
+ "(c) 2015 Gerd Hoffmann <kraxel@redhat.com>\n",
+ APPNAME);
+}
+
+int
+main(int argc, char *argv[])
+{
+ GMainLoop *mainloop;
+ char *uri = NULL;
+ int c;
+
+ for (;;) {
+ if (-1 == (c = getopt(argc, argv, "hdc:")))
+ break;
+ switch (c) {
+ case 'd':
+ debug++;
+ break;
+ case 'c':
+ uri = optarg;
+ break;
+ case 'h':
+ usage(stdout);
+ exit(0);
+ default:
+ usage(stderr);
+ exit(1);
+ }
+ }
+
+ if (uri == NULL)
+ uri = getenv("LIBVIRT_DEFAULT_URI");
+ if (uri == NULL)
+ uri = getenv("VIRSH_DEFAULT_CONNECT_URI");
+
+ if (uri == NULL) {
+ fprintf(stderr, "No libvirt uri\n");
+ exit(1);
+ }
+
+ /* init */
+ mainloop = g_main_loop_new(NULL, false);
+ g_thread_init(NULL);
+ gvir_event_register();
+ mdns = mdns_pub_init(debug);
+ mdns_pub_start(mdns);
+
+ connect_init(uri);
+
+ /* main loop */
+ g_main_loop_run(mainloop);
+
+ /* cleanup */
+ exit(0);
+}
diff --git a/vpublish.h b/vpublish.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/vpublish.h