1 /* pathsearch.c: look up a filename in a path.
2 
3    Copyright 1993, 1994, 1995, 1997, 2007, 2009-2012 Karl Berry.
4    Copyright 1997-2005 Olaf Weber.
5 
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Lesser General Public
8    License as published by the Free Software Foundation; either
9    version 2.1 of the License, or (at your option) any later version.
10 
11    This library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Lesser General Public License for more details.
15 
16    You should have received a copy of the GNU Lesser General Public License
17    along with this library; if not, see <http://www.gnu.org/licenses/>.  */
18 
19 #include <kpathsea/config.h>
20 #include <kpathsea/c-pathch.h>
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 #ifdef __DJGPP__
34 #include <sys/stat.h>   /* for stat bits */
35 #endif
36 
37 #ifdef WIN32
38 #undef fputs
39 #undef puts
40 #define fputs win32_fputs
41 #define puts  win32_puts
42 #endif
43 
44 /* The very first search is for texmf.cnf, called when someone tries to
45    initialize the TFM path or whatever.  init_path calls kpse_cnf_get
46    which calls kpse_all_path_search to find all the texmf.cnf's.  We
47    need to do various special things in this case, since we obviously
48    don't yet have the configuration files when we're searching for the
49    configuration files.  */
50 
51 
52 
53 /* This function is called after every search (except the first, since
54    we definitely want to allow enabling the logging in texmf.cnf) to
55    record the filename(s) found in $TEXMFLOG.  */
56 
57 static void
log_search(kpathsea kpse,str_list_type filenames)58 log_search (kpathsea kpse, str_list_type filenames)
59 {
60 
61   if (kpse->log_opened == false) {
62     /* Get name from either envvar or config file.  */
63       string log_name = kpathsea_var_value (kpse, "TEXMFLOG");
64     kpse->log_opened = true;
65     if (log_name) {
66       kpse->log_file = fopen (log_name, FOPEN_A_MODE);
67       if (!kpse->log_file)
68         perror (log_name);
69       free (log_name);
70     }
71   }
72 
73   if (
74 #ifdef KPSE_DEBUG
75       KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH) ||
76 #endif /* KPSE_DEBUG */
77       kpse->log_file) {
78     unsigned e;
79 
80     /* FILENAMES should never be null, but safety doesn't hurt.  */
81     for (e = 0; e < STR_LIST_LENGTH (filenames) && STR_LIST_ELT (filenames, e);
82          e++) {
83       string filename = STR_LIST_ELT (filenames, e);
84 
85       /* Only record absolute filenames, for privacy.  */
86       if (kpse->log_file && kpathsea_absolute_p (kpse, filename, false))
87         fprintf (kpse->log_file, "%lu %s\n", (long unsigned) time (NULL),
88                  filename);
89 
90 #ifdef KPSE_DEBUG
91       /* And show them online, if debugging.  We've already started
92          the debugging line in `search', where this is called, so
93          just print the filename here, don't use DEBUGF.  */
94       if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH)) {
95         putc (' ', stderr);
96         fputs (filename, stderr);
97       }
98 #endif /* KPSE_DEBUG */
99     }
100   }
101 }
102 
103 /* Concatenate each element in DIRS with NAME (assume each ends with a
104    /, to save time).  If SEARCH_ALL is false, return the first readable
105    regular file.  Else continue to search for more.  In any case, if
106    none, return a list containing just NULL.
107 
108    We keep a single buffer for the potential filenames and reallocate
109    only when necessary.  I'm not sure it's noticeably faster, but it
110    does seem cleaner.  (We do waste a bit of space in the return
111    value, though, since we don't shrink it to the final size returned.)  */
112 
113 #define INIT_ALLOC 75  /* Doesn't much matter what this number is.  */
114 
115 static str_list_type
dir_list_search(kpathsea kpse,str_llist_type * dirs,const_string name,boolean search_all)116 dir_list_search (kpathsea kpse, str_llist_type *dirs,  const_string name,
117                     boolean search_all)
118 {
119   str_llist_elt_type *elt;
120   str_list_type ret;
121   unsigned name_len = strlen (name);
122   unsigned allocated = INIT_ALLOC;
123   string potential = (string)xmalloc (allocated);
124 
125   ret = str_list_init ();
126 
127   for (elt = *dirs; elt; elt = STR_LLIST_NEXT (*elt))
128     {
129       const_string dir = STR_LLIST (*elt);
130       unsigned dir_len = strlen (dir);
131 
132       while (dir_len + name_len + 1 > allocated)
133         {
134           allocated += allocated;
135           XRETALLOC (potential, allocated, char);
136         }
137 
138       strcpy (potential, dir);
139       strcat (potential, name);
140 
141       if (kpathsea_readable_file (kpse, potential))
142         {
143           str_list_add (&ret, potential);
144 
145           /* Move this element towards the top of the list.  */
146           str_llist_float (dirs, elt);
147 
148           /* If caller only wanted one file returned, no need to
149              terminate the list with NULL; the caller knows to only look
150              at the first element.  */
151           if (!search_all)
152             return ret;
153 
154           /* Start new filename.  */
155           allocated = INIT_ALLOC;
156           potential = (string)xmalloc (allocated);
157         }
158     }
159 
160   /* If we get here, either we didn't find any files, or we were finding
161      all the files.  But we're done with the last filename, anyway.  */
162   free (potential);
163 
164   return ret;
165 }
166 
167 /* Note: NAMES[i] is not modified.  */
168 static str_list_type
dir_list_search_list(kpathsea kpse,str_llist_type * dirs,string * names,boolean search_all)169 dir_list_search_list (kpathsea kpse, str_llist_type *dirs, string* names,
170                       boolean search_all)
171 {
172   str_llist_elt_type *elt;
173   str_list_type ret;
174   unsigned allocated = INIT_ALLOC;
175   string potential = XTALLOC(allocated, char);
176 
177   ret = str_list_init ();
178 
179   for (elt = *dirs; elt; elt = STR_LLIST_NEXT(*elt)) {
180       const_string dir = STR_LLIST (*elt);
181       unsigned dir_len = strlen (dir);
182       int i;
183 
184       for (i = 0; names[i]; i++) {
185           const_string name = names[i];
186           unsigned name_len;
187 
188           /* Don't bother with absolute & explicit relative. */
189           if (kpathsea_absolute_p(kpse, name, true))
190               continue;
191 
192           name_len = strlen(name);
193 
194           while (dir_len + name_len + 1 > allocated) {
195               allocated += allocated;
196               XRETALLOC (potential, allocated, char);
197           }
198 
199           strcpy (potential, dir);
200           strcat (potential+dir_len, name);
201 
202           if (kpathsea_readable_file (kpse, potential)) {
203               str_list_add (&ret, potential);
204 
205               /* Move this element towards the top of the list.  */
206               str_llist_float (dirs, elt);
207 
208               /* If caller only wanted one file returned, no need to
209                  terminate the list with NULL; the caller knows to only look
210                  at the first element.  */
211               if (!search_all)
212                   return ret;
213 
214               /* Start new filename. */
215               allocated = INIT_ALLOC;
216               potential = XTALLOC(allocated, char);
217           }
218       }
219   }
220 
221   /* If we get here, either we didn't find any files, or we were finding
222      all the files.  But we're done with the last filename, anyway.  */
223   free (potential);
224 
225   return ret;
226 }
227 
228 /* This is called when NAME is absolute or explicitly relative; if it's
229    readable, return (a list containing) it; otherwise, return NULL.  */
230 
231 static str_list_type
absolute_search(kpathsea kpse,string name)232 absolute_search (kpathsea kpse, string name)
233 {
234   str_list_type ret_list;
235   string found = kpathsea_readable_file (kpse, name);
236 
237   /* Some old compilers can't initialize structs.  */
238   ret_list = str_list_init ();
239 
240   /* If NAME wasn't found, free the expansion.  */
241   if (name != found)
242     free (name);
243 
244   /* Add `found' to the return list even if it's null; that tells
245      the caller we didn't find anything.  */
246   str_list_add (&ret_list, found);
247 
248   return ret_list;
249 }
250 
251 /* This is the hard case -- look for NAME in PATH.  If ALL is false,
252    return the first file found.  Otherwise, search all elements of PATH.  */
253 
254 static str_list_type
path_search(kpathsea kpse,const_string path,string name,boolean must_exist,boolean all)255 path_search (kpathsea kpse, const_string path,  string name,
256              boolean must_exist,  boolean all)
257 {
258   string elt;
259   str_list_type ret_list;
260   boolean done = false;
261   ret_list = str_list_init (); /* some compilers lack struct initialization */
262 
263   for (elt = kpathsea_path_element (kpse, path); !done && elt;
264        elt = kpathsea_path_element (kpse, NULL)) {
265     str_list_type *found;
266     boolean allow_disk_search = true;
267 
268     if (*elt == '!' && *(elt + 1) == '!') {
269       /* Those magic leading chars in a path element means don't search the
270          disk for this elt.  And move past the magic to get to the name.  */
271       allow_disk_search = false;
272       elt += 2;
273     }
274 
275     /* See elt-dirs.c for side effects of this function */
276     kpathsea_normalize_path(kpse, elt);
277 
278     /* Try ls-R, unless we're searching for texmf.cnf.  Our caller
279        (search), also tests first_search, and does the resetting.  */
280     found = kpse->followup_search ? kpathsea_db_search (kpse, name, elt, all)
281                                   : NULL;
282 
283     /* Search the filesystem if (1) the path spec allows it, and either
284          (2a) we are searching for texmf.cnf ; or
285          (2b) no db exists; or
286          (2c) no db's are relevant to this elt; or
287          (3) MUST_EXIST && NAME was not in the db.
288        In (2*), `found' will be NULL.
289        In (3),  `found' will be an empty list. */
290     if (allow_disk_search && (!found || (must_exist && !STR_LIST (*found)))) {
291         str_llist_type *dirs = kpathsea_element_dirs (kpse, elt);
292       if (dirs && *dirs) {
293         if (!found)
294           found = XTALLOC1 (str_list_type);
295         *found = dir_list_search (kpse, dirs, name, all);
296       }
297     }
298 
299     /* Did we find anything anywhere?  */
300     if (found && STR_LIST (*found)) {
301       if (all)
302         str_list_concat (&ret_list, *found);
303       else {
304         str_list_add (&ret_list, STR_LIST_ELT (*found, 0));
305         done = true;
306       }
307     }
308 
309     /* Free the list space, if any (but not the elements).  */
310     if (found) {
311       str_list_free (found);
312       free (found);
313     }
314   }
315 
316   /* Free the expanded name we were passed.  It can't be in the return
317      list, since the path directories got unconditionally prepended.  */
318   free (name);
319 
320   return ret_list;
321 }
322 
323 /* Search PATH for ORIGINAL_NAME.  If ALL is false, or ORIGINAL_NAME is
324    absolute_p, check ORIGINAL_NAME itself.  Otherwise, look at each
325    element of PATH for the first readable ORIGINAL_NAME.
326 
327    Always return a list; if no files are found, the list will
328    contain just NULL.  If ALL is true, the list will be
329    terminated with NULL.  */
330 
331 static string *
search(kpathsea kpse,const_string path,const_string original_name,boolean must_exist,boolean all)332 search (kpathsea kpse, const_string path,  const_string original_name,
333         boolean must_exist,  boolean all)
334 {
335   str_list_type ret_list;
336   string name;
337   boolean absolute_p;
338 
339 #ifdef __DJGPP__
340   /* We will use `stat' heavily, so let's request for
341      the fastest possible version of `stat', by telling
342      it what members of struct stat do we really need.
343 
344      We need to set this on each call because this is a
345      library function; the caller might need other options
346      from `stat'.  Thus save the flags and restore them
347      before exit.
348 
349      This call tells `stat' that we do NOT need to recognize
350      executable files (neither by an extension nor by a magic
351      signature); that we do NOT need time stamp of root directories;
352      and that we do NOT need the write access bit in st_mode.
353 
354      Note that `kpse_set_program_name' needs the EXEC bits,
355      but it was already called by the time we get here.  */
356   unsigned short save_djgpp_flags  = _djstat_flags;
357 
358   _djstat_flags = _STAT_EXEC_MAGIC | _STAT_EXEC_EXT
359                   | _STAT_ROOT_TIME | _STAT_WRITEBIT;
360 #endif
361 
362   /* Make a leading ~ count as an absolute filename, and expand $FOO's.  */
363   name = kpathsea_expand (kpse, original_name);
364 
365   /* If the first name is absolute or explicitly relative, no need to
366      consider PATH at all.  */
367   absolute_p = kpathsea_absolute_p (kpse, name, true);
368 
369 #ifdef KPSE_DEBUG
370   if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH))
371     DEBUGF4 ("start search(file=%s, must_exist=%d, find_all=%d, path=%s).\n",
372              name, must_exist, all, path);
373 #endif /* KPSE_DEBUG */
374 
375   /* Find the file(s). */
376   ret_list = absolute_p ? absolute_search (kpse, name)
377                         : path_search (kpse, path, name, must_exist, all);
378 
379   /* Append NULL terminator if we didn't find anything at all, or we're
380      supposed to find ALL and the list doesn't end in NULL now.  */
381   if (STR_LIST_LENGTH (ret_list) == 0
382       || (all && STR_LIST_LAST_ELT (ret_list) != NULL))
383     str_list_add (&ret_list, NULL);
384 
385   /* The very first search is for texmf.cnf.  We can't log that, since
386      we want to allow setting TEXMFLOG in texmf.cnf.  */
387   if (kpse->followup_search == false) {
388     kpse->followup_search = true;
389   } else {
390     /* Record the filenames we found, if desired.  And wrap them in a
391        debugging line if we're doing that.  */
392 #ifdef KPSE_DEBUG
393     if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH))
394       DEBUGF1 ("search(%s) =>", original_name);
395 #endif /* KPSE_DEBUG */
396     log_search (kpse, ret_list);
397 #ifdef KPSE_DEBUG
398     if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH))
399       putc ('\n', stderr);
400 #endif /* KPSE_DEBUG */
401   }
402 
403 #ifdef __DJGPP__
404   /* Undo any side effects.  */
405   _djstat_flags = save_djgpp_flags;
406 #endif
407 
408   return STR_LIST (ret_list);
409 }
410 
411 /* Search PATH for NAMES.
412 
413    Always return a list; if no files are found, the list will
414    contain just NULL.  If ALL is true, the list will be
415    terminated with NULL.  */
416 
417 string *
kpathsea_path_search_list_generic(kpathsea kpse,const_string path,string * names,boolean must_exist,boolean all)418 kpathsea_path_search_list_generic (kpathsea kpse,
419                                    const_string path, string* names,
420                                    boolean must_exist, boolean all)
421 {
422   str_list_type ret_list;
423   string* namep;
424   string elt;
425   boolean done = false;
426   boolean all_absolute = true;
427 
428 #ifdef __DJGPP__
429   /* We will use `stat' heavily, so let's request for
430      the fastest possible version of `stat', by telling
431      it what members of struct stat do we really need.
432 
433      We need to set this on each call because this is a
434      library function; the caller might need other options
435      from `stat'.  Thus save the flags and restore them
436      before exit.
437 
438      This call tells `stat' that we do NOT need to recognize
439      executable files (neither by an extension nor by a magic
440      signature); that we do NOT need time stamp of root directories;
441      and that we do NOT need the write access bit in st_mode.
442 
443      Note that `kpse_set_program_name' needs the EXEC bits,
444      but it was already called by the time we get here.  */
445   unsigned short save_djgpp_flags  = _djstat_flags;
446 
447   _djstat_flags = _STAT_EXEC_MAGIC | _STAT_EXEC_EXT
448                   | _STAT_ROOT_TIME | _STAT_WRITEBIT;
449 #endif
450 
451   ret_list = str_list_init();
452 
453 #ifdef KPSE_DEBUG
454   if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH)) {
455     DEBUGF1  ("start search(files=[%s", *names);
456     for (namep = names+1; *namep != NULL; namep++) {
457       fputc(' ', stderr);
458       fputs(*namep, stderr);
459     }
460     fprintf (stderr, "], must_exist=%d, find_all=%d, path=%s).\n",
461              must_exist, all, path);
462   }
463 #endif /* KPSE_DEBUG */
464 
465   /* FIXME: is this really true?  No need to do any expansion on names.  */
466 
467   /* First catch any absolute or explicit relative names. */
468   for (namep = names; *namep; namep++) {
469     if (kpathsea_absolute_p (kpse, *namep, true)) {
470       if (kpathsea_readable_file (kpse, *namep)) {
471         str_list_add (&ret_list, xstrdup(*namep));
472         if (!all)
473           goto out;
474       }
475     } else {
476       all_absolute = false;
477     }
478   }
479   /* Shortcut: if we were only given absolute/explicit relative names,
480      we can skip the rest.  Typically, if one name is absolute, they
481      all are, because our caller derived them from each other. */
482   if (all_absolute)
483       goto out;
484 
485   /* Look at each path element in turn. */
486   for (elt = kpathsea_path_element (kpse, path); !done && elt;
487        elt = kpathsea_path_element (kpse, NULL))
488   {
489     str_list_type *found;
490     boolean allow_disk_search = true;
491     if (elt[0] == '!' && elt[1] == '!') {
492       /* !! magic -> disallow disk searches. */
493       allow_disk_search = false;
494       elt += 2;
495     }
496 
497     /* See elt-dirs.c for side effects of this function. */
498     kpathsea_normalize_path (kpse, elt);
499 
500     /* Try ls-R, unless we're searching for texmf.cnf. */
501     found = kpse->followup_search
502             ? kpathsea_db_search_list (kpse, names, elt, all) : NULL;
503 
504     /* Search the filesystem if (1) the path spec allows it, and either
505          (2a) we are searching for texmf.cnf ; or
506          (2b) no db exists; or
507          (2c) no db's are relevant to this elt; or
508          (3) MUST_EXIST && NAME was not in the db.
509        In (2*), `found' will be NULL.
510        In (3),  `found' will be an empty list. */
511     if (allow_disk_search && (!found || (must_exist && !STR_LIST(*found)))) {
512         str_llist_type *dirs = kpathsea_element_dirs (kpse, elt);
513       if (dirs && *dirs) {
514         if (!found)
515           found = XTALLOC1 (str_list_type);
516         *found = dir_list_search_list (kpse, dirs, names, all);
517       }
518     }
519 
520     /* Did we find anything? */
521     if (found && STR_LIST (*found)) {
522       if (all) {
523         str_list_concat (&ret_list, *found);
524       } else {
525         str_list_add (&ret_list, STR_LIST_ELT (*found, 0));
526         done = true;
527       }
528     }
529   }
530 
531  out:
532   /* Uniqify, since our paths can often end up finding the same file
533      more than once.  */
534   str_list_uniqify (&ret_list);
535 
536   /* Add NULL if we will be returning multiple elements.  */
537   if (STR_LIST_LENGTH (ret_list) == 0
538       || (all && STR_LIST_LAST_ELT (ret_list) != NULL))
539     str_list_add (&ret_list, NULL);
540 
541   if (kpse->followup_search == false) {
542     kpse->followup_search = true;
543   } else {
544     /* Record the filenames we found, if desired.  And wrap them in a
545        debugging line if we're doing that.  */
546 #ifdef KPSE_DEBUG
547     if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH)) {
548       DEBUGF1 ("search([%s", *names);
549       for (namep = names+1; *namep != NULL; namep++) {
550         fputc (' ', stderr);
551         fputs (*namep, stderr);
552       }
553       fputs ("]) =>", stderr);
554     }
555 #endif /* KPSE_DEBUG */
556     log_search (kpse, ret_list);
557 #ifdef KPSE_DEBUG
558     if (KPATHSEA_DEBUG_P (KPSE_DEBUG_SEARCH))
559       putc ('\n', stderr);
560 #endif /* KPSE_DEBUG */
561   }
562 
563 #ifdef __DJGPP__
564   /* Undo any side effects.  */
565   _djstat_flags = save_djgpp_flags;
566 #endif
567 
568   return STR_LIST (ret_list);
569 }
570 
571 /* Search PATH for the first NAME according to MUST_EXIST.  */
572 
573 string
kpathsea_path_search(kpathsea kpse,const_string path,const_string name,boolean must_exist)574 kpathsea_path_search (kpathsea kpse, const_string path, const_string name,
575                       boolean must_exist)
576 {
577   string *ret_list = search (kpse, path, name, must_exist, false);
578   string ret = *ret_list;
579   free (ret_list);
580   return ret;
581 }
582 
583 /* Search PATH for all files named NAME.  Might have been better not
584    to assert `must_exist' here, but it's too late to change.  */
585 
586 string *
kpathsea_all_path_search(kpathsea kpse,const_string path,const_string name)587 kpathsea_all_path_search (kpathsea kpse, const_string path, const_string name)
588 {
589     string *ret = search (kpse, path, name, true, true);
590   return ret;
591 }
592 
593 #if defined (KPSE_COMPAT_API)
594 
595 string
kpse_path_search(const_string path,const_string name,boolean must_exist)596 kpse_path_search (const_string path,  const_string name, boolean must_exist)
597 {
598     return kpathsea_path_search (kpse_def, path,  name, must_exist);
599 }
600 
601 string *
kpse_all_path_search(const_string path,const_string name)602 kpse_all_path_search (const_string path,  const_string name)
603 {
604     return kpathsea_all_path_search (kpse_def,  path, name);
605 }
606 #endif
607 
608 
609 #ifdef TEST
610 
611 void
test_path_search(const_string path,const_string file)612 test_path_search (const_string path, const_string file)
613 {
614   string answer;
615   string *answer_list;
616 
617   printf ("\nSearch %s for %s:\t", path, file);
618   answer = kpse_path_search (path, file, 0);
619   puts (answer ? answer : "(nil)");
620 
621   printf ("Search %s for all %s:\t", path, file);
622   answer_list = kpse_all_path_search (path, file);
623   putchar ('\n');
624   while (*answer_list)
625     {
626       putchar ('\t');
627       puts (*answer_list);
628       answer_list++;
629     }
630 }
631 
632 #define TEXFONTS "/usr/local/lib/tex/fonts"
633 
634 int
main(int argc,char ** argv)635 main (int argc, char **argv)
636 {
637   kpse_set_program_name(argv[0], NULL);
638   /* All lists end with NULL.  */
639   test_path_search (".", "nonexistent");
640   test_path_search (".", "/nonexistent");
641   test_path_search ("/k" ENV_SEP_STRING ".", "kpathsea.texi");
642   test_path_search ("/k" ENV_SEP_STRING ".", "/etc/fstab");
643   test_path_search ("." ENV_SEP_STRING TEXFONTS "//", "cmr10.tfm");
644   test_path_search ("." ENV_SEP_STRING TEXFONTS "//", "logo10.tfm");
645   test_path_search (TEXFONTS "//times" ENV_SEP_STRING "."
646                     ENV_SEP_STRING ENV_SEP_STRING, "ptmr.vf");
647   test_path_search (TEXFONTS "/adobe//" ENV_SEP_STRING
648                     "/usr/local/src/TeX+MF/typefaces//", "plcr.pfa");
649 
650   test_path_search ("~karl", ".bashrc");
651   test_path_search ("/k", "~karl/.bashrc");
652 
653   xputenv ("NONEXIST", "nonexistent");
654   test_path_search (".", "$NONEXISTENT");
655   xputenv ("KPATHSEA", "kpathsea");
656   test_path_search ("/k" ENV_SEP_STRING ".", "$KPATHSEA.texi");
657   test_path_search ("/k" ENV_SEP_STRING ".", "${KPATHSEA}.texi");
658   test_path_search ("$KPATHSEA" ENV_SEP_STRING ".", "README");
659   test_path_search ("." ENV_SEP_STRING "$KPATHSEA", "README");
660 
661   return 0;
662 }
663 
664 #endif /* TEST */
665 
666 
667 /*
668 Local variables:
669 standalone-compile-command: "gcc -posix -g -I. -I.. -DTEST pathsearch.c kpathsea.a"
670 End:
671 */
672