2007-01-26 14:54:29 +03:00
/*
2013-08-26 13:53:43 +04:00
* virsh - console . c : A dumb serial console client
2007-01-26 14:54:29 +03:00
*
2014-10-28 21:38:04 +03:00
* Copyright ( C ) 2007 - 2008 , 2010 - 2014 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
2012-09-21 02:30:55 +04:00
* License along with this library . If not , see
2012-07-21 14:06:23 +04:00
* < http : //www.gnu.org/licenses/>.
2007-01-26 14:54:29 +03:00
*/
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 <sys / types.h>
# include <sys / stat.h>
# include <fcntl.h>
# include <termios.h>
# include <poll.h>
# include <unistd.h>
# include <signal.h>
2007-01-26 14:54:29 +03:00
2010-03-09 21:22:22 +03:00
# include "internal.h"
2013-08-29 20:15:07 +04:00
# include "virsh.h"
2013-08-26 13:53:43 +04:00
# include "virsh-console.h"
2021-09-26 14:27:26 +03:00
# include "virsh-util.h"
2012-12-12 21:59:27 +04:00
# include "virlog.h"
2012-12-12 22:06:53 +04:00
# include "viralloc.h"
2012-12-13 19:49:48 +04:00
# include "virthread.h"
2012-12-13 22:21:53 +04:00
# include "virerror.h"
2021-01-06 14:56:11 +03:00
# include "virobject.h"
2010-07-27 13:40:30 +04:00
2014-02-28 16:16:17 +04:00
VIR_LOG_INIT ( " tools.virsh-console " ) ;
2011-11-22 20:08:05 +04:00
/*
* Convert given character to control character .
* Basically , we assume ASCII , and take lower 6 bits .
*/
# define CONTROL(c) ((c) ^ 0x40)
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 ;
} ;
2013-08-26 13:53:43 +04:00
2010-07-27 13:40:30 +04:00
typedef struct virConsole virConsole ;
struct virConsole {
2019-03-18 11:53:06 +03:00
virObjectLockable parent ;
2010-07-27 13:40:30 +04:00
virStreamPtr st ;
bool quit ;
2011-10-11 17:05:52 +04:00
virCond cond ;
2010-07-27 13:40:30 +04:00
int stdinWatch ;
int stdoutWatch ;
struct virConsoleBuffer streamToTerminal ;
struct virConsoleBuffer terminalToStream ;
2011-11-22 20:08:05 +04:00
char escapeChar ;
2019-02-14 17:41:21 +03:00
virError error ;
2010-07-27 13:40:30 +04:00
} ;
2021-03-11 10:16:13 +03:00
static virClass * virConsoleClass ;
2019-03-18 11:53:06 +03:00
static void virConsoleDispose ( void * obj ) ;
static int
virConsoleOnceInit ( void )
{
if ( ! VIR_CLASS_NEW ( virConsole , virClassForObjectLockable ( ) ) )
return - 1 ;
return 0 ;
}
VIR_ONCE_GLOBAL_INIT ( virConsole ) ;
2013-08-26 13:53:43 +04:00
static void
2019-10-14 15:44:29 +03:00
virConsoleHandleSignal ( int sig G_GNUC_UNUSED )
2013-08-26 13:53:43 +04:00
{
2007-01-26 14:54:29 +03:00
}
2013-08-26 13:53:43 +04:00
2010-11-11 18:15:46 +03:00
static void
2021-03-11 10:16:13 +03:00
virConsoleShutdown ( virConsole * con ,
2019-08-23 14:37:25 +03:00
bool graceful )
2010-11-11 18:15:46 +03:00
{
2019-02-14 17:41:21 +03:00
virErrorPtr err = virGetLastError ( ) ;
if ( con - > error . code = = VIR_ERR_OK & & err )
virCopyLastError ( & con - > error ) ;
2011-03-01 17:59:45 +03:00
if ( con - > st ) {
2019-08-23 14:37:25 +03:00
int rc ;
2011-03-01 17:59:45 +03:00
virStreamEventRemoveCallback ( con - > st ) ;
2019-08-23 14:37:25 +03:00
if ( graceful )
rc = virStreamFinish ( con - > st ) ;
else
rc = virStreamAbort ( con - > st ) ;
if ( rc < 0 ) {
2019-08-21 16:33:18 +03:00
virReportError ( VIR_ERR_INTERNAL_ERROR , " %s " ,
_ ( " cannot terminate console stream " ) ) ;
2019-08-23 14:37:25 +03:00
}
2021-09-26 14:27:26 +03:00
g_clear_pointer ( & con - > st , virshStreamFree ) ;
2011-03-01 17:59:45 +03:00
}
2011-12-20 13:40:13 +04:00
VIR_FREE ( con - > streamToTerminal . data ) ;
VIR_FREE ( con - > terminalToStream . data ) ;
2010-11-11 18:15:46 +03:00
if ( con - > stdinWatch ! = - 1 )
2011-03-02 18:08:31 +03:00
virEventRemoveHandle ( con - > stdinWatch ) ;
2011-08-10 19:03:23 +04:00
if ( con - > stdoutWatch ! = - 1 )
2011-03-02 18:08:31 +03:00
virEventRemoveHandle ( con - > stdoutWatch ) ;
2010-11-11 18:15:46 +03:00
con - > stdinWatch = - 1 ;
con - > stdoutWatch = - 1 ;
2019-03-18 11:57:56 +03:00
if ( ! con - > quit ) {
con - > quit = true ;
virCondSignal ( & con - > cond ) ;
}
2010-11-11 18:15:46 +03:00
}
2013-08-26 13:53:43 +04:00
static void
2019-03-18 11:53:06 +03:00
virConsoleDispose ( void * obj )
2013-08-26 13:53:43 +04:00
{
2021-03-11 10:16:13 +03:00
virConsole * con = obj ;
2013-08-26 13:53:43 +04:00
2021-09-26 14:27:26 +03:00
virshStreamFree ( con - > st ) ;
2019-03-18 11:53:06 +03:00
2013-08-26 13:53:43 +04:00
virCondDestroy ( & con - > cond ) ;
2019-02-14 17:41:21 +03:00
virResetError ( & con - > error ) ;
2013-08-26 13:53:43 +04:00
}
2010-07-27 13:40:30 +04:00
static void
virConsoleEventOnStream ( virStreamPtr st ,
int events , void * opaque )
{
2021-03-11 10:16:13 +03:00
virConsole * con = opaque ;
2010-07-27 13:40:30 +04:00
2019-02-25 17:05:01 +03:00
virObjectLock ( con ) ;
2019-02-25 17:10:01 +03:00
/* we got late event after console was shutdown */
if ( ! con - > st )
goto cleanup ;
2010-07-27 13:40:30 +04:00
if ( events & VIR_STREAM_EVENT_READABLE ) {
size_t avail = con - > streamToTerminal . length -
con - > streamToTerminal . offset ;
int got ;
if ( avail < 1024 ) {
2021-03-20 02:37:05 +03:00
VIR_REALLOC_N ( con - > streamToTerminal . data ,
con - > streamToTerminal . length + 1024 ) ;
2010-07-27 13:40:30 +04:00
con - > streamToTerminal . length + = 1024 ;
avail + = 1024 ;
}
got = virStreamRecv ( st ,
con - > streamToTerminal . data +
con - > streamToTerminal . offset ,
avail ) ;
if ( got = = - 2 )
2019-02-25 17:05:01 +03:00
goto cleanup ; /* blocking */
2010-07-27 13:40:30 +04:00
if ( got < = 0 ) {
2019-08-23 14:37:25 +03:00
virConsoleShutdown ( con , got = = 0 ) ;
2019-02-25 17:05:01 +03:00
goto cleanup ;
2010-07-27 13:40:30 +04:00
}
con - > streamToTerminal . offset + = got ;
if ( con - > streamToTerminal . offset )
2011-03-02 18:08:31 +03:00
virEventUpdateHandle ( con - > stdoutWatch ,
VIR_EVENT_HANDLE_WRITABLE ) ;
2010-07-27 13:40:30 +04:00
}
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 )
2019-02-25 17:05:01 +03:00
goto cleanup ; /* blocking */
2010-07-27 13:40:30 +04:00
if ( done < 0 ) {
2019-08-23 14:37:25 +03:00
virConsoleShutdown ( con , false ) ;
2019-02-25 17:05:01 +03:00
goto cleanup ;
2010-07-27 13:40:30 +04:00
}
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 ) {
2021-03-20 02:37:05 +03:00
VIR_REALLOC_N ( con - > terminalToStream . data ,
con - > terminalToStream . offset + 1024 ) ;
2010-07-27 13:40:30 +04:00
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 ) {
2019-08-23 14:37:25 +03:00
virConsoleShutdown ( con , false ) ;
2010-07-27 13:40:30 +04:00
}
2019-02-25 17:05:01 +03:00
cleanup :
virObjectUnlock ( con ) ;
2010-07-27 13:40:30 +04:00
}
2013-08-26 13:53:43 +04:00
2010-07-27 13:40:30 +04:00
static void
2019-10-14 15:44:29 +03:00
virConsoleEventOnStdin ( int watch G_GNUC_UNUSED ,
int fd G_GNUC_UNUSED ,
2010-07-27 13:40:30 +04:00
int events ,
void * opaque )
{
2021-03-11 10:16:13 +03:00
virConsole * con = opaque ;
2010-07-27 13:40:30 +04:00
2019-02-25 17:05:01 +03:00
virObjectLock ( con ) ;
2019-02-25 17:10:01 +03:00
/* we got late event after console was shutdown */
if ( ! con - > st )
goto cleanup ;
2010-07-27 13:40:30 +04:00
if ( events & VIR_EVENT_HANDLE_READABLE ) {
size_t avail = con - > terminalToStream . length -
con - > terminalToStream . offset ;
int got ;
if ( avail < 1024 ) {
2021-03-20 02:37:05 +03:00
VIR_REALLOC_N ( con - > terminalToStream . data ,
con - > terminalToStream . length + 1024 ) ;
2010-07-27 13:40:30 +04:00
con - > terminalToStream . length + = 1024 ;
avail + = 1024 ;
}
got = read ( fd ,
con - > terminalToStream . data +
con - > terminalToStream . offset ,
avail ) ;
if ( got < 0 ) {
2019-02-14 17:41:21 +03:00
if ( errno ! = EAGAIN ) {
virReportSystemError ( errno , " %s " , _ ( " cannot read from stdin " ) ) ;
2019-08-23 14:37:25 +03:00
virConsoleShutdown ( con , false ) ;
2019-02-14 17:41:21 +03:00
}
2019-02-25 17:05:01 +03:00
goto cleanup ;
2010-07-27 13:40:30 +04:00
}
if ( got = = 0 ) {
2019-02-14 17:41:21 +03:00
virReportError ( VIR_ERR_INTERNAL_ERROR , " %s " , _ ( " EOF on stdin " ) ) ;
2019-08-23 14:37:25 +03:00
virConsoleShutdown ( con , false ) ;
2019-02-25 17:05:01 +03:00
goto cleanup ;
2010-07-27 13:40:30 +04:00
}
2011-11-22 20:08:05 +04:00
if ( con - > terminalToStream . data [ con - > terminalToStream . offset ] = = con - > escapeChar ) {
2019-08-23 14:37:25 +03:00
virConsoleShutdown ( con , true ) ;
2019-02-25 17:05:01 +03:00
goto cleanup ;
2010-07-27 13:40:30 +04:00
}
con - > terminalToStream . offset + = got ;
if ( con - > terminalToStream . offset )
virStreamEventUpdateCallback ( con - > st ,
VIR_STREAM_EVENT_READABLE |
VIR_STREAM_EVENT_WRITABLE ) ;
}
2019-02-14 17:41:21 +03:00
if ( events & VIR_EVENT_HANDLE_ERROR ) {
virReportError ( VIR_ERR_INTERNAL_ERROR , " %s " , _ ( " IO error on stdin " ) ) ;
2019-08-23 14:37:25 +03:00
virConsoleShutdown ( con , false ) ;
2019-02-14 17:41:21 +03:00
goto cleanup ;
}
if ( events & VIR_EVENT_HANDLE_HANGUP ) {
virReportError ( VIR_ERR_INTERNAL_ERROR , " %s " , _ ( " EOF on stdin " ) ) ;
2019-08-23 14:37:25 +03:00
virConsoleShutdown ( con , false ) ;
2019-02-14 17:41:21 +03:00
goto cleanup ;
2010-07-27 13:40:30 +04:00
}
2019-02-25 17:05:01 +03:00
cleanup :
virObjectUnlock ( con ) ;
2010-07-27 13:40:30 +04:00
}
2013-08-26 13:53:43 +04:00
2010-07-27 13:40:30 +04:00
static void
2019-10-14 15:44:29 +03:00
virConsoleEventOnStdout ( int watch G_GNUC_UNUSED ,
2010-07-27 13:40:30 +04:00
int fd ,
int events ,
void * opaque )
{
2021-03-11 10:16:13 +03:00
virConsole * con = opaque ;
2010-07-27 13:40:30 +04:00
2019-02-25 17:05:01 +03:00
virObjectLock ( con ) ;
2019-02-25 17:10:01 +03:00
/* we got late event after console was shutdown */
if ( ! con - > st )
goto cleanup ;
2010-07-27 13:40:30 +04:00
if ( events & VIR_EVENT_HANDLE_WRITABLE & &
con - > streamToTerminal . offset ) {
ssize_t done ;
size_t avail ;
2022-02-14 18:39:00 +03:00
done = write ( fd , /* sc_avoid_write */
2010-07-27 13:40:30 +04:00
con - > streamToTerminal . data ,
con - > streamToTerminal . offset ) ;
if ( done < 0 ) {
2019-02-14 17:41:21 +03:00
if ( errno ! = EAGAIN ) {
virReportSystemError ( errno , " %s " , _ ( " cannot write to stdout " ) ) ;
2019-08-23 14:37:25 +03:00
virConsoleShutdown ( con , false ) ;
2019-02-14 17:41:21 +03:00
}
2019-02-25 17:05:01 +03:00
goto cleanup ;
2010-07-27 13:40:30 +04:00
}
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 ) {
2021-03-20 02:37:05 +03:00
VIR_REALLOC_N ( con - > streamToTerminal . data ,
con - > streamToTerminal . offset + 1024 ) ;
2010-07-27 13:40:30 +04:00
con - > streamToTerminal . length = con - > streamToTerminal . offset + 1024 ;
}
}
if ( ! con - > streamToTerminal . offset )
2011-03-02 18:08:31 +03:00
virEventUpdateHandle ( con - > stdoutWatch , 0 ) ;
2010-07-27 13:40:30 +04:00
2019-02-14 17:41:21 +03:00
if ( events & VIR_EVENT_HANDLE_ERROR ) {
virReportError ( VIR_ERR_INTERNAL_ERROR , " %s " , _ ( " IO error stdout " ) ) ;
2019-08-23 14:37:25 +03:00
virConsoleShutdown ( con , false ) ;
2019-02-14 17:41:21 +03:00
goto cleanup ;
}
if ( events & VIR_EVENT_HANDLE_HANGUP ) {
virReportError ( VIR_ERR_INTERNAL_ERROR , " %s " , _ ( " EOF on stdout " ) ) ;
2019-08-23 14:37:25 +03:00
virConsoleShutdown ( con , false ) ;
2019-02-14 17:41:21 +03:00
goto cleanup ;
2010-07-27 13:40:30 +04:00
}
2019-02-25 17:05:01 +03:00
cleanup :
virObjectUnlock ( con ) ;
2010-07-27 13:40:30 +04:00
}
2021-03-11 10:16:13 +03:00
static virConsole *
2019-03-18 11:53:06 +03:00
virConsoleNew ( void )
{
2021-03-11 10:16:13 +03:00
virConsole * con ;
2019-03-18 11:53:06 +03:00
if ( virConsoleInitialize ( ) < 0 )
return NULL ;
2019-08-05 19:03:45 +03:00
if ( ! ( con = virObjectLockableNew ( virConsoleClass ) ) )
2019-03-18 11:53:06 +03:00
return NULL ;
if ( virCondInit ( & con - > cond ) < 0 ) {
virReportError ( VIR_ERR_INTERNAL_ERROR , " %s " ,
_ ( " cannot initialize console condition " ) ) ;
goto error ;
}
con - > stdinWatch = - 1 ;
con - > stdoutWatch = - 1 ;
return con ;
error :
virObjectUnref ( con ) ;
return NULL ;
}
2011-11-22 20:08:05 +04:00
static char
2015-06-15 19:53:58 +03:00
virshGetEscapeChar ( const char * s )
2011-11-22 20:08:05 +04:00
{
if ( * s = = ' ^ ' )
2019-11-18 17:16:33 +03:00
return CONTROL ( g_ascii_toupper ( s [ 1 ] ) ) ;
2011-11-22 20:08:05 +04:00
return * s ;
}
2013-08-26 13:53:43 +04:00
2012-09-05 03:35:27 +04:00
int
2015-06-15 19:53:58 +03:00
virshRunConsole ( vshControl * ctl ,
virDomainPtr dom ,
const char * dev_name ,
unsigned int flags )
2010-07-27 13:40:30 +04:00
{
2021-03-11 10:16:13 +03:00
virConsole * con = NULL ;
virshControl * priv = ctl - > privData ;
2013-08-29 20:30:06 +04:00
int ret = - 1 ;
struct sigaction old_sigquit ;
struct sigaction old_sigterm ;
struct sigaction old_sigint ;
struct sigaction old_sighup ;
struct sigaction old_sigpipe ;
struct sigaction sighandler = { . sa_handler = virConsoleHandleSignal ,
. sa_flags = SA_SIGINFO } ;
sigemptyset ( & sighandler . sa_mask ) ;
2007-01-26 14:54:29 +03:00
2013-08-29 20:30:06 +04:00
/* 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 */
2013-08-29 20:15:07 +04:00
if ( vshTTYMakeRaw ( ctl , true ) < 0 )
2010-07-27 13:40:30 +04:00
goto resettty ;
2007-01-26 14:54:29 +03:00
2019-03-18 11:53:06 +03:00
if ( ! ( con = virConsoleNew ( ) ) )
goto resettty ;
virObjectLock ( con ) ;
2013-08-29 20:30:06 +04: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 ! */
sigaction ( SIGQUIT , & sighandler , & old_sigquit ) ;
sigaction ( SIGTERM , & sighandler , & old_sigterm ) ;
sigaction ( SIGINT , & sighandler , & old_sigint ) ;
sigaction ( SIGHUP , & sighandler , & old_sighup ) ;
sigaction ( SIGPIPE , & sighandler , & old_sigpipe ) ;
2007-01-26 14:54:29 +03:00
2015-06-15 19:53:58 +03:00
con - > escapeChar = virshGetEscapeChar ( priv - > escapeChar ) ;
2010-07-27 13:40:30 +04:00
con - > st = virStreamNew ( virDomainGetConnect ( dom ) ,
VIR_STREAM_NONBLOCK ) ;
if ( ! con - > st )
goto cleanup ;
2011-10-12 14:59:15 +04:00
if ( virDomainOpenConsole ( dom , dev_name , con - > st , flags ) < 0 )
2010-07-27 13:40:30 +04:00
goto cleanup ;
2019-03-18 11:57:56 +03:00
virObjectRef ( con ) ;
if ( ( con - > stdinWatch = virEventAddHandle ( STDIN_FILENO ,
VIR_EVENT_HANDLE_READABLE ,
virConsoleEventOnStdin ,
con ,
2022-07-13 23:26:51 +03:00
virObjectUnref ) ) < 0 ) {
2019-03-18 11:57:56 +03:00
virObjectUnref ( con ) ;
goto cleanup ;
}
virObjectRef ( con ) ;
if ( ( con - > stdoutWatch = virEventAddHandle ( STDOUT_FILENO ,
0 ,
virConsoleEventOnStdout ,
con ,
2022-07-13 23:26:51 +03:00
virObjectUnref ) ) < 0 ) {
2019-03-18 11:57:56 +03:00
virObjectUnref ( con ) ;
goto cleanup ;
}
virObjectRef ( con ) ;
if ( virStreamEventAddCallback ( con - > st ,
VIR_STREAM_EVENT_READABLE ,
virConsoleEventOnStream ,
con ,
2022-07-13 23:26:51 +03:00
virObjectUnref ) < 0 ) {
2019-03-18 11:57:56 +03:00
virObjectUnref ( con ) ;
goto cleanup ;
}
2010-07-27 13:40:30 +04:00
while ( ! con - > quit ) {
2019-03-18 11:53:06 +03:00
if ( virCondWait ( & con - > cond , & con - > parent . lock ) < 0 ) {
2019-02-14 17:41:21 +03:00
virReportError ( VIR_ERR_INTERNAL_ERROR , " %s " ,
_ ( " unable to wait on console condition " ) ) ;
2011-10-11 17:05:52 +04:00
goto cleanup ;
}
2010-07-27 13:40:30 +04:00
}
2007-01-26 14:54:29 +03:00
2019-02-14 17:41:21 +03:00
if ( con - > error . code = = VIR_ERR_OK )
ret = 0 ;
2007-01-26 14:54:29 +03:00
2014-03-25 10:53:59 +04:00
cleanup :
2019-08-23 14:37:25 +03:00
virConsoleShutdown ( con , ret = = 0 ) ;
2019-02-14 17:41:21 +03:00
if ( ret < 0 ) {
vshResetLibvirtError ( ) ;
virSetError ( & con - > error ) ;
vshSaveLibvirtHelperError ( ) ;
}
2019-03-18 11:53:06 +03:00
virObjectUnlock ( con ) ;
virObjectUnref ( con ) ;
2010-07-27 13:40:30 +04:00
2007-01-26 14:54:29 +03:00
/* Restore original signal handlers */
2013-08-29 20:30:06 +04:00
sigaction ( SIGQUIT , & old_sigquit , NULL ) ;
sigaction ( SIGTERM , & old_sigterm , NULL ) ;
sigaction ( SIGINT , & old_sigint , NULL ) ;
sigaction ( SIGHUP , & old_sighup , NULL ) ;
sigaction ( SIGPIPE , & old_sigpipe , NULL ) ;
2007-01-26 14:54:29 +03:00
2014-03-25 10:53:59 +04:00
resettty :
2007-01-26 14:54:29 +03:00
/* Put STDIN back into the (sane?) state we found
it in before starting */
2013-08-29 20:15:07 +04:00
vshTTYRestore ( ctl ) ;
2007-01-26 14:54:29 +03:00
return ret ;
}
2010-05-04 00:44:12 +04:00
# endif /* !WIN32 */