home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / octa21fs.zip / octave / kpathsea / elt-dirs.c < prev    next >
C/C++ Source or Header  |  2000-01-15  |  13KB  |  433 lines

  1. /* elt-dirs.c: Translate a path element to its corresponding director{y,ies}.
  2.  
  3. Copyright (C) 1993, 94, 95, 96, 97 Karl Berry.
  4.  
  5. This library is free software; you can redistribute it and/or
  6. modify it under the terms of the GNU Library General Public
  7. License as published by the Free Software Foundation; either
  8. version 2 of the License, or (at your option) any later version.
  9.  
  10. This library is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  13. Library General Public License for more details.
  14.  
  15. You should have received a copy of the GNU Library General Public
  16. License along with this library; if not, write to the Free Software
  17. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
  18.  
  19. #include <kpathsea/config.h>
  20.  
  21. #include <kpathsea/c-pathch.h>
  22. #include <kpathsea/expand.h>
  23. #include <kpathsea/fn.h>
  24. #include <kpathsea/pathsearch.h>
  25. #include <kpathsea/xopendir.h>
  26.  
  27. /* To avoid giving prototypes for all the routines and then their real
  28.    definitions, we give all the subroutines first.  The entry point is
  29.    the last routine in the file.  */
  30.  
  31. /* Make a copy of DIR (unless it's null) and save it in L.  Ensure that
  32.    DIR ends with a DIR_SEP for the benefit of later searches.  */
  33.  
  34. static void
  35. dir_list_add P2C(str_llist_type *, l,  const_string, dir)
  36. {
  37.   char last_char = dir[strlen (dir) - 1];
  38.   string saved_dir
  39.     = IS_DIR_SEP (last_char) || IS_DEVICE_SEP (last_char)
  40.       ? xstrdup (dir)
  41.       : concat (dir, DIR_SEP_STRING);
  42.   
  43.   str_llist_add (l, saved_dir);
  44. }
  45.  
  46.  
  47. /* If DIR is a directory, add it to the list L.  */
  48.  
  49. static void
  50. checked_dir_list_add P2C(str_llist_type *, l,  const_string, dir)
  51. {
  52.   if (dir_p (dir))
  53.     dir_list_add (l, dir);
  54. }
  55.  
  56. /* The cache.  Typically, several paths have the same element; for
  57.    example, /usr/local/lib/texmf/fonts//.  We don't want to compute the
  58.    expansion of such a thing more than once.  Even though we also cache
  59.    the dir_links call, that's not enough -- without this path element
  60.    caching as well, the execution time doubles.  */
  61.  
  62. typedef struct
  63. {
  64.   const_string key;
  65.   str_llist_type *value;
  66. } cache_entry;
  67.  
  68. static cache_entry *the_cache = NULL;
  69. static unsigned cache_length = 0;
  70.  
  71. void
  72. kpse_clear_dir_cache P1H(void)
  73. {
  74.   while (cache_length > 0)
  75.     {
  76.       str_llist_type elt = *the_cache[--cache_length].value;
  77.  
  78.       while (elt)
  79.     {
  80.       str_llist_type next = STR_LLIST_NEXT (*elt);
  81.  
  82.       string s = STR_LLIST (*elt);
  83.  
  84.       if (s)
  85.         free (s);
  86.  
  87.       free (elt);
  88.  
  89.       elt = next;
  90.     }
  91.     }
  92.  
  93.   if (the_cache)
  94.     free (the_cache);
  95.  
  96.   the_cache = NULL;
  97. }
  98.  
  99. /* Associate KEY with VALUE.  We implement the cache as a simple linear
  100.    list, since it's unlikely to ever be more than a dozen or so elements
  101.    long.  We don't bother to check here if PATH has already been saved;
  102.    we always add it to our list.  We copy KEY but not VALUE; not sure
  103.    that's right, but it seems to be all that's needed.  */
  104.  
  105. static void
  106. cache P2C(const_string, key,  str_llist_type *, value)
  107. {
  108.   cache_length++;
  109.   XRETALLOC (the_cache, cache_length, cache_entry);
  110.   the_cache[cache_length - 1].key = xstrdup (key);
  111.   the_cache[cache_length - 1].value = value;
  112. }
  113.  
  114.  
  115. /* To retrieve, just check the list in order.  */
  116.  
  117. static str_llist_type *
  118. cached P1C(const_string, key)
  119. {
  120.   unsigned p;
  121.   
  122.   for (p = 0; p < cache_length; p++)
  123.     {
  124.       if (FILESTRCASEEQ (the_cache[p].key, key))
  125.         return the_cache[p].value;
  126.     }
  127.   
  128.   return NULL;
  129. }
  130.  
  131. /* Handle the magic path constructs.  */
  132.  
  133. /* Declare recursively called routine.  */
  134. static void expand_elt P3H(str_llist_type *, const_string, unsigned);
  135.  
  136.  
  137. /* POST is a pointer into the original element (which may no longer be
  138.    ELT) to just after the doubled DIR_SEP, perhaps to the null.  Append
  139.    subdirectories of ELT (up to ELT_LENGTH, which must be a /) to
  140.    STR_LIST_PTR.  */
  141.  
  142. #ifdef WIN32
  143. /* Shared across recursive calls, it acts like a stack. */
  144. static char dirname[MAX_PATH];
  145. #endif
  146.  
  147. static void
  148. do_subdir P4C(str_llist_type *, str_list_ptr,  const_string, elt,
  149.               unsigned, elt_length,  const_string, post)
  150. {
  151. #ifdef WIN32
  152.   WIN32_FIND_DATA find_file_data;
  153.   HANDLE hnd;
  154.   int proceed;
  155. #else
  156.   DIR *dir;
  157.   struct dirent *e;
  158. #endif /* not WIN32 */
  159.   fn_type name;
  160.   
  161.   /* Some old compilers don't allow aggregate initialization.  */
  162.   name = fn_copy0 (elt, elt_length);
  163.   
  164.   assert (IS_DIR_SEP (elt[elt_length - 1])
  165.           || IS_DEVICE_SEP (elt[elt_length - 1]));
  166.   
  167. #if defined (WIN32)
  168.   strcpy(dirname, FN_STRING(name));
  169.   strcat(dirname, "/*.*");         /* "*.*" or "*" -- seems equivalent. */
  170.   hnd = FindFirstFile(dirname, &find_file_data);
  171.  
  172.   if (hnd == INVALID_HANDLE_VALUE) {
  173.     fn_free(&name);
  174.     return;
  175.   }
  176.  
  177.   /* Include top level before subdirectories, if nothing to match.  */
  178.   if (*post == 0)
  179.     dir_list_add (str_list_ptr, FN_STRING (name));
  180.   else {
  181.     /* If we do have something to match, see if it exists.  For
  182.        example, POST might be `pk/ljfour', and they might have a
  183.        directory `$TEXMF/fonts/pk/ljfour' that we should find.  */
  184.     fn_str_grow (&name, post);
  185.     expand_elt (str_list_ptr, FN_STRING (name), elt_length);
  186.     fn_shrink_to (&name, elt_length);
  187.   }
  188.   proceed = 1;
  189.   while (proceed) {
  190.     if (find_file_data.cFileName[0] != '.') {
  191.       /* Construct the potential subdirectory name.  */
  192.       fn_str_grow (&name, find_file_data.cFileName);
  193.       if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  194.     unsigned potential_len = FN_LENGTH (name);
  195.     
  196.     /* It's a directory, so append the separator.  */
  197.     fn_str_grow (&name, DIR_SEP_STRING);
  198.  
  199.     do_subdir (str_list_ptr, FN_STRING (name),
  200.            potential_len, post);
  201.       }
  202.       fn_shrink_to (&name, elt_length);
  203.     }
  204.     proceed = FindNextFile (hnd, &find_file_data);
  205.   }
  206.   fn_free (&name);
  207.   FindClose(hnd);
  208.  
  209. #else /* not WIN32 */
  210.  
  211.   /* If we can't open it, quit.  */
  212.   dir = opendir (FN_STRING (name));
  213.   if (dir == NULL)
  214.     {
  215.       fn_free (&name);
  216.       return;
  217.     }
  218.   
  219.   /* Include top level before subdirectories, if nothing to match.  */
  220.   if (*post == 0)
  221.     dir_list_add (str_list_ptr, FN_STRING (name));
  222.   else
  223.     { /* If we do have something to match, see if it exists.  For
  224.          example, POST might be `pk/ljfour', and they might have a
  225.          directory `$TEXMF/fonts/pk/ljfour' that we should find.  */
  226.       fn_str_grow (&name, post);
  227.       expand_elt (str_list_ptr, FN_STRING (name), elt_length);
  228.       fn_shrink_to (&name, elt_length);
  229.     }
  230.  
  231.   while ((e = readdir (dir)) != NULL)
  232.     { /* If it begins with a `.', never mind.  (This allows ``hidden''
  233.          directories that the algorithm won't find.)  */
  234.       if (e->d_name[0] != '.')
  235.         {
  236.           int links;
  237.           
  238.           /* Construct the potential subdirectory name.  */
  239.           fn_str_grow (&name, e->d_name);
  240.           
  241.           /* If we can't stat it, or if it isn't a directory, continue.  */
  242.           links = dir_links (FN_STRING (name));
  243.  
  244.           if (links >= 0)
  245.             { 
  246.               unsigned potential_len = FN_LENGTH (name);
  247.               
  248.               /* It's a directory, so append the separator.  */
  249.               fn_str_grow (&name, DIR_SEP_STRING);
  250.               
  251.               /* Should we recurse?  To see if the subdirectory is a
  252.                  leaf, check if it has two links (one for . and one for
  253.                  ..).  This means that symbolic links to directories do
  254.                  not affect the leaf-ness.  This is arguably wrong, but
  255.                  the only alternative I know of is to stat every entry
  256.                  in the directory, and that is unacceptably slow.
  257.                  
  258.                  The #ifdef here makes all this configurable at
  259.                  compile-time, so that if we're using VMS directories or
  260.                  some such, we can still find subdirectories, even if it
  261.                  is much slower.  */
  262. #ifdef ST_NLINK_TRICK
  263. #ifdef AMIGA
  264.               /* With SAS/C++ 6.55 on the Amiga, `stat' sets the `st_nlink'
  265.                  field to -1 for a file, or to 1 for a directory.  */
  266.               if (links == 1)
  267. #else
  268.               if (links > 2)
  269. #endif /* not AMIGA */
  270. #endif /* not ST_NLINK_TRICK */
  271.                 /* All criteria are met; find subdirectories.  */
  272.                 do_subdir (str_list_ptr, FN_STRING (name),
  273.                            potential_len, post);
  274. #ifdef ST_NLINK_TRICK
  275.               else if (*post == 0)
  276.                 /* Nothing to match, no recursive subdirectories to
  277.                    look for: we're done with this branch.  Add it.  */
  278.                 dir_list_add (str_list_ptr, FN_STRING (name));
  279. #endif
  280.             }
  281.  
  282.           /* Remove the directory entry we just checked from `name'.  */
  283.           fn_shrink_to (&name, elt_length);
  284.         }
  285.     }
  286.   
  287.   fn_free (&name);
  288.   xclosedir (dir);
  289. #endif /* not WIN32 */
  290. }
  291.  
  292.  
  293. /* Assume ELT is non-empty and non-NULL.  Return list of corresponding
  294.    directories (with no terminating NULL entry) in STR_LIST_PTR.  Start
  295.    looking for magic constructs at START.  */
  296.  
  297. static void
  298. expand_elt P3C(str_llist_type *, str_list_ptr,  const_string, elt,
  299.                unsigned, start)
  300. {
  301.   const_string dir = elt + start, post;
  302.   
  303.   while (*dir != 0)
  304.     {
  305.       if (IS_DIR_SEP (*dir))
  306.         {
  307.           /* If two or more consecutive /'s, find subdirectories.  */
  308.           if (IS_DIR_SEP (dir[1]))
  309.             {
  310.           for (post = dir + 1; IS_DIR_SEP (*post); post++) ;
  311.               do_subdir (str_list_ptr, elt, dir - elt + 1, post);
  312.           return;
  313.             }
  314.  
  315.           /* No special stuff at this slash.  Keep going.  */
  316.         }
  317.       
  318.       dir++;
  319.     }
  320.   
  321.   /* When we reach the end of ELT, it will be a normal filename.  */
  322.   checked_dir_list_add (str_list_ptr, elt);
  323. }
  324.  
  325. /* Here is the entry point.  Returns directory list for ELT.  */
  326.  
  327. str_llist_type *
  328. kpse_element_dirs P1C(const_string, elt)
  329. {
  330.   str_llist_type *ret;
  331.  
  332.   /* If given nothing, return nothing.  */
  333.   if (!elt || !*elt)
  334.     return NULL;
  335.  
  336.   /* If we've already cached the answer for ELT, return it.  */
  337.   ret = cached (elt);
  338.   if (ret)
  339.     return ret;
  340.  
  341.   /* We're going to have a real directory list to return.  */
  342.   ret = XTALLOC1 (str_llist_type);
  343.   *ret = NULL;
  344.  
  345.   /* We handle the hard case in a subroutine.  */
  346.   expand_elt (ret, elt, 0);
  347.  
  348.   /* Remember the directory list we just found, in case future calls are
  349.      made with the same ELT.  */
  350.   cache (elt, ret);
  351.  
  352. #ifdef KPSE_DEBUG
  353.   if (KPSE_DEBUG_P (KPSE_DEBUG_EXPAND))
  354.     {
  355.       DEBUGF1 ("path element %s =>", elt);
  356.       if (ret)
  357.         {
  358.           str_llist_elt_type *e;
  359.           for (e = *ret; e; e = STR_LLIST_NEXT (*e))
  360.             fprintf (stderr, " %s", STR_LLIST (*e));
  361.         }
  362.       putc ('\n', stderr);
  363.       fflush (stderr);
  364.     }
  365. #endif /* KPSE_DEBUG */
  366.  
  367.   return ret;
  368. }
  369.  
  370. #ifdef TEST
  371.  
  372. void
  373. print_element_dirs (const_string elt)
  374. {
  375.   str_llist_type *dirs;
  376.   
  377.   printf ("Directories of %s:\t", elt ? elt : "(nil)");
  378.   fflush (stdout);
  379.   
  380.   dirs = kpse_element_dirs (elt);
  381.   
  382.   if (!dirs)
  383.     printf ("(nil)");
  384.   else
  385.     {
  386.       str_llist_elt_type *dir;
  387.       for (dir = *dirs; dir; dir = STR_LLIST_NEXT (*dir))
  388.         {
  389.           string d = STR_LLIST (*dir);
  390.           printf ("%s ", *d ? d : "`'");
  391.         }
  392.     }
  393.   
  394.   putchar ('\n');
  395. }
  396.  
  397. int
  398. main ()
  399. {
  400.   /* DEBUG_SET (DEBUG_STAT); */
  401.   /* All lists end with NULL.  */
  402.   print_element_dirs (NULL);    /* */
  403.   print_element_dirs ("");    /* ./ */
  404.   print_element_dirs ("/k");    /* */
  405.   print_element_dirs (".//");    /* ./ ./archive/ */
  406.   print_element_dirs (".//archive");    /* ./ ./archive/ */
  407. #ifdef AMIGA
  408.   print_element_dirs ("TeXMF:AmiWeb2c/texmf/fonts//"); /* lots */
  409.   print_element_dirs ("TeXMF:AmiWeb2c/share/texmf/fonts//bakoma"); /* just one */
  410.   print_element_dirs ("TeXMF:AmiWeb2c/texmf/fonts//"); /* lots again [cache] */
  411.   print_element_dirs ("TeXMF:");    /* TeXMF: */
  412.   print_element_dirs ("TeXMF:/");    /* TeXMF: and all subdirs */
  413. #else /* not AMIGA */
  414.   print_element_dirs ("/tmp/fonts//");    /* no need to stat anything */
  415.   print_element_dirs ("/usr/local/lib/tex/fonts//");      /* lots */
  416.   print_element_dirs ("/usr/local/lib/tex/fonts//times"); /* just one */
  417.   print_element_dirs ("/usr/local/lib/tex/fonts//"); /* lots again [cache] */
  418.   print_element_dirs ("~karl");        /* tilde expansion */
  419.   print_element_dirs ("$karl");        /* variable expansion */  
  420.   print_element_dirs ("~${LOGNAME}");    /* both */  
  421. #endif /* not AMIGA */
  422.   return 0;
  423. }
  424.  
  425. #endif /* TEST */
  426.  
  427.  
  428. /*
  429. Local variables:
  430. test-compile-command: "gcc -g -I. -I.. -DTEST elt-dirs.c kpathsea.a"
  431. End:
  432. */
  433.