/* * console.c: A dumb serial console client * * Copyright (C) 2007-2008, 2010-2012 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, see * . * * Daniel Berrange */ #include #ifndef WIN32 # include # include # include # include # include # include # include # include # include # include # include # include "internal.h" # include "console.h" # include "virlog.h" # include "virfile.h" # include "viralloc.h" # include "virthread.h" # include "virerror.h" /* * Convert given character to control character. * Basically, we assume ASCII, and take lower 6 bits. */ # define CONTROL(c) ((c) ^ 0x40) # 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; virMutex lock; virCond cond; int stdinWatch; int stdoutWatch; struct virConsoleBuffer streamToTerminal; struct virConsoleBuffer terminalToStream; char escapeChar; }; 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) { if (con->st) { virStreamEventRemoveCallback(con->st); virStreamAbort(con->st); virStreamFree(con->st); con->st = NULL; } VIR_FREE(con->streamToTerminal.data); VIR_FREE(con->terminalToStream.data); if (con->stdinWatch != -1) virEventRemoveHandle(con->stdinWatch); if (con->stdoutWatch != -1) virEventRemoveHandle(con->stdoutWatch); con->stdinWatch = -1; con->stdoutWatch = -1; con->quit = true; virCondSignal(&con->cond); } 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] == con->escapeChar) { 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); } } static char vshGetEscapeChar(const char *s) { if (*s == '^') return CONTROL(c_toupper(s[1])); return *s; } int vshMakeStdinRaw(struct termios *ttyattr, bool report_errors) { struct termios rawattr; char ebuf[1024]; if (tcgetattr(STDIN_FILENO, ttyattr) < 0) { if (report_errors) VIR_ERROR(_("unable to get tty attributes: %s"), virStrerror(errno, ebuf, sizeof(ebuf))); return -1; } rawattr = *ttyattr; cfmakeraw(&rawattr); if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &rawattr) < 0) { if (report_errors) VIR_ERROR(_("unable to set tty attributes: %s"), virStrerror(errno, ebuf, sizeof(ebuf))); return -1; } return 0; } int vshRunConsole(virDomainPtr dom, const char *dev_name, const char *escape_seq, unsigned int flags) { int ret = -1; struct termios ttyattr; 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 (vshMakeStdinRaw(&ttyattr, true) < 0) 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->escapeChar = vshGetEscapeChar(escape_seq); con->st = virStreamNew(virDomainGetConnect(dom), VIR_STREAM_NONBLOCK); if (!con->st) goto cleanup; if (virDomainOpenConsole(dom, dev_name, con->st, flags) < 0) goto cleanup; if (virCondInit(&con->cond) < 0 || virMutexInit(&con->lock) < 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 (virCondWait(&con->cond, &con->lock) < 0) { VIR_ERROR(_("unable to wait on console condition")); goto cleanup; } } ret = 0; cleanup: if (con) { if (con->st) virStreamFree(con->st); virMutexDestroy(&con->lock); virCondDestroy(&con->cond); 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 */