On 08/17/2010 11:02 AM, Daniel P. Berrange wrote:
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
---
tools/Makefile.am | 1 +
tools/console.c | 330 ++++++++++++++++++++++++++++++++++++++++-------------
tools/console.h | 2 +-
tools/virsh.c | 76 ++++---------
4 files changed, 274 insertions(+), 135 deletions(-)
+
+struct virConsoleBuffer {
+ size_t length;
+ size_t offset;
s/size_t/off_t/ for offset? (but see below)
+ char *data;
+};
+
+typedef struct virConsole virConsole;
+typedef virConsole *virConsolePtr;
+struct virConsole {
+ virStreamPtr st;
+ int quit;
s/int/bool/, and adjust clients to use false/true instead of 0/1
+
+ 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 +86,192 @@ 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;
Oh, never mind. size_t for offset is probably just fine after all.
+ int got;
+
+ if (avail < 1024) {
+ if (VIR_REALLOC_N(con->streamToTerminal.data,
+ con->streamToTerminal.length + 1024) < 0) {
/me Note to self - another VIR_EXPAND_N or VIR_RESIZE_N candidate.
+ virReportOOMError();
+ con->quit = 1;
+ 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 = 1;
+ 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) {
+ virErrorPtr err = virGetLastError();
+ con->quit = 1;
+ 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)
+ {}
/me Note to self - a VIR_SHRINK_N candidate.
+ 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 = 1;
+ }
+}
+
+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 = 1;
+ 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 = 1;
+ }
+ return;
+ }
+ if (got == 0) {
+ con->quit = 1;
+ return;
+ }
+ if (con->terminalToStream.data[con->terminalToStream.offset] ==
CTRL_CLOSE_BRACKET) {
+ con->quit = 1;
+ 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 = 1;
+ }
+}
+
+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);
Do we want to use safewrite here?
Conditional ACK, if those are deemed easy nits to fix.
--
Eric Blake eblake(a)redhat.com +1-801-349-2682
Libvirt virtualization library
http://libvirt.org