Modern DNS clients (like vinagre) support mDNS/DNS-SD discovery of VNC servers. VNC servers like Vino link directly against libavahi to publish themselves. Fitting libavahi into QEMU though would be pretty hairy and not as flexible as simply calling out to a helper script and using the avahi utilities.
This patch adds support for a ',script=/patch/to/script' option to the existing -vnc option. It also includes an example script which uses avahi-publish the presence of the QEMU instance. Care has been taken to ensure that errors are silenced if either bash or avahi aren't present and the user hasn't explicitly specified ',script='. diff --git a/qemu-doc.texi b/qemu-doc.texi index f9924d2..cd59aab 100644 --- a/qemu-doc.texi +++ b/qemu-doc.texi @@ -486,6 +486,21 @@ path following this option specifies where the x509 certificates are to be loaded from. See the @ref{vnc_security} section for details on generating certificates. [EMAIL PROTECTED] [EMAIL PROTECTED]/path/to/script} + +Launch a script when the VNC server is started. The script is expected to +continue running as long as the VNC server is running. When the VNC server +closes, the script will receive either a SIGTERM or standard input will be +shutdown. The script will be passed arguments in the order of @var{NAME} [EMAIL PROTECTED] @var{PORT} where @var{NAME} is the value passed to the [EMAIL PROTECTED] option, @var{ADDRESS} is the address the server is bound to, +and @var{PORT} is the port the server is listening on. + +This script will not be executed if @var{reverse} is being used or if the +server is listening on a Unix domain socket. If the @option{-name} option is +not specified, the script will not be executed. The default location of the +script is @file{/etc/qemu-vncup}. + @end table @item -k @var{language} diff --git a/qemu-vncup b/qemu-vncup new file mode 100755 index 0000000..78b9d2d --- /dev/null +++ b/qemu-vncup @@ -0,0 +1,27 @@ +#!/bin/bash + +function dokill() { + kill -SIGTERM $1 >& /dev/null +} + +if [ -z "$1" -o -z "$3" ]; then + echo "Usage: $0 NAME ADDRESS PORT" + exit 1 +fi + +which avahi-publish >& /dev/null +if [ $? != 0 ] ; then + exit 0 +fi + +avahi-publish -s "$1 Virtual Console" '_rfb._tcp' $3 > /dev/null 2>&1 < /dev/null & + +pid=`jobs -p %1` + +trap "dokill -SIGTERM $pid; exit" SIGINT SIGTERM + +while read LINE ; do + echo $LINE +done + +dokill -SIGTERM $pid diff --git a/vnc.c b/vnc.c index b19c6d0..58a6ed4 100644 --- a/vnc.c +++ b/vnc.c @@ -31,6 +31,8 @@ #define VNC_REFRESH_INTERVAL (1000 / 30) +#define VNC_DEFAULT_SCRIPT "/etc/qemu-vncup" + #include "vnc_keysym.h" #include "keymaps.c" #include "d3des.h" @@ -40,6 +42,11 @@ #include <gnutls/x509.h> #endif /* CONFIG_VNC_TLS */ +#ifndef _WIN32 +#include <signal.h> +#include <sys/wait.h> +#endif + // #define _VNC_DEBUG 1 #if _VNC_DEBUG @@ -174,6 +181,11 @@ struct VncState size_t read_handler_expect; /* input */ uint8_t modifiers_state[256]; + +#ifndef _WIN32 + pid_t script_pid; + int script_fd; +#endif }; static VncState *vnc_state; /* needed for info vnc */ @@ -2098,6 +2110,13 @@ void vnc_display_close(DisplayState *ds) vs->wiremode = VNC_WIREMODE_CLEAR; #endif /* CONFIG_VNC_TLS */ } +#ifndef _WIN32 + if (vs->script_pid) { + close(vs->script_fd); + while (waitpid(vs->script_pid, NULL, 0) == -1 && errno == EINTR); + vs->script_pid = -1; + } +#endif vs->auth = VNC_AUTH_INVALID; #if CONFIG_VNC_TLS vs->subauth = VNC_AUTH_INVALID; @@ -2121,6 +2140,54 @@ int vnc_display_password(DisplayState *ds, const char *password) return 0; } +#ifndef _WIN32 +static int launch_vnc_script(VncState *vs, const char *script, + const char *name, const char *addr, + int port) +{ + int fds[2]; + int quiet = 0; + + vs->script_pid = 0; + + if (pipe(fds) == -1) { + fprintf(stderr, "qemu: pipe failed\n"); + return -1; + } + + if (script == NULL) { + quiet = 1; + script = strdup(VNC_DEFAULT_SCRIPT); + } + + vs->script_pid = fork(); + vs->script_fd = fds[1]; + if (vs->script_pid == 0) { + int fd; + char port_str[32]; + + dup2(fds[0], STDIN_FILENO); + for (fd = 3; fd < sysconf(_SC_OPEN_MAX); fd++) + close(fd); + + snprintf(port_str, sizeof(port_str), "%d", port); + execlp(script, script, name, addr, port_str, NULL); + if (!quiet) + fprintf(stderr, "qemu: failed to exec vnc script: %m\n"); + exit(1); + } else if (vs->script_pid < 0) { + fprintf(stderr, "qemu: fork failed\n"); + return -1; + } else + close(fds[0]); + + if (quiet) + qemu_free((char *)script); + + return 0; +} +#endif + int vnc_display_open(DisplayState *ds, const char *display) { struct sockaddr *addr; @@ -2138,6 +2205,8 @@ int vnc_display_open(DisplayState *ds, const char *display) #if CONFIG_VNC_TLS int tls = 0, x509 = 0; #endif + char *script = NULL; + char *bind_address = NULL; vnc_display_close(ds); if (strcmp(display, "none") == 0) @@ -2153,6 +2222,17 @@ int vnc_display_open(DisplayState *ds, const char *display) password = 1; /* Require password auth */ } else if (strncmp(options, "reverse", 7) == 0) { reverse = 1; + } else if (strncmp(options, "script=", 7) == 0) { + const char *ptr, *end; + + ptr = options + 7; + end = strchr(ptr, ','); + if (end == NULL) + end = ptr + strlen(ptr); + + script = qemu_malloc((end - ptr) + 1); + memcpy(script, ptr, end - ptr); + script[end - ptr] = 0; #if CONFIG_VNC_TLS } else if (strncmp(options, "tls", 3) == 0) { tls = 1; /* Require TLS */ @@ -2252,6 +2332,8 @@ int vnc_display_open(DisplayState *ds, const char *display) } else #endif { + char *ptr; + addr = (struct sockaddr *)&iaddr; addrlen = sizeof(iaddr); @@ -2264,6 +2346,18 @@ int vnc_display_open(DisplayState *ds, const char *display) iaddr.sin_port = htons(ntohs(iaddr.sin_port) + (reverse ? 0 : 5900)); + bind_address = strdup(display); + if (bind_address == NULL) { + fprintf(stderr, "qemu: strdup failed\n"); + return -1; + } + + ptr = strchr(bind_address, ':'); + if (ptr == NULL) + ptr = strchr(bind_address, ','); + if (ptr) + *ptr = 0; + vs->lsock = socket(PF_INET, SOCK_STREAM, 0); if (vs->lsock == -1) { fprintf(stderr, "Could not create socket\n"); @@ -2319,5 +2413,24 @@ int vnc_display_open(DisplayState *ds, const char *display) return -1; } +#ifndef _WIN32 + if (script && addr->sa_family != AF_INET) { + fprintf(stderr, "qemu: cannot call vnc script with unix socket\n"); + } else if (script && reverse) { + fprintf(stderr, + "qemu: cannot call vnc script with reverse connect\n"); + } else if (script && !qemu_name) { + fprintf(stderr, "qemu: cannot call vnc script without -name\n"); + } else { + ret = launch_vnc_script(vs, script, qemu_name, bind_address, + ntohs(iaddr.sin_port)); + if (ret == -1) + return -1; + } +#endif + + qemu_free(script); + qemu_free(bind_address); + return qemu_set_fd_handler2(vs->lsock, vnc_listen_poll, vnc_listen_read, NULL, vs); }