Major rewrite with new read_* interfaces
diff --git a/Demo/cwilib/telnetlib.py b/Demo/cwilib/telnetlib.py
index 5c862e7..3a48a79 100755
--- a/Demo/cwilib/telnetlib.py
+++ b/Demo/cwilib/telnetlib.py
@@ -1,13 +1,47 @@
-# Telnet client library
+# A TELNET client class.  Based on RFC 854: TELNET Protocol
+# Specification, by J. Postel and J. Reynolds
 
+
+# Example:
+#
+# >>> from telnetlib import Telnet
+# >>> tn = Telnet('voorn.cwi.nl', 79) # connect to finger port
+# >>> tn.write('guido\r\n')
+# >>> print tn.read_all()
+# Login name: guido                       In real life: Guido van Rossum
+# Office: M353,  x4127                    Home phone: 020-6225521
+# Directory: /ufs/guido                   Shell: /usr/local/bin/esh
+# On since Oct 28 11:02:16 on ttyq1   
+# Project: Multimedia Kernel Systems
+# No Plan.
+# >>>
+#
+# Note that read() won't read until eof -- it just reads some data
+# (but it guarantees to read at least one byte unless EOF is hit).
+#
+# It is possible to pass a Telnet object to select.select() in order
+# to wait until more data is available.  Note that in this case,
+# read_eager() may return '' even if there was data on the socket,
+# because the protocol negotiation may have eaten the data.
+# This is why EOFError is needed to distinguish between "no data"
+# and "connection closed" (since the socket also appears ready for
+# reading when it is closed).
+#
+# Bugs:
+# - may hang when connection is slow in the middle of an IAC sequence
+#
+# To do:
+# - option negotiation
+
+
+# Imported modules
 import socket
 import select
 import string
 import regsub
 
 # Tunable parameters
-TIMEOUT = 30.0
-DEBUGLEVEL = 1
+DEBUGLEVEL = 0
 
 # Telnet protocol defaults
 TELNET_PORT = 23
@@ -24,158 +58,264 @@
 
 class Telnet:
 
-	# Constructor
-	def __init__(self, host, port):
-		self.debuglevel = DEBUGLEVEL
-		self.host = host
-		if not port: port = TELNET_PORT
-		self.port = port
-		self.timeout = TIMEOUT
-		self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-		self.sock.connect((self.host, self.port))
-		self.rawq = ''
-		self.irawq = 0
-		self.cookedq = ''
+    # Constructor
+    def __init__(self, host, *args):
+	if not args:
+	    port = TELNET_PORT
+	else:
+	    if len(args) > 1: raise TypeError, 'too many args'
+	    port = args[0]
+	    if not port: port = TELNET_PORT
+	self.debuglevel = DEBUGLEVEL
+	self.host = host
+	self.port = port
+	self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+	self.sock.connect((self.host, self.port))
+	self.rawq = ''
+	self.irawq = 0
+	self.cookedq = ''
+	self.eof = 0
 
-	# Destructor
-	def __del__(self):
-		self.close()
+    # Destructor
+    def __del__(self):
+	self.close()
 
-	# Print debug message
-	def msg(self, msg, *args):
-		if self.debuglevel > 0:
-			print 'TELNET:', msg%args
+    # Debug message
+    def msg(self, msg, *args):
+	if self.debuglevel > 0:
+	    print 'Telnet(%s,%d):' % (self.host, self.port), msg % args
 
-	# Set debug level
-	def set_debuglevel(self, debuglevel):
-		self.debuglevel = debuglevel
+    # Set debug level
+    def set_debuglevel(self, debuglevel):
+	self.debuglevel = debuglevel
 
-	# Set time-out on certain reads
-	def set_timeout(self, timeout):
-		self.timeout = float(timeout)
+    # Explicit close
+    def close(self):
+	if self.sock:
+	    self.sock.close()
+	self.sock = None
+	self.eof = 1
 
-	# Explicit close
-	def close(self):
-		if self.sock:
-			self.sock.close()
-		self.sock = None
+    # Return socket (e.g. for select)
+    def get_socket(self):
+	return self.sock
 
-	# Return socket (e.g. for select)
-	def get_socket(self):
-		return self.sock
+    # Return socket's fileno (e.g. for select)
+    def fileno(self):
+	return self.sock.fileno()
 
-	# Return socket's fileno (e.g. for select)
-	def fileno(self):
-		return self.sock.fileno()
+    # Write a string to the socket, doubling any IAC characters
+    # Might block if the connection is blocked
+    # May raise socket.error if the connection is closed
+    def write(self, buffer):
+	if IAC in buffer:
+	    buffer = regsub.gsub(IAC, IAC+IAC, buffer)
+	self.sock.send(buffer)
 
-	# Write a string to the socket, doubling any IAC characters
-	def write(self, buffer):
-		if IAC in buffer:
-			buffer = regsub.gsub(IAC, IAC+IAC, buffer)
-		self.sock.send(buffer)
+    # The following read_* methods exist:
+    # Special case:
+    # - read_until() reads until a string is encountered or a timeout is hit
+    # These may block:
+    # - read_all() reads all data until EOF
+    # - read_some() reads at least one byte until EOF
+    # These may do I/O but won't block doing it:
+    # - read_very_eager() reads all data available on the socket
+    # - read_eager() reads either data already queued or some data
+    #                available on the socket
+    # These don't do I/O:
+    # - read_lazy() reads all data in the raw queue (processing it first)
+    # - read_very_lazy() reads all data in the cooked queue
 
-	# Read until a given string is encountered or until timeout
-	def read_until(self, match):
-##		self.msg('read_until(%s)' % `match`)
-		n = len(match)
-		self.process_rawq()
-		i = string.find(self.cookedq, match)
-		if i < 0:
-			i = max(0, len(self.cookedq)-n)
-			self.fill_cookedq()
-			i = string.find(self.cookedq, match, i)
-		if i >= 0:
-			i = i+n
-			buf = self.cookedq[:i]
-			self.cookedq = self.cookedq[i:]
-##			self.msg('read_until(%s) -> %s' % (`match`, `buf`))
-			return buf
-		while select.select([self], [], [], self.timeout) == \
-			  ([self], [], []):
-			i = max(0, len(self.cookedq)-n)
-			self.fill_rawq()
-			self.process_rawq()
-			i = string.find(self.cookedq, match, i)
-			if i >= 0:
-				i = i+n
-				buf = self.cookedq[:i]
-				self.cookedq = self.cookedq[i:]
-##				self.msg('read_until(%s) -> %s' %
-##					  (`match`, `buf`))
-				return buf
-		buf = self.cookedq
-		self.cookedq = ''
-##		self.msg('read_until(%s) -> %s' % (`match`, `buf`))
+    # Read until a given string is encountered or until timeout
+    # Raise EOFError if connection closed and no cooked data available
+    # Return '' if no cooked data available otherwise
+    def read_until(self, match, *args):
+	if not args:
+	    timeout = None
+	else:
+	    if len(args) > 1: raise TypeError, 'too many args'
+	    timeout = args[0]
+	n = len(match)
+	self.process_rawq()
+	i = string.find(self.cookedq, match)
+	if i >= 0:
+	    i = i+n
+	    buf = self.cookedq[:i]
+	    self.cookedq = self.cookedq[i:]
+	    return buf
+	s_reply = ([self], [], [])
+	s_args = s_reply
+	if timeout is not None:
+	    s_args = s_args + (timeout,)
+	while not self.eof and apply(select.select, s_args) == s_reply:
+	    i = max(0, len(self.cookedq)-n)
+	    self.fill_rawq()
+	    self.process_rawq()
+	    i = string.find(self.cookedq, match, i)
+	    if i >= 0:
+		i = i+n
+		buf = self.cookedq[:i]
+		self.cookedq = self.cookedq[i:]
 		return buf
+	return self.read_very_lazy()
 
-	# Read everything that's possible without really blocking
-	def read_now(self):
-		self.fill_cookedq()
-		buf = self.cookedq
-		self.cookedq = ''
-##		self.msg('read_now() --> %s' % `buf`)
-		return buf
+    # Read all data until EOF
+    # Block until connection closed
+    def read_all(self):
+	self.process_rawq()
+	while not self.eof:
+	    self.fill_rawq()
+	    self.process_rawq()
+	buf = self.cookedq
+	self.cookedq = ''
+	return buf
 
-	# Fill cooked queue without blocking
-	def fill_cookedq(self):
-		self.process_rawq()
-		while select.select([self], [], [], 0) == ([self], [], []):
-			self.fill_rawq()
-			if not self.rawq:
-				raise EOFError
-			self.process_rawq()
+    # Read at least one byte of cooked data unless EOF is hit
+    # Return '' if EOF is hit
+    # Block if no data is immediately available
+    def read_some(self):
+	self.process_rawq()
+	while not self.cookedq and not self.eof:
+	    self.fill_rawq()
+	    self.process_rawq()
+	buf = self.cookedq
+	self.cookedq = ''
+	return buf
 
-	# Transfer from raw queue to cooked queue
-	def process_rawq(self):
-		# There is some silliness going on here in an attempt
-		# to avoid quadratic behavior with large inputs...
-		buf = ''
-		while self.rawq:
-			c = self.rawq_getchar()
-			if c != IAC:
-				buf = buf + c
-				if len(buf) >= 44:
-##					self.msg('transfer: %s' % `buf`)
-					self.cookedq = self.cookedq + buf
-					buf = ''
-				continue
-			c = self.rawq_getchar()
-			if c == IAC:
-				buf = buf + c
-			elif c in (DO, DONT):
-				opt = self.rawq_getchar()
-				self.msg('IAC %s %d',
-					  c == DO and 'DO' or 'DONT',
-					  ord(c))
-				self.sock.send(IAC + WONT + opt)
-			elif c in (WILL, WONT):
-				opt = self.rawq_getchar()
-				self.msg('IAC %s %d',
-					  c == WILL and 'WILL' or 'WONT',
-					  ord(c))
-			else:
-				self.msg('IAC %s not recognized' % `c`)
-##		self.msg('transfer: %s' % `buf`)
-		self.cookedq = self.cookedq + buf
+    # Read everything that's possible without blocking in I/O (eager)
+    # Raise EOFError if connection closed and no cooked data available
+    # Return '' if no cooked data available otherwise
+    # Don't block unless in the midst of an IAC sequence
+    def read_very_eager(self):
+	self.process_rawq()
+	while not self.eof and self.sock_avail():
+	    self.fill_rawq()
+	    self.process_rawq()
+	return self.read_very_lazy()
 
-	# Get next char from raw queue, blocking if necessary
-	def rawq_getchar(self):
-		if not self.rawq:
-			self.fill_rawq()
-		if self.irawq >= len(self.rawq):
-			raise EOFError
-		c = self.rawq[self.irawq]
-		self.irawq = self.irawq + 1
-		if self.irawq >= len(self.rawq):
-			self.rawq = ''
-			self.irawq = 0
-		return c
+    # Read readily available data
+    # Raise EOFError if connection closed and no cooked data available
+    # Return '' if no cooked data available otherwise
+    # Don't block unless in the midst of an IAC sequence
+    def read_eager(self):
+	self.process_rawq()
+	while not self.cookedq and not self.eof and self.sock_avail():
+	    self.fill_rawq()
+	    self.process_rawq()
+	return self.read_very_lazy()
 
-	# Fill raw queue
-	def fill_rawq(self):
-		if self.irawq >= len(self.rawq):
-			self.rawq = ''
-			self.irawq = 0
-		buf = self.sock.recv(50)
-##		self.msg('fill_rawq(): %s' % `buf`)
-		self.rawq = self.rawq + buf
+    # Process and return data that's already in the queues (lazy)
+    # Raise EOFError if connection closed and no data available
+    # Return '' if no cooked data available otherwise
+    # Don't block unless in the midst of an IAC sequence
+    def read_lazy(self):
+	self.process_rawq()
+	return self.read_very_lazy()
+
+    # Return any data available in the cooked queue (very lazy)
+    # Raise EOFError if connection closed and no data available
+    # Return '' if no cooked data available otherwise
+    # Don't block
+    def read_very_lazy(self):
+	buf = self.cookedq
+	self.cookedq = ''
+	if not buf and self.eof and not self.rawq:
+	    raise EOFError, 'telnet connection closed'
+	return buf
+
+    # Transfer from raw queue to cooked queue
+    # Set self.eof when connection is closed
+    # Don't block unless in the midst of an IAC sequence
+    def process_rawq(self):
+	buf = ''
+	try:
+	    while self.rawq:
+		c = self.rawq_getchar()
+		if c != IAC:
+		    buf = buf + c
+		    continue
+		c = self.rawq_getchar()
+		if c == IAC:
+		    buf = buf + c
+		elif c in (DO, DONT):
+		    opt = self.rawq_getchar()
+		    self.msg('IAC %s %d', c == DO and 'DO' or 'DONT', ord(c))
+		    self.sock.send(IAC + WONT + opt)
+		elif c in (WILL, WONT):
+		    opt = self.rawq_getchar()
+		    self.msg('IAC %s %d',
+			  c == WILL and 'WILL' or 'WONT', ord(c))
+		else:
+		    self.msg('IAC %s not recognized' % `c`)
+	except EOFError: # raised by self.rawq_getchar()
+	    pass
+	self.cookedq = self.cookedq + buf
+
+    # Get next char from raw queue
+    # Block if no data is immediately available
+    # Raise EOFError when connection is closed
+    def rawq_getchar(self):
+	if not self.rawq:
+	    self.fill_rawq()
+	    if self.eof:
+		raise EOFError
+	c = self.rawq[self.irawq]
+	self.irawq = self.irawq + 1
+	if self.irawq >= len(self.rawq):
+	    self.rawq = ''
+	    self.irawq = 0
+	return c
+
+    # Fill raw queue from exactly one recv() system call
+    # Block if no data is immediately available
+    # Set self.eof when connection is closed
+    def fill_rawq(self):
+	if self.irawq >= len(self.rawq):
+	    self.rawq = ''
+	    self.irawq = 0
+	# The buffer size should be fairly small so as to avoid quadratic
+	# behavior in process_rawq() above
+	buf = self.sock.recv(50)
+	self.eof = (not buf)
+	self.rawq = self.rawq + buf
+
+    # Test whether data is available on the socket
+    def sock_avail(self):
+	return select.select([self], [], [], 0) == ([self], [], [])
+
+
+# Test program
+# Usage: test [-d] ... [host [port]]
+def test():
+    import sys, string, socket, select
+    debuglevel = 0
+    while sys.argv[1:] and sys.argv[1] == '-d':
+	debuglevel = debuglevel+1
+	del sys.argv[1]
+    host = 'localhost'
+    if sys.argv[1:]:
+	host = sys.argv[1]
+    port = 0
+    if sys.argv[2:]:
+	portstr = sys.argv[2]
+	try:
+	    port = string.atoi(portstr)
+	except string.atoi_error:
+	    port = socket.getservbyname(portstr, 'tcp')
+    tn = Telnet(host, port)
+    tn.set_debuglevel(debuglevel)
+    while 1:
+	rfd, wfd, xfd = select.select([tn, sys.stdin], [], [])
+	if sys.stdin in rfd:
+	    line = sys.stdin.readline()
+	    tn.write(line)
+	if tn in rfd:
+	    try:
+		text = tn.read_eager()
+	    except EOFError:
+		print '*** Connection closed by remote host ***'
+		break
+	    if text:
+		sys.stdout.write(text)
+		sys.stdout.flush()
+    tn.close()