dvitomp fix from Akira
[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 (w32, 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
377   } else if (IS_UNC_NAME(elt)) {
378     for (ret = 2; elt[ret] && !IS_DIR_SEP(elt[ret]); ++ret)
379       ;
380     for (i = ret; elt[i] && IS_DIR_SEP(elt[i]); ++i)
381       ;
382     if (i > ret+1)
383       memmove (elt+ret+1, elt+i, strlen(elt+i) + 1);
384
385   } else {
386     for (ret = 0; IS_DIR_SEP(elt[ret]); ++ret)
387       ;
388   }
389   
390   if (KPSE_DEBUG_P (KPSE_DEBUG_STAT) && ret != 1)
391     DEBUGF2 ("kpse_normalize_path (%s) => %u\n", elt, ret);
392
393   return ret;
394 }
395 \f
396 /* Here is the entry point.  Returns directory list for ELT.  */
397
398 str_llist_type *
399 kpse_element_dirs P1C(string, elt)
400 {
401   str_llist_type *ret;
402
403   /* If given nothing, return nothing.  */
404   if (!elt || !*elt)
405     return NULL;
406
407   /* If we've already cached the answer for ELT, return it.  */
408   ret = cached (elt);
409   if (ret)
410     return ret;
411
412   /* We're going to have a real directory list to return.  */
413   ret = XTALLOC1 (str_llist_type);
414   *ret = NULL;
415
416   /* We handle the hard case in a subroutine.  */
417   expand_elt (ret, elt, kpse_normalize_path (elt));
418
419   /* Remember the directory list we just found, in case future calls are
420      made with the same ELT.  */
421   cache (elt, ret);
422
423 #ifdef KPSE_DEBUG
424   if (KPSE_DEBUG_P (KPSE_DEBUG_EXPAND))
425     {
426       DEBUGF1 ("path element %s =>", elt);
427       if (ret)
428         {
429           str_llist_elt_type *e;
430           for (e = *ret; e; e = STR_LLIST_NEXT (*e))
431             fprintf (stderr, " %s", STR_LLIST (*e));
432         }
433       putc ('\n', stderr);
434       fflush (stderr);
435     }
436 #endif /* KPSE_DEBUG */
437
438   return ret;
439 }
440 \f
441 #ifdef TEST
442
443 void
444 print_element_dirs (const_string elt)
445 {
446   str_llist_type *dirs;
447   
448   printf ("Directories of %s:\t", elt ? elt : "(nil)");
449   fflush (stdout);
450   
451   dirs = kpse_element_dirs (elt);
452   
453   if (!dirs)
454     printf ("(nil)");
455   else
456     {
457       str_llist_elt_type *dir;
458       for (dir = *dirs; dir; dir = STR_LLIST_NEXT (*dir))
459         {
460           string d = STR_LLIST (*dir);
461           printf ("%s ", *d ? d : "`'");
462         }
463     }
464   
465   putchar ('\n');
466 }
467
468 int
469 main ()
470 {
471   /* DEBUG_SET (DEBUG_STAT); */
472   /* All lists end with NULL.  */
473   print_element_dirs (NULL);    /* */
474   print_element_dirs ("");      /* ./ */
475   print_element_dirs ("/k");    /* */
476   print_element_dirs (".//");   /* ./ ./archive/ */
477   print_element_dirs (".//archive");    /* ./ ./archive/ */
478 #ifdef AMIGA
479   print_element_dirs ("TeXMF:AmiWeb2c/texmf/fonts//"); /* lots */
480   print_element_dirs ("TeXMF:AmiWeb2c/share/texmf/fonts//bakoma"); /* just one */
481   print_element_dirs ("TeXMF:AmiWeb2c/texmf/fonts//"); /* lots again [cache] */
482   print_element_dirs ("TeXMF:");        /* TeXMF: */
483   print_element_dirs ("TeXMF:/");       /* TeXMF: and all subdirs */
484 #else /* not AMIGA */
485   print_element_dirs ("/tmp/fonts//");  /* no need to stat anything */
486   print_element_dirs ("/usr/local/lib/tex/fonts//");      /* lots */
487   print_element_dirs ("/usr/local/lib/tex/fonts//times"); /* just one */
488   print_element_dirs ("/usr/local/lib/tex/fonts//"); /* lots again [cache] */
489   print_element_dirs ("~karl");         /* tilde expansion */
490   print_element_dirs ("$karl");         /* variable expansion */  
491   print_element_dirs ("~${LOGNAME}");   /* both */  
492 #endif /* not AMIGA */
493   return 0;
494 }
495
496 #endif /* TEST */
497
498
499 /*
500 Local variables:
501 test-compile-command: "gcc -g -I. -I.. -DTEST elt-dirs.c kpathsea.a"
502 End:
503 */