home *** CD-ROM | disk | FTP | other *** search
/ Geek Gadgets 1 / ADE-1.bin / ade-dist / unixtex-6.1b-src.tgz / tar.out / contrib / unixtex / kpathsea / pathsearch.c < prev    next >
C/C++ Source or Header  |  1996-09-28  |  13KB  |  406 lines

  1. /* pathsearch.c: look up a filename in a path.
  2.  
  3. Copyright (C) 1993, 94 Karl Berry.
  4.  
  5. This program is free software; you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation; either version 2, or (at your option)
  8. any later version.
  9.  
  10. This program 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
  13. GNU General Public License for more details.
  14.  
  15. You should have received a copy of the GNU General Public License
  16. along with this program; if not, write to the Free Software
  17. Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
  18.  
  19. #include <kpathsea/config.h>
  20.  
  21. #include <kpathsea/c-fopen.h>
  22. #include <kpathsea/absolute.h>
  23. #include <kpathsea/expand.h>
  24. #include <kpathsea/db.h>
  25. #include <kpathsea/pathsearch.h>
  26. #include <kpathsea/readable.h>
  27. #include <kpathsea/str-list.h>
  28. #include <kpathsea/str-llist.h>
  29. #include <kpathsea/variable.h>
  30.  
  31. #include <time.h> /* for `time' */
  32.  
  33. /* This function is called after every search to record the filename(s)
  34.    found in $TEXMFLOG or if debugging.  */
  35.  
  36. static void
  37. log_search P1C(str_list_type, filenames)
  38. {
  39.   static FILE *log_file = NULL;
  40.   static boolean first_time = true;
  41.   
  42.   if (first_time)
  43.     {
  44.       string log_name = kpse_var_expand ("$TEXMFLOG");
  45.       first_time = false;
  46.       /* Get name from either envvar or config file.  */
  47.       if (log_name && *log_name)
  48.         {
  49.           log_file = fopen (log_name, FOPEN_A_MODE);
  50.           if (!log_file)
  51.             perror (log_name);
  52.           free (log_name);
  53.         }
  54.     }
  55.  
  56.   if (KPSE_DEBUG_P (KPSE_DEBUG_SEARCH) || log_file)
  57.     {
  58.       unsigned e;
  59.       
  60.       /* FILENAMES should never be null, but safety doesn't hurt.  */
  61.       for (e = 0;
  62.            e < STR_LIST_LENGTH (filenames) && STR_LIST_ELT (filenames, e);
  63.            e++)
  64.         {
  65.           string filename = STR_LIST_ELT (filenames, e);
  66.           
  67.           /* Only record absolute filenames, for privacy.  */
  68.           if (log_file && kpse_absolute_p (filename, false))
  69.             fprintf (log_file, "%u %s\n", time (NULL), filename);
  70.  
  71.           /* And show them online, if debugging.  We've already started
  72.              the debugging line in `search', where this is called, so
  73.              just print the filename here, don't use DEBUGF.  */
  74.           if (KPSE_DEBUG_P (KPSE_DEBUG_SEARCH))
  75.             fputs (filename, stderr);
  76.         }
  77.     }
  78. }
  79.  
  80. /* Concatenate each element in DIRS with NAME (assume each ends with a
  81.    /, to save time).  If SEARCH_ALL is false, return the first readable
  82.    regular file.  Else continue to search for more.  In any case, if
  83.    none, return a list containing just NULL.
  84.  
  85.    We keep a single buffer for the potential filenames and reallocate
  86.    only when necessary.  I'm not sure it's noticeably faster, but it
  87.    does seem cleaner.  (We do waste a bit of space in the return
  88.    value, though, since we don't shrink it to the final size returned.)  */
  89.  
  90. #define INIT_ALLOC 75  /* Doesn't much matter what this number is.  */
  91.  
  92. static str_list_type
  93. dir_list_search P3C(str_llist_type *, dirs,  const_string, name,
  94.                     boolean, search_all)
  95. {
  96.   str_llist_elt_type *elt;
  97.   str_list_type ret;
  98.   unsigned name_len = strlen (name);
  99.   unsigned allocated = INIT_ALLOC;
  100.   string potential = xmalloc (allocated);
  101.  
  102.   ret = str_list_init ();
  103.   
  104.   for (elt = *dirs; elt; elt = STR_LLIST_NEXT (*elt))
  105.     {
  106.       const_string dir = STR_LLIST (*elt);
  107.       unsigned dir_len = strlen (dir);
  108.       
  109.       while (dir_len + name_len + 1 > allocated)
  110.         {
  111.           allocated += allocated;
  112.           XRETALLOC (potential, allocated, char);
  113.         }
  114.       
  115.       strcpy (potential, dir);
  116.       strcat (potential, name);
  117.       
  118.       if (kpse_readable_file (potential))
  119.         { 
  120.           str_list_add (&ret, potential);
  121.           
  122.           /* Move this element towards the top of the list.  */
  123.           str_llist_float (dirs, elt);
  124.           
  125.           /* If caller only wanted one file returned, no need to
  126.              terminate the list with NULL; the caller knows to only look
  127.              at the first element.  */
  128.           if (!search_all)
  129.             return ret;
  130.  
  131.           /* Start new filename.  */
  132.           allocated = INIT_ALLOC;
  133.           potential = xmalloc (allocated);
  134.         }
  135.     }
  136.   
  137.   /* If we get here, either we didn't find any files, or we were finding
  138.      all the files.  But we're done with the last filename, anyway.  */
  139.   free (potential);
  140.   
  141.   return ret;
  142. }
  143.  
  144. /* This is called when NAME is absolute or explicitly relative; if it's
  145.    readable, return (a list containing) it; otherwise, return NULL.  */
  146.  
  147. static str_list_type
  148. absolute_search P1C(string, name)
  149. {
  150.   str_list_type ret_list;
  151.   string found = kpse_readable_file (name);
  152.   
  153.   /* Some old compilers can't initialize structs.  */
  154.   ret_list = str_list_init ();
  155.  
  156.   /* If NAME wasn't found, free the expansion.  */
  157.   if (name != found)
  158.     free (name);
  159.  
  160.   /* Add `found' to the return list even if it's null; that tells
  161.      the caller we didn't find anything.  */
  162.   str_list_add (&ret_list, found);
  163.   
  164.   return ret_list;
  165. }
  166.  
  167. /* If DB_DIR is a prefix of PATH_ELT, return true; otherwise false.
  168.    That is, the question is whether to try the db for a file looked up
  169.    in PATH_ELT.  If PATH_ELT == ".", for example, the answer is no. If
  170.    PATH_ELT == "/usr/local/lib/texmf/fonts//tfm", the answer is yes.
  171.    
  172.    In practice, ls-R is only needed for lengthy subdirectory
  173.    comparisons, but there's no gain to checking PATH_ELT to see if it is
  174.    a subdir match, since the only way to do that is to do a string
  175.    search in it, which is all we do anyway.
  176.    
  177.    In fact, we do a simple string compare, ignoring // complications,
  178.    since in practice I believe //'s will always be after `kpse_db_dir',
  179.    i.e., we would never want to find ls-R in /usr//texmf.  */
  180.    
  181. static boolean
  182. elt_in_db P1C(const_string, path_elt)
  183. {
  184.   boolean found = false;
  185.   
  186.   /* If `kpse_db_dir' is not set, we're being called from `read_files'
  187.      for the very first time -- for cnf file initialization.  We can't
  188.      use ls-R for that.  */
  189.   if (kpse_db_dir)
  190.     {
  191.       string db_temp = kpse_db_dir;
  192.  
  193.       while (!found && *db_temp++ == *path_elt++)
  194.         { /* If we've matched the entire db directory, it's good.  */
  195.           if (*db_temp == 0)
  196.             found = true;
  197.           /* If we've reached the end of PATH_ELT, but not the end of the db
  198.              directory, it's no good.  */
  199.           else if (*path_elt == 0)
  200.             break;
  201.         }
  202.     }
  203.  
  204.   return found;
  205. }
  206.  
  207.  
  208. /* This is the hard case -- look for NAME in PATH.  If
  209.    ALL is false, just return the first file found.  Otherwise,
  210.    search all elements of PATH.  */
  211.  
  212. static str_list_type
  213. path_search P4C(const_string, path,  string, name,
  214.                 boolean, must_exist,  boolean, all)
  215. {
  216.   string elt;
  217.   str_list_type ret_list;
  218.   boolean done = false;
  219.   ret_list = str_list_init (); /* some compilers lack struct initialization */
  220.  
  221.   for (elt = kpse_path_element (path); !done && elt;
  222.        elt = kpse_path_element (NULL))
  223.     {
  224.       boolean try_db;
  225.       boolean allow_disk_search = true;
  226.       str_list_type *found = NULL;
  227.       
  228.       if (*elt == '!' && *(elt + 1) == '!')
  229.         { /* Magic leading chars in a path element means don't search the
  230.              disk regardless.  And move past the magic to get to the name.  */
  231.           allow_disk_search = false;
  232.           elt += 2;
  233.         }
  234.       
  235.       /* Try the prebuilt db only if it's relevant to this path element. */
  236.       try_db = elt_in_db (elt);
  237.       found = try_db ? kpse_db_search (name, elt, all) : NULL;
  238.       
  239.       /* Search the filesystem if (1) the path spec allows it, and either
  240.          (2a) the db was irrelevant to ELT (try_db == false); or
  241.          (2b) no db exists (kpse_db_search returns NULL); or
  242.          (3) NAME was not in the db (kpse_db_search returns an empty list)
  243.              and MUST_EXIST.
  244.          In (2a) and (2b), `found' will be NULL.  */
  245.       if (allow_disk_search && (!found || (!STR_LIST (*found) && must_exist)))
  246.         {
  247.           str_llist_type *dirs = kpse_element_dirs (elt);
  248.           if (dirs && *dirs)
  249.             {
  250.               if (!found)
  251.                 found = XTALLOC1 (str_list_type);
  252.               *found = dir_list_search (dirs, name, all);
  253.             }
  254.         }
  255.  
  256.       /* Did we find anything anywhere?  */
  257.       if (found && STR_LIST (*found))
  258.         if (all)
  259.           str_list_concat (&ret_list, *found);
  260.         else
  261.           {
  262.             str_list_add (&ret_list, STR_LIST_ELT (*found, 0));
  263.             done = true;
  264.           }
  265.       
  266.       /* Free the list space, if any (but not the elements).  */
  267.       if (found)
  268.         free (found);
  269.     }
  270.  
  271.   /* Free the expanded name we were passed.  It can't be in the return
  272.      list, since the path directories got unconditionally prepended.  */
  273.   free (name);
  274.   
  275.   return ret_list;
  276. }      
  277.  
  278. /* Search PATH for ORIGINAL_NAME.  If ALL is false, or ORIGINAL_NAME is
  279.    absolute_p, check ORIGINAL_NAME itself.  Otherwise, look at each
  280.    element of PATH for the first readable ORIGINAL_NAME.
  281.    
  282.    Always return a list; if no files are found, the list will
  283.    contain just NULL.  If ALL is true, the list will be
  284.    terminated with NULL.  */
  285.  
  286. static string *
  287. search P4C(const_string, path,  const_string, original_name,
  288.            boolean, must_exist,  boolean, all)
  289. {
  290.   str_list_type ret_list;
  291.  
  292.   /* Make a leading ~ count as an absolute filename, and expand $FOO's.  */
  293.   string name = kpse_expand (original_name);
  294.   
  295.   /* If the first name is absolute or explicitly relative, no need to
  296.      consider PATH at all.  */
  297.   boolean absolute_p = kpse_absolute_p (name, true);
  298.   
  299.   if (KPSE_DEBUG_P (KPSE_DEBUG_SEARCH))
  300.     DEBUGF4 ("search(file=%s, must_exist=%d, find_all=%d, path=%s).\n",
  301.              name, must_exist, all, path);
  302.  
  303.   /* Find the file(s). */
  304.   ret_list = absolute_p ? absolute_search (name)
  305.                         : path_search (path, name, must_exist, all);
  306.   
  307.   /* Append NULL terminator if we didn't find anything at all, or we're
  308.      supposed to find ALL and the list doesn't end in NULL now.  */
  309.   if (STR_LIST_LENGTH (ret_list) == 0
  310.       || (all && STR_LIST_LAST_ELT (ret_list) != NULL))
  311.     str_list_add (&ret_list, NULL);
  312.  
  313.   /* Record the filenames we found, if desired.  And wrap them in a
  314.      debugging line if we're doing that.  */
  315.   if (KPSE_DEBUG_P (KPSE_DEBUG_SEARCH))
  316.     DEBUGF1 ("search(%s) =>", original_name);
  317.   log_search (ret_list);
  318.   if (KPSE_DEBUG_P (KPSE_DEBUG_SEARCH))
  319.     putc ('\n', stderr);
  320.   
  321.   return STR_LIST (ret_list);
  322. }
  323.  
  324. /* Search PATH for the first NAME.  */
  325.  
  326. string
  327. kpse_path_search P3C(const_string, path,  const_string, name,
  328.                      boolean, must_exist)
  329. {
  330.   string *ret_list = search (path, name, must_exist, false);
  331.   return *ret_list;
  332. }
  333.  
  334.  
  335. /* Search all elements of PATH for files named NAME.  Not sure if it's
  336.    right to assert `must_exist' here, but it suffices now.  */
  337.  
  338. string *
  339. kpse_all_path_search P2C(const_string, path,  const_string, name)
  340. {
  341.   string *ret = search (path, name, true, true);
  342.   return ret;
  343. }
  344.  
  345. #ifdef TEST
  346.  
  347. void
  348. test_path_search (const_string path, const_string file)
  349. {
  350.   string answer;
  351.   string *answer_list;
  352.   
  353.   printf ("\nSearch %s for %s:\t", path, file);
  354.   answer = kpse_path_search (path, file);
  355.   puts (answer ? answer : "(null)");
  356.  
  357.   printf ("Search %s for all %s:\t", path, file);
  358.   answer_list = kpse_all_path_search (path, file);
  359.   putchar ('\n');
  360.   while (*answer_list)
  361.     {
  362.       putchar ('\t');
  363.       puts (*answer_list);
  364.       answer_list++;
  365.     }
  366. }
  367.  
  368. #define TEXFONTS "/usr/local/lib/tex/fonts"
  369.  
  370. int
  371. main ()
  372. {
  373.   /* All lists end with NULL.  */
  374.   test_path_search (".", "nonexistent");
  375.   test_path_search (".", "/nonexistent");
  376.   test_path_search ("/k:.", "kpathsea.texi");
  377.   test_path_search ("/k:.", "/etc/fstab");
  378.   test_path_search (".:" TEXFONTS "//", "cmr10.tfm");
  379.   test_path_search (".:" TEXFONTS "//", "logo10.tfm");
  380.   test_path_search (TEXFONTS "//times:.::", "ptmr.vf");
  381.   test_path_search (TEXFONTS "/adobe//:"
  382.                     "/usr/local/src/TeX+MF/typefaces//", "plcr.pfa");
  383.   
  384.   test_path_search ("~karl", ".bashrc");
  385.   test_path_search ("/k", "~karl/.bashrc");
  386.  
  387.   xputenv ("NONEXIST", "nonexistent");
  388.   test_path_search (".", "$NONEXISTENT");
  389.   xputenv ("KPATHSEA", "kpathsea");
  390.   test_path_search ("/k:.", "$KPATHSEA.texi");  
  391.   test_path_search ("/k:.", "${KPATHSEA}.texi");  
  392.   test_path_search ("$KPATHSEA:.", "README");  
  393.   test_path_search (".:$KPATHSEA", "README");  
  394.   
  395.   return 0;
  396. }
  397.  
  398. #endif /* TEST */
  399.  
  400.  
  401. /*
  402. Local variables:
  403. test-compile-command: "gcc -posix -g -I. -I.. -DTEST pathsearch.c kpathsea.a"
  404. End:
  405. */
  406.