2006-08-11 00:47:14 +04:00
#
2013-06-11 11:32:13 +04:00
# Copyright (C) 2006, 2013 Red Hat, Inc.
2006-08-11 00:47:14 +04:00
# Copyright (C) 2006 Daniel P. Berrange <berrange@redhat.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
2007-11-20 19:12:20 +03:00
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA.
2006-08-11 00:47:14 +04:00
#
import os
import termios
import tty
2008-02-03 20:42:21 +03:00
import pty
2008-10-03 00:17:25 +04:00
import fcntl
2009-07-15 03:42:16 +04:00
import logging
2006-08-11 00:47:14 +04:00
2016-04-18 23:42:12 +03:00
import gi
2012-05-14 17:24:56 +04:00
from gi . repository import Gdk
2013-02-16 23:03:30 +04:00
from gi . repository import GLib
from gi . repository import Gtk
2016-04-18 23:42:12 +03:00
# We can use either 2.91 or 2.90. This is just to silence runtime warnings
# pylint: disable=wrong-import-position
try :
gi . require_version ( " Vte " , " 2.91 " )
2016-05-08 03:44:22 +03:00
logging . debug ( " Using VTE API 2.91 " )
2016-04-18 23:42:12 +03:00
except ValueError :
gi . require_version ( " Vte " , " 2.90 " )
2016-05-08 03:44:22 +03:00
logging . debug ( " Using VTE API 2.90 " )
2012-05-14 17:24:56 +04:00
from gi . repository import Vte
2011-04-12 01:48:21 +04:00
2008-10-03 00:17:25 +04:00
import libvirt
2006-08-11 00:47:14 +04:00
2014-09-13 00:10:45 +04:00
from . baseclass import vmmGObject
2011-04-12 01:48:21 +04:00
2013-02-07 00:32:30 +04:00
2011-06-20 02:53:15 +04:00
class ConsoleConnection ( vmmGObject ) :
def __init__ ( self , vm ) :
vmmGObject . __init__ ( self )
self . vm = vm
2011-07-23 00:43:26 +04:00
self . conn = vm . conn
2011-06-20 02:53:15 +04:00
2011-07-24 05:16:54 +04:00
def _cleanup ( self ) :
2011-06-20 02:53:15 +04:00
self . close ( )
self . vm = None
self . conn = None
2013-11-10 02:54:47 +04:00
def is_open ( self ) :
raise NotImplementedError ( )
2011-06-20 04:49:41 +04:00
def open ( self , dev , terminal ) :
2011-06-20 02:53:15 +04:00
raise NotImplementedError ( )
def close ( self ) :
raise NotImplementedError ( )
def send_data ( self , src , text , length , terminal ) :
"""
Callback when data has been entered into VTE terminal
"""
raise NotImplementedError ( )
2013-02-07 00:32:30 +04:00
2011-06-20 02:53:15 +04:00
class LocalConsoleConnection ( ConsoleConnection ) :
def __init__ ( self , vm ) :
ConsoleConnection . __init__ ( self , vm )
self . fd = None
self . source = None
self . origtermios = None
2013-11-10 02:54:47 +04:00
def is_open ( self ) :
return self . fd is not None
2011-06-20 04:49:41 +04:00
def open ( self , dev , terminal ) :
2012-11-08 17:15:02 +04:00
if self . fd is not None :
2011-06-20 02:53:15 +04:00
self . close ( )
2011-06-20 04:49:41 +04:00
ipty = dev and dev . source_path or None
2012-01-17 07:04:40 +04:00
logging . debug ( " Opening serial tty path: %s " , ipty )
2012-11-08 17:15:02 +04:00
if ipty is None :
2011-06-20 02:53:15 +04:00
return
self . fd = pty . slave_open ( ipty )
fcntl . fcntl ( self . fd , fcntl . F_SETFL , os . O_NONBLOCK )
2013-02-16 23:03:30 +04:00
self . source = GLib . io_add_watch ( self . fd ,
GLib . IO_IN | GLib . IO_ERR | GLib . IO_HUP ,
2011-06-20 02:53:15 +04:00
self . display_data , terminal )
# Save term settings & set to raw mode
self . origtermios = termios . tcgetattr ( self . fd )
tty . setraw ( self . fd , termios . TCSANOW )
def close ( self ) :
2012-11-08 17:15:02 +04:00
if self . fd is None :
2011-06-20 02:53:15 +04:00
return
# Restore term settings
try :
2011-06-21 03:09:31 +04:00
if self . origtermios :
termios . tcsetattr ( self . fd , termios . TCSANOW , self . origtermios )
2011-06-20 02:53:15 +04:00
except :
# domain may already have exited, destroying the pty, so ignore
pass
os . close ( self . fd )
self . fd = None
2011-06-21 03:09:31 +04:00
2013-02-16 23:03:30 +04:00
GLib . source_remove ( self . source )
2011-06-20 02:53:15 +04:00
self . source = None
self . origtermios = None
def send_data ( self , src , text , length , terminal ) :
ignore = src
ignore = length
ignore = terminal
if self . fd is None :
return
os . write ( self . fd , text )
def display_data ( self , src , cond , terminal ) :
ignore = src
2013-02-16 23:03:30 +04:00
if cond != GLib . IO_IN :
2011-06-20 02:53:15 +04:00
self . close ( )
return False
data = os . read ( self . fd , 1024 )
2013-02-07 00:32:30 +04:00
terminal . feed ( data )
2011-06-20 02:53:15 +04:00
return True
2013-02-07 00:32:30 +04:00
2011-06-21 19:04:48 +04:00
class LibvirtConsoleConnection ( ConsoleConnection ) :
def __init__ ( self , vm ) :
ConsoleConnection . __init__ ( self , vm )
self . stream = None
self . streamToTerminal = " "
self . terminalToStream = " "
def _event_on_stream ( self , stream , events , opaque ) :
ignore = stream
terminal = opaque
if ( events & libvirt . VIR_EVENT_HANDLE_ERROR or
events & libvirt . VIR_EVENT_HANDLE_HANGUP ) :
logging . debug ( " Received stream ERROR/HANGUP, closing console " )
self . close ( )
2013-06-13 05:10:52 +04:00
return
2011-06-20 02:53:15 +04:00
2011-06-21 19:04:48 +04:00
if events & libvirt . VIR_EVENT_HANDLE_READABLE :
try :
2012-01-30 03:46:53 +04:00
got = self . stream . recv ( 1024 * 100 )
2011-06-21 19:04:48 +04:00
except :
logging . exception ( " Error receiving stream data " )
self . close ( )
return
2011-06-20 02:53:15 +04:00
2011-06-21 19:04:48 +04:00
if got == - 2 :
2013-06-13 05:10:52 +04:00
# This is basically EAGAIN
2011-06-21 19:04:48 +04:00
return
2013-06-11 11:32:13 +04:00
if len ( got ) == 0 :
2013-06-13 05:10:52 +04:00
logging . debug ( " Received EOF from stream, closing " )
2013-06-11 11:32:13 +04:00
self . close ( )
2013-06-13 05:10:52 +04:00
return
2011-06-21 19:04:48 +04:00
2012-01-30 03:46:53 +04:00
queued_text = bool ( self . streamToTerminal )
2011-06-21 19:04:48 +04:00
self . streamToTerminal + = got
2012-01-30 03:46:53 +04:00
if not queued_text :
2012-02-10 23:07:51 +04:00
self . idle_add ( self . display_data , terminal )
2011-06-21 19:04:48 +04:00
if ( events & libvirt . VIR_EVENT_HANDLE_WRITABLE and
self . terminalToStream ) :
try :
done = self . stream . send ( self . terminalToStream )
except :
logging . exception ( " Error sending stream data " )
self . close ( )
return
if done == - 2 :
2013-06-13 05:10:52 +04:00
# This is basically EAGAIN
2011-06-21 19:04:48 +04:00
return
self . terminalToStream = self . terminalToStream [ done : ]
if not self . terminalToStream :
self . stream . eventUpdateCallback ( libvirt . VIR_STREAM_EVENT_READABLE |
libvirt . VIR_STREAM_EVENT_ERROR |
libvirt . VIR_STREAM_EVENT_HANGUP )
2013-11-10 02:54:47 +04:00
def is_open ( self ) :
return self . stream is not None
2011-06-21 19:04:48 +04:00
def open ( self , dev , terminal ) :
if self . stream :
self . close ( )
name = dev and dev . alias . name or None
2012-01-17 07:04:40 +04:00
logging . debug ( " Opening console stream for dev= %s alias= %s " ,
dev , name )
2015-04-26 18:50:01 +03:00
# libxl doesn't set aliases, their open_console just defaults to
# opening the first console device, so don't force prescence of
# an alias
2012-01-29 22:02:13 +04:00
2013-11-10 02:54:47 +04:00
stream = self . conn . get_backend ( ) . newStream ( libvirt . VIR_STREAM_NONBLOCK )
self . vm . open_console ( name , stream )
self . stream = stream
2011-06-21 19:04:48 +04:00
self . stream . eventAddCallback ( ( libvirt . VIR_STREAM_EVENT_READABLE |
libvirt . VIR_STREAM_EVENT_ERROR |
libvirt . VIR_STREAM_EVENT_HANGUP ) ,
self . _event_on_stream ,
terminal )
def close ( self ) :
if self . stream :
try :
self . stream . eventRemoveCallback ( )
except :
logging . exception ( " Error removing stream callback " )
try :
self . stream . finish ( )
except :
logging . exception ( " Error finishing stream " )
self . stream = None
def send_data ( self , src , text , length , terminal ) :
ignore = src
ignore = length
ignore = terminal
if self . stream is None :
return
self . terminalToStream + = text
if self . terminalToStream :
self . stream . eventUpdateCallback ( libvirt . VIR_STREAM_EVENT_READABLE |
libvirt . VIR_STREAM_EVENT_WRITABLE |
libvirt . VIR_STREAM_EVENT_ERROR |
libvirt . VIR_STREAM_EVENT_HANGUP )
def display_data ( self , terminal ) :
if not self . streamToTerminal :
return
2013-02-07 00:32:30 +04:00
terminal . feed ( self . streamToTerminal )
2011-06-21 19:04:48 +04:00
self . streamToTerminal = " "
2011-06-20 02:53:15 +04:00
2011-06-21 00:49:11 +04:00
2013-02-07 00:32:30 +04:00
class vmmSerialConsole ( vmmGObject ) :
2011-06-21 19:04:48 +04:00
@staticmethod
def support_remote_console ( vm ) :
"""
Check if we can connect to a remote console
"""
return bool ( vm . remote_console_supported )
2011-06-21 00:49:11 +04:00
@staticmethod
def can_connect ( vm , dev ) :
"""
Check if we think we can actually open passed console / serial dev
"""
usable_types = [ " pty " ]
2013-07-16 17:14:37 +04:00
ctype = dev . type
2011-06-21 00:49:11 +04:00
path = dev . source_path
2011-07-23 00:43:26 +04:00
is_remote = vm . conn . is_remote ( )
2011-06-21 19:04:48 +04:00
support_tunnel = vmmSerialConsole . support_remote_console ( vm )
2011-06-21 00:49:11 +04:00
err = " "
2015-09-22 18:05:05 +03:00
if is_remote and not support_tunnel :
err = _ ( " Remote serial console not supported for this "
" connection " )
2011-06-21 00:49:11 +04:00
elif not vm . is_active ( ) :
err = _ ( " Serial console not available for inactive guest " )
2014-05-02 18:20:59 +04:00
elif ctype not in usable_types :
2011-06-21 00:49:11 +04:00
err = ( _ ( " Console for device type ' %s ' not yet supported " ) %
ctype )
2011-06-21 19:04:48 +04:00
elif ( not is_remote and
not support_tunnel and
( path and not os . access ( path , os . R_OK | os . W_OK ) ) ) :
2011-06-21 00:49:11 +04:00
err = _ ( " Can not access console path ' %s ' " ) % str ( path )
return err
2011-06-21 02:10:04 +04:00
def __init__ ( self , vm , target_port , name ) :
2011-04-12 01:48:21 +04:00
vmmGObject . __init__ ( self )
2006-08-11 00:47:14 +04:00
2008-10-03 00:17:25 +04:00
self . vm = vm
2009-07-15 03:42:16 +04:00
self . target_port = target_port
2011-06-21 02:10:04 +04:00
self . name = name
2011-06-20 04:49:41 +04:00
self . lastpath = None
2006-08-11 00:47:14 +04:00
2011-06-21 19:04:48 +04:00
# Always use libvirt console streaming if available, so
# we exercise the same code path (it's what virsh console does)
if vmmSerialConsole . support_remote_console ( self . vm ) :
self . console = LibvirtConsoleConnection ( self . vm )
else :
self . console = LocalConsoleConnection ( self . vm )
2011-04-12 01:48:21 +04:00
2011-06-21 02:10:04 +04:00
self . serial_popup = None
self . serial_copy = None
self . serial_paste = None
self . serial_close = None
self . init_popup ( )
2011-04-12 01:48:21 +04:00
self . terminal = None
self . init_terminal ( )
self . box = None
2011-06-21 03:09:31 +04:00
self . error_label = None
2011-04-12 01:48:21 +04:00
self . init_ui ( )
2015-04-10 01:02:42 +03:00
self . vm . connect ( " state-changed " , self . vm_status_changed )
2011-04-12 01:48:21 +04:00
def init_terminal ( self ) :
2012-05-14 17:24:56 +04:00
self . terminal = Vte . Terminal ( )
2008-09-22 18:19:19 +04:00
self . terminal . set_scrollback_lines ( 1000 )
self . terminal . set_audible_bell ( False )
2006-08-11 00:47:14 +04:00
2011-06-21 02:10:04 +04:00
self . terminal . connect ( " button-press-event " , self . show_serial_rcpopup )
2011-06-20 02:53:15 +04:00
self . terminal . connect ( " commit " , self . console . send_data , self . terminal )
2008-09-22 18:19:19 +04:00
self . terminal . show ( )
2006-08-11 00:47:14 +04:00
2011-06-21 02:10:04 +04:00
def init_popup ( self ) :
2012-05-14 17:24:56 +04:00
self . serial_popup = Gtk . Menu ( )
2011-06-21 02:10:04 +04:00
2013-07-11 21:32:41 +04:00
self . serial_copy = Gtk . ImageMenuItem . new_from_stock ( Gtk . STOCK_COPY ,
None )
self . serial_copy . connect ( " activate " , self . serial_copy_text )
2011-06-21 02:10:04 +04:00
self . serial_popup . add ( self . serial_copy )
2013-07-11 21:32:41 +04:00
self . serial_paste = Gtk . ImageMenuItem . new_from_stock ( Gtk . STOCK_PASTE ,
None )
self . serial_paste . connect ( " activate " , self . serial_paste_text )
2011-06-21 02:10:04 +04:00
self . serial_popup . add ( self . serial_paste )
2011-04-12 01:48:21 +04:00
def init_ui ( self ) :
2012-05-14 17:24:56 +04:00
self . box = Gtk . Notebook ( )
2011-06-21 03:09:31 +04:00
self . box . set_show_tabs ( False )
self . box . set_show_border ( False )
2013-02-07 00:32:30 +04:00
align = Gtk . Alignment ( )
2011-06-21 21:37:10 +04:00
align . set_padding ( 2 , 2 , 2 , 2 )
2012-05-14 17:24:56 +04:00
evbox = Gtk . EventBox ( )
evbox . modify_bg ( Gtk . StateType . NORMAL , Gdk . Color ( 0 , 0 , 0 ) )
terminalbox = Gtk . HBox ( )
scrollbar = Gtk . VScrollbar ( )
self . error_label = Gtk . Label ( )
2011-07-12 18:46:49 +04:00
self . error_label . set_width_chars ( 40 )
self . error_label . set_line_wrap ( True )
2006-08-11 00:47:14 +04:00
2012-01-30 04:00:45 +04:00
if self . terminal :
2013-02-07 00:32:30 +04:00
scrollbar . set_adjustment ( self . terminal . get_vadjustment ( ) )
2012-01-30 04:00:45 +04:00
align . add ( self . terminal )
2011-06-21 21:37:10 +04:00
evbox . add ( align )
2012-05-14 17:24:56 +04:00
terminalbox . pack_start ( evbox , True , True , 0 )
2013-02-07 00:32:30 +04:00
terminalbox . pack_start ( scrollbar , False , False , 0 )
2011-06-21 03:09:31 +04:00
2013-02-07 00:32:30 +04:00
self . box . append_page ( terminalbox , Gtk . Label ( " " ) )
self . box . append_page ( self . error_label , Gtk . Label ( " " ) )
2011-06-21 03:09:31 +04:00
self . box . show_all ( )
2006-08-11 00:47:14 +04:00
2014-04-26 03:13:44 +04:00
scrollbar . hide ( )
scrollbar . get_adjustment ( ) . connect (
" changed " , self . _scrollbar_adjustment_changed , scrollbar )
def _scrollbar_adjustment_changed ( self , adjustment , scrollbar ) :
scrollbar . set_visible (
adjustment . get_upper ( ) > adjustment . get_page_size ( ) )
2011-07-24 05:16:54 +04:00
def _cleanup ( self ) :
2011-06-20 02:53:15 +04:00
self . console . cleanup ( )
self . console = None
2011-04-12 01:48:21 +04:00
self . vm = None
self . terminal = None
self . box = None
2006-08-11 00:47:14 +04:00
2013-11-10 02:54:47 +04:00
def close ( self ) :
if self . console :
self . console . close ( )
2011-06-21 03:09:31 +04:00
def show_error ( self , msg ) :
self . error_label . set_markup ( " <b> %s </b> " % msg )
self . box . set_current_page ( 1 )
2006-08-11 00:47:14 +04:00
2011-06-21 03:09:31 +04:00
def open_console ( self ) :
try :
2013-11-10 02:54:47 +04:00
if not self . console . is_open ( ) :
self . console . open ( self . lookup_dev ( ) , self . terminal )
2011-06-21 03:09:31 +04:00
self . box . set_current_page ( 0 )
return True
2017-05-05 19:47:21 +03:00
except Exception as e :
2011-06-21 03:09:31 +04:00
logging . exception ( " Error opening serial console " )
self . show_error ( _ ( " Error connecting to text console: %s " ) % e )
try :
self . console . close ( )
except :
pass
return False
2006-08-11 00:47:14 +04:00
2015-04-09 23:07:09 +03:00
def vm_status_changed ( self , vm ) :
if vm . status ( ) in [ libvirt . VIR_DOMAIN_RUNNING ] :
2011-06-21 03:09:31 +04:00
self . open_console ( )
2008-10-03 00:17:25 +04:00
else :
2011-06-20 02:53:15 +04:00
self . console . close ( )
2006-11-03 18:02:02 +03:00
2011-06-20 04:49:41 +04:00
def lookup_dev ( self ) :
devs = self . vm . get_serial_devs ( )
for dev in devs :
port = dev . vmmindex
path = dev . source_path
2009-07-15 03:42:16 +04:00
if port == self . target_port :
2011-06-20 04:49:41 +04:00
if path != self . lastpath :
2012-01-17 07:04:40 +04:00
logging . debug ( " Serial console ' %s ' path changed to %s " ,
self . target_port , path )
2011-06-20 04:49:41 +04:00
self . lastpath = path
return dev
2009-07-28 17:32:18 +04:00
2012-01-17 07:04:40 +04:00
logging . debug ( " No devices found for serial target port ' %s ' " ,
2009-07-15 03:42:16 +04:00
self . target_port )
2011-06-20 04:49:41 +04:00
self . lastpath = None
return None
2011-06-21 02:10:04 +04:00
#######################
# Popup menu handling #
#######################
def show_serial_rcpopup ( self , src , event ) :
if event . button != 3 :
return
self . serial_popup . show_all ( )
if src . get_has_selection ( ) :
self . serial_copy . set_sensitive ( True )
else :
self . serial_copy . set_sensitive ( False )
2013-04-14 21:16:02 +04:00
self . serial_popup . popup ( None , None , None , None , 0 , event . time )
2011-06-21 02:10:04 +04:00
2013-07-11 21:32:41 +04:00
def serial_copy_text ( self , src_ignore ) :
self . terminal . copy_clipboard ( )
2011-06-21 02:10:04 +04:00
2013-07-11 21:32:41 +04:00
def serial_paste_text ( self , src_ignore ) :
self . terminal . paste_clipboard ( )