Merge pull request #322 from mininet/devel/pty

Attach a pty to each node's bash process
This commit is contained in:
Brian O'Connor
2014-07-31 18:36:35 -07:00
2 changed files with 40 additions and 30 deletions
+9 -6
View File
@@ -55,7 +55,7 @@ class CLI( Cmd ):
Cmd.__init__( self ) Cmd.__init__( self )
info( '*** Starting CLI:\n' ) info( '*** Starting CLI:\n' )
# Setup history if readline is available # Set up history if readline is available
try: try:
import readline import readline
except ImportError: except ImportError:
@@ -77,7 +77,7 @@ class CLI( Cmd ):
node.sendInt() node.sendInt()
node.monitor() node.monitor()
if self.isatty(): if self.isatty():
quietRun( 'stty sane' ) quietRun( 'stty echo sane intr "^C"' )
self.cmdloop() self.cmdloop()
break break
except KeyboardInterrupt: except KeyboardInterrupt:
@@ -352,8 +352,7 @@ class CLI( Cmd ):
for arg in rest ] for arg in rest ]
rest = ' '.join( rest ) rest = ' '.join( rest )
# Run cmd on node: # Run cmd on node:
builtin = isShellBuiltin( first ) node.sendCmd( rest )
node.sendCmd( rest, printPid=( not builtin ) )
self.waitForNode( node ) self.waitForNode( node )
else: else:
error( '*** Unknown command: %s\n' % line ) error( '*** Unknown command: %s\n' % line )
@@ -361,7 +360,7 @@ class CLI( Cmd ):
# pylint: enable-msg=R0201 # pylint: enable-msg=R0201
def waitForNode( self, node ): 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 # Pollers
nodePoller = poll() nodePoller = poll()
nodePoller.register( node.stdout ) nodePoller.register( node.stdout )
@@ -379,7 +378,7 @@ class CLI( Cmd ):
if False and self.inputFile: if False and self.inputFile:
key = self.inputFile.read( 1 ) key = self.inputFile.read( 1 )
if key is not '': if key is not '':
node.write(key) node.write( key )
else: else:
self.inputFile = None self.inputFile = None
if isReadable( self.inPoller ): if isReadable( self.inPoller ):
@@ -391,8 +390,12 @@ class CLI( Cmd ):
if not node.waiting: if not node.waiting:
break break
except KeyboardInterrupt: 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() node.sendInt()
# Helper functions # Helper functions
def isReadable( poller ): def isReadable( poller ):
+31 -24
View File
@@ -50,6 +50,7 @@ Future enhancements:
""" """
import os import os
import pty
import re import re
import signal import signal
import select import select
@@ -123,16 +124,22 @@ class Node( object ):
return return
# mnexec: (c)lose descriptors, (d)etach from tty, # mnexec: (c)lose descriptors, (d)etach from tty,
# (p)rint pid, and run in (n)amespace # (p)rint pid, and run in (n)amespace
opts = '-cdp' opts = '-cd'
if self.inNamespace: if self.inNamespace:
opts += 'n' 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 # -s: pass $* to shell, and make process easy to find in ps
cmd = [ 'mnexec', opts, 'bash', '-ms', 'mininet:' + self.name ] # prompt is set to sentinel chr( 127 )
self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT, os.environ[ 'PS1' ] = chr( 127 )
close_fds=True ) cmd = [ 'mnexec', opts, 'bash', '--norc', '-mis', 'mininet:' + self.name ]
self.stdin = self.shell.stdin # Spawn a shell subprocess in a pseudo-tty, to disable buffering
self.stdout = self.shell.stdout # 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.pid = self.shell.pid
self.pollOut = select.poll() self.pollOut = select.poll()
self.pollOut.register( self.stdout ) self.pollOut.register( self.stdout )
@@ -145,7 +152,14 @@ class Node( object ):
self.lastCmd = None self.lastCmd = None
self.lastPid = None self.lastPid = None
self.readbuf = '' self.readbuf = ''
# Wait for prompt
while True:
data = self.read( 1024 )
if data[ -1 ] == chr( 127 ):
break
self.pollOut.poll()
self.waiting = False self.waiting = False
self.cmd( 'stty -echo' )
def cleanup( self ): def cleanup( self ):
"Help python collect its garbage." "Help python collect its garbage."
@@ -224,36 +238,29 @@ class Node( object ):
# Replace empty commands with something harmless # Replace empty commands with something harmless
cmd = 'echo -n' cmd = 'echo -n'
self.lastCmd = cmd self.lastCmd = cmd
printPid = printPid and not isShellBuiltin( cmd ) if printPid and not isShellBuiltin( cmd ):
if len( cmd ) > 0 and cmd[ -1 ] == '&': if len( cmd ) > 0 and cmd[ -1 ] == '&':
# print ^A{pid}\n{sentinel} # print ^A{pid}\n so monitor() can set lastPid
cmd += ' printf "\\001%d\n\\177" $! \n' cmd += ' printf "\\001%d\n" $! \n'
else: else:
# print sentinel
cmd += '; printf "\\177"'
if printPid and not isShellBuiltin( cmd ):
cmd = 'mnexec -p ' + cmd cmd = 'mnexec -p ' + cmd
self.write( cmd + '\n' ) self.write( cmd + '\n' )
self.lastPid = None self.lastPid = None
self.waiting = True self.waiting = True
def sendInt( self, sig=signal.SIGINT ): def sendInt( self, intr=chr( 3 ) ):
"Interrupt running command." "Interrupt running command."
if self.lastPid: self.write( intr )
try:
os.kill( self.lastPid, sig )
except OSError:
pass
def monitor( self, timeoutms=None ): def monitor( self, timeoutms=None, findPid=True ):
"""Monitor and return the output of a command. """Monitor and return the output of a command.
Set self.waiting to False if command has completed. Set self.waiting to False if command has completed.
timeoutms: timeout in ms or None to wait indefinitely.""" timeoutms: timeout in ms or None to wait indefinitely."""
self.waitReadable( timeoutms ) self.waitReadable( timeoutms )
data = self.read( 1024 ) data = self.read( 1024 )
# Look for PID # Look for PID
marker = chr( 1 ) + r'\d+\n' marker = chr( 1 ) + r'\d+\r\n'
if chr( 1 ) in data: if findPid and chr( 1 ) in data:
markers = re.findall( marker, data ) markers = re.findall( marker, data )
if markers: if markers:
self.lastPid = int( markers[ 0 ][ 1: ] ) self.lastPid = int( markers[ 0 ][ 1: ] )