Skip to content
Snippets Groups Projects
Commit 549f1ebc authored by Bob Lantz's avatar Bob Lantz
Browse files

Attach a pty to each node's bash process

This should enable node commands that are expecting a tty to
behave better.
parent 00803bcd
No related branches found
No related tags found
No related merge requests found
......@@ -55,7 +55,7 @@ def __init__( self, mininet, stdin=sys.stdin, script=None ):
Cmd.__init__( self )
info( '*** Starting CLI:\n' )
# Setup history if readline is available
# Set up history if readline is available
try:
import readline
except ImportError:
......@@ -77,7 +77,7 @@ def __init__( self, mininet, stdin=sys.stdin, script=None ):
node.sendInt()
node.monitor()
if self.isatty():
quietRun( 'stty sane' )
quietRun( 'stty echo sane intr "^C"' )
self.cmdloop()
break
except KeyboardInterrupt:
......@@ -352,8 +352,7 @@ def default( self, line ):
for arg in rest ]
rest = ' '.join( rest )
# Run cmd on node:
builtin = isShellBuiltin( first )
node.sendCmd( rest, printPid=( not builtin ) )
node.sendCmd( rest )
self.waitForNode( node )
else:
error( '*** Unknown command: %s\n' % line )
......@@ -361,7 +360,7 @@ def default( self, line ):
# pylint: enable-msg=R0201
def waitForNode( self, node ):
"Wait for a node to finish, and print its output."
"Wait for a node to finish, and print its output."
# Pollers
nodePoller = poll()
nodePoller.register( node.stdout )
......@@ -379,7 +378,7 @@ def waitForNode( self, node ):
if False and self.inputFile:
key = self.inputFile.read( 1 )
if key is not '':
node.write(key)
node.write( key )
else:
self.inputFile = None
if isReadable( self.inPoller ):
......@@ -391,8 +390,12 @@ def waitForNode( self, node ):
if not node.waiting:
break
except KeyboardInterrupt:
# There is an at least one race condition here, since
# it's possible to interrupt ourselves after we've
# read data but before it has been printed.
node.sendInt()
# Helper functions
def isReadable( poller ):
......
......@@ -45,6 +45,7 @@
"""
import os
import pty
import re
import signal
import select
......@@ -118,16 +119,21 @@ def startShell( self ):
return
# mnexec: (c)lose descriptors, (d)etach from tty,
# (p)rint pid, and run in (n)amespace
opts = '-cdp'
opts = '-cd'
if self.inNamespace:
opts += 'n'
# bash -m: enable job control
# bash -m: enable job control, i: force interactive
# -s: pass $* to shell, and make process easy to find in ps
cmd = [ 'mnexec', opts, 'bash', '-ms', 'mininet:' + self.name ]
self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
close_fds=True )
self.stdin = self.shell.stdin
self.stdout = self.shell.stdout
cmd = [ 'mnexec', opts, 'env', 'PS1=' + chr( 127 ), 'bash',
'--norc', '-mis', 'mininet:' + self.name ]
# Spawn a shell subprocess in a pseudo-tty, to disable buffering
# in the subprocess and insulate it from signals (e.g. SIGINT)
# received by the parent
master, slave = pty.openpty()
self.shell = Popen( cmd, stdin=slave, stdout=slave, stderr=slave,
close_fds=False )
self.stdin = os.fdopen( master )
self.stdout = self.stdin
self.pid = self.shell.pid
self.pollOut = select.poll()
self.pollOut.register( self.stdout )
......@@ -141,6 +147,14 @@ def startShell( self ):
self.lastPid = None
self.readbuf = ''
self.waiting = False
# Wait for prompt
while True:
data = self.read( 1024 )
if chr( 127 ) in data:
break
self.pollOut.poll()
self.waiting = False
self.cmd( 'stty -echo' )
def cleanup( self ):
"Help python collect its garbage."
......@@ -205,7 +219,7 @@ def sendCmd( self, *args, **kwargs ):
args: command and arguments, or string
printPid: print command's PID?"""
assert not self.waiting
printPid = kwargs.get( 'printPid', True )
printPid = kwargs.get( 'printPid', False )
# Allow sendCmd( [ list ] )
if len( args ) == 1 and type( args[ 0 ] ) is list:
cmd = args[ 0 ]
......@@ -219,28 +233,17 @@ def sendCmd( self, *args, **kwargs ):
# Replace empty commands with something harmless
cmd = 'echo -n'
self.lastCmd = cmd
printPid = printPid and not isShellBuiltin( cmd )
if len( cmd ) > 0 and cmd[ -1 ] == '&':
# print ^A{pid}\n{sentinel}
cmd += ' printf "\\001%d\n\\177" $! \n'
else:
# print sentinel
cmd += '; printf "\\177"'
if printPid and not isShellBuiltin( cmd ):
cmd = 'mnexec -p ' + cmd
if printPid and not isShellBuiltin( cmd ):
cmd = 'mnexec -p ' + cmd
self.write( cmd + '\n' )
self.lastPid = None
self.waiting = True
def sendInt( self, sig=signal.SIGINT ):
def sendInt( self, intr=chr( 3 ) ):
"Interrupt running command."
if self.lastPid:
try:
os.kill( self.lastPid, sig )
except OSError:
pass
self.write( intr )
def monitor( self, timeoutms=None ):
def monitor( self, timeoutms=None, findPid=True ):
"""Monitor and return the output of a command.
Set self.waiting to False if command has completed.
timeoutms: timeout in ms or None to wait indefinitely."""
......@@ -248,7 +251,7 @@ def monitor( self, timeoutms=None ):
data = self.read( 1024 )
# Look for PID
marker = chr( 1 ) + r'\d+\n'
if chr( 1 ) in data:
if findPid and chr( 1 ) in data:
markers = re.findall( marker, data )
if markers:
self.lastPid = int( markers[ 0 ][ 1: ] )
......@@ -317,7 +320,10 @@ def popen( self, *args, **kwargs ):
# Shell requires a string, not a list!
if defaults.get( 'shell', False ):
cmd = ' '.join( cmd )
return Popen( cmd, **defaults )
old = signal.signal( signal.SIGINT, signal.SIG_IGN )
popen = Popen( cmd, **defaults )
signal.signal( signal.SIGINT, old )
return popen
def pexec( self, *args, **kwargs ):
"""Execute a command using popen
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment