This re-writes the 'virsh console' command so that it uses
the new streams API. This lets it run remotely and/or as a
non-root user. This requires that virsh be linked against
the simple event loop from libvirtd in daemon/event.c
As an added bonus, it can now connect to any console device,
not just the first one.
* tools/Makefile.am: Link to event.c
* tools/console.c, tools/console.h: Rewrite to use the
virDomainOpenConsole() APIs with streams
* tools/virsh.c: Support choosing the console name
via --devname $NAME
---
.x-sc_avoid_write | 1 +
tools/Makefile.am | 1 +
tools/console.c | 330 ++++++++++++++++++++++++++++++++++++++++-------------
tools/console.h | 2 +-
tools/virsh.c | 76 ++++---------
tools/virsh.pod | 7 +-
6 files changed, 280 insertions(+), 137 deletions(-)
diff --git a/.x-sc_avoid_write b/.x-sc_avoid_write
index 1f893b8..7bb8078 100644
--- a/.x-sc_avoid_write
+++ b/.x-sc_avoid_write
@@ -5,3 +5,4 @@
^src/xen/xend_internal\.c$
^daemon/libvirtd.c$
^gnulib/
+^tools/console.c$
diff --git a/tools/Makefile.am b/tools/Makefile.am
index bfe4455..f6f19bd 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -38,6 +38,7 @@ virt-pki-validate.1: virt-pki-validate
virsh_SOURCES = \
console.c console.h \
+ ../daemon/event.c ../daemon/event.h \
virsh.c
virsh_LDFLAGS = $(WARN_LDFLAGS) $(COVERAGE_LDFLAGS)
diff --git a/tools/console.c b/tools/console.c
index 60e62e2..d003ab4 100644
--- a/tools/console.c
+++ b/tools/console.c
@@ -34,15 +34,41 @@
# include <errno.h>
# include <unistd.h>
# include <signal.h>
+# include <stdbool.h>
-# include "console.h"
# include "internal.h"
+# include "console.h"
# include "logging.h"
# include "util.h"
+# include "memory.h"
+# include "virterror_internal.h"
+
+# include "daemon/event.h"
/* ie Ctrl-] as per telnet */
# define CTRL_CLOSE_BRACKET '\35'
+# define VIR_FROM_THIS VIR_FROM_NONE
+
+struct virConsoleBuffer {
+ size_t length;
+ size_t offset;
+ char *data;
+};
+
+typedef struct virConsole virConsole;
+typedef virConsole *virConsolePtr;
+struct virConsole {
+ virStreamPtr st;
+ bool quit;
+
+ int stdinWatch;
+ int stdoutWatch;
+
+ struct virConsoleBuffer streamToTerminal;
+ struct virConsoleBuffer terminalToStream;
+};
+
static int got_signal = 0;
static void do_signal(int sig ATTRIBUTE_UNUSED) {
got_signal = 1;
@@ -61,22 +87,191 @@ cfmakeraw (struct termios *attr)
}
# endif /* !HAVE_CFMAKERAW */
-int vshRunConsole(const char *tty) {
- int ttyfd, ret = -1;
+static void
+virConsoleEventOnStream(virStreamPtr st,
+ int events, void *opaque)
+{
+ virConsolePtr con = opaque;
+
+ if (events & VIR_STREAM_EVENT_READABLE) {
+ size_t avail = con->streamToTerminal.length -
+ con->streamToTerminal.offset;
+ int got;
+
+ if (avail < 1024) {
+ if (VIR_REALLOC_N(con->streamToTerminal.data,
+ con->streamToTerminal.length + 1024) < 0) {
+ virReportOOMError();
+ con->quit = true;
+ return;
+ }
+ con->streamToTerminal.length += 1024;
+ avail += 1024;
+ }
+
+ got = virStreamRecv(st,
+ con->streamToTerminal.data +
+ con->streamToTerminal.offset,
+ avail);
+ if (got == -2)
+ return; /* blocking */
+ if (got <= 0) {
+ con->quit = true;
+ return;
+ }
+ con->streamToTerminal.offset += got;
+ if (con->streamToTerminal.offset)
+ virEventUpdateHandleImpl(con->stdoutWatch,
+ VIR_EVENT_HANDLE_WRITABLE);
+ }
+
+ if (events & VIR_STREAM_EVENT_WRITABLE &&
+ con->terminalToStream.offset) {
+ ssize_t done;
+ size_t avail;
+ done = virStreamSend(con->st,
+ con->terminalToStream.data,
+ con->terminalToStream.offset);
+ if (done == -2)
+ return; /* blocking */
+ if (done < 0) {
+ con->quit = true;
+ return;
+ }
+ memmove(con->terminalToStream.data,
+ con->terminalToStream.data + done,
+ con->terminalToStream.offset - done);
+ con->terminalToStream.offset -= done;
+
+ avail = con->terminalToStream.length - con->terminalToStream.offset;
+ if (avail > 1024) {
+ if (VIR_REALLOC_N(con->terminalToStream.data,
+ con->terminalToStream.offset + 1024) < 0)
+ {}
+ con->terminalToStream.length = con->terminalToStream.offset + 1024;
+ }
+ }
+ if (!con->terminalToStream.offset)
+ virStreamEventUpdateCallback(con->st,
+ VIR_STREAM_EVENT_READABLE);
+
+ if (events & VIR_STREAM_EVENT_ERROR ||
+ events & VIR_STREAM_EVENT_HANGUP) {
+ con->quit = true;
+ }
+}
+
+static void
+virConsoleEventOnStdin(int watch ATTRIBUTE_UNUSED,
+ int fd ATTRIBUTE_UNUSED,
+ int events,
+ void *opaque)
+{
+ virConsolePtr con = opaque;
+
+ if (events & VIR_EVENT_HANDLE_READABLE) {
+ size_t avail = con->terminalToStream.length -
+ con->terminalToStream.offset;
+ int got;
+
+ if (avail < 1024) {
+ if (VIR_REALLOC_N(con->terminalToStream.data,
+ con->terminalToStream.length + 1024) < 0) {
+ virReportOOMError();
+ con->quit = true;
+ return;
+ }
+ con->terminalToStream.length += 1024;
+ avail += 1024;
+ }
+
+ got = read(fd,
+ con->terminalToStream.data +
+ con->terminalToStream.offset,
+ avail);
+ if (got < 0) {
+ if (errno != EAGAIN) {
+ con->quit = true;
+ }
+ return;
+ }
+ if (got == 0) {
+ con->quit = true;
+ return;
+ }
+ if (con->terminalToStream.data[con->terminalToStream.offset] ==
CTRL_CLOSE_BRACKET) {
+ con->quit = true;
+ return;
+ }
+
+ con->terminalToStream.offset += got;
+ if (con->terminalToStream.offset)
+ virStreamEventUpdateCallback(con->st,
+ VIR_STREAM_EVENT_READABLE |
+ VIR_STREAM_EVENT_WRITABLE);
+ }
+
+ if (events & VIR_EVENT_HANDLE_ERROR ||
+ events & VIR_EVENT_HANDLE_HANGUP) {
+ con->quit = true;
+ }
+}
+
+static void
+virConsoleEventOnStdout(int watch ATTRIBUTE_UNUSED,
+ int fd,
+ int events,
+ void *opaque)
+{
+ virConsolePtr con = opaque;
+
+ if (events & VIR_EVENT_HANDLE_WRITABLE &&
+ con->streamToTerminal.offset) {
+ ssize_t done;
+ size_t avail;
+ done = write(fd,
+ con->streamToTerminal.data,
+ con->streamToTerminal.offset);
+ if (done < 0) {
+ if (errno != EAGAIN) {
+ con->quit = true;
+ }
+ return;
+ }
+ memmove(con->streamToTerminal.data,
+ con->streamToTerminal.data + done,
+ con->streamToTerminal.offset - done);
+ con->streamToTerminal.offset -= done;
+
+ avail = con->streamToTerminal.length - con->streamToTerminal.offset;
+ if (avail > 1024) {
+ if (VIR_REALLOC_N(con->streamToTerminal.data,
+ con->streamToTerminal.offset + 1024) < 0)
+ {}
+ con->streamToTerminal.length = con->streamToTerminal.offset + 1024;
+ }
+ }
+
+ if (!con->streamToTerminal.offset)
+ virEventUpdateHandleImpl(con->stdoutWatch, 0);
+
+ if (events & VIR_EVENT_HANDLE_ERROR ||
+ events & VIR_EVENT_HANDLE_HANGUP) {
+ con->quit = true;
+ }
+}
+
+
+int vshRunConsole(virDomainPtr dom, const char *devname)
+{
+ int ret = -1;
struct termios ttyattr, rawattr;
void (*old_sigquit)(int);
void (*old_sigterm)(int);
void (*old_sigint)(int);
void (*old_sighup)(int);
void (*old_sigpipe)(int);
-
-
- /* We do not want this to become the controlling TTY */
- if ((ttyfd = open(tty, O_NOCTTY | O_RDWR)) < 0) {
- VIR_ERROR(_("unable to open tty %s: %s"),
- tty, strerror(errno));
- return -1;
- }
+ virConsolePtr con = NULL;
/* Put STDIN into raw mode so that stuff typed
does not echo to the screen (the TTY reads will
@@ -86,7 +281,7 @@ int vshRunConsole(const char *tty) {
if (tcgetattr(STDIN_FILENO, &ttyattr) < 0) {
VIR_ERROR(_("unable to get tty attributes: %s"),
strerror(errno));
- goto closetty;
+ return -1;
}
rawattr = ttyattr;
@@ -95,7 +290,7 @@ int vshRunConsole(const char *tty) {
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &rawattr) < 0) {
VIR_ERROR(_("unable to set tty attributes: %s"),
strerror(errno));
- goto closetty;
+ goto resettty;
}
@@ -110,76 +305,55 @@ int vshRunConsole(const char *tty) {
old_sigpipe = signal(SIGPIPE, do_signal);
got_signal = 0;
+ if (VIR_ALLOC(con) < 0) {
+ virReportOOMError();
+ goto cleanup;
+ }
- /* Now lets process STDIN & tty forever.... */
- for (; !got_signal ;) {
- unsigned int i;
- struct pollfd fds[] = {
- { STDIN_FILENO, POLLIN, 0 },
- { ttyfd, POLLIN, 0 },
- };
-
- /* Wait for data to be available for reading on
- STDIN or the tty */
- if (poll(fds, (sizeof(fds)/sizeof(struct pollfd)), -1) < 0) {
- if (got_signal)
- goto cleanup;
-
- if (errno == EINTR || errno == EAGAIN)
- continue;
+ con->st = virStreamNew(virDomainGetConnect(dom),
+ VIR_STREAM_NONBLOCK);
+ if (!con->st)
+ goto cleanup;
+
+ if (virDomainOpenConsole(dom, devname, con->st, 0) < 0)
+ goto cleanup;
+
+ con->stdinWatch = virEventAddHandleImpl(STDIN_FILENO,
+ VIR_EVENT_HANDLE_READABLE,
+ virConsoleEventOnStdin,
+ con,
+ NULL);
+ con->stdoutWatch = virEventAddHandleImpl(STDOUT_FILENO,
+ 0,
+ virConsoleEventOnStdout,
+ con,
+ NULL);
+
+ virStreamEventAddCallback(con->st,
+ VIR_STREAM_EVENT_READABLE,
+ virConsoleEventOnStream,
+ con,
+ NULL);
+
+ while (!con->quit) {
+ if (virEventRunOnce() < 0)
+ break;
+ }
- VIR_ERROR(_("failure waiting for I/O: %s"), strerror(errno));
- goto cleanup;
- }
+ virStreamEventRemoveCallback(con->st);
+ virEventRemoveHandleImpl(con->stdinWatch);
+ virEventRemoveHandleImpl(con->stdoutWatch);
- for (i = 0 ; i < (sizeof(fds)/sizeof(struct pollfd)) ; i++) {
- if (!fds[i].revents)
- continue;
-
- /* Process incoming data available for read */
- if (fds[i].revents & POLLIN) {
- char buf[4096];
- int got, sent = 0, destfd;
-
- if ((got = read(fds[i].fd, buf, sizeof(buf))) < 0) {
- VIR_ERROR(_("failure reading input: %s"),
- strerror(errno));
- goto cleanup;
- }
-
- /* Quit if end of file, or we got the Ctrl-] key */
- if (!got ||
- (got == 1 &&
- buf[0] == CTRL_CLOSE_BRACKET))
- goto done;
-
- /* Data from stdin goes to the TTY,
- data from the TTY goes to STDOUT */
- if (fds[i].fd == STDIN_FILENO)
- destfd = ttyfd;
- else
- destfd = STDOUT_FILENO;
-
- while (sent < got) {
- int done;
- if ((done = safewrite(destfd, buf + sent, got - sent))
- <= 0) {
- VIR_ERROR(_("failure writing output: %s"),
- strerror(errno));
- goto cleanup;
- }
- sent += done;
- }
- } else { /* Any other flag from poll is an error condition */
- goto cleanup;
- }
- }
- }
- done:
ret = 0;
cleanup:
+ if (con) {
+ if (con->st)
+ virStreamFree(con->st);
+ VIR_FREE(con);
+ }
+
/* Restore original signal handlers */
signal(SIGQUIT, old_sigpipe);
signal(SIGQUIT, old_sighup);
@@ -187,13 +361,11 @@ int vshRunConsole(const char *tty) {
signal(SIGQUIT, old_sigterm);
signal(SIGQUIT, old_sigquit);
+resettty:
/* Put STDIN back into the (sane?) state we found
it in before starting */
tcsetattr(STDIN_FILENO, TCSAFLUSH, &ttyattr);
- closetty:
- close(ttyfd);
-
return ret;
}
diff --git a/tools/console.h b/tools/console.h
index d0df78d..580268d 100644
--- a/tools/console.h
+++ b/tools/console.h
@@ -25,7 +25,7 @@
# ifndef WIN32
-int vshRunConsole(const char *tty);
+int vshRunConsole(virDomainPtr dom, const char *devname);
# endif /* !WIN32 */
diff --git a/tools/virsh.c b/tools/virsh.c
index b485eff..948c256 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -50,6 +50,7 @@
#include "util.h"
#include "memory.h"
#include "xml.h"
+#include "../daemon/event.h"
static char *progname;
@@ -676,36 +677,16 @@ static const vshCmdInfo info_console[] = {
static const vshCmdOptDef opts_console[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or
uuid")},
+ {"devname", VSH_OT_STRING, 0, N_("character device name")},
{NULL, 0, 0, NULL}
};
static int
-cmdRunConsole(vshControl *ctl, virDomainPtr dom)
+cmdRunConsole(vshControl *ctl, virDomainPtr dom, const char *devname)
{
- xmlDocPtr xml = NULL;
- xmlXPathObjectPtr obj = NULL;
- xmlXPathContextPtr ctxt = NULL;
int ret = FALSE;
- char *doc;
- char *thatHost = NULL;
- char *thisHost = NULL;
virDomainInfo dominfo;
- if (!(thisHost = virGetHostname(ctl->conn))) {
- vshError(ctl, "%s", _("Failed to get local hostname"));
- goto cleanup;
- }
-
- if (!(thatHost = virConnectGetHostname(ctl->conn))) {
- vshError(ctl, "%s", _("Failed to get connection hostname"));
- goto cleanup;
- }
-
- if (STRNEQ(thisHost, thatHost)) {
- vshError(ctl, "%s", _("Cannot connect to a remote console
device"));
- goto cleanup;
- }
-
if (virDomainGetInfo(dom, &dominfo) < 0) {
vshError(ctl, "%s", _("Unable to get domain status"));
goto cleanup;
@@ -716,38 +697,12 @@ cmdRunConsole(vshControl *ctl, virDomainPtr dom)
goto cleanup;
}
- doc = virDomainGetXMLDesc(dom, 0);
- if (!doc)
- goto cleanup;
-
- xml = xmlReadDoc((const xmlChar *) doc, "domain.xml", NULL,
- XML_PARSE_NOENT | XML_PARSE_NONET |
- XML_PARSE_NOWARNING);
- VIR_FREE(doc);
- if (!xml)
- goto cleanup;
- ctxt = xmlXPathNewContext(xml);
- if (!ctxt)
- goto cleanup;
-
- obj = xmlXPathEval(BAD_CAST "string(/domain/devices/console/@tty)", ctxt);
- if ((obj != NULL) && ((obj->type == XPATH_STRING) &&
- (obj->stringval != NULL) && (obj->stringval[0] !=
0))) {
- vshPrintExtra(ctl, _("Connected to domain %s\n"),
virDomainGetName(dom));
- vshPrintExtra(ctl, "%s", _("Escape character is ^]\n"));
- if (vshRunConsole((const char *)obj->stringval) == 0)
- ret = TRUE;
- } else {
- vshPrintExtra(ctl, "%s", _("No console available for
domain\n"));
- }
- xmlXPathFreeObject(obj);
+ vshPrintExtra(ctl, _("Connected to domain %s\n"), virDomainGetName(dom));
+ vshPrintExtra(ctl, "%s", _("Escape character is ^]\n"));
+ if (vshRunConsole(dom, devname) == 0)
+ ret = TRUE;
cleanup:
- xmlXPathFreeContext(ctxt);
- if (xml)
- xmlFreeDoc(xml);
- VIR_FREE(thisHost);
- VIR_FREE(thatHost);
return ret;
}
@@ -757,6 +712,7 @@ cmdConsole(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom;
int ret;
+ const char *devname;
if (!vshConnectionUsability(ctl, ctl->conn))
return FALSE;
@@ -764,7 +720,9 @@ cmdConsole(vshControl *ctl, const vshCmd *cmd)
if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
return FALSE;
- ret = cmdRunConsole(ctl, dom);
+ devname = vshCommandOptString(cmd, "devname", NULL);
+
+ ret = cmdRunConsole(ctl, dom, devname);
virDomainFree(dom);
return ret;
@@ -1241,7 +1199,7 @@ cmdCreate(vshControl *ctl, const vshCmd *cmd)
virDomainGetName(dom), from);
#ifndef WIN32
if (console)
- cmdRunConsole(ctl, dom);
+ cmdRunConsole(ctl, dom, NULL);
#endif
virDomainFree(dom);
} else {
@@ -1406,7 +1364,7 @@ cmdStart(vshControl *ctl, const vshCmd *cmd)
virDomainGetName(dom));
#ifndef WIN32
if (console)
- cmdRunConsole(ctl, dom);
+ cmdRunConsole(ctl, dom, NULL);
#endif
} else {
vshError(ctl, _("Failed to start domain %s"), virDomainGetName(dom));
@@ -11079,6 +11037,14 @@ vshInit(vshControl *ctl)
/* set up the signals handlers to catch disconnections */
vshSetupSignals();
+ virEventRegisterImpl(virEventAddHandleImpl,
+ virEventUpdateHandleImpl,
+ virEventRemoveHandleImpl,
+ virEventAddTimeoutImpl,
+ virEventUpdateTimeoutImpl,
+ virEventRemoveTimeoutImpl);
+ virEventInit();
+
ctl->conn = virConnectOpenAuth(ctl->name,
virConnectAuthPtrDefault,
ctl->readonly ? VIR_CONNECT_RO : 0);
diff --git a/tools/virsh.pod b/tools/virsh.pod
index 5932aaa..2e7a653 100644
--- a/tools/virsh.pod
+++ b/tools/virsh.pod
@@ -311,9 +311,12 @@ Configure a domain to be automatically started at boot.
The option I<--disable> disables autostarting.
-=item B<console> I<domain-id>
+=item B<console> I<domain-id> [I<devname>]
-Connect the virtual serial console for the guest.
+Connect the virtual serial console for the guest. The optional
+I<devname> parameter refers to the device alias of an alternate
+console, serial or parallel device configured for the guest.
+If omitted, the primary console will be opened.
=item B<create> I<FILE> optional I<--console> I<--paused>
--
1.7.2.3