Index: ChangeLog
===================================================================
RCS file: /cvs/gnome/vte/ChangeLog,v
retrieving revision 1.703
diff -u -p -r1.703 ChangeLog
--- ChangeLog	17 Nov 2006 00:51:02 -0000	1.703
+++ ChangeLog	17 Nov 2006 06:15:18 -0000
@@ -1,3 +1,13 @@
+2006-11-16  Ryan Lortie  <desrt@desrt.ca>
+
+	Bug 354061 – Excessive use of strlen by _vte_termcap_create
+
+	* src/vtetc.c: near-complete rewrite to use a GMappedFile rather than
+	reading a line at a time with excessive strlen/realloc use.
+
+	* str/matcher.c: this shouldn't be calling the strip code as the
+	strings it has are already unescaped.
+
 2006-11-16 Mariano Suárez-Alvarez <mariano@gnome.org>
 
 	Bug 307396 – Mouse scroll mode not controllable
Index: src/matcher.c
===================================================================
RCS file: /cvs/gnome/vte/src/matcher.c,v
retrieving revision 1.11
diff -u -p -r1.11 matcher.c
--- src/matcher.c	16 Nov 2006 19:46:00 -0000	1.11
+++ src/matcher.c	17 Nov 2006 06:15:19 -0000
@@ -66,7 +66,6 @@ _vte_matcher_init(struct _vte_matcher *m
 	gboolean found_cr = FALSE, found_lf = FALSE;
 	gssize stripped_length;
 	char *stripped;
-	char *tmp;
 	int i;
 
 #ifdef VTE_DEBUG
@@ -84,9 +83,11 @@ _vte_matcher_init(struct _vte_matcher *m
 			continue;
 		}
 		code = _vte_terminal_capability_strings[i].capability;
-		tmp = _vte_termcap_find_string(termcap, emulation, code);
-		if ((tmp != NULL) && (tmp[0] != '\0')) {
-			_vte_termcap_strip(tmp, &stripped, &stripped_length);
+		stripped = _vte_termcap_find_string_length(termcap,
+                                                           emulation,
+                                                           code,
+                                                           &stripped_length);
+		if (stripped[0] != '\0') {
 			_vte_matcher_add(matcher, stripped, stripped_length,
 					 code, 0);
 			if (stripped[0] == '\r') {
@@ -98,9 +99,8 @@ _vte_matcher_init(struct _vte_matcher *m
 					found_lf = TRUE;
 				}
 			}
-			g_free(stripped);
 		}
-		g_free(tmp);
+		g_free(stripped);
 	}
 
 	/* Add emulator-specific sequences. */
@@ -111,10 +111,8 @@ _vte_matcher_init(struct _vte_matcher *m
 		     i++) {
 			code = _vte_xterm_capability_strings[i].code;
 			value = _vte_xterm_capability_strings[i].value;
-			_vte_termcap_strip(code, &stripped, &stripped_length);
-			_vte_matcher_add(matcher, stripped, stripped_length,
+			_vte_matcher_add(matcher, code, strlen (code),
 					 value, 0);
-			g_free(stripped);
 		}
 	}
 
Index: src/vtetc.c
===================================================================
RCS file: /cvs/gnome/vte/src/vtetc.c,v
retrieving revision 1.11
diff -u -p -r1.11 vtetc.c
--- src/vtetc.c	16 Nov 2006 19:46:01 -0000	1.11
+++ src/vtetc.c	17 Nov 2006 06:15:19 -0000
@@ -1,5 +1,6 @@
 /*
- * Copyright (C) 2000-2002 Red Hat, Inc.
+ * Copyright © 2006 Ryan Lortie <desrt@desrt.ca>
+ * based on code © 2000-2002 Red Hat, Inc. and others.
  *
  * This is free software; you can redistribute it and/or modify it under
  * the terms of the GNU Library General Public License as published by
@@ -16,766 +17,594 @@
  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
 
-#include "../config.h"
-#include <sys/types.h>
-#include <ctype.h>
-#include <limits.h>
-#ifdef HAVE_SYS_SYSLIMITS_H
-#include <sys/syslimits.h>
-#endif
-#include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
-
+#include <stdlib.h>
 #include <glib.h>
 
-#ifndef TERMCAP_MAYBE_STATIC
-#define TERMCAP_MAYBE_STATIC
 #include "vtetc.h"
-#endif
 
-#include "debug.h"
+/*
+ * --- a termcap file is represented by a simple tree ---
+ */
+typedef struct _vte_termcap
+{
+  GMappedFile *file;
+  GTree *tree;
+  const char *end;
+} VteTermcap;
 
-struct _vte_termcap {
-	char *comment;
-	struct _vte_termcap_entry {
-		char *comment;
-		char *string;
-		gssize length;
-		struct _vte_termcap_entry *next;
-	} *entries;
-	struct _vte_termcap_alias {
-		char *name;
-		struct _vte_termcap_entry *entry;
-		struct _vte_termcap_alias *next;
-	} *names;
-	GTree *nametree;
-};
+/* a special strcmp that treats any character in
+ * its third argument (plus '\0') as end of
+ * string.
+ *
+ *  we have to be a little bit careful, however.
+ *  note that '=' < 'A' < '|' and consider the
+ *  following three strings with "=|" given as
+ *  the 3rd argument:
+ *
+ *  foo=
+ *  fooA
+ *  foo|
+ *
+ *  if we just do the normal *a - *b thing when
+ *  the strings don't match then we will find
+ *
+ *      "foo=" < "fooA"
+ *
+ *  and
+ *
+ *      "foo|" > "fooA"
+ *
+ *  but of course,
+ *
+ *      "foo=" == "foo|"
+ *
+ *  which means that our ordering isn't proper
+ *  which may cause odd things to happen inside of
+ *  the tree.  for this reason, all of the
+ *  terminating characters are treated as '\0' for
+ *  purposes of deciding greater or less than.
+ *
+ *  note: if anything in this file should be
+ *        micro-optimised then it is probably
+ *        this function!
+ */
+static int
+_vte_termcap_strcmp (const char *a,
+                     const char *b,
+                     const char *enders)
+{
+  /* note: strchr on '\0' returns the
+   * end of the string (not NULL)
+   */
+
+  while (!strchr (enders, *a))
+  {
+    if (*a != *b)
+    {
+      /* we're in the loop so we know that *a is not a terminator.
+       * but maybe *b is?
+       */
+      if (strchr (enders, *b))
+        return *a - '\0';
+      else
+        /* b is not a terminator.  proceed normally. */
+        return *a - *b;
+    }
+    a++;
+    b++;
+  }
+
+  /* *a is a terminator for sure, but maybe *b is too. */
+  if (strchr (enders, *b))
+    /* b is too, so we have a match. */
+    return 0;
+
+  /* else, since *a is a terminator character and *b is not, *a is
+   * less than *b. */
+  return -1;
+}
 
-static GStaticMutex _vte_termcap_mutex = G_STATIC_MUTEX_INIT;
-static GCache *_vte_termcap_cache = NULL;
+/*
+ * --- routines for searching the tree ---
+ */
+static const char *
+_vte_termcap_find_start (VteTermcap *termcap,
+                         const char *tname,
+                         const char *cap)
+{
+  const char *contents;
+  const char *start;
+  char *chain;
+
+  /* find the terminal */
+  contents = g_tree_lookup (termcap->tree, tname);
+
+  if (contents == NULL)
+    return NULL;
+
+  start = contents;
+  while (contents != termcap->end)
+  {
+    if (*contents == '\\' &&
+        contents + 1 != termcap->end &&
+        contents[1] == '\n')
+    {
+      /* we've hit \ at the end of a line.  skip. */
+      contents++;
+    }
+    else if (*contents == ':' || *contents == '\n')
+    {
+      if (!_vte_termcap_strcmp (start, cap, "=#:\n"))
+        return start;
+
+      start = contents + 1;
+      if (*contents == '\n')
+        break;
+    }
+
+    contents++;
+  }
+
+  /* else, try to find it in the term listed in our 'tc' entry.
+   * obviously, don't recurse when we're trying to find "tc"
+   * itself else we infinite loop.
+   */
+  if (!strcmp (cap, "tc"))
+    return NULL;
+
+  chain = _vte_termcap_find_string (termcap, tname, "tc");
+  if (chain[0])
+    start = _vte_termcap_find_start (termcap, chain, cap);
+  g_free (chain);
+
+  return start;
+}
+
+static int
+_vte_termcap_unescape_string(const char *string, char *result)
+{
+  int value = -1;
+  int length = 0;
+
+  while (TRUE)
+  {
+    /* Each time through the loop puts a value into 'value' if it
+     * wants to have it written into the string.  We do the write
+     * here because it is complicated (check for NULL result, etc)
+     *
+     * We finish and return the length whenb value is 0.
+     */
+    if (value >= 0)
+    {
+      if (result != NULL)
+        result[length] = value;
+      length++;
+
+      if (value == 0)
+        return length;
+
+      value = -1;
+    }
+
+    /* Now, decide what value should be for the next iteration.
+     * Here, "continue;" means "I've possibly set 'value' and I want
+     * to continue looking at the string starting at the next
+     * character pointed to by 'string'.
+     */
+    switch (*string++)
+    {
+      case '\n':
+      case '\0':
+      case ':':
+        value = 0;
+        continue;
+
+      case '\\':
+        switch (*string++)
+        {
+          case '\n':
+            while (*string == ' ' || *string == '\t')
+              string++;
+            continue;
+          case 'E':
+          case 'e':
+            value = 27;
+            continue;
+          case 'n':
+            value = 10;
+            continue;
+          case 'r':
+            value = 13;
+            continue;
+          case 't':
+            value = 8;
+            continue;
+          case 'b':
+            value = 9;
+            continue;
+          case 'f':
+            value = 12;
+            continue;
+          case '0':
+          case '1':
+            value = strtol(string - 1, (void *) &string, 8);
+            continue;
+          default:
+            /* invalid escape sequence.  write the \ and
+             * continue as if we never saw it...
+             */
+            value = '\\';
+            string--;
+            continue;
+        }
+
+      case '^':
+        if (*string >= 'A' && *string <= 'Z')
+        {
+          value = *string++ - '@';
+          break;
+        }
+
+        /* else, invalid control sequnce.  write the ^
+         * and continue as if we never saw it...
+         */
+
+      default:
+        /* else, the value is this character and the pointer has
+         * already been advanced to the next character. */
+        value = string[-1];
+    }
+  }
+}
+
+char *
+_vte_termcap_find_string_length (VteTermcap *termcap,
+                                 const char *tname,
+                                 const char *cap,
+                                 int *length)
+{
+  const char *result = _vte_termcap_find_start (termcap, tname, cap);
+  char *string;
+
+  if (result == NULL || result[2] != '=')
+  {
+    *length = 0;
+    return g_strdup ("");
+  }
+
+  result += 3;
+
+  *length = _vte_termcap_unescape_string (result, NULL);
+  string = g_malloc (*length);
+  _vte_termcap_unescape_string (result, string);
+
+  (*length)--;
+
+  return string;
+}
+
+char *
+_vte_termcap_find_string (VteTermcap *termcap,
+                          const char *tname,
+                          const char *cap)
+{
+  int length;
+
+  return _vte_termcap_find_string_length (termcap, tname, cap, &length);
+}
+
+long
+_vte_termcap_find_numeric (VteTermcap *termcap,
+                           const char *tname,
+                           const char *cap)
+{
+  const char *result = _vte_termcap_find_start (termcap, tname, cap);
+  long value;
+  char *end;
+
+  if (result == NULL || result[2] != '#')
+    return 0;
+
+  result += 3;
+
+  value = strtol (result, &end, 0);
+  if (*end != ':' && *end != '\0' && *end != '\n')
+    return 0;
+
+  return value;
+}
+
+gboolean
+_vte_termcap_find_boolean (VteTermcap *termcap,
+                           const char *tname,
+                           const char *cap)
+{
+  const char *result = _vte_termcap_find_start (termcap, tname, cap);
+
+  if (result == NULL)
+    return 0;
+
+  result += 2;
 
-static char *
-nextline(FILE *fp, gssize *outlen)
-{
-	char buf[LINE_MAX];
-	gssize len = 0;
-	char *ret = NULL;
-	gssize retlen = 0;
-	char *tmp = NULL;
-
-	if (!feof(fp)) do {
-		if (fgets(buf, sizeof(buf), fp) != buf) {
-			break;
-		}
-		len = strlen(buf);
-		tmp = g_malloc(retlen + len + 1);
-		if (retlen > 0) {
-			memcpy(tmp, ret, retlen);
-		}
-		memcpy(tmp + retlen, buf, len + 1);
-		if (ret != NULL) {
-			g_free(ret);
-		}
-		retlen += len;
-		ret = tmp;
-		ret[retlen] = '\0';
-	} while ((len > 0) && (buf[retlen - 1] != '\n') && !feof(fp));
-
-	if ((ret != NULL) && (retlen > 0) && (ret[retlen - 1] == '\n')) {
-		retlen--;
-		ret[retlen] = '\0';
-	}
-
-	if ((ret != NULL) && (retlen > 0) && (ret[retlen - 1] == '\r')) {
-		retlen--;
-		ret[retlen] = '\0';
-	}
-
-	*outlen = retlen;
-	return ret;
-}
-
-static char *
-nextline_with_continuation(FILE *fp)
-{
-	char *ret = NULL;
-	gssize rlen = 0, slen = 0;
-	char *s, *tmp;
-	gboolean continuation = FALSE;
-	do {
-		s = nextline(fp, &slen);
-		if (s == NULL) {
-			break;
-		}
-		tmp = g_malloc(slen + rlen + 1);
-		if (rlen > 0) {
-			memcpy(tmp, ret, rlen);
-		}
-		memcpy(tmp + rlen, s, slen + 1);
-		if (ret != NULL) {
-			g_free(ret);
-		}
-		g_free(s);
-		ret = tmp;
-		rlen += slen;
-		if ((rlen > 0) && (ret[rlen - 1] == '\\')) {
-			ret[rlen - 1] = '\0';
-			rlen--;
-			continuation = TRUE;
-		} else {
-			continuation = FALSE;
-		}
-	} while ((rlen == 0) || continuation);
-	return ret;
-}
+  if (*result != ':' && *result != '\0' && *result != '\n')
+    return FALSE;
 
-static void
-_vte_termcap_add_aliases(struct _vte_termcap *termcap,
-			 struct _vte_termcap_entry *entry,
-			 const char *aliases)
-{
-	gssize l;
-	struct _vte_termcap_alias *alias = NULL;
-	const char *p;
-
-	for (p = aliases, l = 0; p != NULL; l++) {
-		if (aliases[l] == '\\') {
-			l++;
-		} else
-		if ((aliases[l] == '|') ||
-		   (aliases[l] == ':') ||
-		   (aliases[l] == '\0')) {
-			alias = g_slice_new0(struct _vte_termcap_alias);
-			alias->name = g_strndup(p, &aliases[l] - p);
-			alias->entry = entry;
-			alias->next = termcap->names;
-			termcap->names = alias;
-			if (aliases[l] == '\0') {
-				p = NULL;
-			} else {
-				p = &aliases[l + 1];
-			}
-			g_tree_insert(termcap->nametree,
-				      GINT_TO_POINTER(g_quark_from_string(alias->name)),
-				      alias);
-			l++;
-		}
-	}
+  return TRUE;
 }
 
+/*
+ * --- routines for building the tree from the file ---
+ */
 static void
-_vte_termcap_add_entry(struct _vte_termcap *termcap,
-		       const char *s, gssize length, char *comment)
+_vte_termcap_parse_entry (GTree *termcap, const char **cnt, const char *end)
 {
-	struct _vte_termcap_entry *entry = NULL;
-	char *p = NULL;
-	gssize l;
-
-	entry = g_slice_new0(struct _vte_termcap_entry);
-	entry->string = g_malloc(length + 1);
-	if (length > 0) {
-		memcpy(entry->string, s, length);
-	}
-	entry->string[length] = '\0';
-	entry->length = length;
-	entry->comment = comment;
-	entry->next = termcap->entries;
-	termcap->entries = entry;
-	for (l = 0; l < length; l++) {
-		if (s[l] == '\\') {
-			l++;
-			continue;
-		}
-		if (s[l] == ':') {
-			break;
-		}
-	}
-	if (l <= length) {
-		p = g_malloc(l + 1);
-		strncpy(p, s, l);
-		p[l] = '\0';
-		_vte_termcap_add_aliases(termcap, entry, p);
-		g_free(p);
-	}
-}
+  gboolean seen_content;
+  const char *contents;
+  const char *start;
+  const char *caps;
+
+  contents = *cnt;
+
+  /* look for the start of the capabilities.
+   */
+  caps = contents;
+  while (caps != end)
+    if (*caps == ':')
+      break;
+    else
+      caps++;
+
+  if (*caps != ':')
+    return;
+
+  /* parse all of the aliases and insert one item into the termcap
+   * tree for each alias, pointing it to our caps.
+   */
+  seen_content = FALSE;
+  start = contents;
+  while (contents != end)
+  {
+    /* 
+    if (contents == end)
+    {
+       * we can't deal with end of file directly following a
+       * terminal name without any delimiters or even a newline.
+       * but honestly, what did they expect?  end of file without
+       * newline in the middle of a terminal alias with no
+       * capability definitions?  i'll doubt they notice that
+       * anything is missing.
+    }
+    */
+
+    if (*contents == '\\' && contents + 1 != end && contents[1] == '\n')
+    {
+      /* we've hit \ at the end of a line.  skip. */
+      contents++;
+    }
+    else if (*contents == '|' || *contents == ':' || *contents == '\n')
+    {
+      /* we wait to find the terminator before putting anything in
+       * the tree to ensure that _vte_termcap_strcmp will always
+       * terminate.  we also only add the alias if we've seen
+       * actual characters (not just spaces, continuations, etc)
+       */
+      if (seen_content)
+        g_tree_insert (termcap, (gpointer) start, (gpointer) caps);
+      start = contents + 1;
+      seen_content = FALSE;
+
+      /* we've either hit : and need to move on to capabilities or
+       * end of line and then there are no capabilities for this
+       * terminal.  any aliases have already been added to the tree
+       * so we can just move on.  if it was '\n' then the next while
+       * loop will exit immediately.
+       */
+      if (*contents == ':' || *contents == '\n')
+        break;
+    }
+    else if (*contents != ' ' && *contents != '\t')
+      seen_content = TRUE;
+
+    contents++;
+  }
+
+  /* we've processed all of the aliases.  now skip past the capabilities
+   * so that we're ready to go on the next entry. */
+  while (contents != end)
+  {
+    if (*contents == '\\' && contents + 1 != end && contents[1] == '\n')
+    {
+      /* we've hit \ at the end of a line.  skip. */
+      contents++;
+    }
+    else if (*contents == '\n')
+      break;
+
+    contents++;
+  }
+
+  *cnt = contents;
+}
+
+static GTree *
+_vte_termcap_parse_file (const char *contents, int length)
+{
+  const char *end = contents + length;
+  GTree *termcap;
+
+  /* this tree contains terminal alias names which in a proper
+   * termcap file will always be followed by : or |.  we
+   * include \n to be extra-permissive. \0 is here to allow
+   * us to notice the end of strings passed to us by vte.
+   */
+  termcap = g_tree_new_full ((GCompareDataFunc) _vte_termcap_strcmp,
+                             ":|\n", NULL, NULL);
+
+  while (contents != end)
+  {
+    switch (*contents++)
+    {
+      /* comments */
+      case '#':
+        /* eat up to (but not) the \n */
+        while (contents != end && *contents != '\n')
+          contents++;
+
+      /* whitespace */
+      case ' ':
+      case '\t':
+      case '\n':
+        continue;
+
+      default:
+        /* bring back the character */
+        contents--;
+
+        /* parse one entry (ie: one line) */
+        _vte_termcap_parse_entry (termcap, &contents, end);
+    }
+  }
+
+  return termcap;
+}
+
+static VteTermcap *
+_vte_termcap_create (const char *filename)
+{
+  const char *contents;
+  VteTermcap *termcap;
+  GMappedFile *file;
+  int length;
+
+  file = g_mapped_file_new (filename, FALSE, NULL);
+  
+  if (file == NULL)
+    return NULL;
+
+  contents = g_mapped_file_get_contents (file);
+  length = g_mapped_file_get_length (file);
+
+  termcap = g_slice_new (VteTermcap);
+  termcap->file = file;
+  termcap->tree = _vte_termcap_parse_file (contents, length);
+  termcap->end = contents + length;
 
-static void
-_vte_termcap_strip_with_pad(const char *termcap, char **stripped, gssize *len)
-{
-	char *ret;
-	gssize i, o, length;
-	length = strlen(termcap);
-
-	ret = g_malloc(length + 2);
-	for (i = o = 0; i < length; i++) {
-		ret[o++] = termcap[i];
-		if (termcap[i] == '\\') {
-			char *p;
-			switch (termcap[i + 1]) {
-				case '\n':
-					while ((termcap[i + 1] == ' ') ||
-					       (termcap[i + 1] == '\t')) {
-						i++;
-					}
-					continue;
-				case 'E':
-				case 'e':
-					i++;
-					ret[o - 1] = 27;
-					continue;
-				case 'n':
-					i++;
-					ret[o - 1] = 10;
-					continue;
-				case 'r':
-					i++;
-					ret[o - 1] = 13;
-					continue;
-				case 't':
-					i++;
-					ret[o - 1] = 8;
-					continue;
-				case 'b':
-					i++;
-					ret[o - 1] = 9;
-					continue;
-				case 'f':
-					i++;
-					ret[o - 1] = 12;
-					continue;
-				case '0':
-				case '1':
-					i++;
-					ret[o - 1] = strtol(termcap + i, &p, 8);
-					p--;
-					i = p - termcap;
-					continue;
-			}
-		} else
-		if (termcap[i] == '^') {
-			switch (termcap[i + 1]) {
-				case 'A':
-				case 'B':
-				case 'C':
-				case 'D':
-				case 'E':
-				case 'F':
-				case 'G':
-				case 'H':
-				case 'I':
-				case 'J':
-				case 'K':
-				case 'L':
-				case 'M':
-				case 'N':
-				case 'O':
-				case 'P':
-				case 'Q':
-				case 'R':
-				case 'S':
-				case 'T':
-				case 'U':
-				case 'V':
-				case 'W':
-				case 'X':
-				case 'Y':
-				case 'Z':
-					i++;
-					ret[o - 1] = termcap[i] - ('A' - 1);
-					continue;
-				default:
-					break;
-			}
-		} else {
-			if (termcap[i] == ':') {
-				while ((termcap[i + 1] == ' ') ||
-				       (termcap[i + 1] == '\t')) {
-					i++;
-				}
-				continue;
-			}
-		}
-	}
-	ret[o] = ':';
-	o++;
-	ret[o] = '\0';
-	*stripped = ret;
-	*len = o;
-}
-
-/**
- * _vte_termcap_strip:
- * @termcap: a termcap structure
- * @stripped: a location to store the new stripped version of the string
- * @len: a location to store the length of the new string
- *
- * Converts various types of sequences used to represent non-printable data
- * into the data they represent.  Specifically, this resolves ^{char} sequences,
- * octal escapes. and the \r, \n, \E, \t, \b, and \f sequences.
- *
- */
-void
-_vte_termcap_strip(const char *termcap, char **stripped, gssize *len)
-{
-	_vte_termcap_strip_with_pad(termcap, stripped, len);
-	while (((*len) > 0) && ((*stripped)[(*len) - 1] == ':')) {
-		(*len)--;
-		(*stripped)[*len] = '\0';
-	}
-}
-
-static gint
-_vte_direct_compare(gconstpointer a, gconstpointer b)
-{
-	return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
-}
-
-/* Allocates and initializes new termcap instance. */
-static gpointer
-_vte_termcap_create(gpointer key)
-{
-	const char *filename = key;
-	struct _vte_termcap *ret = NULL;
-	FILE *fp;
-	char *s, *stripped, *comment = NULL;
-
-#ifdef VTE_DEBUG
-	if (_vte_debug_on(VTE_DEBUG_LIFECYCLE)) {
-		fprintf(stderr, "_vte_termcap_create()\n");
-	}
-#endif
-	fp = fopen(filename, "r");
-	if (fp != NULL) {
-		while ((s = nextline_with_continuation(fp)) != NULL) {
-			gssize slen;
-			if ((s[0] != '#') && (isprint(s[0]))) {
-				if (ret == NULL) {
-					ret = g_slice_new0(struct _vte_termcap);
-					ret->nametree = g_tree_new(_vte_direct_compare);
-				}
-				stripped = NULL;
-				_vte_termcap_strip_with_pad(s, &stripped, &slen);
-				if (stripped) {
-					_vte_termcap_add_entry(ret, stripped,
-							       slen, comment);
-					comment = NULL;
-					g_free(stripped);
-				}
-			} else {
-				slen = strlen(s);
-				if (comment == NULL) {
-					comment = g_malloc(slen + 2);
-					memcpy(comment, s, slen);
-					comment[slen] = '\n';
-					comment[slen + 1] = '\0';
-				} else {
-					char *tmp;
-					gssize clen;
-					clen = strlen(comment);
-					tmp = g_malloc(slen + clen + 2);
-					if (tmp == NULL) {
-						return NULL;
-					}
-					memcpy(tmp, comment, clen);
-					memcpy(tmp + clen, s, slen);
-					tmp[clen + slen] = '\n';
-					tmp[clen + slen + 1] = '\0';
-					g_free(comment);
-					comment = tmp;
-				}
-			}
-			g_free(s);
-		}
-		ret->comment = comment;
-		fclose(fp);
-	}
-	return ret;
+  return termcap;
 }
 
-/* Noone uses termcap, destroy it. */
 static void
-_vte_termcap_destroy(gpointer key)
+_vte_termcap_destroy (VteTermcap *termcap)
 {
-	struct _vte_termcap *termcap = key;
-	struct _vte_termcap_entry *entry, *nextentry;
-	struct _vte_termcap_alias *alias, *nextalias;
-
-	if (termcap == NULL)
-		return;
-
-#ifdef VTE_DEBUG
-	if (_vte_debug_on(VTE_DEBUG_LIFECYCLE)) {
-		fprintf(stderr, "_vte_termcap_destroy()\n");
-	}
-#endif
-	for (entry = termcap->entries; entry != NULL; entry = nextentry) {
-		nextentry = entry->next;
-		g_free(entry->comment);
-		entry->comment = NULL;
-		g_free(entry->string);
-		entry->string = NULL;
-		g_slice_free(struct _vte_termcap_entry, entry);
-	}
-	for (alias = termcap->names; alias != NULL; alias = nextalias) {
-		nextalias = alias->next;
-		g_free(alias->name);
-		alias->name = NULL;
-		g_slice_free(struct _vte_termcap_alias, alias);
-	}
-	g_tree_destroy(termcap->nametree);
-	termcap->nametree = NULL;
-	g_free(termcap->comment);
-	termcap->comment = NULL;
-	g_slice_free(struct _vte_termcap, termcap);
+  g_tree_destroy (termcap->tree);
+  g_mapped_file_free (termcap->file);
+  g_slice_free (VteTermcap, termcap);
 }
 
-TERMCAP_MAYBE_STATIC struct _vte_termcap *
+/*
+ * --- cached interface to create/destroy termcap trees ---
+ */
+static GStaticMutex _vte_termcap_mutex = G_STATIC_MUTEX_INIT;
+static GCache *_vte_termcap_cache = NULL;
+
+VteTermcap *
 _vte_termcap_new(const char *filename)
 {
-	struct _vte_termcap *ret = NULL;
-	g_static_mutex_lock(&_vte_termcap_mutex);
+  VteTermcap *result;
 
-	if (_vte_termcap_cache == NULL) {
-		_vte_termcap_cache = g_cache_new(_vte_termcap_create,
-				_vte_termcap_destroy,
-				(GCacheDupFunc) g_strdup, g_free,
-				g_str_hash, g_direct_hash, g_str_equal);
-	}
-	ret = g_cache_insert(_vte_termcap_cache, (gpointer) filename);
-
-	g_static_mutex_unlock(&_vte_termcap_mutex);
-	return ret;
-}
-
-/**
- * _vte_termcap_free:
- * @termcap: the structure to be freed
- *
- * Frees the indicated structure.
- *
- */
-TERMCAP_MAYBE_STATIC void
-_vte_termcap_free(struct _vte_termcap *termcap)
-{
-	g_assert(_vte_termcap_cache != NULL);
-	g_static_mutex_lock(&_vte_termcap_mutex);
-	g_cache_remove(_vte_termcap_cache, termcap);
-	g_static_mutex_unlock(&_vte_termcap_mutex);
-}
+  g_static_mutex_lock (&_vte_termcap_mutex);
 
-static const char *
-_vte_termcap_find_l(struct _vte_termcap *termcap, const char *tname, gssize len,
-		    const char *cap)
-{
-	const char *ret;
-	struct _vte_termcap_alias *alias;
-	char *ttname;
-	gssize clen;
-
-	g_assert(termcap != NULL);
-	g_assert(tname != NULL);
-	g_assert(len > 0);
-	g_assert(cap != NULL);
-	g_assert(strlen(cap) > 0);
-
-	/* Find the entry by this name. */
-	ttname = g_strndup(tname, len);
-	alias = g_tree_lookup(termcap->nametree,
-			      GINT_TO_POINTER(g_quark_from_string(ttname)));
-	g_free(ttname);
-
-	/* If we found the entry, poke around in it. */
-	if (alias != NULL) {
-		char *str = alias->entry->string;
-		const char *nextcap = "tc";
-		gssize len = alias->entry->length;
-
-		clen = strlen(cap);
-		ret = str;
-
-		/* Search for the capability in this entry. */
-		for (ret = str - 1;
-		     ret != NULL;
-		     ret = memchr(ret, ':', str + len - ret - clen)) {
-			/* We've hit the first separator, or are before the
-			 * very first part of the entry, so hit the next
-			 * capability. */
-			ret++;
-			/* If the end of the entry's name isn't the end of the
-			 * string, and it isn't a boolean/string/numeric, or
-			 * its name is wrong, keep looking. */
-			if (((ret[clen] != '\0') &&
-			     (ret[clen] != ':') &&
-			     (ret[clen] != '=') &&
-			     (ret[clen] != '#')) ||
-			    (memcmp(ret, cap, clen) != 0)) {
-				continue;
-			}
-			/* Found it. */
-			return ret;
-		}
-
-		/* Now find the "tc=" entries, and scan those entries. */
-		clen = strlen(nextcap);
-		ret = str - 1;
-
-		while (ret != NULL) {
-			for (;
-			     ret != NULL;
-			     ret = memchr(ret, ':', str + len - ret - clen)) {
-				ret++;
-				if (((ret[clen] != '\0') &&
-				     (ret[clen] != ':') &&
-				     (ret[clen] != '=') &&
-				     (ret[clen] != '#')) ||
-				    (memcmp(ret, nextcap, clen) != 0)) {
-					continue;
-				}
-				break;
-			}
-
-			if (ret != NULL) {
-				const char *t;
-				char *end;
-				end = strchr(ret + clen + 1, ':');
-				if (end != NULL) {
-					t = _vte_termcap_find_l(termcap,
-								ret + clen + 1,
-								end -
-								(ret + clen + 1),
-								cap);
-				} else {
-					t = _vte_termcap_find_l(termcap,
-								ret + clen + 1,
-								strlen(ret +
-								       clen + 1),
-								cap);
-				}
-				if ((t != NULL) && (t[0] != '\0')) {
-					return t;
-				}
-				ret++;
-			}
-		}
-	}
-	return "";
-}
+  if (_vte_termcap_cache == NULL)
+    _vte_termcap_cache = g_cache_new((GCacheNewFunc) _vte_termcap_create,
+                                     (GCacheDestroyFunc) _vte_termcap_destroy,
+                                     (GCacheDupFunc) g_strdup,
+                                     (GCacheDestroyFunc) g_free,
+                                     g_str_hash, g_direct_hash, g_str_equal);
 
-static const char *
-_vte_termcap_find(struct _vte_termcap *termcap,
-		  const char *tname, const char *cap)
-{
-	g_assert(termcap != NULL);
-	return _vte_termcap_find_l(termcap, tname, strlen(tname), cap);
+  result = g_cache_insert (_vte_termcap_cache, (gpointer) filename);
+
+  g_static_mutex_unlock (&_vte_termcap_mutex);
+
+  return result;
 }
 
-/**
- * _vte_termcap_find_boolean:
- * @termcap: a termcap structure
- * @tname: the name of the terminal type being queried
- * @cap: the name of the capability being queried
- *
- * Checks if the given boolean capability is defined.
- *
- * Returns: %TRUE if the terminal type is known and the capability is defined
- * for it
- */
-TERMCAP_MAYBE_STATIC gboolean
-_vte_termcap_find_boolean(struct _vte_termcap *termcap, const char *tname,
-			  const char *cap)
-{
-	const char *val;
-	g_return_val_if_fail(termcap != NULL, FALSE);
-	val = _vte_termcap_find(termcap, tname, cap);
-	if ((val != NULL) && (val[0] != '\0')) {
-		return TRUE;
-	}
-	return FALSE;
-}
-
-/**
- * _vte_termcap_find_numeric:
- * @termcap: a termcap structure
- * @tname: the name of the terminal type being queried
- * @cap: the name of the capability being queried
- *
- * Checks if the given numeric capability is defined.
- *
- * Returns: non-zero if the terminal type is known and the capability is defined
- * to a non-zero value for it
- */
-TERMCAP_MAYBE_STATIC long
-_vte_termcap_find_numeric(struct _vte_termcap *termcap, const char *tname,
-			  const char *cap)
-{
-	const char *val;
-	char *p;
-	gssize l;
-	long ret;
-	g_return_val_if_fail(termcap != NULL, 0);
-	val = _vte_termcap_find(termcap, tname, cap);
-	if ((val != NULL) && (val[0] != '\0')) {
-		l = strlen(cap);
-		ret = strtol(val + l + 1, &p, 0);
-		if ((p != NULL) && ((*p == '\0') || (*p == ':'))) {
-			return ret;
-		}
-	}
-	return 0;
-}
-
-/**
- * _vte_termcap_find_string:
- * @termcap: a termcap structure
- * @tname: the name of the terminal type being queried
- * @cap: the name of the capability being queried
- *
- * Checks if the given string capability is defined.
- *
- * Returns: the value of the capability if the terminal type is known and the
- * capability is defined for it, else an empty string.  The return value must
- * always be freed by the caller.
- */
-TERMCAP_MAYBE_STATIC char *
-_vte_termcap_find_string(struct _vte_termcap *termcap, const char *tname,
-			 const char *cap)
-{
-	const char *val, *p;
-	gssize l;
-	val = _vte_termcap_find(termcap, tname, cap);
-	if ((val != NULL) && (val[0] != '\0')) {
-		l = strlen(cap);
-		val += (l + 1);
-		p = strchr(val, ':');
-		if (p) {
-			return g_strndup(val, p - val);
-		} else {
-			return g_strdup(val);
-		}
-	}
-	return g_strdup("");
-}
-
-/**
- * _vte_termcap_find_string_length:
- * @termcap: a termcap structure
- * @tname: the name of the terminal type being queried
- * @cap: the name of the capability being queried
- * @length: the location to store the length of the returned string
- *
- * Checks if the given string capability is defined.  This version of
- * _vte_termcap_find_string() properly handles zero bytes in the result.
- *
- * Returns: the value of the capability if the terminal type is known and the
- * capability is defined for it, else an empty string.  The return value must
- * always be freed by the caller.
- */
-TERMCAP_MAYBE_STATIC char *
-_vte_termcap_find_string_length(struct _vte_termcap *termcap, const char *tname,
-				const char *cap, gssize *length)
-{
-	const char *val, *p;
-	char *ret;
-	gssize l;
-	val = _vte_termcap_find(termcap, tname, cap);
-	if ((val != NULL) && (val[0] != '\0')) {
-		l = strlen(cap);
-		val += (l + 1);
-		p = val;
-		while (*p != ':') p++;
-		l = p - val;
-		if (length) {
-			*length = l;
-		}
-		ret = g_malloc(l + 1);
-		if (l > 0) {
-			memcpy(ret, val, l);
-		}
-		ret[l] = '\0';
-		return ret;
-	}
-	return g_strdup("");
-}
-
-#if 0
-TERMCAP_MAYBE_STATIC const char *
-vte_termcap_comment(struct vte_termcap *termcap, const char *tname)
-{
-	struct vte_termcap_alias *alias;
-	gssize len;
-	if ((tname == NULL) || (tname[0] == '\0')) {
-		return termcap->comment;
-	}
-	len = strlen(tname);
-	for (alias = termcap->names; alias != NULL; alias = alias->next) {
-		if (strncmp(tname, alias->name, len) == 0) {
-			if (alias->name[len] == '\0') {
-				break;
-			}
-		}
-	}
-	if (alias && (alias->entry != NULL)) {
-		return alias->entry->comment;
-	}
-	return NULL;
-}
-
-/* FIXME: should escape characters we've previously decoded. */
-TERMCAP_MAYBE_STATIC char *
-vte_termcap_generate(struct vte_termcap *termcap)
-{
-	gssize size;
-	char *ret = NULL;
-	struct vte_termcap_entry *entry;
-	size = strlen(termcap->comment ? termcap->comment: "");
-	for (entry = termcap->entries; entry != NULL; entry = entry->next) {
-		size += (entry->comment ? strlen(entry->comment) : 0);
-		size += (entry->string ? strlen(entry->string) : 0) + 1;
-	}
-	ret = g_malloc(size + 1);
-	if (ret == NULL) {
-		return NULL;
-	}
-	memset(ret, '\0', size);
-	size = 0;
-	for (entry = termcap->entries; entry != NULL; entry = entry->next) {
-		if (entry->comment) {
-			memcpy(ret + size, entry->comment,
-			       strlen(entry->comment));
-			size += strlen(entry->comment);
-		}
-		if (entry->string) {
-			memcpy(ret + size, entry->string,
-			       strlen(entry->string));
-			size += strlen(entry->string);
-			ret[size] = '\n';
-			size++;
-		}
-	}
-	if (termcap->comment) {
-		memcpy(ret + size, termcap->comment,
-		       strlen(termcap->comment));
-		size += strlen(termcap->comment);
-	}
-	return ret;
+void
+_vte_termcap_free (VteTermcap *termcap)
+{
+  g_static_mutex_lock (&_vte_termcap_mutex);
+  g_cache_remove (_vte_termcap_cache, termcap);
+  g_static_mutex_unlock (&_vte_termcap_mutex);
 }
-#endif
 
 #ifdef TERMCAP_MAIN
+#include <stdio.h>
+
 int
-main(int argc, char **argv)
+main (int argc, char **argv)
 {
-	const char *tc = (argc > 1) ? argv[1] : "linux";
-	const char *cap = (argc > 2) ? argv[2] : "so";
-	char *value;
-	struct _vte_termcap *termcap;
-	_vte_debug_parse_string(getenv("VTE_DEBUG_FLAGS"));
-	termcap = _vte_termcap_new("/etc/termcap");
-	value = _vte_termcap_find_string(termcap, tc, cap);
-	printf("%s\n", value);
-	g_free(value);
-	_vte_termcap_free(termcap);
-	return 0;
+  VteTermcap *tc;
+  char *str;
+  int len;
+  int i;
+
+  if (argc < 4)
+  {
+    fprintf (stderr, "vtetc /path/to/termcap termname attrs...\n"
+                     "  where attrs are\n"
+                     "    :xx for boolean\n"
+                     "    =xx for string\n"
+                     "    +xx for string displayed in hex\n"
+                     "    #xx for numeric\n");
+    return 1;
+  }
+
+  tc = _vte_termcap_new (argv[1]);
+
+  if (tc == NULL)
+  {
+    perror ("open");
+    return 1;
+  }
+
+  for (i = 3; i < argc; i++)
+  {
+    printf ("%s -> ", argv[i]);
+
+    switch (argv[i][0])
+    {
+      case ':':
+        printf ("%s\n", _vte_termcap_find_boolean (tc, argv[2], argv[i] + 1)?
+                        "true" : "false");
+        break;
+
+      case '=':
+      case '+':
+        str = _vte_termcap_find_string_length (tc, argv[2], argv[i] + 1, &len);
+
+        if (argv[i][0] == '=')
+          printf ("'%s' (%d)\n", str, len);
+        else
+        {
+          int i;
+
+          for (i = 0; str[i]; i++)
+            printf ("%02x", str[i]);
+          printf (" (%d) \n", len);
+        }
+        g_free (str);
+        break;
+
+      case '#':
+        printf ("%ld\n", _vte_termcap_find_numeric (tc, argv[2], argv[i] + 1));
+        break;
+
+      default:
+        fprintf (stderr, "unrecognised type '%c'\n", argv[i][0]);
+    }
+  }
+
+  _vte_termcap_free(tc);
+
+  return 0;
 }
 #endif
