1 /* elt-dirs.C: Translate a path element to its corresponding director{y,ies}.
3 Copyright 1993, 1994, 1995, 1996, 1997, 2008 Karl Berry.
4 Copyright 1997, 1998, 1999, 2000, 2005 Olaf Weber.
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.
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.
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/>. */
19 #include <kpathsea/config.h>
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>
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. */
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. */
35 dir_list_add P2C(str_llist_type *, l, const_string, dir)
37 char last_char = dir[strlen (dir) - 1];
39 = IS_DIR_SEP (last_char) || IS_DEVICE_SEP (last_char)
41 : concat (dir, DIR_SEP_STRING);
43 str_llist_add (l, saved_dir);
47 /* If DIR is a directory, add it to the list L. */
50 checked_dir_list_add P2C(str_llist_type *, l, const_string, dir)
53 dir_list_add (l, dir);
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. */
65 str_llist_type *value;
68 static cache_entry *the_cache = NULL;
69 static unsigned cache_length = 0;
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. */
79 cache P2C(const_string, key, str_llist_type *, value)
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;
88 /* To retrieve, just check the list in order. */
90 static str_llist_type *
91 cached P1C(const_string, key)
95 for (p = 0; p < cache_length; p++)
97 if (FILESTRCASEEQ (the_cache[p].key, key))
98 return the_cache[p].value;
104 /* Handle the magic path constructs. */
106 /* Declare recursively called routine. */
107 static void expand_elt P3H(str_llist_type *, const_string, unsigned);
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
116 /* Shared across recursive calls, it acts like a stack. */
117 static char dirname[MAX_PATH];
121 do_subdir P4C(str_llist_type *, str_list_ptr, const_string, elt,
122 unsigned, elt_length, const_string, post)
125 WIN32_FIND_DATA find_file_data;
132 #endif /* not WIN32 */
135 /* Some old compilers don't allow aggregate initialization. */
136 name = fn_copy0 (elt, elt_length);
138 assert (IS_DIR_SEP (elt[elt_length - 1])
139 || IS_DEVICE_SEP (elt[elt_length - 1]));
142 strcpy(dirname, FN_STRING(name));
143 strcat(dirname, "/*.*"); /* "*.*" or "*" -- seems equivalent. */
144 hnd = FindFirstFile(dirname, &find_file_data);
146 if (hnd == INVALID_HANDLE_VALUE) {
151 /* Include top level before subdirectories, if nothing to match. */
153 dir_list_add (str_list_ptr, FN_STRING (name));
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);
164 if (find_file_data.cFileName[0] != '.') {
167 /* Construct the potential subdirectory name. */
168 fn_str_grow (&name, find_file_data.cFileName);
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);
175 if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
176 unsigned potential_len = FN_LENGTH (name);
177 /* in any case, compute the leafness */
180 /* It's a directory, so append the separator. */
181 fn_str_grow (&name, DIR_SEP_STRING);
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);
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. */
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);
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));
208 fn_shrink_to (&name, elt_length);
210 proceed = FindNextFile (hnd, &find_file_data);
212 /* Update the leafness of name. */
213 dir_links(FN_STRING(name), nlinks);
217 #else /* not WIN32 */
219 /* If we can't open it, quit. */
220 dir = opendir (FN_STRING (name));
227 /* Include top level before subdirectories, if nothing to match. */
229 dir_list_add (str_list_ptr, FN_STRING (name));
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);
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] != '.')
246 /* Construct the potential subdirectory name. */
247 fn_str_grow (&name, e->d_name);
249 /* If we can't stat it, or if it isn't a directory, continue. */
250 links = dir_links (FN_STRING (name), 0);
254 unsigned potential_len = FN_LENGTH (name);
256 /* It's a directory, so append the separator. */
257 fn_str_grow (&name, DIR_SEP_STRING);
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);
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.
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
282 #ifdef ST_NLINK_TRICK
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. */
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
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));
302 /* Remove the directory entry we just checked from `name'. */
303 fn_shrink_to (&name, elt_length);
309 #endif /* not WIN32 */
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. */
318 expand_elt P3C(str_llist_type *, str_list_ptr, const_string, elt,
321 const_string dir = elt + start, post;
325 if (IS_DIR_SEP (*dir))
327 /* If two or more consecutive /'s, find subdirectories. */
328 if (IS_DIR_SEP (dir[1]))
330 for (post = dir + 1; IS_DIR_SEP (*post); post++) ;
331 do_subdir (str_list_ptr, elt, dir - elt + 1, post);
335 /* No special stuff at this slash. Keep going. */
341 /* When we reach the end of ELT, it will be a normal filename. */
342 checked_dir_list_add (str_list_ptr, elt);
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
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
359 - If possible, we merely skip multiple leading slashes to prevent
360 expanding from the root of a UNIX filesystem tree. */
363 kpse_normalize_path P1C(string, elt)
368 if (NAME_BEGINS_WITH_DEVICE(elt)) {
369 if (*elt >= 'A' && *elt <= 'Z')
371 for (i = 2; IS_DIR_SEP(elt[i]); ++i)
374 memmove (elt+3, elt+i, strlen(elt+i) + 1);
377 } else if (IS_UNC_NAME(elt)) {
378 for (ret = 2; elt[ret] && !IS_DIR_SEP(elt[ret]); ++ret)
380 for (i = ret; elt[i] && IS_DIR_SEP(elt[i]); ++i)
383 memmove (elt+ret+1, elt+i, strlen(elt+i) + 1);
386 for (ret = 0; IS_DIR_SEP(elt[ret]); ++ret)
390 if (KPSE_DEBUG_P (KPSE_DEBUG_STAT) && ret != 1)
391 DEBUGF2 ("kpse_normalize_path (%s) => %u\n", elt, ret);
396 /* Here is the entry point. Returns directory list for ELT. */
399 kpse_element_dirs P1C(string, elt)
403 /* If given nothing, return nothing. */
407 /* If we've already cached the answer for ELT, return it. */
412 /* We're going to have a real directory list to return. */
413 ret = XTALLOC1 (str_llist_type);
416 /* We handle the hard case in a subroutine. */
417 expand_elt (ret, elt, kpse_normalize_path (elt));
419 /* Remember the directory list we just found, in case future calls are
420 made with the same ELT. */
424 if (KPSE_DEBUG_P (KPSE_DEBUG_EXPAND))
426 DEBUGF1 ("path element %s =>", elt);
429 str_llist_elt_type *e;
430 for (e = *ret; e; e = STR_LLIST_NEXT (*e))
431 fprintf (stderr, " %s", STR_LLIST (*e));
436 #endif /* KPSE_DEBUG */
444 print_element_dirs (const_string elt)
446 str_llist_type *dirs;
448 printf ("Directories of %s:\t", elt ? elt : "(nil)");
451 dirs = kpse_element_dirs (elt);
457 str_llist_elt_type *dir;
458 for (dir = *dirs; dir; dir = STR_LLIST_NEXT (*dir))
460 string d = STR_LLIST (*dir);
461 printf ("%s ", *d ? d : "`'");
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/ */
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 */
501 test-compile-command: "gcc -g -I. -I.. -DTEST elt-dirs.c kpathsea.a"