Skip to content

Instantly share code, notes, and snippets.

@brownan
Created October 3, 2011 19:47
Show Gist options
  • Save brownan/1260038 to your computer and use it in GitHub Desktop.
Save brownan/1260038 to your computer and use it in GitHub Desktop.
Run commands as if connecting through a slow modem
#!/usr/bin/env python
from __future__ import division
import sys
import pty
import os
import subprocess
import termios
import fcntl
import signal
import time
usage = """
Run a program as if through a slow modem.
Usage: baud.py <baud> <program> [args] ...
e.x.: $ baud.py 300 bash
"""
#Good reference on Linux TTY semantics:
#http://www.win.tue.nl/~aeb/linux/lk/lk-10.html
def postfork():
"""This is executed in the forked child, before it executes the requested
program
"""
# Become a session leader by creating a new session. This detaches us from
# the previous controlling terminal
os.setsid()
# Now use the TIOCSCTTY ioctl to set our controlling terminal to whatever
# stdout is (the pty created earlier)
fcntl.ioctl(1, termios.TIOCSCTTY)
def passsignalto(proc):
"""Return a signal handler that calls passes through to the given proc on
call"""
def handler(signal, frame):
proc.send_signal(signal)
return handler
def main():
try:
baud = int(sys.argv[1])
path = sys.argv[2]
arglist = sys.argv[3:]
except (IndexError, ValueError):
print usage
sys.exit(1)
# Create a new pseudo-terminal pair. We'll read from the master side and
# connect the child to the slave side.
master, slave = pty.openpty()
# Set terminal options to the same as the current controlling terminal
termios.tcsetattr(slave, termios.TCSANOW,
termios.tcgetattr(sys.stdout.fileno())
)
proc = subprocess.Popen([path]+arglist,#stdin=slave,
stdout=slave, stderr=slave, close_fds=True,
preexec_fn=postfork)
os.close(slave)
# Since we're still technically controlling the terminal the user is
# interacting with, we still get keyboard interrupts from it. Pass them
# through with a special signal handler.
signal.signal(signal.SIGINT, passsignalto(proc))
while proc.poll() == None:
try:
a = os.read(master, 1)
except OSError as e:
if e.errno == 4:
# Interrupted system call, probably due to a ctrl-c. Ignore
continue
print "OSError %s" % e
break
if not a:
break
sys.stdout.write(a)
sys.stdout.flush()
time.sleep(1/baud)
print
if __name__ == "__main__":
try:
main()
finally:
print "%s exited." % sys.argv[0]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment