diff --git a/.gitattributes b/.gitattributes
index 6387d6aee78f25ab200efb7a8fe9d7f7f7d45a60..5bd3ff54cffa2a644ddc01c397d76f394ab67679 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -74,6 +74,8 @@ testfont binary
 /lib/modules/Sql.pmod/sql.pike foreign_ident
 /lib/modules/Sql.pmod/sql_result.pike foreign_ident
 /lib/modules/Stdio.pmod foreign_ident
+/lib/modules/Stdio.pmod/Readline.pmod foreign_ident
+/lib/modules/Stdio.pmod/Terminfo.pmod foreign_ident
 /lib/modules/Stdio.pmod/module.pmod foreign_ident
 /lib/modules/Yabu.pmod/module.pmod foreign_ident
 /lib/modules/error.pmod foreign_ident
diff --git a/lib/modules/Stdio.pmod/Readline.pmod b/lib/modules/Stdio.pmod/Readline.pmod
new file mode 100644
index 0000000000000000000000000000000000000000..e8d9069c4dbcef5c3c02d7c2eb1f097d8f6f44a9
--- /dev/null
+++ b/lib/modules/Stdio.pmod/Readline.pmod
@@ -0,0 +1,964 @@
+// $Id: Readline.pmod,v 1.1 1999/03/13 01:12:37 marcus Exp $
+
+class OutputController
+{
+  static private object outfd, term;
+  static private int xpos = 0, columns = 0;
+
+  void check_columns()
+  {
+    catch {
+      int c = outfd->tcgetattr()->columns;
+      if(c)
+	columns = c;
+    };
+    if(!columns)
+      columns = term->tgetnum("co") || 80;
+  }
+
+  int get_number_of_columns()
+  {
+    return columns;
+  }
+
+  static string escapify(string s)
+  {
+    for(int i=0; i<strlen(s); i++)
+      if(s[i]<' ')
+	s = s[..i-1]+sprintf("^%c", s[i]+'@')+s[i+1..];
+      else if(s[i]==127)
+	s = s[..i-1]+"^?"+s[i+1..];
+      else if(s[i]>=128 && s[i]<160)
+	s = s[..i-1]+sprintf("~%c", s[i]-128+'@')+s[i+1..];
+    return s;
+  }
+
+  static int width(string s)
+  {
+    return strlen(s);
+  }
+
+  static int escapified_width(string s)
+  {
+    return width(escapify(s));
+  }
+
+  void low_write(string s)
+  {
+    int n = width(s);
+    if(!n)
+      return;
+    while(xpos+n>=columns) {
+      int l = columns-xpos;
+      outfd->write(s[..l-1]);
+      s = s[l..];
+      n -= l;
+      xpos = 0;
+      if(!term->tgetflag("am"))
+	outfd->write("\r\n");
+    }
+    if(xpos==0 && term->tgetflag("am"))
+      outfd->write(" "+term->put("le"));
+    if(n>0) {
+      outfd->write(s);
+      xpos += n;
+    }
+  }
+
+  void write(string s)
+  {
+    low_write(escapify(s));
+  }
+
+  void low_move_downward(int n)
+  {
+    if(n<=0)
+      return;
+    outfd->write(term->put("DO", n) || (term->put("do")||"")*n);
+  }
+
+  void low_move_upward(int n)
+  {
+    if(n<=0)
+      return;
+    outfd->write(term->put("UP", n) || (term->put("up")||"")*n);
+  }
+
+  void low_move_forward(int n)
+  {
+    if(n<=0)
+      return;
+    if(xpos+n<columns) {
+      outfd->write(term->put("RI", n) || (term->put("ri")||"")*n);
+      xpos += n;
+    } else {
+      int l = (xpos+n)/columns;
+      low_move_downward(l);
+      n -= l*columns;
+      if(n<0)
+	low_move_backward(-n);
+      else if(n>0)
+	low_move_forward(n);
+    }
+  }
+
+  void low_move_backward(int n)
+  {
+    if(n<=0)
+      return;
+    if(xpos-n>=0) {
+      outfd->write(term->put("LE", n) || (term->put("le")||"")*n);
+      xpos -= n;
+    } else {
+      int l = 1+(n-xpos-1)/columns;
+      low_move_upward(l);
+      n -= l*columns;
+      if(n<0)
+	low_move_forward(-n);
+      else if(n>0)
+	low_move_backward(n);
+    }
+  }
+
+  void low_erase(int n)
+  {
+    string e = term->put("ec", n);
+    if (e)
+      outfd->write(e);
+    else
+    {
+      low_write(" "*n);
+      low_move_backward(n);
+    }
+  }
+
+  void move_forward(string s)
+  {
+    low_move_forward(escapified_width(s));
+  }
+
+  void move_backward(string s)
+  {
+    low_move_backward(escapified_width(s));
+  }
+
+  void erase(string s)
+  {
+    low_erase(escapified_width(s));
+  }
+
+  void newline()
+  {
+    outfd->write("\r\n");
+    xpos = 0;
+  }
+
+  void bol()
+  {
+    outfd->write("\r");
+    xpos = 0;
+  }
+
+  void clear(int|void partial)
+  {
+    string s;
+    if(!partial && (s = term->put("cl"))) {
+      outfd->write(s);
+      xpos = 0;
+      return;
+    }
+    if(!partial) {
+      outfd->write(term->put("ho")||term->put("cm", 0, 0)||"\f");
+      xpos = 0;
+    }
+    outfd->write(term->put("cd")||"");
+  }
+
+  void create(object|void _outfd, object|string|void _term)
+  {
+    outfd = _outfd || Stdio.File("stdout");
+    term = objectp(_term)? _term : .Terminfo.getTerm(_term);
+    check_columns();
+  }
+
+}
+
+class InputController
+{
+  static private object infd, term;
+  static private int enabled = -1;
+  static private function(:int) close_callback = 0;
+  static private string prefix="";
+  static private mapping(int:function|mapping(string:function)) bindings=([]);
+  static private function grab_binding = 0;
+  static private mapping oldattrs = 0;
+
+  void destroy()
+  {
+    catch{ infd->set_blocking(); };
+    catch{ if(oldattrs) infd->tcsetattr(oldattrs); };
+    catch{ infd->tcsetattr((["ECHO":1,"ICANON":1])); };
+  }
+
+  static private string process_input(string s)
+  {
+    int i;
+
+    for (i=0; i<sizeof(s); i++)
+    {
+      if (!enabled)
+	return s[i..];
+      function|mapping(string:function) b = grab_binding || bindings[s[i]];
+      grab_binding = 0;
+      if (!b)
+	/* do nothing */;
+      else if(mappingp(b)) {
+	int ml = 0, l = sizeof(s)-i;
+	string m;
+	foreach (indices(b), string k)
+	{
+	  if (sizeof(k)>l && k[..l-1]==s[i..])
+	    /* Valid prefix, need more data */
+	    return s[i..];
+	  else if (sizeof(k) > ml && s[i..i+sizeof(k)-1] == k)
+	  {
+	    /* New longest match found */
+	    ml = sizeof(m = k);
+	  }
+	}
+	if (ml)
+	{
+	  i += ml-1;
+	  b[m](m);
+	}
+      } else
+	b(s[i..i]);
+    }
+    return "";
+  }
+
+  static private void read_cb(mixed _, string s)
+  {
+    if (!s || s=="")
+      return;
+    if (sizeof(prefix))
+    {
+      s = prefix+s;
+      prefix = "";
+    }
+    prefix = process_input(s);
+  }
+
+  static private void close_cb()
+  {
+    if (close_callback && close_callback())
+      return;
+    destruct(this_object());
+  }
+
+  static private int set_enabled(int e)
+  {
+    if (e != enabled)
+    {
+      enabled = e;
+      if (enabled)
+      {
+	string oldprefix = prefix;
+	prefix = "";
+	prefix = process_input(oldprefix);
+	infd->set_nonblocking(read_cb, 0, close_cb);
+      }
+      else
+	infd->set_blocking();
+      return !enabled;
+    }
+    else
+      return enabled;
+  }
+
+  int isenabled()
+  {
+    return enabled;
+  }
+
+  int enable(int ... e)
+  {
+    if (sizeof(e))
+      return set_enabled(!!e[0]);
+    else
+      return set_enabled(1);
+  }
+
+  int disable()
+  {
+    return set_enabled(0);
+  }
+
+  void run_blocking()
+  {
+    disable();
+    string data = prefix;
+    prefix = "";
+    enabled = 1;
+    for (;;)
+    {
+      prefix = process_input(data);
+      if (!enabled)
+	return;
+      data = prefix+infd->read(1024, 1);
+      prefix = "";
+    }
+  }
+
+  void set_close_callback(function (:int) ccb)
+  {
+    close_callback = ccb;
+  }
+
+  void nullbindings()
+  {
+    bindings = ([]);
+  }
+
+  void grabnextkey(function g)
+  {
+    if(functionp(g))
+      grab_binding = g;
+  }
+
+  function bindstr(string str, function f)
+  {
+    function oldf = 0;
+    if (mappingp(f))
+      f = 0; // Paranoia
+    switch (sizeof(str||""))
+    {
+    case 0:
+      break;
+    case 1:
+      if (mappingp(bindings[str[0]]))
+      {
+	oldf = bindings[str[0]][str];
+	if (f)
+	  bindings[str[0]][str] = f;
+	else
+	  m_delete(bindings[str[0]], str);
+      } else {
+	oldf = bindings[str[0]];
+	if (f)
+	  bindings[str[0]] = f;
+	else
+	  m_delete(bindings, str[0]);
+      }
+      break;
+    default:
+      if (mappingp(bindings[str[0]]))
+	oldf = bindings[str[0]][str];
+      else
+	bindings[str[0]] =
+	  bindings[str[0]]? ([str[0..0]:bindings[str[0]]]) : ([]);
+      if (f)
+	bindings[str[0]][str] = f;
+      else {
+	m_delete(bindings[str[0]], str);
+	if (!sizeof(bindings[str[0]]))
+	  m_delete(bindings, str[0]);
+	else if(sizeof(bindings[str[0]])==1 && bindings[str[0]][str[0..0]])
+	  bindings[str[0]] = bindings[str[0]][str[0..0]];
+      }
+      break;
+    }
+    return oldf;
+  }
+
+  function unbindstr(string str)
+  {
+    return bindstr(str, 0);
+  }
+
+  function getbindingstr(string str)
+  {
+    switch (sizeof(str||""))
+    {
+    case 0:
+      return 0;
+    case 1:
+      return mappingp(bindings[str[0]])?
+	bindings[str[0]][str] : bindings[str[0]];
+    default:
+      return mappingp(bindings[str[0]]) && bindings[str[0]][str];
+    }
+  }
+
+  function bindtc(string cap, function f)
+  {
+    return bindstr(term->tgetstr(cap), f);
+  }
+
+  function unbindtc(string cap)
+  {
+    return unbindstr(term->tgetstr(cap));
+  }
+
+  function getbindingtc(string cap)
+  {
+    return getbindingstr(term->tgetstr(cap));
+  }
+
+  string parsekey(string k)
+  {
+    if (k[..1]=="\\!")
+      k = term->tgetstr(k[2..]);
+    else for(int i=0; i<sizeof(k); i++)
+      switch(k[i])
+      {
+      case '\\':
+	if(i<sizeof(k)-1) 
+	  switch(k[i+1]) {
+	  case '0':
+	  case '1':
+	  case '2':
+	  case '3':
+	  case '4':
+	  case '5':
+	  case '6':
+	  case '7':
+	    int n, l;
+	    if (2<sscanf(k[i+1..], "%o%n", n, l))
+	    {
+	      n = k[i+1]-'0';
+	      l = 1;
+	    }
+	    k = k[..i-1]+sprintf("%c", n)+k[i+1+l..];
+	    break;
+	  case 'e':
+	  case 'E':
+	    k = k[..i-1]+"\033"+k[i+2..];
+	    break;
+	  case 'n':
+	    k = k[..i-1]+"\n"+k[i+2..];
+	    break;
+	  case 'r':
+	    k = k[..i-1]+"\r"+k[i+2..];
+	    break;
+	  case 't':
+	    k = k[..i-1]+"\t"+k[i+2..];
+	    break;
+	  case 'b':
+	    k = k[..i-1]+"\b"+k[i+2..];
+	    break;
+	  case 'f':
+	    k = k[..i-1]+"\f"+k[i+2..];
+	    break;
+	  default:
+	    k = k[..i-1]+k[i+1..];
+	    break;
+	  }
+	break;
+      case '^':
+	if(i<sizeof(k)-1 && k[i+1]>='?' && k[i+1]<='_') 
+	  k = k[..i-1]+(k[i+1]=='?'? "\177":sprintf("%c",k[i+1]-'@'))+k[i+2..];
+	break;
+      }
+    return k;
+  }
+
+  function bind(string k, function f)
+  {
+    return bindstr(parsekey(k), f);
+  }
+
+  function unbind(string k)
+  {
+    return unbindstr(parsekey(k));
+  }
+
+  function getbinding(string k, string cap)
+  {
+    return getbindingstr(parsekey(k));
+  }
+
+  mapping(string:function) getbindings()
+  {
+    mapping(int:function) bb = bindings-Array.filter(bindings, mappingp);
+    return `|(mkmapping(Array.map(indices(bb), lambda(int n) {
+						 return sprintf("%c", n);
+					       }), values(bb)),
+	      @Array.filter(values(bindings), mappingp));
+  }
+
+  void create(object|void _infd, object|string|void _term)
+  {
+    infd = _infd || Stdio.File("stdin");
+    term = objectp(_term)? _term : .Terminfo.getTerm(_term);
+    disable();
+    catch { oldattrs = infd->tcgetattr(); };
+    catch { infd->tcsetattr((["ECHO":0,"ICANON":0,"VMIN":1,"VTIME":0,
+			      "VLNEXT":0])); };
+  }
+
+}
+
+class DefaultEditKeys
+{
+
+  static object _readline;
+
+  void self_insert_command(string str)
+  {
+    _readline->insert(str, _readline->getcursorpos());
+  }
+
+  void quoted_insert()
+  {
+    _readline->get_input_controller()->grabnextkey(self_insert_command);
+  }
+
+  void newline()
+  {
+    _readline->newline();
+  }
+
+  void up_history()
+  {
+    _readline->delta_history(-1);
+  }
+
+  void down_history()
+  {
+    _readline->delta_history(1);
+  }
+
+  void backward_delete_char()
+  {
+    int p = _readline->getcursorpos();
+    _readline->delete(p-1,p);
+  }
+
+  void delete_char_or_eof()
+  {
+    int p = _readline->getcursorpos();
+    if (p<strlen(_readline->gettext()))
+      _readline->delete(p,p+1);
+    else if(!strlen(_readline->gettext()))
+      _readline->eof();
+  }
+
+  void forward_char()
+  {
+    _readline->setcursorpos(_readline->getcursorpos()+1);
+  }
+
+  void backward_char()
+  {
+    _readline->setcursorpos(_readline->getcursorpos()-1);
+  }
+
+  void beginning_of_line()
+  {
+    _readline->setcursorpos(0);
+  }
+
+  void end_of_line()
+  {
+    _readline->setcursorpos(strlen(_readline->gettext()));
+  }
+
+  void transpose_chars()
+  {
+    int p = _readline->getcursorpos();
+    if (p<0 || p>=strlen(_readline->gettext()))
+      return;
+    string c = _readline->gettext()[p-1..p];
+    _readline->delete(p-1, p+1);
+    _readline->insert(reverse(c), p-1);
+  }
+
+  void kill_line()
+  {
+    _readline->delete(_readline->getcursorpos(), strlen(_readline->gettext()));
+  }
+
+  void kill_whole_line()
+  {
+    _readline->delete(0, strlen(_readline->gettext()));
+  }
+
+  void redisplay()
+  {
+    _readline->redisplay(0);
+  }
+
+  void clear_screen()
+  {
+    _readline->redisplay(1);
+  }
+
+  static array(array(string|function)) default_bindings = ({
+    ({ "^[[A", up_history }),
+    ({ "^[[B", down_history }),
+    ({ "^[[C", forward_char }),
+    ({ "^[[D", backward_char }),
+    ({ "^A", beginning_of_line }),
+    ({ "^B", backward_char }),
+    ({ "^D", delete_char_or_eof }),
+    ({ "^E", end_of_line }),
+    ({ "^F", forward_char }),
+    ({ "^H", backward_delete_char }),
+    ({ "^J", newline }),
+    ({ "^K", kill_line }),
+    ({ "^L", clear_screen }),
+    ({ "^M", newline }),
+    ({ "^N", down_history }),
+    ({ "^P", up_history }),
+    ({ "^R", redisplay }),
+    ({ "^T", transpose_chars }),
+    ({ "^U", kill_whole_line }),
+    ({ "^V", quoted_insert }),
+    ({ "^?", backward_delete_char }),
+    ({ "\\!ku", up_history }),
+    ({ "\\!kd", down_history }),
+    ({ "\\!kr", forward_char }),
+    ({ "\\!kl", backward_char })
+  });
+
+  static void set_default_bindings()
+  {
+    object ic = _readline->get_input_controller();
+    ic->nullbindings();
+    for(int i=' '; i<'\177'; i++)
+      ic->bindstr(sprintf("%c", i), self_insert_command);
+    for(int i='\240'; i<='\377'; i++)
+      ic->bindstr(sprintf("%c", i), self_insert_command);
+    foreach(default_bindings, array(string|function) b)
+      ic->bind(@b);
+  }
+
+  void create(object readline)
+  {
+    _readline = readline;
+    set_default_bindings();
+  }
+
+}
+
+class History
+{
+  static private array(string) historylist;
+  static private int minhistory, maxhistory, historynum;
+
+  int get_history_num()
+  {
+    return historynum;
+  }
+
+  string history(int n, string text)
+  {
+    if (n<minhistory)
+      n = minhistory;
+    else if (n-minhistory>=sizeof(historylist))
+      n = sizeof(historylist)+minhistory-1;
+    historylist[historynum-minhistory]=text;
+    return historylist[(historynum=n)-minhistory];
+  }
+
+  void initline()
+  {
+    if (sizeof(historylist)==0 || historylist[-1]!="") {
+      historylist += ({ "" });
+      if (maxhistory && sizeof(historylist)>maxhistory)
+      {
+	int n = sizeof(historylist)-maxhistory;
+	historylist = historylist[n..];
+	minhistory += n;
+      }
+    }
+    historynum = sizeof(historylist)-1+minhistory;
+  }
+
+  void finishline(string text)
+  {
+    historylist[historynum-minhistory]=text;
+  }
+
+  void set_max_history(int maxhist)
+  {
+    maxhistory = maxhist;
+  }
+
+  void create(int maxhist)
+  {
+    historylist = ({ "" });
+    minhistory = historynum = 0;
+    maxhistory = maxhist;
+  }
+
+}
+
+class Readline
+{
+  static private object(OutputController) output_controller;
+  static private object(InputController) input_controller;
+  static private string prompt="";
+  static private string text="", readtext;
+  static private function(string:void) newline_func;
+  static private int cursorpos = 0;
+  static private object(History) historyobj = 0;
+
+  object(OutputController) get_output_controller()
+  {
+    return output_controller;
+  }
+
+  object(InputController) get_input_controller()
+  {
+    return input_controller;
+  }
+
+  string get_prompt()
+  {
+    return prompt;
+  }
+
+  string set_prompt(string newp)
+  {
+    string oldp = prompt;
+    prompt = newp;
+    return oldp;
+  }
+
+  string gettext()
+  {
+    return text;
+  }
+
+  int getcursorpos()
+  {
+    return cursorpos;
+  }
+
+  int setcursorpos(int p)
+  {
+    if (p<0)
+      p = 0;
+    if (p>strlen(text))
+      p = strlen(text);
+    if (p<cursorpos)
+    {
+      output_controller->move_backward(text[p..cursorpos-1]);
+      cursorpos = p;
+    }
+    else if (p>cursorpos)
+    {
+      output_controller->move_forward(text[cursorpos..p-1]);
+      cursorpos = p;
+    }
+    return cursorpos;
+  }
+
+  void insert(string s, int p)
+  {
+    if (p<0)
+      p = 0;
+    if (p>strlen(text))
+      p = strlen(text);
+    setcursorpos(p);
+    output_controller->write(s);
+    cursorpos += strlen(s);
+    string rest = text[p..];
+    if (strlen(rest))
+    {
+      output_controller->write(rest);
+      output_controller->move_backward(rest);
+    }
+    text = text[..p-1]+s+rest;
+  }
+
+  void delete(int p1, int p2)
+  {
+    if (p1<0)
+      p1 = 0;
+    if (p2>strlen(text))
+      p2 = strlen(text);
+    setcursorpos(p1);
+    if (p1>=p2)
+      return;
+    output_controller->write(text[p2..]);
+    output_controller->erase(text[p1..p2-1]);
+    text = text[..p1-1]+text[p2..];
+    cursorpos = strlen(text);
+    setcursorpos(p1);
+  }
+
+  void history(int n)
+  {
+    if(historyobj) {
+      string h = historyobj->history(n, text);
+      delete(0, sizeof(text));
+      insert(h, 0);
+    }
+  }
+
+  void delta_history(int d)
+  {
+    if(historyobj)
+      history(historyobj->get_history_num()+d);
+  }
+
+  void redisplay(int clear, int|void nobackup)
+  {
+    int p = cursorpos;
+    if(clear)
+      output_controller->clear();
+    else if(!nobackup) {
+      setcursorpos(0);
+      output_controller->bol();
+      output_controller->clear(1);
+    }
+    output_controller->check_columns();
+    if(newline_func == read_newline)
+      output_controller->write(prompt);
+    output_controller->write(text);
+    cursorpos = sizeof(text);
+    setcursorpos(p);
+  }
+
+  static private void initline()
+  {
+    text = "";
+    cursorpos = 0;
+    if (historyobj)
+      historyobj->initline();
+  }
+
+  string newline()
+  {
+    setcursorpos(sizeof(text));
+    output_controller->newline();
+    string data = text;
+    if (historyobj)
+      historyobj->finishline(text);
+    initline();
+    if(newline_func)
+      newline_func(data);
+  }
+
+  void eof()
+  {
+    if (historyobj)
+      historyobj->finishline(text);
+    initline();
+    if(newline_func)
+      newline_func(0);    
+  }
+
+  static private void read_newline(string s)
+  {
+    input_controller->disable();
+    readtext = s;
+  }
+
+  void set_nonblocking(function f)
+  {
+    if (newline_func = f)
+      input_controller->enable();
+    else
+      input_controller->disable();
+  }
+
+  void set_blocking()
+  {
+    set_nonblocking(0);
+  }
+
+  string read()
+  {
+    if(newline_func == read_newline)
+      return 0;
+    function oldnl = newline_func;
+    output_controller->write(prompt);
+    initline();
+    newline_func = read_newline;
+    readtext = "";
+    input_controller->run_blocking();
+    set_nonblocking(oldnl);
+    return readtext;
+  }
+
+  void enable_history(object(History)|int hist)
+  {
+    if (objectp(hist))
+      historyobj = hist;
+    else if(!hist)
+      historyobj = 0;
+    else if(historyobj)
+      historyobj->set_max_history(hist);
+    else
+      historyobj = History(hist);
+  }
+
+  void destroy()
+  {
+    destruct(input_controller);
+    destruct(output_controller);
+  }
+
+  void create(object|void infd, object|string|void interm,
+	      object|void outfd, object|string|void outterm)
+  {
+    output_controller = OutputController(outfd || infd, outterm || interm);
+    input_controller = InputController(infd, interm);
+    DefaultEditKeys(this_object());
+  }
+}
+
+
+
+/* Emulation of old readline() function.  Don't use in new code. */
+
+static private object(History) readline_history = History(512);
+
+string readline(string prompt, function|void complete_callback)
+{
+  object rl = Readline();
+  rl->enable_history(readline_history);
+  rl->set_prompt(prompt);
+  if(complete_callback)
+    rl->get_input_controller()->
+      bind("^I", lambda() {
+		   array(string) compl = ({ });
+		   string c, buf = rl->gettext();
+		   int st = 0, point = rl->getcursorpos();
+		   int wordstart = search(replace(reverse(buf),
+						  ({"\t","\r","\n"}),
+						  ({" "," "," "})),
+					  " ", sizeof(buf)-point);
+		   string word = buf[(wordstart>=0 && sizeof(buf)-wordstart)..
+				    point-1];
+		   while((c = complete_callback(word, st++, buf, point)))
+		     compl += ({ c });
+		   switch(sizeof(compl)) {
+		   case 0:
+		     break;
+		   case 1:
+		     rl->delete(point-sizeof(word), point);
+		     rl->insert(compl[0], point-sizeof(word));
+		     rl->setcursorpos(point-sizeof(word)+sizeof(compl[0]));
+		     break;
+		   default:
+		     rl->setcursorpos(strlen(rl->gettext()));
+		     rl->get_output_controller()->newline();
+		     foreach(sprintf("%-"+rl->get_output_controller()->
+				     get_number_of_columns()+"#s", compl*"\n")
+			     /"\n", string l) {
+		       rl->get_output_controller()->write(l);
+		       rl->get_output_controller()->newline();
+		     }
+		     rl->redisplay(0, 1);
+		     rl->setcursorpos(point);
+		   }
+		 });
+  string res = rl->read();
+  destruct(rl);
+  return res;
+}
diff --git a/lib/modules/Stdio.pmod/Terminfo.pmod b/lib/modules/Stdio.pmod/Terminfo.pmod
new file mode 100644
index 0000000000000000000000000000000000000000..e22ac5dfa22376eca46c7e50f7221c25a89cb600
--- /dev/null
+++ b/lib/modules/Stdio.pmod/Terminfo.pmod
@@ -0,0 +1,756 @@
+// $Id: Terminfo.pmod,v 1.1 1999/03/13 01:12:37 marcus Exp $
+
+
+#if constant(thread_create)
+#define LOCK object m_key = mutex->lock()
+#define UNLOCK destruct(m_key)
+#define MUTEX static private object mutex = Thread.Mutex();
+#else
+#define LOCK
+#define UNLOCK
+#define MUTEX
+#endif
+
+MUTEX
+
+static private array ctrlcharsfrom =
+   Array.map(indices(allocate(32)),
+	     lambda(int z) { return sprintf("^%c",z+64); })+
+   Array.map(indices(allocate(32)),
+	     lambda(int z) { return sprintf("^%c",z+96); });
+static private array ctrlcharsto =
+   Array.map(indices(allocate(32)),
+	     lambda(int z) { return sprintf("%c",z); })+
+   Array.map(indices(allocate(32)),
+	     lambda(int z) { return sprintf("%c",z); });
+
+
+static private class TermMachine {
+
+  mapping(string:string|int) map = ([]);
+
+  int tgetflag(string id)
+  {
+    return map[id]==1;
+  }
+
+  int tgetnum(string id)
+  {
+    return intp(map[id]) && map[id];
+  }
+  
+  string tgetstr(string id)
+  {
+    return stringp(map[id]) && map[id];
+  }
+
+  string tparam(string f, mixed ... args)
+  {
+    array fmt=f/"%";
+    string res=fmt[0];
+    string tmp;
+    int z;
+    mapping var=([]);
+    array args0=args;
+    
+#define POP (z=args[0],args=args[1..],z)
+#define PUSH(x) (args=({x})+args)
+    
+    while ( (fmt=fmt[1..])!=({}) )
+      if (fmt[0]=="") res+="%";
+      else
+      {
+	switch (fmt[0][0])
+	{
+	case 'd': res+=sprintf("%d%s",POP,fmt[0][1..]); break;
+	case 'x': res+=sprintf("%x%s",POP,fmt[0][1..]); break;
+	case '0': case '2': case '3':
+	  sscanf(fmt[0],"%[0-9]%s",tmp,fmt[0]);
+	  res+=sprintf("%"+tmp+fmt[0][..0]+"%s",POP,fmt[0][1..]);
+	  break;
+	case 'c': res+=sprintf("%c%s",POP,fmt[0][1..]); break;
+	case 's': res+=sprintf("%s%s",POP,fmt[0][1..]); break;
+	  
+	case '\'': 
+	  sscanf(fmt[0],"'%s'%s",tmp,fmt[0]);
+	  if (tmp=="") tmp="\0";
+	  if (tmp[0]=='\\') tmp=sprintf("%c",(int)("0"+tmp[1..]));
+	  PUSH(tmp[0]);
+	  res+=fmt[0];
+	  break;
+	case '{': 
+	  sscanf(fmt[0],"{%d}%s",z,fmt[0]);
+	  res+=fmt[0];
+	  PUSH(z);
+	  break;
+	case 'p':
+	  PUSH(args0[fmt[0][1]-'1']);
+	  res+=fmt[0][2..];
+	  break;
+	case 'P':
+	  var[fmt[0][1]]=POP;
+	  res+=fmt[0][2..];
+	  break;
+	case 'g':
+	  PUSH(var[fmt[0][1]]);
+	  res+=fmt[0][2..];
+	  break;
+	case 'i':
+	  args[0]+=1;
+	  args[1]+=1;
+	  break;
+	case '+': PUSH(POP+POP); res+=fmt[0][1..]; break;
+	case '-': PUSH(POP-POP); res+=fmt[0][1..]; break;
+	case '*': PUSH(POP*POP); res+=fmt[0][1..]; break;
+	case '/': PUSH(POP/POP); res+=fmt[0][1..]; break;
+	case 'm': PUSH(POP%POP); res+=fmt[0][1..]; break;
+	case '&': PUSH(POP&POP); res+=fmt[0][1..]; break;
+	case '|': PUSH(POP|POP); res+=fmt[0][1..]; break;
+	case '^': PUSH(POP^POP); res+=fmt[0][1..]; break;
+	case '=': PUSH(POP==POP); res+=fmt[0][1..]; break;
+	case '>': PUSH(POP>POP); res+=fmt[0][1..]; break;
+	case '<': PUSH(POP<POP); res+=fmt[0][1..]; break;
+	case 'A': PUSH(POP && POP); res+=fmt[0][1..]; break;
+	case 'O': PUSH(POP || POP); res+=fmt[0][1..]; break;
+	case '!': PUSH(!POP); res+=fmt[0][1..]; break;
+	case '~': PUSH(~POP); res+=fmt[0][1..]; break;
+	case '?':
+	  error("Sorry, Terminal can't handle if-else's\n");
+	default:
+	  error("Unknown opcode: %%%s\n",fmt[0][..0]);
+	}
+      }
+    return res;
+  }
+
+  string tgoto(string cap, int col, int row)
+  {
+    return tparam(cap, col, row);
+  }
+
+  string tputs(string s)
+  {
+    return s;
+  }
+
+  string put(string cap, mixed ... args)
+  {
+    string str = tgetstr(cap);
+    string tstr = str && tparam(str, @args);
+    return tstr && tputs(tstr);
+  }
+
+}
+
+class Termcap {
+
+  inherit TermMachine;
+
+  array(string) aliases;
+  object parent;
+
+  string tputs(string s)
+  {
+    // Delay stuff completely ignored...
+    sscanf(s, "%*d%s", s);
+    return s;
+  }
+
+  private static multiset(string) load_cap(string en)
+  {
+    string br=":";
+    int i=search(en,":");
+    int j=search(en,",");
+    multiset(string) clears = (<>);
+
+    if (i==-1) { i=j; br=","; }
+    else if (j!=-1) { i=min(i,j); if (i==j) br=","; }
+    if (i<1)
+      error("Termcap: Unparsable entry\n");
+    aliases=en[..i-1]/"|";
+    en=en[i..];
+    
+    while (en!="")
+    {
+      string name;
+      string data;
+      sscanf(en,"%*[ \t]%[a-zA-Z_0-9&]%s"+br+"%s",name,data,en);
+      
+      if (data=="") // boolean
+      {
+	if (name!="") map[name]=1;
+      }
+      else if (data[0]=='@') // boolean off
+      {
+	clears[name]=1;
+      }
+      else if (data[0]=='#') // number
+      {
+	int z;
+	sscanf(data,"#%d",z);
+	map[name]=z;
+      }
+      else if (data[0]=='=') // string
+      {
+	data=data[1..];
+	while (data[-1]=="\\")
+	{
+	  string add;
+	  if (sscanf(en,"%s"+br+"%s",add,en)<2) break;
+	  data+="\\"+add;
+	}
+	
+	data=replace(data,"\\^","\\*");
+	
+	if (search(data,"^")!=-1)
+	  data=replace(data,ctrlcharsfrom,ctrlcharsto);
+	
+	data = replace(data,
+		  ({"\\E","\\e","\\n","\\r","\\t","\\b","\\f",
+		    "\\*","\\\\","\\,","\\:","#",
+		    "\\0","\\1","\\2","\\3","\\4","\\5","\\6","\\7"}),
+		  ({"\033","\033","\n","\r","\t","\b","\f",
+		    "^","\\",",",":","#!",
+		    "#0","#1","#2","#3","#4","#5","#6","#7"}));
+
+	array(string) parts = data/"#";
+	data = parts[0];
+	foreach (parts[1..], string p)
+	  if (sizeof(p) && p[0]=='!')
+	    data += "#"+p[1..];
+	  else
+	  {
+	    int n;
+	    string x;
+	    if(2==sscanf(p[..2], "%o%s", n, x))
+	      data += sprintf("%c%s%s", n, x, p[3..]);
+	    else
+	      data += p;
+	  }
+
+	map[name]=data;
+	
+      }
+      else // wierd
+      {
+	// ignore
+      }
+    }
+
+    return clears;
+  }
+
+  void create(string cap, object|void tcdb, int|void maxrecurse)
+  {
+    int i=0;
+    while((i=search(cap, "\\\n", i))>=0) {
+      string capr;
+      if(2!=sscanf(cap[i..], "\\\n%*[ \t\r]%s", capr))
+	break;
+      cap = cap[..i-1]+capr;
+    }
+    multiset(string) clears = load_cap(cap);
+    if(map->tc) {
+      if(maxrecurse==1)
+	error("Termcap: maximum inheritance depth exceeded (loop?)\n");
+      parent = (tcdb||defaultTermcapDB())->
+	load(map->tc, maxrecurse? (maxrecurse-1):25);
+      if(!parent)
+	error("Termcap: can't find parent terminal \"%s\"\n", map->tc);
+      map = parent->map | map;
+    }
+    map |= mkmapping(indices(clears), allocate(sizeof(clears)));
+  }
+}
+
+
+
+class Terminfo {
+
+  inherit TermMachine;
+
+  array(string) aliases;
+
+  static private constant boolnames =
+  ({ "bw","am","xb","xs","xn","eo","gn","hc","km","hs","in","da","db","mi",
+     "ms","os","es","xt","hz","ul","xo","nx","5i","HC","NR","NP","ND","cc",
+     "ut","hl","YA","YB","YC","YD","YE","YF","YG" });
+  static private constant numnames =
+  ({ "co","it","li","lm","sg","pb","vt","ws","Nl","lh","lw","ma","MW","Co",
+     "pa","NC","Ya","Yb","Yc","Yd","Ye","Yf","Yg","Yh","Yi","Yj","Yk","Yl",
+     "Ym","Yn","BT","Yo","Yp" });
+  static private constant strnames =
+  ({ "bt","bl","cr","cs","ct","cl","ce","cd","ch","CC","cm","do","ho","vi",
+     "le","CM","ve","nd","ll","up","vs","dc","dl","ds","hd","as","mb","md",
+     "ti","dm","mh","im","mk","mp","mr","so","us","ec","ae","me","te","ed",
+     "ei","se","ue","vb","ff","fs","i1","is","i3","if","ic","al","ip","kb",
+     "ka","kC","kt","kD","kL","kd","kM","kE","kS","k0","k1","k;","k2","k3",
+     "k4","k5","k6","k7","k8","k9","kh","kI","kA","kl","kH","kN","kP","kr",
+     "kF","kR","kT","ku","ke","ks","l0","l1","la","l2","l3","l4","l5","l6",
+     "l7","l8","l9","mo","mm","nw","pc","DC","DL","DO","IC","SF","AL","LE",
+     "RI","SR","UP","pk","pl","px","ps","pf","po","rp","r1","r2","r3","rf",
+     "rc","cv","sc","sf","sr","sa","st","wi","ta","ts","uc","hu","iP","K1",
+     "K3","K2","K4","K5","pO","rP","ac","pn","kB","SX","RX","SA","RA","XN",
+     "XF","eA","LO","LF","@1","@2","@3","@4","@5","@6","@7","@8","@9","@0",
+     "%1","%2","%3","%4","%5","%6","%7","%8","%9","%0","&1","&2","&3","&4",
+     "&5","&6","&7","&8","&9","&0","*1","*2","*3","*4","*5","*6","*7","*8",
+     "*9","*0","#1","#2","#3","#4","%a","%b","%c","%d","%e","%f","%g","%h",
+     "%i","%j","!1","!2","!3","RF","F1","F2","F3","F4","F5","F6","F7","F8",
+     "F9","FA","FB","FC","FD","FE","FF","FG","FH","FI","FJ","FK","FL","FM",
+     "FN","FO","FP","FQ","FR","FS","FT","FU","FV","FW","FX","FY","FZ","Fa",
+     "Fb","Fc","Fd","Fe","Ff","Fg","Fh","Fi","Fj","Fk","Fl","Fm","Fn","Fo",
+     "Fp","Fq","Fr","cb","MC","ML","MR","Lf","SC","DK","RC","CW","WG","HU",
+     "DI","QD","TO","PU","fh","PA","WA","u0","u1","u2","u3","u4","u5","u6",
+     "u7","u8","u9","op","oc","Ic","Ip","sp","Sf","Sb","ZA","ZB","ZC","ZD",
+     "ZE","ZF","ZG","ZH","ZI","ZJ","ZK","ZL","ZM","ZN","ZO","ZP","ZQ","ZR",
+     "ZS","ZT","ZU","ZV","ZW","ZX","ZY","ZZ","Za","Zb","Zc","Zd","Ze","Zf",
+     "Zg","Zh","Zi","Zj","Zk","Zl","Zm","Zn","Zo","Zp","Zq","Zr","Zs","Zt",
+     "Zu","Zv","Zw","Zx","Zy","Km","Mi","RQ","Gm","AF","AB","xl","dv","ci",
+     "s0","s1","s2","s3","ML","MT","Xy","Zz","Yv","Yw","Yx","Yy","Yz","YZ",
+     "S1","S2","S3","S4","S5","S6","S7","S8","Xh","Xl","Xo","Xr","Xt","Xv",
+     "sA","sL" });
+
+  string tputs(string s)
+  {
+    // Delay stuff completely ignored...
+    string pre, post;
+    while (3==sscanf(s, "%s$<%*[0-9.]>%s", pre, post))
+      s = pre+post;
+    return s;
+  }
+
+  static private string swab(string s)
+  {
+    return Array.map(s/2, reverse)*"";
+  }
+
+  static private int load_cap(object f)
+  {
+    int magic, sname, nbool, nnum, nstr, sstr;
+
+    if (6!=sscanf(swab(f->read(12)), "%2c%2c%2c%2c%2c%2c",
+		  magic, sname, nbool, nnum, nstr, sstr) ||
+	magic != 0432)
+      return 0;
+    aliases = (f->read(sname)-"\0")/"|";
+    {
+      array(int) bools = values(f->read(nbool+(nbool&1))[..nbool-1]);
+      if (sizeof(bools)>sizeof(boolnames))
+	bools = bools[..sizeof(boolnames)-1];
+      map = mkmapping(boolnames[..sizeof(bools)-1], bools);
+    }
+    {
+      array(int) nums = array_sscanf(swab(f->read(nnum*2)), "%2c"*nnum);
+      if (sizeof(nums)>sizeof(numnames))
+	nums = nums[..sizeof(numnames)-1];
+      mapping(string:int) tmp = mkmapping(numnames[..sizeof(nums)-1], nums);
+      foreach (numnames[..sizeof(nums)-1], string name)
+	if (tmp[name]>=0xfffe)
+	  m_delete(tmp, name);
+      map += tmp;
+    }
+    {
+      string stroffs = swab(f->read(nstr*2));
+      string strbuf = f->read(sstr);
+      if(strlen(strbuf)!=sstr)
+	return 0;
+      array(string) strarr = Array.map(array_sscanf(stroffs, "%2c"*nstr),
+				       lambda(int offs, string buf) {
+					 return offs<0xfffe &&
+					   buf[offs..
+					      search(buf, "\0", offs)-1];
+				       }, strbuf+"\0");
+      if (sizeof(strarr)>sizeof(strnames))
+	strarr = strarr[..sizeof(strnames)-1];
+      mapping(string:string) tmp = mkmapping(strnames[..sizeof(strarr)-1],
+					     strarr);
+      foreach (strnames[..sizeof(strarr)-1], string name)
+	if (!tmp[name])
+	  m_delete(tmp, name);
+      map += tmp;
+    }
+    return 1;
+  }
+
+  void create(string filename)
+  {
+    object f = Stdio.File();
+    if (!f->open(filename, "r"))
+	error("Terminfo: unable to open terminfo file \"%s\"\n", filename);
+    int r = load_cap(f);
+    f->close();
+    if (!r)
+      error("Terminfo: unparsable terminfo file \"%s\"\n", filename);
+  }
+}
+
+class TermcapDB {
+
+  MUTEX
+
+  static private inherit Stdio.File;
+
+  static private string buf="";
+  static private mapping(string:int|object) cache=([]);
+  static private int complete_index=0;
+
+  void create(string|void filename)
+  {
+    if (!filename) {
+      string tce = getenv("TERMCAP");
+      if (tce && strlen(tce) && tce[0]=='/')
+	filename = tce;
+      else
+	filename = "/etc/termcap";
+    }
+    if (!::open(filename, "r"))
+      error("failed to open termcap file %O\n", filename);
+  }
+
+  static private void rewind(int|void pos)
+  {
+    ::seek(pos);
+    buf="";
+  }
+
+  static private int more_data()
+  {
+    string q;
+    q=::read(8192);
+    if (q=="" || !q) return 0;
+    buf+=q; 
+    return 1;
+  }
+
+  static private array(string) get_names(string cap)
+  {
+    sscanf(cap, "%s:", cap);
+    sscanf(cap, "%s,", cap);
+    return cap/"|";
+  }
+
+  static private string read()
+  {
+    int i, st;
+    string res="";
+    for (;;)
+    {
+      if (buf=="" && !more_data()) return 0; // eof
+      
+      sscanf(buf,"%*[ \t\r\n]%s",buf);
+      if (buf=="") continue;
+      
+      if (buf[0]=='#') // comment, scan to newline
+      {
+	while ((i=search(buf,"\n"))<0)
+	{
+	  // The rest of the buffer is comment, toss it and read more
+	  buf="";
+	  if(!more_data()) return 0; // eof
+	}
+	buf=buf[i+1..];
+	continue;
+      }
+
+      break;
+    }
+
+    st = ::tell()-sizeof(buf);
+
+    while ((i=search(buf, "\n"))<0)
+    {
+      if (!more_data()) return 0; // eof
+    }
+    
+    while (buf[i-1]=='\\')
+    {
+      res+=buf[..i-2];
+      buf=buf[i+1..];
+      while (sscanf(buf,"%*[ \t\r]%s",buf)<2 || !sizeof(buf))
+	if (!more_data()) {
+	  buf = "";
+	  return res; // eof, or illegal... wierd
+	}
+      while ((i=search(buf, "\n"))<0)
+      {
+	if (!more_data()) return 0; // eof
+      }
+    }
+    
+    res+=buf[..i-1];
+    buf=buf[i+1..];
+
+    foreach(get_names(res), string name)
+      if(!objectp(cache[name]))
+	cache[name]=st;
+
+    return res;
+  }
+
+  static private string readat(int pos)
+  {
+    rewind(pos);
+    return read();
+  }
+
+  array(string) _indices()
+  {
+    LOCK;
+    if(!complete_index) {
+      rewind();
+      while(read());
+      complete_index = 1;
+    }
+    UNLOCK;
+    return sort(indices(cache));
+  }
+
+  array(object) _values()
+  {
+    array(object|int) res = ({});
+    mapping(int:string) extra = ([]);
+    LOCK;
+    if (complete_index)
+      res = Array.map(sort(indices(cache)),
+		      lambda(string name, mapping(int:string) extra) {
+			if (!objectp(cache[name]) && !extra[cache[name]])
+			  extra[cache[name]] = readat(cache[name]);
+			return cache[name];
+		      }, extra);
+    else {
+      array(string) resi = ({});
+      string cap;
+      int i = 1;
+      rewind();
+      while ((cap = read())) {
+	array(string) names = get_names(cap);
+	object|int o = objectp(cache[names[0]]) && cache[names[0]];
+	if (!o)
+	{
+	  o = i++;
+	  extra[o] = cap;
+	}
+	res += ({ o }) * sizeof(names);
+	resi += names;
+      }
+      sort(resi, res);
+      complete_index = 1;
+    }
+    UNLOCK;
+    return Array.map(res, lambda(int|object x, mapping(int:object) y) {
+			    return objectp(x)? x : y[x];
+			  },
+		     mkmapping(indices(extra),
+			       Array.map(values(extra),
+					 Termcap, this_object())));
+  }
+
+  static private string read_next(string find) // quick search
+  {
+    for (;;)
+    {
+      int i, j;
+
+      if (buf=="" && !more_data()) return 0; // eof
+      
+      i=search(buf,find);
+      if (i!=-1)
+      {
+	int j=i;
+	while (j>=0 && buf[j]!='\n') j--; // find backwards
+	
+	if (buf!="" && buf[j+1]!='#')  // skip comments
+	{
+	  buf=buf[j+1..];
+	  return read();
+	}
+
+	while ((i=search(buf,"\n",j+1))<0)
+	  if (!more_data()) return 0; // eof
+
+	buf = buf[i+1..];
+	
+	continue;
+      }
+      for(j=-1; (i=search(buf,"\n",j+1))>=0; j=i);
+      buf = buf[j+1..];
+      if (!more_data()) return 0; // eof
+    }
+  }
+
+  object load(string term, int|void maxrecurse)
+  {
+    int|string|object cap;
+
+    LOCK;
+    if (zero_type(cache[term]))
+    {
+      if (!complete_index)
+      {
+	rewind();
+	do
+	  cap = read_next(term);
+	while(cap && search(get_names(cap), term)<0);
+      }
+    }
+    else if (intp(cap=cache[term])) {
+      rewind(cap);
+      cap = read();
+    }
+    UNLOCK;
+    if (stringp(cap))
+    {
+      array(string) names = get_names(cap);
+      if ((cap = Termcap(cap, this_object(), maxrecurse)))
+      {
+	LOCK;
+	foreach(names, string name)
+	  cache[name] = cap;
+	UNLOCK;
+      }
+    }
+    return objectp(cap) && cap;
+  }
+
+  object `[](string name)
+  {
+    return load(name);
+  }
+}
+
+
+class TerminfoDB {
+
+  MUTEX
+
+  static private string dir;
+  static private mapping(string:object) cache = ([]);
+  static private int complete_index=0;
+
+  void create(string|void dirname)
+  {
+    if (!dirname)
+    {
+      foreach (({"/usr/share/lib/terminfo", "/usr/share/termcap",
+		 "/usr/lib/terminfo", "/usr/share/misc/terminfo"}), string dn)
+      {
+	array(int) s = file_stat(dn);
+	if (arrayp(s) && sizeof(s)>1 && s[1]==-2)
+	{
+	  dirname = dn;
+	  break;
+	}
+      }
+      if (!dirname) {
+	destruct(this_object());
+	return;
+      }
+    }
+
+    if(sizeof(dirname)<1 || dirname[-1]!='/')
+      dirname += "/";
+
+    if (!get_dir(dir = dirname))
+      error("failed to read terminfo dir %O\n", dirname);
+  }
+
+  array(string) _indices()
+  {
+    LOCK;
+    if (!complete_index) {
+      array(string) files;
+      foreach (get_dir(dir), string a)
+	if (sizeof(a) == 1)
+	  foreach (get_dir(dir+a), string b)
+	    if(zero_type(cache[b]))
+	      cache[b] = 0;
+      complete_index = 1;
+    }
+    UNLOCK;
+    return sort(indices(cache));
+  }
+
+  array(object) _values()
+  {
+    return Array.map(_indices(),
+		    lambda(string name) {
+		      return cache[name] ||
+			Terminfo(dir+name[..0]+"/"+name);
+		    });
+  }
+
+  object load(string term)
+  {
+    object ti;
+
+    if (!strlen(term))
+      return 0;
+    LOCK;
+    if (!(ti = cache[term]))
+    {
+      if (file_stat(dir+term[..0]+"/"+term))
+	ti = Terminfo(dir+term[..0]+"/"+term);
+      if (ti)
+	cache[term] = ti;
+    }
+    UNLOCK;
+    return ti;
+  }
+
+  object `[](string name)
+  {
+    return load(name);
+  }
+
+}
+
+static private object defterm, deftermcap, defterminfo;
+
+object defaultTermcapDB()
+{
+  object tcdb;
+  LOCK;
+  tcdb = deftermcap || (deftermcap = TermcapDB());
+  UNLOCK;
+  return tcdb;
+}
+
+object defaultTerminfoDB()
+{
+  object tidb;
+  LOCK;
+  tidb = defterminfo || (defterminfo = TerminfoDB());
+  UNLOCK;
+  return tidb;
+}
+
+object getTermcap(string term)
+{
+  object tcdb = defaultTermcapDB();
+  return tcdb && tcdb[term];
+}
+
+object getTerminfo(string term)
+{
+  object tidb = defaultTerminfoDB();
+  return tidb && tidb[term];
+}
+
+object getTerm(string|void term)
+{
+  if (!term) {
+    object t = defterm;
+    if (!t)
+    {
+      string tc = getenv("TERMCAP");
+      t = (tc && sizeof(tc) && tc[0]!='/'?
+	   Termcap(tc) : getTerm(getenv("TERM")||"dumb"));
+      LOCK;
+      if (!defterm)
+	defterm = t;
+      UNLOCK;
+    }
+    return t;
+  }
+  return getTerminfo(term) || getTermcap(term);
+}