2007-01-26 14:54:29 +03:00
/*
* console . c : A dumb serial console client
*
2010-05-04 00:44:12 +04:00
* Copyright ( C ) 2007 , 2008 , 2010 Red Hat , Inc .
2007-01-26 14:54:29 +03:00
*
* 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 < berrange @ redhat . com >
*/
2008-01-29 21:15:54 +03:00
# include <config.h>
2007-11-26 15:14:32 +03:00
2010-05-04 00:44:12 +04:00
# ifndef WIN32
2007-12-07 17:56:37 +03:00
2010-03-09 21:22:22 +03:00
# include <stdio.h>
# include <sys / types.h>
# include <sys / stat.h>
# include <fcntl.h>
# include <termios.h>
# include <poll.h>
# include <string.h>
# include <errno.h>
# include <unistd.h>
# include <signal.h>
2010-07-27 13:40:30 +04:00
# include <stdbool.h>
2007-01-26 14:54:29 +03:00
2010-03-09 21:22:22 +03:00
# include "internal.h"
2010-07-27 13:40:30 +04:00
# include "console.h"
2010-03-09 21:22:22 +03:00
# include "logging.h"
# include "util.h"
2010-11-09 23:48:48 +03:00
# include "files.h"
2010-07-27 13:40:30 +04:00
# include "memory.h"
# include "virterror_internal.h"
# include "daemon / event.h"
2007-01-26 14:54:29 +03:00
/* ie Ctrl-] as per telnet */
2010-03-09 21:22:22 +03:00
# define CTRL_CLOSE_BRACKET '\35'
2007-01-26 14:54:29 +03:00
2010-07-27 13:40:30 +04:00
# 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 ;
} ;
2007-01-26 14:54:29 +03:00
static int got_signal = 0 ;
static void do_signal ( int sig ATTRIBUTE_UNUSED ) {
got_signal = 1 ;
}
2010-03-09 21:22:22 +03:00
# ifndef HAVE_CFMAKERAW
2007-11-26 15:14:32 +03:00
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 ;
}
2010-03-09 21:22:22 +03:00
# endif /* !HAVE_CFMAKERAW */
2007-11-26 15:14:32 +03:00
2010-11-11 18:15:46 +03:00
static void
virConsoleShutdown ( virConsolePtr con )
{
con - > quit = true ;
virStreamEventRemoveCallback ( con - > st ) ;
if ( con - > stdinWatch ! = - 1 )
virEventRemoveHandleImpl ( con - > stdinWatch ) ;
if ( con - > stdinWatch ! = - 1 )
virEventRemoveHandleImpl ( con - > stdoutWatch ) ;
con - > stdinWatch = - 1 ;
con - > stdoutWatch = - 1 ;
}
2010-07-27 13:40:30 +04:00
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 ( ) ;
2010-11-11 18:15:46 +03:00
virConsoleShutdown ( con ) ;
2010-07-27 13:40:30 +04:00
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 ) {
2010-11-11 18:15:46 +03:00
virConsoleShutdown ( con ) ;
2010-07-27 13:40:30 +04:00
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 ) {
2010-11-11 18:15:46 +03:00
virConsoleShutdown ( con ) ;
2010-07-27 13:40:30 +04:00
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 ) {
2010-11-11 18:15:46 +03:00
virConsoleShutdown ( con ) ;
2010-07-27 13:40:30 +04:00
}
}
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 ( ) ;
2010-11-11 18:15:46 +03:00
virConsoleShutdown ( con ) ;
2010-07-27 13:40:30 +04:00
return ;
}
con - > terminalToStream . length + = 1024 ;
avail + = 1024 ;
}
got = read ( fd ,
con - > terminalToStream . data +
con - > terminalToStream . offset ,
avail ) ;
if ( got < 0 ) {
if ( errno ! = EAGAIN ) {
2010-11-11 18:15:46 +03:00
virConsoleShutdown ( con ) ;
2010-07-27 13:40:30 +04:00
}
return ;
}
if ( got = = 0 ) {
2010-11-11 18:15:46 +03:00
virConsoleShutdown ( con ) ;
2010-07-27 13:40:30 +04:00
return ;
}
if ( con - > terminalToStream . data [ con - > terminalToStream . offset ] = = CTRL_CLOSE_BRACKET ) {
2010-11-11 18:15:46 +03:00
virConsoleShutdown ( con ) ;
2010-07-27 13:40:30 +04:00
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 ) {
2010-11-11 18:15:46 +03:00
virConsoleShutdown ( con ) ;
2010-07-27 13:40:30 +04:00
}
}
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 ) {
2010-11-11 18:15:46 +03:00
virConsoleShutdown ( con ) ;
2010-07-27 13:40:30 +04:00
}
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 ) {
2010-11-11 18:15:46 +03:00
virConsoleShutdown ( con ) ;
2010-07-27 13:40:30 +04:00
}
}
int vshRunConsole ( virDomainPtr dom , const char * devname )
{
int ret = - 1 ;
2007-01-26 14:54:29 +03:00
struct termios ttyattr , rawattr ;
void ( * old_sigquit ) ( int ) ;
void ( * old_sigterm ) ( int ) ;
void ( * old_sigint ) ( int ) ;
void ( * old_sighup ) ( int ) ;
void ( * old_sigpipe ) ( int ) ;
2010-07-27 13:40:30 +04:00
virConsolePtr con = NULL ;
2007-01-26 14:54:29 +03:00
2009-08-07 12:56:38 +04:00
/* Put STDIN into raw mode so that stuff typed
2007-01-26 14:54:29 +03:00
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 ) {
2010-01-19 16:17:20 +03:00
VIR_ERROR ( _ ( " unable to get tty attributes: %s " ) ,
2009-05-20 17:37:30 +04:00
strerror ( errno ) ) ;
2010-07-27 13:40:30 +04:00
return - 1 ;
2007-01-26 14:54:29 +03:00
}
rawattr = ttyattr ;
cfmakeraw ( & rawattr ) ;
if ( tcsetattr ( STDIN_FILENO , TCSAFLUSH , & rawattr ) < 0 ) {
2010-01-19 16:17:20 +03:00
VIR_ERROR ( _ ( " unable to set tty attributes: %s " ) ,
2009-05-20 17:37:30 +04:00
strerror ( errno ) ) ;
2010-07-27 13:40:30 +04:00
goto resettty ;
2007-01-26 14:54:29 +03:00
}
/* 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 ;
2010-07-27 13:40:30 +04:00
if ( VIR_ALLOC ( con ) < 0 ) {
virReportOOMError ( ) ;
goto cleanup ;
}
2007-01-26 14:54:29 +03:00
2010-07-27 13:40:30 +04:00
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 ;
}
2007-01-26 14:54:29 +03:00
ret = 0 ;
cleanup :
2010-07-27 13:40:30 +04:00
if ( con ) {
if ( con - > st )
virStreamFree ( con - > st ) ;
VIR_FREE ( con ) ;
}
2007-01-26 14:54:29 +03:00
/* Restore original signal handlers */
signal ( SIGQUIT , old_sigpipe ) ;
signal ( SIGQUIT , old_sighup ) ;
signal ( SIGQUIT , old_sigint ) ;
signal ( SIGQUIT , old_sigterm ) ;
signal ( SIGQUIT , old_sigquit ) ;
2010-07-27 13:40:30 +04:00
resettty :
2007-01-26 14:54:29 +03:00
/* Put STDIN back into the (sane?) state we found
it in before starting */
tcsetattr ( STDIN_FILENO , TCSAFLUSH , & ttyattr ) ;
return ret ;
}
2010-05-04 00:44:12 +04:00
# endif /* !WIN32 */