Merge pull request #322 from mininet/devel/pty
Attach a pty to each node's bash process
This commit is contained in:
+9
-6
@@ -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
@@ -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: ] )
|
||||||
|
|||||||
Reference in New Issue
Block a user