/* * console.c: A dumb serial console client * * Copyright (C) 2007, 2008, 2010 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 #ifndef WIN32 # include # include # include # include # include # include # include # include # include # include # include "internal.h" # include "console.h" # include "logging.h" # include "util.h" # include "files.h" # include "memory.h" # include "virterror_internal.h" # include "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; } # ifndef HAVE_CFMAKERAW static void cfmakeraw (struct termios *attr) { attr->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); attr->c_oflag &= ~OPOST; attr->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); attr->c_cflag &= ~(CSIZE | PARENB); attr->c_cflag |= CS8; } # endif /* !HAVE_CFMAKERAW */ static void virConsoleShutdown(virConsolePtr con) { con->quit = true; virStreamEventRemoveCallback(con->st); if (con->st) virStreamFree(con->st); if (con->stdinWatch != -1) virEventRemoveHandle(con->stdinWatch); if (con->stdinWatch != -1) virEventRemoveHandle(con->stdoutWatch); con->stdinWatch = -1; con->stdoutWatch = -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(); virConsoleShutdown(con); 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) { virConsoleShutdown(con); return; } con->streamToTerminal.offset += got; if (con->streamToTerminal.offset) virEventUpdateHandle(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) { virConsoleShutdown(con); 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) { virConsoleShutdown(con); } } 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(); virConsoleShutdown(con); return; } con->terminalToStream.length += 1024; avail += 1024; } got = read(fd, con->terminalToStream.data + con->terminalToStream.offset, avail); if (got < 0) { if (errno != EAGAIN) { virConsoleShutdown(con); } return; } if (got == 0) { virConsoleShutdown(con); return; } if (con->terminalToStream.data[con->terminalToStream.offset] == CTRL_CLOSE_BRACKET) { virConsoleShutdown(con); 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) { virConsoleShutdown(con); } } 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) { virConsoleShutdown(con); } 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) virEventUpdateHandle(con->stdoutWatch, 0); if (events & VIR_EVENT_HANDLE_ERROR || events & VIR_EVENT_HANDLE_HANGUP) { virConsoleShutdown(con); } } 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); virConsolePtr con = NULL; /* 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) { VIR_ERROR(_("unable to get tty attributes: %s"), strerror(errno)); return -1; } rawattr = ttyattr; cfmakeraw(&rawattr); if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &rawattr) < 0) { VIR_ERROR(_("unable to set tty attributes: %s"), strerror(errno)); goto resettty; } /* 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; if (VIR_ALLOC(con) < 0) { virReportOOMError(); goto cleanup; } 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 = virEventAddHandle(STDIN_FILENO, VIR_EVENT_HANDLE_READABLE, virConsoleEventOnStdin, con, NULL); con->stdoutWatch = virEventAddHandle(STDOUT_FILENO, 0, virConsoleEventOnStdout, con, NULL); virStreamEventAddCallback(con->st, VIR_STREAM_EVENT_READABLE, virConsoleEventOnStream, con, NULL); while (!con->quit) { if (virEventRunDefaultImpl() < 0) break; } ret = 0; cleanup: if (con) { if (con->st) virStreamFree(con->st); VIR_FREE(con); } /* Restore original signal handlers */ signal(SIGPIPE, old_sigpipe); signal(SIGHUP, old_sighup); signal(SIGINT, old_sigint); signal(SIGTERM, 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); return ret; } #endif /* !WIN32 */