Back to index

tetex-bin  3.0
elt-dirs.c
Go to the documentation of this file.
00001 /* elt-dirs.c: Translate a path element to its corresponding director{y,ies}.
00002 
00003 Copyright (C) 1993, 94, 95, 96, 97 Karl Berry.
00004 Copyright (C) 1997, 1998, 99, 2000, Olaf Weber.
00005 
00006 This library is free software; you can redistribute it and/or
00007 modify it under the terms of the GNU Library General Public
00008 License as published by the Free Software Foundation; either
00009 version 2 of the License, or (at your option) any later version.
00010 
00011 This library is distributed in the hope that it will be useful,
00012 but WITHOUT ANY WARRANTY; without even the implied warranty of
00013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014 Library General Public License for more details.
00015 
00016 You should have received a copy of the GNU Library General Public
00017 License along with this library; if not, write to the Free Software
00018 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
00019 
00020 #include <kpathsea/config.h>
00021 
00022 #include <kpathsea/c-pathch.h>
00023 #include <kpathsea/expand.h>
00024 #include <kpathsea/fn.h>
00025 #include <kpathsea/pathsearch.h>
00026 #include <kpathsea/xopendir.h>
00027 
00028 /* To avoid giving prototypes for all the routines and then their real
00029    definitions, we give all the subroutines first.  The entry point is
00030    the last routine in the file.  */
00031 
00032 /* Make a copy of DIR (unless it's null) and save it in L.  Ensure that
00033    DIR ends with a DIR_SEP for the benefit of later searches.  */
00034 
00035 static void
00036 dir_list_add P2C(str_llist_type *, l,  const_string, dir)
00037 {
00038   char last_char = dir[strlen (dir) - 1];
00039   string saved_dir
00040     = IS_DIR_SEP (last_char) || IS_DEVICE_SEP (last_char)
00041       ? xstrdup (dir)
00042       : concat (dir, DIR_SEP_STRING);
00043   
00044   str_llist_add (l, saved_dir);
00045 }
00046 
00047 
00048 /* If DIR is a directory, add it to the list L.  */
00049 
00050 static void
00051 checked_dir_list_add P2C(str_llist_type *, l,  const_string, dir)
00052 {
00053   if (dir_p (dir))
00054     dir_list_add (l, dir);
00055 }
00056 
00057 /* The cache.  Typically, several paths have the same element; for
00058    example, /usr/local/lib/texmf/fonts//.  We don't want to compute the
00059    expansion of such a thing more than once.  Even though we also cache
00060    the dir_links call, that's not enough -- without this path element
00061    caching as well, the execution time doubles.  */
00062 
00063 typedef struct
00064 {
00065   const_string key;
00066   str_llist_type *value;
00067 } cache_entry;
00068 
00069 static cache_entry *the_cache = NULL;
00070 static unsigned cache_length = 0;
00071 
00072 
00073 /* Associate KEY with VALUE.  We implement the cache as a simple linear
00074    list, since it's unlikely to ever be more than a dozen or so elements
00075    long.  We don't bother to check here if PATH has already been saved;
00076    we always add it to our list.  We copy KEY but not VALUE; not sure
00077    that's right, but it seems to be all that's needed.  */
00078 
00079 static void
00080 cache P2C(const_string, key,  str_llist_type *, value)
00081 {
00082   cache_length++;
00083   XRETALLOC (the_cache, cache_length, cache_entry);
00084   the_cache[cache_length - 1].key = xstrdup (key);
00085   the_cache[cache_length - 1].value = value;
00086 }
00087 
00088 
00089 /* To retrieve, just check the list in order.  */
00090 
00091 static str_llist_type *
00092 cached P1C(const_string, key)
00093 {
00094   unsigned p;
00095   
00096   for (p = 0; p < cache_length; p++)
00097     {
00098       if (FILESTRCASEEQ (the_cache[p].key, key))
00099         return the_cache[p].value;
00100     }
00101   
00102   return NULL;
00103 }
00104 
00105 /* Handle the magic path constructs.  */
00106 
00107 /* Declare recursively called routine.  */
00108 static void expand_elt P3H(str_llist_type *, const_string, unsigned);
00109 
00110 
00111 /* POST is a pointer into the original element (which may no longer be
00112    ELT) to just after the doubled DIR_SEP, perhaps to the null.  Append
00113    subdirectories of ELT (up to ELT_LENGTH, which must be a /) to
00114    STR_LIST_PTR.  */
00115 
00116 #ifdef WIN32
00117 /* Shared across recursive calls, it acts like a stack. */
00118 static char dirname[MAX_PATH];
00119 #endif
00120 
00121 static void
00122 do_subdir P4C(str_llist_type *, str_list_ptr,  const_string, elt,
00123               unsigned, elt_length,  const_string, post)
00124 {
00125 #ifdef WIN32
00126   WIN32_FIND_DATA find_file_data;
00127   HANDLE hnd;
00128   int proceed;
00129   int nlinks = 2;
00130 #else
00131   DIR *dir;
00132   struct dirent *e;
00133 #endif /* not WIN32 */
00134   fn_type name;
00135   
00136   /* Some old compilers don't allow aggregate initialization.  */
00137   name = fn_copy0 (elt, elt_length);
00138   
00139   assert (IS_DIR_SEP (elt[elt_length - 1])
00140           || IS_DEVICE_SEP (elt[elt_length - 1]));
00141   
00142 #if defined (WIN32)
00143   strcpy(dirname, FN_STRING(name));
00144   strcat(dirname, "/*.*");         /* "*.*" or "*" -- seems equivalent. */
00145   hnd = FindFirstFile(dirname, &find_file_data);
00146 
00147   if (hnd == INVALID_HANDLE_VALUE) {
00148     fn_free(&name);
00149     return;
00150   }
00151 
00152   /* Include top level before subdirectories, if nothing to match.  */
00153   if (*post == 0)
00154     dir_list_add (str_list_ptr, FN_STRING (name));
00155   else {
00156     /* If we do have something to match, see if it exists.  For
00157        example, POST might be `pk/ljfour', and they might have a
00158        directory `$TEXMF/fonts/pk/ljfour' that we should find.  */
00159     fn_str_grow (&name, post);
00160     expand_elt (str_list_ptr, FN_STRING (name), elt_length);
00161     fn_shrink_to (&name, elt_length);
00162   }
00163   proceed = 1;
00164   while (proceed) {
00165     if (find_file_data.cFileName[0] != '.') {
00166       int links;
00167 
00168       /* Construct the potential subdirectory name.  */
00169       fn_str_grow (&name, find_file_data.cFileName);
00170 
00171       /* Maybe we have cached the leafness of this directory.
00172                The function will return 0 if unknown, 
00173                else the actual (Unix-like) value. */
00174       links = dir_links (FN_STRING (name), 0);
00175 
00176       if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
00177        unsigned potential_len = FN_LENGTH (name);
00178        /* in any case, compute the leafness */
00179        nlinks++;
00180 
00181        /* It's a directory, so append the separator.  */
00182        fn_str_grow (&name, DIR_SEP_STRING);
00183         if (*post != 0) { 
00184           fn_str_grow (&name, post);
00185           /* Unfortunately we can't check if the new element is
00186              a leaf directory, because we don't have a directory
00187              name here, we just have a path spec. This means we
00188              may descend into a leaf directory cm/pk, if the
00189              spec is ...fonts//pk//.  */
00190           expand_elt (str_list_ptr, FN_STRING (name), potential_len);
00191           fn_shrink_to (&name, potential_len);
00192         }
00193        /* Should we recurse?  To see if the subdirectory is a
00194           leaf, check if it has two links (one for . and one for
00195           ..).  This means that symbolic links to directories do
00196           not affect the leaf-ness.  This is arguably wrong, but
00197           the only alternative I know of is to stat every entry
00198           in the directory, and that is unacceptably slow. */
00199           
00200        if (links == 0 || links > 2)
00201          /* All criteria are met; find subdirectories.  */
00202          do_subdir (str_list_ptr, FN_STRING (name),
00203                    potential_len, post);
00204        else if (*post == 0)
00205          /* Nothing to match, no recursive subdirectories to
00206             look for: we're done with this branch.  Add it.  */
00207          dir_list_add (str_list_ptr, FN_STRING (name));
00208       }
00209       fn_shrink_to (&name, elt_length);
00210     }
00211     proceed = FindNextFile (hnd, &find_file_data);
00212   }
00213   /* Update the leafness of name. */
00214   dir_links(FN_STRING(name), nlinks);
00215   fn_free (&name);
00216   FindClose(hnd);
00217 
00218 #else /* not WIN32 */
00219 
00220   /* If we can't open it, quit.  */
00221   dir = opendir (FN_STRING (name));
00222   if (dir == NULL)
00223     {
00224       fn_free (&name);
00225       return;
00226     }
00227   
00228   /* Include top level before subdirectories, if nothing to match.  */
00229   if (*post == 0)
00230     dir_list_add (str_list_ptr, FN_STRING (name));
00231   else
00232     { /* If we do have something to match, see if it exists.  For
00233          example, POST might be `pk/ljfour', and they might have a
00234          directory `$TEXMF/fonts/pk/ljfour' that we should find.  */
00235       fn_str_grow (&name, post);
00236       expand_elt (str_list_ptr, FN_STRING (name), elt_length);
00237       fn_shrink_to (&name, elt_length);
00238     }
00239 
00240   while ((e = readdir (dir)) != NULL)
00241     { /* If it begins with a `.', never mind.  (This allows ``hidden''
00242          directories that the algorithm won't find.)  */
00243       if (e->d_name[0] != '.')
00244         {
00245           int links;
00246           
00247           /* Construct the potential subdirectory name.  */
00248           fn_str_grow (&name, e->d_name);
00249           
00250           /* If we can't stat it, or if it isn't a directory, continue.  */
00251           links = dir_links (FN_STRING (name), 0);
00252 
00253           if (links >= 0)
00254             { 
00255               unsigned potential_len = FN_LENGTH (name);
00256 
00257               /* It's a directory, so append the separator.  */
00258               fn_str_grow (&name, DIR_SEP_STRING);
00259 
00260               if (*post != 0)
00261                 { 
00262                   fn_str_grow (&name, post);
00263                   /* Unfortunately we can't check if the new element is
00264                      a leaf directory, because we don't have a directory
00265                      name here, we just have a path spec. This means we
00266                      may descend into a leaf directory cm/pk, if the
00267                      spec is ...fonts//pk//.  */
00268                   expand_elt (str_list_ptr, FN_STRING (name), potential_len);
00269                   fn_shrink_to (&name, potential_len);
00270                 }
00271               
00272               /* Should we recurse?  To see if the subdirectory is a
00273                  leaf, check if it has two links (one for . and one for
00274                  ..).  This means that symbolic links to directories do
00275                  not affect the leaf-ness.  This is arguably wrong, but
00276                  the only alternative I know of is to stat every entry
00277                  in the directory, and that is unacceptably slow.
00278                  
00279                  The #ifdef here makes all this configurable at
00280                  compile-time, so that if we're using VMS directories or
00281                  some such, we can still find subdirectories, even if it
00282                  is much slower.  */
00283 #ifdef ST_NLINK_TRICK
00284 #ifdef AMIGA
00285               /* With SAS/C++ 6.55 on the Amiga, `stat' sets the `st_nlink'
00286                  field to -1 for a file, or to 1 for a directory.  */
00287               if (links == 1)
00288 #else
00289               if (links > 2)
00290 #endif /* not AMIGA */
00291 #endif /* not ST_NLINK_TRICK */
00292                 /* All criteria are met; find subdirectories.  */
00293                 do_subdir (str_list_ptr, FN_STRING (name),
00294                            potential_len, post);
00295 #ifdef ST_NLINK_TRICK
00296               else if (*post == 0)
00297                 /* Nothing to match, no recursive subdirectories to
00298                    look for: we're done with this branch.  Add it.  */
00299                 dir_list_add (str_list_ptr, FN_STRING (name));
00300 #endif
00301             }
00302 
00303           /* Remove the directory entry we just checked from `name'.  */
00304           fn_shrink_to (&name, elt_length);
00305         }
00306     }
00307   
00308   fn_free (&name);
00309   xclosedir (dir);
00310 #endif /* not WIN32 */
00311 }
00312 
00313 
00314 /* Assume ELT is non-empty and non-NULL.  Return list of corresponding
00315    directories (with no terminating NULL entry) in STR_LIST_PTR.  Start
00316    looking for magic constructs at START.  */
00317 
00318 static void
00319 expand_elt P3C(str_llist_type *, str_list_ptr,  const_string, elt,
00320                unsigned, start)
00321 {
00322   const_string dir = elt + start, post;
00323   
00324   while (*dir != 0)
00325     {
00326       if (IS_DIR_SEP (*dir))
00327         {
00328           /* If two or more consecutive /'s, find subdirectories.  */
00329           if (IS_DIR_SEP (dir[1]))
00330             {
00331              for (post = dir + 1; IS_DIR_SEP (*post); post++) ;
00332               do_subdir (str_list_ptr, elt, dir - elt + 1, post);
00333              return;
00334             }
00335 
00336           /* No special stuff at this slash.  Keep going.  */
00337         }
00338       
00339       dir++;
00340     }
00341   
00342   /* When we reach the end of ELT, it will be a normal filename.  */
00343   checked_dir_list_add (str_list_ptr, elt);
00344 }
00345 
00346 /* The first bits of a path element can be problematic because they
00347    look like a request to expand a whole disk, rather than a subtree.
00348    - It can contain a drive specification.
00349    - It can be a UNC path (win32, but they are part of the single
00350      UNIX specification as well).
00351    The argument is a string as the function can diddle into the argument
00352    to canonicalize it, which tends to matter on windows platforms.
00353    - Always lower-case drive letters a-z, even those filesystem that
00354      preserve case in filenames do not care about the case of the drive
00355      letters.
00356    - Remove unneeded double slashes. The problem is Windows does not 
00357      handle well filenames like c://dir/foo. So canonicalize the names.
00358      The resulting name will always be shorter than the one passed, so no
00359      problem.
00360    - If possible, we merely skip multiple leading slashes to prevent
00361      expanding from the root of a UNIX filesystem tree.
00362 */
00363 unsigned
00364 kpse_normalize_path P1C(string, elt)
00365 {
00366   unsigned ret;
00367   unsigned i;
00368 
00369   if (NAME_BEGINS_WITH_DEVICE(elt)) {
00370       if (*elt >= 'A' && *elt <= 'Z')
00371           *elt += 'a' - 'A';
00372       for (i = 2; IS_DIR_SEP(elt[i]); ++i)
00373           ;
00374       if (i > 3)
00375           memmove(elt+3, elt+i, strlen(elt+i) + 1);
00376       ret = 2;
00377   } else if (IS_UNC_NAME(elt)) {
00378       for (ret = 2; elt[ret] && !IS_DIR_SEP(elt[ret]); ++ret)
00379           ;
00380       for (i = ret; elt[i] && IS_DIR_SEP(elt[i]); ++i)
00381           ;
00382       if (i > ret+1)
00383           memmove(elt+ret+1, elt+i, strlen(elt+i) + 1);
00384   } else {
00385       for (ret = 0; IS_DIR_SEP(elt[ret]); ++ret)
00386           ;
00387   }
00388   
00389   if (KPSE_DEBUG_P (KPSE_DEBUG_STAT))
00390        DEBUGF2 ("kpse_normalize_path (%s) => %u\n", elt, ret);
00391 
00392   return ret;
00393 }
00394 
00395 /* Here is the entry point.  Returns directory list for ELT.  */
00396 
00397 str_llist_type *
00398 kpse_element_dirs P1C(string, elt)
00399 {
00400   str_llist_type *ret;
00401 
00402   /* If given nothing, return nothing.  */
00403   if (!elt || !*elt)
00404     return NULL;
00405 
00406   /* If we've already cached the answer for ELT, return it.  */
00407   ret = cached (elt);
00408   if (ret)
00409     return ret;
00410 
00411   /* We're going to have a real directory list to return.  */
00412   ret = XTALLOC1 (str_llist_type);
00413   *ret = NULL;
00414 
00415   /* We handle the hard case in a subroutine.  */
00416   expand_elt (ret, elt, kpse_normalize_path (elt));
00417 
00418   /* Remember the directory list we just found, in case future calls are
00419      made with the same ELT.  */
00420   cache (elt, ret);
00421 
00422 #ifdef KPSE_DEBUG
00423   if (KPSE_DEBUG_P (KPSE_DEBUG_EXPAND))
00424     {
00425       DEBUGF1 ("path element %s =>", elt);
00426       if (ret)
00427         {
00428           str_llist_elt_type *e;
00429           for (e = *ret; e; e = STR_LLIST_NEXT (*e))
00430             fprintf (stderr, " %s", STR_LLIST (*e));
00431         }
00432       putc ('\n', stderr);
00433       fflush (stderr);
00434     }
00435 #endif /* KPSE_DEBUG */
00436 
00437   return ret;
00438 }
00439 
00440 #ifdef TEST
00441 
00442 void
00443 print_element_dirs (const_string elt)
00444 {
00445   str_llist_type *dirs;
00446   
00447   printf ("Directories of %s:\t", elt ? elt : "(nil)");
00448   fflush (stdout);
00449   
00450   dirs = kpse_element_dirs (elt);
00451   
00452   if (!dirs)
00453     printf ("(nil)");
00454   else
00455     {
00456       str_llist_elt_type *dir;
00457       for (dir = *dirs; dir; dir = STR_LLIST_NEXT (*dir))
00458         {
00459           string d = STR_LLIST (*dir);
00460           printf ("%s ", *d ? d : "`'");
00461         }
00462     }
00463   
00464   putchar ('\n');
00465 }
00466 
00467 int
00468 main ()
00469 {
00470   /* DEBUG_SET (DEBUG_STAT); */
00471   /* All lists end with NULL.  */
00472   print_element_dirs (NULL);       /* */
00473   print_element_dirs ("");  /* ./ */
00474   print_element_dirs ("/k");       /* */
00475   print_element_dirs (".//");      /* ./ ./archive/ */
00476   print_element_dirs (".//archive");      /* ./ ./archive/ */
00477 #ifdef AMIGA
00478   print_element_dirs ("TeXMF:AmiWeb2c/texmf/fonts//"); /* lots */
00479   print_element_dirs ("TeXMF:AmiWeb2c/share/texmf/fonts//bakoma"); /* just one */
00480   print_element_dirs ("TeXMF:AmiWeb2c/texmf/fonts//"); /* lots again [cache] */
00481   print_element_dirs ("TeXMF:");   /* TeXMF: */
00482   print_element_dirs ("TeXMF:/");  /* TeXMF: and all subdirs */
00483 #else /* not AMIGA */
00484   print_element_dirs ("/tmp/fonts//");    /* no need to stat anything */
00485   print_element_dirs ("/usr/local/lib/tex/fonts//");      /* lots */
00486   print_element_dirs ("/usr/local/lib/tex/fonts//times"); /* just one */
00487   print_element_dirs ("/usr/local/lib/tex/fonts//"); /* lots again [cache] */
00488   print_element_dirs ("~karl");           /* tilde expansion */
00489   print_element_dirs ("$karl");           /* variable expansion */  
00490   print_element_dirs ("~${LOGNAME}");     /* both */  
00491 #endif /* not AMIGA */
00492   return 0;
00493 }
00494 
00495 #endif /* TEST */
00496 
00497 
00498 /*
00499 Local variables:
00500 test-compile-command: "gcc -g -I. -I.. -DTEST elt-dirs.c kpathsea.a"
00501 End:
00502 */