diff liboctave/kpse-xfns.c @ 4378:7d48a8fba1d4

[project @ 2003-04-19 00:03:47 by jwe]
author jwe
date Sat, 19 Apr 2003 00:03:50 +0000
parents
children 0cbcb9d8b4ff
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/liboctave/kpse-xfns.c	Sat Apr 19 00:03:50 2003 +0000
@@ -0,0 +1,1623 @@
+/* xfns.c: All the x* functions from kpathsearch in one file.
+
+Copyright (C) 1992, 93, 94, 95, 96 Free Software Foundation, Inc.
+Copyright (C) 1993, 94, 95, 96, 97, 98 Karl Berry.
+Copyright (C) 1994, 95, 96, 97 Karl Berry & Olaf Weber.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#if defined (HAVE_CONFIG_H)
+#include <config.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "kpse-lib.h"
+
+/* xfopen.c: fopen and fclose with error checking.  */
+
+/* These routines just check the return status from standard library
+   routines and abort if an error happens.  */
+
+FILE *
+xfopen (const char *filename, const char *mode)
+{
+  FILE *f;
+  
+  assert (filename && mode);
+  
+  f = fopen (filename, mode);
+  if (f == NULL)
+    FATAL_PERROR (filename);
+
+  return f;
+}
+
+void
+xfclose (FILE *f, const char *filename)
+{
+  assert (f);
+  
+  if (fclose (f) == EOF)
+    FATAL_PERROR (filename);
+}
+
+/* xfseek.c: fseek with error checking.  */
+
+void
+xfseek (FILE *f, long offset, int wherefrom, char *filename)
+{
+  if (fseek (f, offset, wherefrom) < 0)
+    FATAL_PERROR (filename);
+}
+
+/* xftell.c: ftell with error checking.  */
+
+unsigned long
+xftell (FILE *f, char *filename)
+{
+  long where = ftell (f);
+
+  if (where < 0)
+    FATAL_PERROR (filename);
+
+  return where;
+}
+
+/* xopendir.c: opendir and closedir with error checking.  */
+
+#ifndef WIN32
+DIR *
+xopendir (char *dirname)
+{
+  DIR *d = opendir (dirname);
+
+  if (d == NULL)
+    FATAL_PERROR (dirname);
+
+  return d;
+}
+#endif /* not WIN32 */
+
+void
+xclosedir (DIR *d)
+{
+#ifdef CLOSEDIR_VOID
+  closedir (d);
+#else
+  int ret = closedir (d);
+  
+  if (ret != 0)
+    FATAL ("closedir failed");
+#endif
+}
+
+/* xstat.c: stat and (maybe) lstat with error checking.  */
+
+struct stat
+xstat (const char *path)
+{
+  struct stat s;
+  
+  if (stat (path, &s) != 0)
+    FATAL_PERROR (path);
+  
+  return s;
+}
+
+/* If we don't have symbolic links, lstat is the same as stat, and
+   a #define is made in the include file.  */
+
+#ifdef S_ISLNK
+struct stat
+xlstat (const char *path)
+{
+  struct stat s;
+  
+  if (lstat (path, &s) != 0)
+    FATAL_PERROR (path);
+  
+  return s;
+}
+#endif
+
+/* xgetcwd.c: a from-scratch version of getwd.  Ideas from the tcsh 5.20
+   source, apparently uncopyrighted.  */
+
+#if ! (defined (HAVE_GETCWD) || defined (HAVE_GETWD))
+
+static void
+xchdir (char *dirname)
+{
+  if (chdir (dirname) != 0)
+    FATAL_PERROR (dirname);
+}
+
+#endif /* not HAVE_GETCWD && not HAVE_GETWD */
+
+/* Return the pathname of the current directory, or give a fatal error.  */
+
+char *
+xgetcwd (void)
+{
+  /* If the system provides getcwd, use it.  If not, use getwd if
+     available.  But provide a way not to use getcwd: on some systems
+     getcwd forks, which is expensive and may in fact be impossible for
+     large programs like tex.  If your system needs this define and it
+     is not detected by configure, let me know.
+                                       -- Olaf Weber <infovore@xs4all.nl */
+#if defined (HAVE_GETCWD) && !defined (GETCWD_FORKS)
+  char *path = (char *) xmalloc (PATH_MAX + 1);
+  
+  if (getcwd (path, PATH_MAX + 1) == 0)
+    {
+      fprintf (stderr, "getcwd: %s", path);
+      exit (1);
+    }
+  
+  return path;
+#elif defined (HAVE_GETWD)
+  char *path = (char *) xmalloc (PATH_MAX + 1);
+  
+  if (getwd (path) == 0)
+    {
+      fprintf (stderr, "getwd: %s", path);
+      exit (1);
+    }
+  
+  return path;
+#else /* not HAVE_GETCWD && not HAVE_GETWD */
+  struct stat root_stat, cwd_stat;
+  char *cwd_path = (char *) xmalloc (2); /* In case we assign "/" below.  */
+  
+  *cwd_path = 0;
+  
+  /* Find the inodes of the root and current directories.  */
+  root_stat = xstat ("/");
+  cwd_stat = xstat (".");
+
+  /* Go up the directory hierarchy until we get to root, prepending each
+     directory we pass through to `cwd_path'.  */
+  while (!SAME_FILE_P (root_stat, cwd_stat))
+    {
+      struct dirent *e;
+      DIR *parent_dir;
+      int found = 0;
+      
+      xchdir ("..");
+      parent_dir = xopendir (".");
+
+      /* Look through the parent directory for the entry with the same
+         inode, so we can get its name.  */
+      while ((e = readdir (parent_dir)) != NULL && !found)
+        {
+          struct stat test_stat;
+          test_stat = xlstat (e->d_name);
+          
+          if (SAME_FILE_P (test_stat, cwd_stat))
+            {
+              /* We've found it.  Prepend the pathname.  */
+              char *temp = cwd_path;
+              cwd_path = concat3 ("/", e->d_name, cwd_path);
+              free (temp);
+              
+              /* Set up to test the next parent.  */
+              cwd_stat = xstat (".");
+              
+              /* Stop reading this directory.  */
+              found = 1;
+            }
+        }
+      if (!found)
+        FATAL2 ("No inode %d/device %d in parent directory",
+                cwd_stat.st_ino, cwd_stat.st_dev);
+      
+      xclosedir (parent_dir);
+    }
+  
+  /* If the current directory is the root, cwd_path will be the empty
+     string, and we will have not gone through the loop.  */
+  if (*cwd_path == 0)
+    strcpy (cwd_path, "/");
+  else
+    /* Go back to where we were.  */
+    xchdir (cwd_path);
+
+#ifdef DOSISH
+  /* Prepend the drive letter to CWD_PATH, since this technique
+     never tells us what the drive is.
+ 
+     Note that on MS-DOS/MS-Windows, the branch that works around
+     missing `getwd' will probably only work for DJGPP (which does
+     have `getwd'), because only DJGPP reports meaningful
+     st_ino numbers.  But someday, somebody might need this...  */
+  {
+    char drive[3];
+    char *temp = cwd_path;
+
+    /* Make the drive letter lower-case, unless it is beyond Z: (yes,
+       there ARE such drives, in case of Novell Netware on MS-DOS).  */
+    drive[0] = root_stat.st_dev + (root_stat.st_dev < 26 ? 'a' : 'A');
+    drive[1] = ':';
+    drive[2] = '\0';
+
+    cwd_path = concat (drive, cwd_path);
+    free (temp);
+  }
+#endif
+
+  return cwd_path;
+#endif /* not HAVE_GETCWD && not HAVE_GETWD */
+}
+
+/* xmalloc.c: malloc with error checking.  */
+
+void *
+xmalloc (unsigned size)
+{
+  void *new_mem = (void *) malloc (size);
+
+  if (new_mem == NULL)
+    {
+      fprintf (stderr, "fatal: memory exhausted (xmalloc of %u bytes).\n",
+               size);
+      /* 1 means success on VMS, so pick a random number (ASCII `K').  */
+      exit (75);
+    }
+
+  return new_mem;
+}
+
+/* xrealloc.c: realloc with error checking.  */
+
+extern void *xmalloc (unsigned);
+
+void *
+xrealloc (void *old_ptr, unsigned size)
+{
+  void *new_mem;
+
+  if (old_ptr == NULL)
+    new_mem = xmalloc (size);
+  else
+    {
+      new_mem = (void *) realloc (old_ptr, size);
+      if (new_mem == NULL)
+        {
+          /* We used to print OLD_PTR here using %x, and casting its
+             value to unsigned, but that lost on the Alpha, where
+             pointers and unsigned had different sizes.  Since the info
+             is of little or no value anyway, just don't print it.  */
+          fprintf (stderr, "fatal: memory exhausted (realloc of %u bytes).\n",
+                   size);
+          /* 1 means success on VMS, so pick a random number (ASCII `B').  */
+          exit (66);
+        }
+    }
+
+  return new_mem;
+}
+
+/* xstrdup.c: strdup with error checking.  */
+
+/* Return a copy of S in new storage.  */
+
+char *
+xstrdup (const char *s)
+{
+  char *new_string = (char *) xmalloc (strlen (s) + 1);
+  return strcpy (new_string, s);
+}
+
+/* basename.c: return the last element in a path.  */
+
+#ifndef HAVE_BASENAME
+
+/* Return NAME with any leading path stripped off.  This returns a
+   pointer into NAME.  For example, `basename ("/foo/bar.baz")'
+   returns "bar.baz".  */
+
+const char *
+basename (const char *name)
+{
+  const char *base = NULL;
+  unsigned len = strlen (name);
+  
+  for (len = strlen (name); len > 0; len--) {
+    if (IS_DIR_SEP (name[len - 1]) || IS_DEVICE_SEP (name[len - 1])) {
+      base = name + len;
+      break;
+    }
+  }
+
+  if (!base)
+    base = name;
+  
+  return base;
+}
+
+#endif
+
+char *
+xbasename (const char *name)
+{
+  return (char *) basename (name);
+}
+
+/* file-p.c: file predicates.  */
+
+/* Test whether FILENAME1 and FILENAME2 are actually the same file.  If
+   stat fails on either of the names, we return false, without error.  */
+
+int
+same_file_p (const char *filename1, const char *filename2)
+{
+    struct stat sb1, sb2;
+    /* These are put in variables only so the results can be inspected
+       under gdb.  */
+    int r1 = stat (filename1, &sb1);
+    int r2 = stat (filename2, &sb2);
+
+    return r1 == 0 && r2 == 0 ? SAME_FILE_P (sb1, sb2) : 0;
+}
+
+/* dir.c: directory operations.  */
+
+/* Return true if FN is a directory or a symlink to a directory,
+   false if not. */
+
+int
+dir_p (const char *fn)
+{
+#ifdef WIN32
+  int fa = GetFileAttributes(fn);
+  return (fa != 0xFFFFFFFF && (fa & FILE_ATTRIBUTE_DIRECTORY));
+#else
+  struct stat stats;
+  return stat (fn, &stats) == 0 && S_ISDIR (stats.st_mode);
+#endif
+}
+
+#ifndef WIN32
+
+/* Return -1 if FN isn't a directory, else its number of links.
+   Duplicate the call to stat; no need to incur overhead of a function
+   call for that little bit of cleanliness. */
+
+int
+dir_links (const char *fn)
+{
+  static hash_table_type link_table;
+  char **hash_ret;
+  long ret;
+  
+  if (link_table.size == 0)
+    link_table = hash_create (457);
+
+#ifdef KPSE_DEBUG
+  /* This is annoying, but since we're storing integers as pointers, we
+     can't print them as strings.  */
+  if (KPSE_DEBUG_P (KPSE_DEBUG_HASH))
+    kpse_debug_hash_lookup_int = 1;
+#endif
+
+  hash_ret = hash_lookup (link_table, fn);
+  
+#ifdef KPSE_DEBUG
+  if (KPSE_DEBUG_P (KPSE_DEBUG_HASH))
+    kpse_debug_hash_lookup_int = 0;
+#endif
+
+  /* Have to cast the int we need to/from the const_string that the hash
+     table stores for values. Let's hope an int fits in a pointer.  */
+  if (hash_ret)
+    ret = (long) *hash_ret;
+  else
+    {
+      struct stat stats;
+      ret = stat (fn, &stats) == 0 && S_ISDIR (stats.st_mode)
+            ? stats.st_nlink : (unsigned) -1;
+
+      /* It's up to us to copy the value.  */
+      hash_insert (&link_table, xstrdup (fn), (const char *) ret);
+      
+#ifdef KPSE_DEBUG
+      if (KPSE_DEBUG_P (KPSE_DEBUG_STAT))
+        DEBUGF2 ("dir_links(%s) => %ld\n", fn, ret);
+#endif
+    }
+
+  return ret;
+}
+
+#endif /* !WIN32 */
+
+/* hash.c: hash table operations.  */
+
+/* The hash function.  We go for simplicity here.  */
+
+/* All our hash tables are related to filenames.  */
+#ifdef MONOCASE_FILENAMES
+#define TRANSFORM(x) toupper (x)
+#else
+#define TRANSFORM(x) (x)
+#endif
+
+static unsigned
+hash (hash_table_type table, const char *key)
+{
+  unsigned n = 0;
+  
+  /* Our keys aren't often anagrams of each other, so no point in
+     weighting the characters.  */
+  while (*key != 0)
+    n = (n + n + TRANSFORM (*key++)) % table.size;
+  
+  return n;
+}
+
+hash_table_type
+hash_create (unsigned size) 
+{
+  /* hash_table_type ret; changed into "static ..." to work around gcc
+     optimizer bug for Alpha.  */
+  static hash_table_type ret;
+  unsigned b;
+  ret.buckets = XTALLOC (size, hash_element_type *);
+  ret.size = size;
+  
+  /* calloc's zeroes aren't necessarily NULL, so be safe.  */
+  for (b = 0; b <ret.size; b++)
+    ret.buckets[b] = NULL;
+    
+  return ret;
+}
+
+/* Whether or not KEY is already in MAP, insert it and VALUE.  Do not
+   duplicate the strings, in case they're being purposefully shared.  */
+
+void
+hash_insert (hash_table_type *table, const char *key, const char *value)
+{
+  unsigned n = hash (*table, key);
+  hash_element_type *new_elt = XTALLOC1 (hash_element_type);
+
+  new_elt->key = key;
+  new_elt->value = value;
+  new_elt->next = NULL;
+  
+  /* Insert the new element at the end of the list.  */
+  if (!table->buckets[n])
+    /* first element in bucket is a special case.  */
+    table->buckets[n] = new_elt;
+  else
+    {
+      hash_element_type *loc = table->buckets[n];
+      while (loc->next)		/* Find the last element.  */
+        loc = loc->next;
+      loc->next = new_elt;	/* Insert the new one after.  */
+    }
+}
+
+/* Remove a (KEY, VALUE) pair.  */
+
+void
+hash_remove (hash_table_type *table, const char *key, const char *value)
+{
+  hash_element_type *p;
+  hash_element_type *q;
+  unsigned n = hash (*table, key);
+
+  /* Find pair.  */
+  for (q = NULL, p = table->buckets[n]; p != NULL; q = p, p = p->next)
+    if (FILESTRCASEEQ (key, p->key) && STREQ (value, p->value))
+      break;
+  if (p) {
+    /* We found something, remove it from the chain.  */
+    if (q) q->next = p->next; else table->buckets[n] = p->next;
+    /* We cannot dispose of the contents.  */
+    free (p);
+  }
+}
+
+/* Look up STR in MAP.  Return a (dynamically-allocated) list of the
+   corresponding strings or NULL if no match.  */ 
+
+#ifdef KPSE_DEBUG
+/* Print the hash values as integers if this is nonzero.  */
+int kpse_debug_hash_lookup_int = 0; 
+#endif
+
+char **
+hash_lookup (hash_table_type table, const char *key)
+{
+  hash_element_type *p;
+  str_list_type ret;
+  unsigned n = hash (table, key);
+  ret = str_list_init ();
+  
+  /* Look at everything in this bucket.  */
+  for (p = table.buckets[n]; p != NULL; p = p->next)
+    if (FILESTRCASEEQ (key, p->key))
+      /* Cast because the general str_list_type shouldn't force const data.  */
+      str_list_add (&ret, (char *) p->value);
+  
+  /* If we found anything, mark end of list with null.  */
+  if (STR_LIST (ret))
+    str_list_add (&ret, NULL);
+
+#ifdef KPSE_DEBUG
+  if (KPSE_DEBUG_P (KPSE_DEBUG_HASH))
+    {
+      DEBUGF1 ("hash_lookup(%s) =>", key);
+      if (!STR_LIST (ret))
+        fputs (" (nil)\n", stderr);
+      else
+        {
+          char **r;
+          for (r = STR_LIST (ret); *r; r++)
+            {
+              putc (' ', stderr);
+              if (kpse_debug_hash_lookup_int)
+                fprintf (stderr, "%ld", (long) *r);
+              else
+                fputs (*r, stderr);
+            }
+          putc ('\n', stderr);
+        }
+      fflush (stderr);
+    }
+#endif
+
+  return STR_LIST (ret);
+}
+
+/* We only print nonempty buckets, to decrease output volume.  */
+
+void
+hash_print (hash_table_type table, int summary_only)
+{
+  unsigned b;
+  unsigned total_elements = 0, total_buckets = 0;
+  
+  for (b = 0; b < table.size; b++) {
+    hash_element_type *bucket = table.buckets[b];
+
+    if (bucket) {
+      unsigned len = 1;
+      hash_element_type *tb;
+
+      total_buckets++;
+      if (!summary_only) fprintf (stderr, "%4d ", b);
+
+      for (tb = bucket->next; tb != NULL; tb = tb->next)
+        len++;
+      if (!summary_only) fprintf (stderr, ":%-5d", len);
+      total_elements += len;
+
+      if (!summary_only) {
+        for (tb = bucket; tb != NULL; tb = tb->next)
+          fprintf (stderr, " %s=>%s", tb->key, tb->value);
+        putc ('\n', stderr);
+      }
+    }
+  }
+  
+  fprintf (stderr,
+          "%u buckets, %u nonempty (%u%%); %u entries, average chain %.1f.\n",
+          table.size,
+          total_buckets,
+          100 * total_buckets / table.size,
+          total_elements,
+          total_buckets ? total_elements / (double) total_buckets : 0.0);
+}
+
+/* concat.c: dynamic string concatenation.  */
+
+/* Return the concatenation of S1 and S2.  See `concatn.c' for a
+   `concatn', which takes a variable number of arguments.  */
+
+char *
+concat (const char *s1, const char *s2)
+{
+  char *answer = (char *) xmalloc (strlen (s1) + strlen (s2) + 1);
+  strcpy (answer, s1);
+  strcat (answer, s2);
+
+  return answer;
+}
+
+/* concat3.c: concatenate three strings.  */
+
+char *
+concat3 (const char *s1, const char *s2, const char *s3)
+{
+  char *answer
+    = (char *) xmalloc (strlen (s1) + strlen (s2) + strlen (s3) + 1);
+  strcpy (answer, s1);
+  strcat (answer, s2);
+  strcat (answer, s3);
+
+  return answer;
+}
+
+/* concatn.c: Concatenate an arbitrary number of strings.  */
+
+/* OK, it would be epsilon more efficient to compute the total length
+   and then do the copying ourselves, but I doubt it matters in reality.  */
+
+char *
+concatn (const char *str1, ...)
+{
+  char *arg;
+  char *ret;
+  va_list ap;
+
+  va_start (ap, str1);
+
+  if (!str1)
+    return NULL;
+  
+  ret = xstrdup (str1);
+  
+  while ((arg = va_arg (ap, char *)) != NULL)
+    {
+      char *temp = concat (ret, arg);
+      free (ret);
+      ret = temp;
+    }
+  va_end (ap);
+  
+  return ret;
+}
+
+/* debug.c: Help the user discover what's going on.  */
+
+#ifdef KPSE_DEBUG
+
+unsigned kpathsea_debug = 0;
+
+/* If the real definitions of fopen or fclose are macros, we lose -- the
+   #undef won't restore them. */
+
+FILE *
+fopen (const char *filename, const char *mode)
+{
+#undef fopen
+  FILE *ret = fopen (filename, mode);
+
+  if (KPSE_DEBUG_P (KPSE_DEBUG_FOPEN))
+    DEBUGF3 ("fopen(%s, %s) => 0x%lx\n", filename, mode, (unsigned long) ret);
+
+  return ret;
+}
+
+int
+fclose (FILE *f)
+{
+#undef fclose
+  int ret = fclose (f);
+  
+  if (KPSE_DEBUG_P (KPSE_DEBUG_FOPEN))
+    DEBUGF2 ("fclose(0x%lx) => %d\n", (unsigned long) f, ret);
+
+  return ret;
+}
+
+#endif
+
+/* libc replacement functions for win32.  */
+
+/*
+  This does make sense only under WIN32.
+  Functions:
+    - popen() rewritten
+    - pclose() rewritten
+    - stat() wrapper for _stat(), removing trailing slashes
+  */
+
+#ifdef WIN32
+
+#include <fcntl.h>
+
+/* The X library (among other things) defines `FALSE' and `TRUE', and so
+   we only want to define them if necessary, for use by application code.  */
+#ifndef FALSE
+#define FALSE 0
+#define TRUE 1
+#endif /* FALSE */
+
+struct _popen_elt {
+  FILE *f;			/* File stream returned */
+  HANDLE hp;			/* Handle of associated process */
+  struct _popen_elt *next;	/* Next list element */
+};
+
+static struct _popen_elt _z = { NULL, 0, &_z };
+static struct _popen_elt *_popen_list = &_z;
+
+FILE *popen (const char *cmd, const char *mode)
+{
+  STARTUPINFO si;
+  PROCESS_INFORMATION pi;
+  SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
+  FILE *f = NULL;
+  int fno, i;
+  HANDLE child_in, child_out;
+  HANDLE father_in, father_out;
+  HANDLE father_in_dup, father_out_dup;
+  HANDLE current_in, current_out;
+  HANDLE current_pid;
+  int binary_mode;
+  char *new_cmd, *app_name = NULL;
+  char *p, *q;
+  struct _popen_elt *new_process;
+  char pname[PATH_MAX], *fp;
+  char *suffixes[] = { ".bat", ".cmd", ".com", ".exe", NULL };
+  char **s;
+  int go_on;
+
+  /* We should look for the application name along the PATH,
+     and decide to prepend "%COMSPEC% /c " or not to the command line.
+     Do nothing for the moment. */
+
+  /* Another way to do that would be to try CreateProcess first without
+     invoking cmd, and look at the error code. If it fails because of
+     command not found, try to prepend "cmd /c" to the cmd line.
+     */
+
+  /* Look for the application name */
+  for (p = cmd; *p && isspace(*p); p++);
+  if (*p == '"') {
+    q = ++p;
+    while(*p && *p != '"') p++;
+    if (*p != '\0') {
+      fprintf(stderr, "popen: malformed command (\" not terminated)\n");
+      return NULL;
+    }
+  }
+  else
+    for (q = p; *p && !isspace(*p); p++);
+  /* q points to the beginning of appname, p to the last + 1 char */
+  if ((app_name = malloc(p - q + 1)) == NULL) {
+    fprintf(stderr, "xpopen: malloc(app_name) failed.\n");
+    return NULL;
+  }
+  strncpy(app_name, q, p - q );
+  app_name[p - q] = '\0';
+  pname[0] = '\0';
+#ifdef TRACE
+  fprintf(stderr, "popen: app_name = %s\n", app_name);
+#endif
+
+  /* Looking for appname on the path */
+  for (s = suffixes, go_on = 1; go_on; *s++) {
+    if (SearchPath(NULL,	/* Address of search path */
+		   app_name,	/* Address of filename */
+		   *s,		/* Address of extension */
+		   PATH_MAX,	/* Size of destination buffer */
+		   pname,	/* Address of destination buffer */
+		   &fp)		/* File part of app_name */
+      != 0) {
+#ifdef TRACE
+      fprintf(stderr, "%s found with suffix %s\n", app_name, *s);
+#endif
+      new_cmd = xstrdup(cmd);
+      free(app_name);
+      app_name = xstrdup(pname);
+      break;
+    }
+    go_on = (*s != NULL);
+  }
+  if (go_on == 0) {
+    /* the app_name was not found */
+#ifdef TRACE
+    fprintf(stderr, "%s not found, concatenating comspec\n", app_name);
+#endif
+    new_cmd = concatn(getenv("COMSPEC"), " /c ", cmd, NULL);
+    free(app_name);
+    app_name = NULL;
+  }
+  else {
+  }
+#ifdef TRACE
+  fprintf(stderr, "popen: app_name = %s\n", app_name);
+  fprintf(stderr, "popen: cmd_line = %s\n", new_cmd);
+#endif
+
+  current_in = GetStdHandle(STD_INPUT_HANDLE);
+  current_out = GetStdHandle(STD_OUTPUT_HANDLE);
+  current_pid = GetCurrentProcess();
+  ZeroMemory( &si, sizeof(STARTUPINFO) );
+  si.cb = sizeof(STARTUPINFO);
+  si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
+  si.wShowWindow = SW_HIDE;
+
+  if (strchr(mode, 'b'))
+    binary_mode = _O_BINARY;
+  else
+    binary_mode = _O_TEXT;
+
+  /* Opening the pipe for writing */
+  if (strchr(mode, 'w')) {
+    binary_mode |= _O_WRONLY;
+    if (CreatePipe(&child_in, &father_out, &sa, 0) == FALSE) {
+      fprintf(stderr, "popen: error CreatePipe\n");
+      return NULL;
+    }
+#if 0
+    if (SetStdHandle(STD_INPUT_HANDLE, child_in) == FALSE) {
+      fprintf(stderr, "popen: error SetStdHandle child_in\n");
+      return NULL;
+    }
+#endif
+    si.hStdInput = child_in;
+    si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
+    si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+
+    if (DuplicateHandle(current_pid, father_out, 
+			current_pid, &father_out_dup, 
+			0, FALSE, DUPLICATE_SAME_ACCESS) == FALSE) {
+      fprintf(stderr, "popen: error DuplicateHandle father_out\n");
+      return NULL;
+    }
+    CloseHandle(father_out);
+    fno = _open_osfhandle((long)father_out_dup, binary_mode);
+    f = _fdopen(fno, mode);
+    i = setvbuf( f, NULL, _IONBF, 0 );
+  }
+  /* Opening the pipe for reading */
+  else if (strchr(mode, 'r')) {
+    binary_mode |= _O_RDONLY;
+    if (CreatePipe(&father_in, &child_out, &sa, 0) == FALSE) {
+      fprintf(stderr, "popen: error CreatePipe\n");
+      return NULL;
+    }
+#if 0
+    if (SetStdHandle(STD_OUTPUT_HANDLE, child_out) == FALSE) {
+      fprintf(stderr, "popen: error SetStdHandle child_out\n");
+      return NULL;
+    }
+#endif
+    si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
+    si.hStdOutput = child_out;
+    si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+    if (DuplicateHandle(current_pid, father_in, 
+			current_pid, &father_in_dup, 
+			0, FALSE, DUPLICATE_SAME_ACCESS) == FALSE) {
+      fprintf(stderr, "popen: error DuplicateHandle father_in\n");
+      return NULL;
+    }
+    CloseHandle(father_in);
+    fno = _open_osfhandle((long)father_in_dup, binary_mode);
+    f = _fdopen(fno, mode);
+    i = setvbuf( f, NULL, _IONBF, 0 );
+  }
+  else {
+    fprintf(stderr, "popen: invalid mode %s\n", mode);
+    return NULL;
+  }
+
+  /* creating child process */
+  if (CreateProcess(app_name,	/* pointer to name of executable module */
+		    new_cmd,	/* pointer to command line string */
+		    NULL,	/* pointer to process security attributes */
+		    NULL,	/* pointer to thread security attributes */
+		    TRUE,	/* handle inheritance flag */
+		    CREATE_NEW_CONSOLE,		/* creation flags */
+		    NULL,	/* pointer to environment */
+		    NULL,	/* pointer to current directory */
+		    &si,	/* pointer to STARTUPINFO */
+		    &pi		/* pointer to PROCESS_INFORMATION */
+		  ) == FALSE) {
+    fprintf(stderr, "popen: CreateProcess %x\n", GetLastError());
+    return NULL;
+  }
+  
+#if 0
+  /* Restoring saved values for stdin/stdout */
+  if (SetStdHandle(STD_INPUT_HANDLE, current_in) == FALSE) 
+    fprintf(stderr, "popen: error re-redirecting Stdin\n");  
+  if (SetStdHandle(STD_OUTPUT_HANDLE, current_out) == FALSE) 
+    fprintf(stderr, "popen: error re-redirecting Stdout\n");  
+#endif  
+   /* Only the process handle is needed */
+  if (CloseHandle(pi.hThread) == FALSE) {
+    fprintf(stderr, "popen: error closing thread handle\n");
+    return NULL;
+  }
+
+  if (new_cmd) free(new_cmd);
+  if (app_name) free(app_name);
+
+#if 0
+  /* This does not seem to make sense for console apps */
+  while (1) {
+    i = WaitForInputIdle(pi.hProcess, 5); /* Wait 5ms  */
+    if (i == 0xFFFFFFFF) {
+      fprintf(stderr, "popen: process can't initialize\n");
+      return NULL;
+    }
+    else if (i == WAIT_TIMEOUT)
+      fprintf(stderr, "popen: warning, process still not initialized\n");
+    else
+      break;
+  }
+#endif
+
+  /* Add the pair (f, pi.hProcess) to the list */
+  if ((new_process = malloc(sizeof(struct _popen_elt))) == NULL) {
+    fprintf (stderr, "popen: malloc(new_process) error\n");
+    return NULL;
+  }
+  /* Saving the FILE * pointer, access key for retrieving the process
+     handle later on */
+  new_process->f = f;
+  /* Closing the unnecessary part of the pipe */
+  if (strchr(mode, 'r')) {
+    CloseHandle(child_out);
+  }
+  else if (strchr(mode, 'w')) {
+    CloseHandle(child_in);
+  }
+  /* Saving the process handle */
+  new_process->hp = pi.hProcess;
+  /* Linking it to the list of popen() processes */
+  new_process->next = _popen_list;
+  _popen_list = new_process;
+
+  return f;
+
+}
+
+int pclose (FILE *f)
+{
+  struct _popen_elt *p, *q;
+  int exit_code;
+
+  /* Look for f is the access key in the linked list */
+  for (q = NULL, p = _popen_list; 
+       p != &_z && p->f != f; 
+       q = p, p = p->next);
+
+  if (p == &_z) {
+    fprintf(stderr, "pclose: error, file not found.");
+    return -1;
+  }
+
+  /* Closing the FILE pointer */
+  fclose(f);
+
+  /* Waiting for the process to terminate */
+  if (WaitForSingleObject(p->hp, INFINITE) != WAIT_OBJECT_0) {
+    fprintf(stderr, "pclose: error, process still active\n");
+    return -1;
+  }
+
+  /* retrieving the exit code */
+  if (GetExitCodeProcess(p->hp, &exit_code) == 0) {
+    fprintf(stderr, "pclose: can't get process exit code\n");
+    return -1;
+  }
+
+  /* Closing the process handle, this will cause the system to
+     remove the process from memory */
+  if (CloseHandle(p->hp) == FALSE) {
+    fprintf(stderr, "pclose: error closing process handle\n");
+    return -1;
+  }
+
+  /* remove the elt from the list */
+  if (q != NULL)
+    q->next = p->next;
+  else
+    _popen_list = p->next;
+  free(p);
+    
+  return exit_code;
+}
+
+#endif
+
+/* find-suffix.c: return the stuff after a dot.  */
+
+/* Return pointer to first character after `.' in last directory element
+   of NAME.  If the name is `foo' or `/foo.bar/baz', we have no extension.  */
+
+char *
+find_suffix (const char *name)
+{
+  const char *slash_pos;
+  char *dot_pos = strrchr (name, '.');
+  
+  if (dot_pos == NULL)
+    return NULL;
+  
+  for (slash_pos = name + strlen (name);
+       slash_pos > dot_pos && !IS_DIR_SEP (*slash_pos);
+       slash_pos--)
+    ;
+  
+  return slash_pos > dot_pos ? NULL : dot_pos + 1;
+}
+
+/* rm-suffix.c: remove any suffix.  */
+
+/* Generic const warning -- see extend-fname.c.  */
+
+char *
+remove_suffix (const char *s)
+{
+  char *ret;
+  const char *suffix = find_suffix (s);
+  
+  if (suffix)
+    {
+      /* Back up to before the dot.  */
+      suffix--;
+      ret = (char *) xmalloc (suffix - s + 1);
+      strncpy (ret, s, suffix - s);
+      ret[suffix - s] = 0;
+    }
+  else
+    ret = (char *) s;
+    
+  return ret;
+}
+
+/* make-suffix.c: unconditionally add a filename suffix.  */
+
+/* Return a new string: S suffixed with SUFFIX, regardless of what it
+   was before. This returns a newly allocated string.  */ 
+
+char *
+make_suffix (const char *s, const char *suffix)
+{
+  char *new_s;
+  const char *dot_pos = strrchr (s, '.');
+  const char *slash_pos;
+  
+  for (slash_pos = s + strlen (s) - 1; slash_pos > dot_pos && slash_pos > s;
+       slash_pos--) {
+    if (IS_DIR_SEP (*slash_pos))
+      break;
+  }
+
+  if (dot_pos == NULL || slash_pos > dot_pos )
+    new_s = concat3 (s, ".", suffix);
+  else
+    {
+      unsigned past_dot_index = dot_pos + 1 - s;
+      
+      new_s = (char *) xmalloc (past_dot_index + strlen (suffix) + 1);
+      strncpy (new_s, s, dot_pos + 1 - s);
+      strcpy (new_s + past_dot_index, suffix);
+    }
+
+  return new_s;
+}
+
+/* readable.c: check if a filename is a readable non-directory file.  */
+
+/* Truncate any too-long components in NAME, returning the result.  It's
+   too bad this is necessary.  See comments in readable.c for why.  */
+
+static char *
+kpse_truncate_filename (const char *name)
+{
+  unsigned c_len = 0;        /* Length of current component.  */
+  unsigned ret_len = 0;      /* Length of constructed result.  */
+  
+  /* Allocate enough space.  */
+  char *ret = (char *) xmalloc (strlen (name) + 1);
+
+  for (; *name; name++)
+    {
+      if (IS_DIR_SEP (*name) || IS_DEVICE_SEP (*name))
+        { /* At a directory delimiter, reset component length.  */
+          c_len = 0;
+        }
+      else if (c_len > NAME_MAX)
+        { /* If past the max for a component, ignore this character.  */
+          continue;
+        }
+
+      /* Copy this character.  */
+      ret[ret_len++] = *name;
+      c_len++;
+    }
+  ret[ret_len] = 0;
+
+  return ret;
+}
+
+/* If access can read FN, run stat (assigning to stat buffer ST) and
+   check that fn is not a directory.  Don't check for just being a
+   regular file, as it is potentially useful to read fifo's or some
+   kinds of devices.  */
+
+#ifdef __DJGPP__
+/* `stat' is way too expensive for such a simple job.  */
+#define READABLE(fn, st) \
+  (access (fn, R_OK) == 0 && access (fn, D_OK) == -1)
+#elif WIN32
+#define READABLE(fn, st) \
+  (GetFileAttributes(fn) != 0xFFFFFFFF && \
+   !(GetFileAttributes(fn) & FILE_ATTRIBUTE_DIRECTORY))
+#else
+#define READABLE(fn, st) \
+  (access (fn, R_OK) == 0 && stat (fn, &(st)) == 0 && !S_ISDIR (st.st_mode))
+#endif
+
+/* POSIX invented the brain-damage of not necessarily truncating
+   filename components; the system's behavior is defined by the value of
+   the symbol _POSIX_NO_TRUNC, but you can't change it dynamically!
+   
+   Generic const return warning.  See extend-fname.c.  */
+
+char *
+kpse_readable_file (const char *name)
+{
+  struct stat st;
+  char *ret;
+  
+  if (READABLE (name, st)) {
+    ret = (char *) name;
+
+#ifdef ENAMETOOLONG
+  } else if (errno == ENAMETOOLONG) {
+    ret = kpse_truncate_filename (name);
+
+    /* Perhaps some other error will occur with the truncated name, so
+       let's call access again.  */
+    if (!READABLE (ret, st))
+      { /* Failed.  */
+        if (ret != name) free (ret);
+        ret = NULL;
+      }
+#endif /* ENAMETOOLONG */
+
+  } else { /* Some other error.  */
+    if (errno == EACCES) { /* Maybe warn them if permissions are bad.  */
+      perror (name);
+    }
+    ret = NULL;
+  }
+  
+  return ret;
+}
+
+/* absolute.c: Test if a filename is absolute or explicitly relative.  */
+
+/* Sorry this is such a system-dependent mess, but I can't see any way
+   to usefully generalize.  */
+
+int
+kpse_absolute_p (const char *filename, int relative_ok)
+{
+  int absolute = IS_DIR_SEP (*filename)
+#ifdef DOSISH
+                     /* Novell allows non-alphanumeric drive letters. */
+                     || (*filename && IS_DEVICE_SEP (filename[1]))
+#endif /* DOSISH */
+#ifdef WIN32
+                     /* UNC names */
+                     || (*filename == '\\' && filename[1] == '\\')
+#endif
+		      ;
+  int explicit_relative
+    = relative_ok
+      && (*filename == '.' && (IS_DIR_SEP (filename[1])
+                         || (filename[1] == '.' && IS_DIR_SEP (filename[2]))));
+
+  return absolute || explicit_relative;
+}
+
+/* str-list.c: define routines for string lists.  */
+
+/* See the lib.h file for comments.  */
+
+str_list_type
+str_list_init (void)
+{
+  str_list_type ret;
+  
+  STR_LIST_LENGTH (ret) = 0;
+  STR_LIST (ret) = NULL;
+  
+  return ret;
+}
+
+void
+str_list_add (str_list_type *l, char *s)
+{
+  STR_LIST_LENGTH (*l)++;
+  XRETALLOC (STR_LIST (*l), STR_LIST_LENGTH (*l), char *);
+  STR_LIST_LAST_ELT (*l) = s;
+}
+
+/* May as well save some reallocations and do everything in a chunk
+   instead of calling str_list_add on each element.  */
+   
+void
+str_list_concat (str_list_type *target, str_list_type more)
+{
+  unsigned e;
+  unsigned prev_len = STR_LIST_LENGTH (*target);
+
+  STR_LIST_LENGTH (*target) += STR_LIST_LENGTH (more);
+  XRETALLOC (STR_LIST (*target), STR_LIST_LENGTH (*target), char *);
+  
+  for (e = 0; e < STR_LIST_LENGTH (more); e++)
+    STR_LIST_ELT (*target, prev_len + e) = STR_LIST_ELT (more, e);
+}
+
+/* Free the list (but not the elements within it).  */
+
+void
+str_list_free (str_list_type *l)
+{
+  if (STR_LIST (*l))
+    {
+      free (STR_LIST (*l));
+      STR_LIST (*l) = NULL;
+    }
+}
+
+/* str-llist.c: Implementation of a linked list of strings.  */
+
+/* Add the new string STR to the end of the list L.  */
+
+void
+str_llist_add (str_llist_type *l, char *str)
+{
+  str_llist_elt_type *e;
+  str_llist_elt_type *new_elt = XTALLOC1 (str_llist_elt_type);
+  
+  /* The new element will be at the end of the list.  */
+  STR_LLIST (*new_elt) = str;
+  STR_LLIST_MOVED (*new_elt) = 0;
+  STR_LLIST_NEXT (*new_elt) = NULL;
+  
+  /* Find the current end of the list.  */
+  for (e = *l; e && STR_LLIST_NEXT (*e); e = STR_LLIST_NEXT (*e))
+    ;
+  
+  if (!e)
+    *l = new_elt;
+  else
+    STR_LLIST_NEXT (*e) = new_elt;
+}
+
+/* Move an element towards the top. The idea is that when a file is
+   found in a given directory, later files will likely be in that same
+   directory, and looking for the file in all the directories in between
+   is thus a waste.  */
+
+void
+str_llist_float (str_llist_type *l, str_llist_elt_type *mover)
+{
+  str_llist_elt_type *last_moved, *unmoved;
+  
+  /* If we've already moved this element, never mind.  */
+  if (STR_LLIST_MOVED (*mover))
+    return;
+  
+  /* Find the first unmoved element (to insert before).  We're
+     guaranteed this will terminate, since MOVER itself is currently
+     unmoved, and it must be in L (by hypothesis).  */
+  for (last_moved = NULL, unmoved = *l; STR_LLIST_MOVED (*unmoved);
+       last_moved = unmoved, unmoved = STR_LLIST_NEXT (*unmoved))
+    ;
+
+  /* If we are the first unmoved element, nothing to relink.  */
+  if (unmoved != mover)
+    { /* Remember `mover's current successor, so we can relink `mover's
+         predecessor to it.  */
+      str_llist_elt_type *before_mover;
+      str_llist_elt_type *after_mover = STR_LLIST_NEXT (*mover);
+      
+      /* Find `mover's predecessor.  */
+      for (before_mover = unmoved; STR_LLIST_NEXT (*before_mover) != mover;
+           before_mover = STR_LLIST_NEXT (*before_mover))
+        ;
+      
+      /* `before_mover' now links to `after_mover'.  */
+      STR_LLIST_NEXT (*before_mover) = after_mover;
+
+      /* Insert `mover' before `unmoved' and after `last_moved' (or at
+         the head of the list).  */
+      STR_LLIST_NEXT (*mover) = unmoved;
+      if (!last_moved)
+        *l = mover;
+      else
+        STR_LLIST_NEXT (*last_moved) = mover;
+    }
+
+  /* We've moved it.  */
+  STR_LLIST_MOVED (*mover) = 1;
+}
+
+/* fn.c: arbitrarily long filenames (or just strings).  */
+
+/* /usr/local/lib/texmf/fonts/public/cm/pk/ljfour/cmr10.300pk is 58
+   chars, so ASCII `K' seems a good choice. */
+#define CHUNK_SIZE 75
+
+fn_type
+fn_init (void)
+{
+  fn_type ret;
+  
+  FN_ALLOCATED (ret) = FN_LENGTH (ret) = 0;
+  FN_STRING (ret) = NULL;
+  
+  return ret;
+}
+
+fn_type
+fn_copy0 (const char *s, unsigned len)
+{
+  fn_type ret;
+  
+  FN_ALLOCATED (ret) = CHUNK_SIZE > len ? CHUNK_SIZE : len + 1;
+  FN_STRING (ret) = (char *) xmalloc (FN_ALLOCATED (ret));
+  
+  strncpy (FN_STRING (ret), s, len);
+  FN_STRING (ret)[len] = 0;
+  FN_LENGTH (ret) = len + 1;
+  
+  return ret;
+}
+
+/* Don't think we ever try to free something that might usefully be
+   empty, so give fatal error if nothing allocated.  */
+
+void
+fn_free (fn_type *f)
+{
+  assert (FN_STRING (*f) != NULL);
+  free (FN_STRING (*f));
+  FN_STRING (*f) = NULL;
+  FN_ALLOCATED (*f) = 0;
+  FN_LENGTH (*f) = 0;
+}
+
+/* An arithmetic increase seems more reasonable than geometric.  We
+   don't increase the length member since it may be more convenient for
+   the caller to add than subtract when appending the stuff that will
+   presumably follow.  */
+
+static void
+grow (fn_type *f, unsigned len)
+{
+  while (FN_LENGTH (*f) + len > FN_ALLOCATED (*f))
+    {
+      FN_ALLOCATED (*f) += CHUNK_SIZE;
+      XRETALLOC (FN_STRING (*f), FN_ALLOCATED (*f), char);
+    }
+}
+
+void
+fn_1grow (fn_type *f, char c)
+{
+  grow (f, 1);
+  FN_STRING (*f)[FN_LENGTH (*f)] = c;
+  FN_LENGTH (*f)++;
+}
+
+void
+fn_grow (fn_type *f, void *source, unsigned len)
+{
+  grow (f, len);
+  strncpy (FN_STRING (*f) + FN_LENGTH (*f), (char *) source, len);
+  FN_LENGTH (*f) += len;
+}
+
+void
+fn_str_grow (fn_type *f, const char *s)
+{
+  unsigned more_len = strlen (s);
+  grow (f, more_len);
+  strcat (FN_STRING (*f), s);
+  FN_LENGTH (*f) += more_len;
+}
+
+void
+fn_shrink_to (fn_type *f, unsigned loc)
+{
+  assert (FN_LENGTH (*f) > loc);
+  FN_STRING (*f)[loc] = 0;
+  FN_LENGTH (*f) = loc + 1;
+}
+
+/* variable.c: variable expansion.  */
+
+/* Here's the simple one, when a program just wants a value.  */
+
+char *
+kpse_var_value (const char *var)
+{
+  char *ret = getenv (var);
+
+  if (ret)
+    ret = kpse_var_expand (ret);
+
+#ifdef KPSE_DEBUG
+  if (KPSE_DEBUG_P (KPSE_DEBUG_VARS))
+    DEBUGF2("variable: %s = %s\n", var, ret ? ret : "(nil)");
+#endif
+
+  return ret;
+}
+
+/* We have to keep track of variables being expanded, otherwise
+   constructs like TEXINPUTS = $TEXINPUTS result in an infinite loop.
+   (Or indirectly recursive variables, etc.)  Our simple solution is to
+   add to a list each time an expansion is started, and check the list
+   before expanding.  */
+
+typedef struct {
+  const char *var;
+  int expanding;
+} expansion_type;
+static expansion_type *expansions; /* The sole variable of this type.  */
+static unsigned expansion_len = 0;
+
+static void
+expanding (const char *var, int xp)
+{
+  unsigned e;
+  for (e = 0; e < expansion_len; e++) {
+    if (STREQ (expansions[e].var, var)) {
+      expansions[e].expanding = xp;
+      return;
+    }
+  }
+
+  /* New variable, add it to the list.  */
+  expansion_len++;
+  XRETALLOC (expansions, expansion_len, expansion_type);
+  expansions[expansion_len - 1].var = xstrdup (var);
+  expansions[expansion_len - 1].expanding = xp;
+}
+
+
+/* Return whether VAR is currently being expanding.  */
+
+static int
+expanding_p (const char *var)
+{
+  unsigned e;
+  for (e = 0; e < expansion_len; e++) {
+    if (STREQ (expansions[e].var, var))
+      return expansions[e].expanding;
+  }
+  
+  return 0;
+}
+
+/* Append the result of value of `var' to EXPANSION, where `var' begins
+   at START and ends at END.  If `var' is not set, do not complain.
+   This is a subroutine for the more complicated expansion function.  */
+
+static void
+expand (fn_type *expansion, const char *start, const char *end)
+{
+  char *value;
+  unsigned len = end - start + 1;
+  char *var = (char *) xmalloc (len + 1);
+  strncpy (var, start, len);
+  var[len] = 0;
+  
+  if (expanding_p (var)) {
+    WARNING1 ("kpathsea: variable `%s' references itself (eventually)", var);
+  } else {
+    /* Check for an environment variable.  */
+    value = getenv (var);
+
+    if (value) {
+      expanding (var, 1);
+      value = kpse_var_expand (value);
+      expanding (var, 0);
+      fn_grow (expansion, value, strlen (value));
+      free (value);
+    }
+
+    free (var);
+  }
+}
+
+/* Can't think of when it would be useful to change these (and the
+   diagnostic messages assume them), but ... */
+#ifndef IS_VAR_START /* starts all variable references */
+#define IS_VAR_START(c) ((c) == '$')
+#endif
+#ifndef IS_VAR_CHAR  /* variable name constituent */
+#define IS_VAR_CHAR(c) (ISALNUM (c) || (c) == '_')
+#endif
+#ifndef IS_VAR_BEGIN_DELIMITER /* start delimited variable name (after $) */
+#define IS_VAR_BEGIN_DELIMITER(c) ((c) == '{')
+#endif
+#ifndef IS_VAR_END_DELIMITER
+#define IS_VAR_END_DELIMITER(c) ((c) == '}')
+#endif
+
+
+/* Maybe we should support some or all of the various shell ${...}
+   constructs, especially ${var-value}.  */
+
+char *
+kpse_var_expand (const char *src)
+{
+  const char *s;
+  char *ret;
+  fn_type expansion;
+  expansion = fn_init ();
+  
+  /* Copy everything but variable constructs.  */
+  for (s = src; *s; s++) {
+    if (IS_VAR_START (*s)) {
+      s++;
+
+      /* Three cases: `$VAR', `${VAR}', `$<anything-else>'.  */
+      if (IS_VAR_CHAR (*s)) {
+        /* $V: collect name constituents, then expand.  */
+        const char *var_end = s;
+
+        do {
+          var_end++;
+        } while (IS_VAR_CHAR (*var_end));
+
+        var_end--; /* had to go one past */
+        expand (&expansion, s, var_end);
+        s = var_end;
+
+      } else if (IS_VAR_BEGIN_DELIMITER (*s)) {
+        /* ${: scan ahead for matching delimiter, then expand.  */
+        const char *var_end = ++s;
+
+        while (*var_end && !IS_VAR_END_DELIMITER (*var_end))
+          var_end++;
+
+        if (! *var_end) {
+          WARNING1 ("%s: No matching } for ${", src);
+          s = var_end - 1; /* will incr to null at top of loop */
+        } else {
+          expand (&expansion, s, var_end - 1);
+          s = var_end; /* will incr past } at top of loop*/
+        }
+
+      } else {
+        /* $<something-else>: error.  */
+        WARNING2 ("%s: Unrecognized variable construct `$%c'", src, *s);
+        /* Just ignore those chars and keep going.  */
+      }
+    } else
+     fn_1grow (&expansion, *s);
+  }
+  fn_1grow (&expansion, 0);
+          
+  ret = FN_STRING (expansion);
+  return ret;
+}