diff --git a/ChangeLog b/ChangeLog index 2949a8c625..284d33326f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +Fri Jan 26 06:53:52 EST 2007 Daniel Berrange + + * src/virshc: Added 'console' and 'vncdisplay' commands to + the virsh tool + * src/console.c, src/console.h: Impl of a simple interactive + serial console + Fri Jan 26 12:48:13 CET 2007 Daniel Veillard * src/virsh.c: Richard W.M. Jones pointed out a missing option diff --git a/src/Makefile.am b/src/Makefile.am index 07405d998d..26b31cee43 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -32,7 +32,7 @@ libvirt_la_SOURCES = \ bin_PROGRAMS = virsh -virsh_SOURCES = virsh.c +virsh_SOURCES = virsh.c console.c console.h virsh_LDFLAGS = virsh_DEPENDENCIES = $(DEPS) virsh_LDADD = $(LDADDS) $(VIRSH_LIBS) diff --git a/src/console.c b/src/console.c new file mode 100644 index 0000000000..6968ef9757 --- /dev/null +++ b/src/console.c @@ -0,0 +1,188 @@ +/* + * console.c: A dumb serial console client + * + * Copyright (C) 2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Daniel Berrange + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "console.h" +#include "internal.h" + +/* ie Ctrl-] as per telnet */ +#define CTRL_CLOSE_BRACKET '\35' + +static int got_signal = 0; +static void do_signal(int sig ATTRIBUTE_UNUSED) { + got_signal = 1; +} + +int virRunConsole(const char *tty) { + int ttyfd, 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) { + fprintf(stderr, _("unable to open tty %s: %s\n"), + tty, strerror(errno)); + return -1; + } + + /* Put STDIN into raw mode so that stuff typed + does not echo to the screen (the TTY reads will + result in it being echoed back already), and + also ensure Ctrl-C, etc is blocked, and misc + other bits */ + if (tcgetattr(STDIN_FILENO, &ttyattr) < 0) { + fprintf(stderr, _("unable to get tty attributes: %s\n"), + strerror(errno)); + goto closetty; + } + + rawattr = ttyattr; + cfmakeraw(&rawattr); + + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &rawattr) < 0) { + fprintf(stderr, _("unable to set tty attributes: %s\n"), + strerror(errno)); + goto closetty; + } + + + /* Trap all common signals so that we can safely restore + the original terminal settings on STDIN before the + process exits - people don't like being left with a + messed up terminal ! */ + old_sigquit = signal(SIGQUIT, do_signal); + old_sigterm = signal(SIGTERM, do_signal); + old_sigint = signal(SIGINT, do_signal); + old_sighup = signal(SIGHUP, do_signal); + old_sigpipe = signal(SIGPIPE, do_signal); + got_signal = 0; + + + /* 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; + + fprintf(stderr, _("failure waiting for I/O: %s\n"), + strerror(errno)); + goto cleanup; + } + + 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) { + fprintf(stderr, _("failure reading input: %s\n"), + 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 = write(destfd, buf + sent, got - sent)) <= 0) { + fprintf(stderr, _("failure writing output: %s\n"), + strerror(errno)); + goto cleanup; + } + sent += done; + } + } else { /* Any other flag from poll is an error condition */ + goto cleanup; + } + } + } + done: + ret = 0; + + cleanup: + + /* Restore original signal handlers */ + signal(SIGQUIT, old_sigpipe); + signal(SIGQUIT, old_sighup); + signal(SIGQUIT, old_sigint); + signal(SIGQUIT, old_sigterm); + signal(SIGQUIT, old_sigquit); + + /* Put STDIN back into the (sane?) state we found + it in before starting */ + tcsetattr(STDIN_FILENO, TCSAFLUSH, &ttyattr); + + closetty: + close(ttyfd); + + return ret; +} + +/* + * Local variables: + * indent-tabs-mode: nil + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/src/console.h b/src/console.h new file mode 100644 index 0000000000..da2616e3e8 --- /dev/null +++ b/src/console.h @@ -0,0 +1,45 @@ +/* + * console.c: A dumb serial console client + * + * Copyright (C) 2007 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Daniel Berrange + */ + +#ifndef __VIR_CONSOLE_H__ +#define __VIR_CONSOLE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + + int virRunConsole(const char *tty); + +#ifdef __cplusplus +} +#endif + +#endif /* __VIR_CONSOLE_H__ */ + +/* + * Local variables: + * indent-tabs-mode: nil + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/src/virsh.c b/src/virsh.c index 62b864dbba..66225291c2 100644 --- a/src/virsh.c +++ b/src/virsh.c @@ -1,5 +1,5 @@ /* - * virsh.c: a Xen shell used to exercise the libvir API + * virsh.c: a Xen shell used to exercise the libvirt API * * Copyright (C) 2005 Red Hat, Inc. * @@ -29,11 +29,16 @@ #include #include +#include +#include +#include + #include #include #include "config.h" #include "internal.h" +#include "console.h" static char *progname; @@ -305,6 +310,71 @@ cmdConnect(vshControl * ctl, vshCmd * cmd) return ctl->conn ? TRUE : FALSE; } +/* + * "console" command + */ +static vshCmdInfo info_console[] = { + {"syntax", "console "}, + {"help", gettext_noop("connect to the guest console")}, + {"desc", + gettext_noop("Connect the virtual serial console for the guest")}, + {NULL, NULL} +}; + +static vshCmdOptDef opts_console[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("domain name, id or uuid")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdConsole(vshControl * ctl, vshCmd * cmd) +{ + xmlDocPtr xml = NULL; + xmlXPathObjectPtr obj = NULL; + xmlXPathContextPtr ctxt = NULL; + virDomainPtr dom; + int ret = FALSE; + char *doc; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + if (!(dom = vshCommandOptDomain(ctl, cmd, "domain", NULL))) + return FALSE; + + 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); + 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))) { + if (virRunConsole((const char *)obj->stringval) == 0) + ret = TRUE; + } else { + vshPrintExtra(ctl, _("No console available for domain\n")); + } + xmlXPathFreeObject(obj); + + cleanup: + if (ctxt) + xmlXPathFreeContext(ctxt); + if (xml) + xmlFreeDoc(xml); + virDomainFree(dom); + return ret; +} + /* * "list" command */ @@ -1632,6 +1702,88 @@ cmdVersion(vshControl * ctl, vshCmd * cmd ATTRIBUTE_UNUSED) return TRUE; } +/* + * "dumpxml" command + */ +static vshCmdInfo info_vncdisplay[] = { + {"syntax", "vncdisplay "}, + {"help", gettext_noop("vnc display")}, + {"desc", gettext_noop("Ouput the IP address and port number for the VNC display.")}, + {NULL, NULL} +}; + +static vshCmdOptDef opts_vncdisplay[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("domain name, id or uuid")}, + {NULL, 0, 0, NULL} +}; + +static int +cmdVNCDisplay(vshControl * ctl, vshCmd * cmd) +{ + xmlDocPtr xml = NULL; + xmlXPathObjectPtr obj = NULL; + xmlXPathContextPtr ctxt = NULL; + virDomainPtr dom; + int ret = FALSE; + int port = 0; + char *doc; + + if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) + return FALSE; + + if (!(dom = vshCommandOptDomain(ctl, cmd, "domain", NULL))) + return FALSE; + + 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); + free(doc); + if (!xml) + goto cleanup; + ctxt = xmlXPathNewContext(xml); + if (!ctxt) + goto cleanup; + + obj = xmlXPathEval(BAD_CAST "string(/domain/devices/graphics[@type='vnc']/@port)", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + goto cleanup; + } + port = strtol((const char *)obj->stringval, NULL, 10); + if (port == -1) { + goto cleanup; + } + xmlXPathFreeObject(obj); + + obj = xmlXPathEval(BAD_CAST "string(/domain/devices/graphics[@type='vnc']/@listen)", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + goto cleanup; + } + if (!strcmp((const char*)obj->stringval, "0.0.0.0")) { + vshPrint(ctl, ":%d\n", port-5900); + } else { + vshPrint(ctl, "%s:%d\n", (const char *)obj->stringval, port-5900); + } + xmlXPathFreeObject(obj); + obj = NULL; + + cleanup: + if (obj) + xmlXPathFreeObject(obj); + if (ctxt) + xmlXPathFreeContext(ctxt); + if (xml) + xmlFreeDoc(xml); + virDomainFree(dom); + return ret; +} + + /* * "quit" command */ @@ -1653,6 +1805,7 @@ cmdQuit(vshControl * ctl, vshCmd * cmd ATTRIBUTE_UNUSED) */ static vshCmdDef commands[] = { {"connect", cmdConnect, opts_connect, info_connect}, + {"console", cmdConsole, opts_console, info_console}, {"create", cmdCreate, opts_create, info_create}, {"start", cmdStart, opts_start, info_start}, {"destroy", cmdDestroy, opts_destroy, info_destroy}, @@ -1681,6 +1834,7 @@ static vshCmdDef commands[] = { {"vcpuinfo", cmdVcpuinfo, opts_vcpuinfo, info_vcpuinfo}, {"vcpupin", cmdVcpupin, opts_vcpupin, info_vcpupin}, {"version", cmdVersion, NULL, info_version}, + {"vncdisplay", cmdVNCDisplay, opts_vncdisplay, info_vncdisplay}, {NULL, NULL, NULL, NULL} };