tentative fix for issue 3 (ex 53)
[mplib] / src / texk / kpathsea / elt-dirs.c
1 /* elt-dirs.c: Translate a path element to its corresponding director{y,ies}.
2
3    Copyright 1993, 1994, 1995, 1996, 1997, 2008 Karl Berry.
4    Copyright 1997, 1998, 1999, 2000, 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
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 \f
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 \f
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
72 /* Associate KEY with VALUE.  We implement the cache as a simple linear
73    list, since it's unlikely to ever be more than a dozen or so elements
74    long.  We don't bother to check here if PATH has already been saved;
75    we always add it to our list.  We copy KEY but not VALUE; not sure
76    that's right, but it seems to be all that's needed.  */
77
78 static void
79 cache P2C(const_string, key,  str_llist_type *, value)
80 {
81   cache_length++;
82   XRETALLOC (the_cache, cache_length, cache_entry);
83   the_cache[cache_length - 1].key = xstrdup (key);
84   the_cache[cache_length - 1].value = value;
85 }
86
87
88 /* To retrieve, just check the list in order.  */
89
90 static str_llist_type *
91 cached P1C(const_string, key)
92 {
93   unsigned p;
94   
95   for (p = 0; p < cache_length; p++)
96     {
97       if (FILESTRCASEEQ (the_cache[p].key, key))
98         return the_cache[p].value;
99     }
100   
101   return NULL;
102 }
103 \f
104 /* Handle the magic path constructs.  */
105
106 /* Declare recursively called routine.  */
107 static void expand_elt P3H(str_llist_type *, const_string, unsigned);
108
109
110 /* POST is a pointer into the original element (which may no longer be
111    ELT) to just after the doubled DIR_SEP, perhaps to the null.  Append
112    subdirectories of ELT (up to ELT_LENGTH, which must be a /) to
113    STR_LIST_PTR.  */
114
115 #ifdef WIN32
116 /* Shared across recursive calls, it acts like a stack. */
117 static char dirname[MAX_PATH];
118 #endif
119
120 static void
121 do_subdir P4C(str_llist_type *, str_list_ptr,  const_string, elt,
122               unsigned, elt_length,  const_string, post)
123 {
124 #ifdef WIN32
125   WIN32_FIND_DATA find_file_data;
126   HANDLE hnd;
127   int proceed;
128   int nlinks = 2;
129 #else
130   DIR *dir;
131   struct dirent *e;
132 #endif /* not WIN32 */
133   fn_type name;
134   
135   /* Some old compilers don't allow aggregate initialization.  */
136   name = fn_copy0 (elt, elt_length);
137   
138   assert (IS_DIR_SEP (elt[elt_length - 1])
139           || IS_DEVICE_SEP (elt[elt_length - 1]));
140   
141 #if defined (WIN32)
142   strcpy(dirname, FN_STRING(name));
143   strcat(dirname, "/*.*");         /* "*.*" or "*" -- seems equivalent. */
144   hnd = FindFirstFile(dirname, &find_file_data);
145
146   if (hnd == INVALID_HANDLE_VALUE) {
147     fn_free(&name);
148     return;
149   }
150
151   /* Include top level before subdirectories, if nothing to match.  */
152   if (*post == 0)
153     dir_list_add (str_list_ptr, FN_STRING (name));
154   else {
155     /* If we do have something to match, see if it exists.  For
156        example, POST might be `pk/ljfour', and they might have a
157        directory `$TEXMF/fonts/pk/ljfour' that we should find.  */
158     fn_str_grow (&name, post);
159     expand_elt (str_list_ptr, FN_STRING (name), elt_length);
160     fn_shrink_to (&name, elt_length);
161   }
162   proceed = 1;
163   while (proceed) {
164     if (find_file_data.cFileName[0] != '.') {
165       int links;
166
167       /* Construct the potential subdirectory name.  */
168       fn_str_grow (&name, find_file_data.cFileName);
169
170       /* Maybe we have cached the leafness of this directory.
171                  The function will return 0 if unknown, 
172                  else the actual (Unix-like) value. */
173       links = dir_links (FN_STRING (name), 0);
174
175       if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
176         unsigned potential_len = FN_LENGTH (name);
177         /* in any case, compute the leafness */
178         nlinks++;
179
180         /* It's a directory, so append the separator.  */
181         fn_str_grow (&name, DIR_SEP_STRING);
182         if (*post != 0) { 
183           fn_str_grow (&name, post);
184           /* Unfortunately we can't check if the new element is
185              a leaf directory, because we don't have a directory
186              name here, we just have a path spec. This means we
187              may descend into a leaf directory cm/pk, if the
188              spec is ...fonts//pk//.  */
189           expand_elt (str_list_ptr, FN_STRING (name), potential_len);
190           fn_shrink_to (&name, potential_len);
191         }
192         /* Should we recurse?  To see if the subdirectory is a
193            leaf, check if it has two links (one for . and one for
194            ..).  This means that symbolic links to directories do
195            not affect the leaf-ness.  This is arguably wrong, but
196            the only alternative I know of is to stat every entry
197            in the directory, and that is unacceptably slow. */
198            
199         if (links == 0 || links > 2)
200           /* All criteria are met; find subdirectories.  */
201           do_subdir (str_list_ptr, FN_STRING (name),
202                      potential_len, post);
203         else if (*post == 0)
204           /* Nothing to match, no recursive subdirectories to
205              look for: we're done with this branch.  Add it.  */
206           dir_list_add (str_list_ptr, FN_STRING (name));
207       }
208       fn_shrink_to (&name, elt_length);
209     }
210     proceed = FindNextFile (hnd, &find_file_data);
211   }
212   /* Update the leafness of name. */
213   dir_links(FN_STRING(name), nlinks);
214   fn_free (&name);
215   FindClose(hnd);
216
217 #else /* not WIN32 */
218
219   /* If we can't open it, quit.  */
220   dir = opendir (FN_STRING (name));
221   if (dir == NULL)
222     {
223       fn_free (&name);
224       return;
225     }
226   
227   /* Include top level before subdirectories, if nothing to match.  */
228   if (*post == 0)
229     dir_list_add (str_list_ptr, FN_STRING (name));
230   else
231     { /* If we do have something to match, see if it exists.  For
232          example, POST might be `pk/ljfour', and they might have a
233          directory `$TEXMF/fonts/pk/ljfour' that we should find.  */
234       fn_str_grow (&name, post);
235       expand_elt (str_list_ptr, FN_STRING (name), elt_length);
236       fn_shrink_to (&name, elt_length);
237     }
238
239   while ((e = readdir (dir)) != NULL)
240     { /* If it begins with a `.', never mind.  (This allows ``hidden''
241          directories that the algorithm won't find.)  */
242       if (e->d_name[0] != '.')
243         {
244           int links;
245           
246           /* Construct the potential subdirectory name.  */
247           fn_str_grow (&name, e->d_name);
248           
249           /* If we can't stat it, or if it isn't a directory, continue.  */
250           links = dir_links (FN_STRING (name), 0);
251
252           if (links >= 0)
253             { 
254               unsigned potential_len = FN_LENGTH (name);
255
256               /* It's a directory, so append the separator.  */
257               fn_str_grow (&name, DIR_SEP_STRING);
258
259               if (*post != 0)
260                 { 
261                   fn_str_grow (&name, post);
262                   /* Unfortunately we can't check if the new element is
263                      a leaf directory, because we don't have a directory
264                      name here, we just have a path spec. This means we
265                      may descend into a leaf directory cm/pk, if the
266                      spec is ...fonts//pk//.  */
267                   expand_elt (str_list_ptr, FN_STRING (name), potential_len);
268                   fn_shrink_to (&name, potential_len);
269                 }
270               
271               /* Should we recurse?  To see if the subdirectory is a
272                  leaf, check if it has two links (one for . and one for
273                  ..).  This means that symbolic links to directories do
274                  not affect the leaf-ness.  This is arguably wrong, but
275                  the only alternative I know of is to stat every entry
276                  in the directory, and that is unacceptably slow.
277                  
278                  The #ifdef here makes all this configurable at
279                  compile-time, so that if we're using VMS directories or
280                  some such, we can still find subdirectories, even if it
281                  is much slower.  */
282 #ifdef ST_NLINK_TRICK
283 #ifdef AMIGA
284               /* With SAS/C++ 6.55 on the Amiga, `stat' sets the `st_nlink'
285                  field to -1 for a file, or to 1 for a directory.  */
286               if (links == 1)
287 #else
288               if (links > 2)
289 #endif /* not AMIGA */
290 #endif /* not ST_NLINK_TRICK */
291                 /* All criteria are met; find subdirectories.  */
292                 do_subdir (str_list_ptr, FN_STRING (name),
293                            potential_len, post);
294 #ifdef ST_NLINK_TRICK
295               else if (*post == 0)
296                 /* Nothing to match, no recursive subdirectories to
297                    look for: we're done with this branch.  Add it.  */
298                 dir_list_add (str_list_ptr, FN_STRING (name));
299 #endif
300             }
301
302           /* Remove the directory entry we just checked from `name'.  */
303           fn_shrink_to (&name, elt_length);
304         }
305     }
306   
307   fn_free (&name);
308   xclosedir (dir);
309 #endif /* not WIN32 */
310 }
311
312
313 /* Assume ELT is non-empty and non-NULL.  Return list of corresponding
314    directories (with no terminating NULL entry) in STR_LIST_PTR.  Start
315    looking for magic constructs at START.  */
316
317 static void
318 expand_elt P3C(str_llist_type *, str_list_ptr,  const_string, elt,
319                unsigned, start)
320 {
321   const_string dir = elt + start, post;
322   
323   while (*dir != 0)
324     {
325       if (IS_DIR_SEP (*dir))
326         {
327           /* If two or more consecutive /'s, find subdirectories.  */
328           if (IS_DIR_SEP (dir[1]))
329             {
330               for (post = dir + 1; IS_DIR_SEP (*post); post++) ;
331               do_subdir (str_list_ptr, elt, dir - elt + 1, post);
332               return;
333             }
334
335           /* No special stuff at this slash.  Keep going.  */
336         }
337       
338       dir++;
339     }
340   
341   /* When we reach the end of ELT, it will be a normal filename.  */
342   checked_dir_list_add (str_list_ptr, elt);
343 }
344 \f
345 /* The first bits of a path element can be problematic because they
346    look like a request to expand a whole disk, rather than a subtree.
347    - It can contain a drive specification.
348    - It can be a UNC path (win32, but they are part of the single
349      UNIX specification as well).
350    The argument is a string as the function can diddle into the argument
351    to canonicalize it, which tends to matter on windows platforms.
352    - Always lower-case drive letters a-z, even those filesystem that
353      preserve case in filenames do not care about the case of the drive
354      letters.
355    - Remove unneeded double slashes. The problem is Windows does not 
356      handle well filenames like c://dir/foo. So canonicalize the names.
357      The resulting name will always be shorter than the one passed, so no
358      problem.
359    - If possible, we merely skip multiple leading slashes to prevent
360      expanding from the root of a UNIX filesystem tree.
361 */
362 unsigned
363 kpse_normalize_path P1C(string, elt)
364 {
365   unsigned ret;
366   unsigned i;
367
368   if (NAME_BEGINS_WITH_DEVICE(elt)) {
369       if (*elt >= 'A' && *elt <= 'Z')
370           *elt += 'a' - 'A';
371       for (i = 2; IS_DIR_SEP(elt[i]); ++i)
372           ;
373       if (i > 3)
374           memmove(elt+3, elt+i, strlen(elt+i) + 1);
375       ret = 2;
376   } else if (IS_UNC_NAME(elt)) {
377       for (ret = 2; elt[ret] && !IS_DIR_SEP(elt[ret]); ++ret)
378           ;
379       for (i = ret; elt[i] && IS_DIR_SEP(elt[i]); ++i)
380           ;
381       if (i > ret+1)
382           memmove(elt+ret+1, elt+i, strlen(elt+i) + 1);
383   } else {
384       for (ret = 0; IS_DIR_SEP(elt[ret]); ++ret)
385           ;
386   }
387   
388   if (KPSE_DEBUG_P (KPSE_DEBUG_STAT))
389         DEBUGF2 ("kpse_normalize_path (%s) => %u\n", elt, ret);
390
391   return ret;
392 }
393 \f
394 /* Here is the entry point.  Returns directory list for ELT.  */
395
396 str_llist_type *
397 kpse_element_dirs P1C(string, elt)
398 {
399   str_llist_type *ret;
400
401   /* If given nothing, return nothing.  */
402   if (!elt || !*elt)
403     return NULL;
404
405   /* If we've already cached the answer for ELT, return it.  */
406   ret = cached (elt);
407   if (ret)
408     return ret;
409
410   /* We're going to have a real directory list to return.  */
411   ret = XTALLOC1 (str_llist_type);
412   *ret = NULL;
413
414   /* We handle the hard case in a subroutine.  */
415   expand_elt (ret, elt, kpse_normalize_path (elt));
416
417   /* Remember the directory list we just found, in case future calls are
418      made with the same ELT.  */
419   cache (elt, ret);
420
421 #ifdef KPSE_DEBUG
422   if (KPSE_DEBUG_P (KPSE_DEBUG_EXPAND))
423     {
424       DEBUGF1 ("path element %s =>", elt);
425       if (ret)
426         {
427           str_llist_elt_type *e;
428           for (e = *ret; e; e = STR_LLIST_NEXT (*e))
429             fprintf (stderr, " %s", STR_LLIST (*e));
430         }
431       putc ('\n', stderr);
432       fflush (stderr);
433     }
434 #endif /* KPSE_DEBUG */
435
436   return ret;
437 }
438 \f
439 #ifdef TEST
440
441 void
442 print_element_dirs (const_string elt)
443 {
444   str_llist_type *dirs;
445   
446   printf ("Directories of %s:\t", elt ? elt : "(nil)");
447   fflush (stdout);
448   
449   dirs = kpse_element_dirs (elt);
450   
451   if (!dirs)
452     printf ("(nil)");
453   else
454     {
455       str_llist_elt_type *dir;
456       for (dir = *dirs; dir; dir = STR_LLIST_NEXT (*dir))
457         {
458           string d = STR_LLIST (*dir);
459           printf ("%s ", *d ? d : "`'");
460         }
461     }
462   
463   putchar ('\n');
464 }
465
466 int
467 main ()
468 {
469   /* DEBUG_SET (DEBUG_STAT); */
470   /* All lists end with NULL.  */
471   print_element_dirs (NULL);    /* */
472   print_element_dirs ("");      /* ./ */
473   print_element_dirs ("/k");    /* */
474   print_element_dirs (".//");   /* ./ ./archive/ */
475   print_element_dirs (".//archive");    /* ./ ./archive/ */
476 #ifdef AMIGA
477   print_element_dirs ("TeXMF:AmiWeb2c/texmf/fonts//"); /* lots */
478   print_element_dirs ("TeXMF:AmiWeb2c/share/texmf/fonts//bakoma"); /* just one */
479   print_element_dirs ("TeXMF:AmiWeb2c/texmf/fonts//"); /* lots again [cache] */
480   print_element_dirs ("TeXMF:");        /* TeXMF: */
481   print_element_dirs ("TeXMF:/");       /* TeXMF: and all subdirs */
482 #else /* not AMIGA */
483   print_element_dirs ("/tmp/fonts//");  /* no need to stat anything */
484   print_element_dirs ("/usr/local/lib/tex/fonts//");      /* lots */
485   print_element_dirs ("/usr/local/lib/tex/fonts//times"); /* just one */
486   print_element_dirs ("/usr/local/lib/tex/fonts//"); /* lots again [cache] */
487   print_element_dirs ("~karl");         /* tilde expansion */
488   print_element_dirs ("$karl");         /* variable expansion */  
489   print_element_dirs ("~${LOGNAME}");   /* both */  
490 #endif /* not AMIGA */
491   return 0;
492 }
493
494 #endif /* TEST */
495
496
497 /*
498 Local variables:
499 test-compile-command: "gcc -g -I. -I.. -DTEST elt-dirs.c kpathsea.a"
500 End:
501 */