[PATCH 09/11] Re-write virsh console to use streams

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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 b1cfd2a..232504f 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

--
libvir-list mailing list
libvir-list@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/libvir-list


[Index of Archives]     [Virt Tools]     [Libvirt Users]     [Lib OS Info]     [Fedora Users]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [KDE Users]     [Fedora Tools]