Commit | Line | Data |
---|---|---|
9320ab9e KB |
1 | /* |
2 | * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. | |
3 | * Copyright (c) 1988, 1989 by Adam de Boor | |
4 | * Copyright (c) 1989 by Berkeley Softworks | |
5 | * All rights reserved. | |
6 | * | |
7 | * This code is derived from software contributed to Berkeley by | |
8 | * Adam de Boor. | |
9 | * | |
87198c0c | 10 | * %sccs.include.redist.c% |
9320ab9e KB |
11 | */ |
12 | ||
13 | #ifndef lint | |
b243757d | 14 | static char sccsid[] = "@(#)dir.c 5.7 (Berkeley) %G%"; |
9320ab9e KB |
15 | #endif /* not lint */ |
16 | ||
ab950546 KB |
17 | /*- |
18 | * dir.c -- | |
19 | * Directory searching using wildcards and/or normal names... | |
20 | * Used both for source wildcarding in the Makefile and for finding | |
21 | * implicit sources. | |
22 | * | |
ab950546 KB |
23 | * The interface for this module is: |
24 | * Dir_Init Initialize the module. | |
25 | * | |
26 | * Dir_HasWildcards Returns TRUE if the name given it needs to | |
27 | * be wildcard-expanded. | |
28 | * | |
29 | * Dir_Expand Given a pattern and a path, return a Lst of names | |
30 | * which match the pattern on the search path. | |
31 | * | |
32 | * Dir_FindFile Searches for a file on a given search path. | |
33 | * If it exists, the entire path is returned. | |
34 | * Otherwise NULL is returned. | |
35 | * | |
36 | * Dir_MTime Return the modification time of a node. The file | |
37 | * is searched for along the default search path. | |
38 | * The path and mtime fields of the node are filled | |
39 | * in. | |
40 | * | |
41 | * Dir_AddDir Add a directory to a search path. | |
42 | * | |
43 | * Dir_MakeFlags Given a search path and a command flag, create | |
44 | * a string with each of the directories in the path | |
45 | * preceded by the command flag and all of them | |
46 | * separated by a space. | |
47 | * | |
48 | * Dir_Destroy Destroy an element of a search path. Frees up all | |
49 | * things that can be freed for the element as long | |
50 | * as the element is no longer referenced by any other | |
51 | * search path. | |
52 | * Dir_ClearPath Resets a search path to the empty list. | |
53 | * | |
54 | * For debugging: | |
55 | * Dir_PrintDirectories Print stats about the directory cache. | |
56 | */ | |
ab950546 KB |
57 | |
58 | #include <stdio.h> | |
59 | #include <sys/types.h> | |
60 | #include <sys/dir.h> | |
61 | #include <sys/stat.h> | |
62 | #include "make.h" | |
63 | #include "hash.h" | |
64 | ||
65 | /* | |
66 | * A search path consists of a Lst of Path structures. A Path structure | |
67 | * has in it the name of the directory and a hash table of all the files | |
68 | * in the directory. This is used to cut down on the number of system | |
69 | * calls necessary to find implicit dependents and their like. Since | |
70 | * these searches are made before any actions are taken, we need not | |
71 | * worry about the directory changing due to creation commands. If this | |
72 | * hampers the style of some makefiles, they must be changed. | |
73 | * | |
74 | * A list of all previously-read directories is kept in the | |
75 | * openDirectories Lst. This list is checked first before a directory | |
76 | * is opened. | |
77 | * | |
78 | * The need for the caching of whole directories is brought about by | |
79 | * the multi-level transformation code in suff.c, which tends to search | |
80 | * for far more files than regular make does. In the initial | |
81 | * implementation, the amount of time spent performing "stat" calls was | |
82 | * truly astronomical. The problem with hashing at the start is, | |
83 | * of course, that pmake doesn't then detect changes to these directories | |
84 | * during the course of the make. Three possibilities suggest themselves: | |
85 | * | |
86 | * 1) just use stat to test for a file's existence. As mentioned | |
87 | * above, this is very inefficient due to the number of checks | |
88 | * engendered by the multi-level transformation code. | |
89 | * 2) use readdir() and company to search the directories, keeping | |
90 | * them open between checks. I have tried this and while it | |
91 | * didn't slow down the process too much, it could severely | |
92 | * affect the amount of parallelism available as each directory | |
93 | * open would take another file descriptor out of play for | |
94 | * handling I/O for another job. Given that it is only recently | |
95 | * that UNIX OS's have taken to allowing more than 20 or 32 | |
96 | * file descriptors for a process, this doesn't seem acceptable | |
97 | * to me. | |
98 | * 3) record the mtime of the directory in the Path structure and | |
99 | * verify the directory hasn't changed since the contents were | |
100 | * hashed. This will catch the creation or deletion of files, | |
101 | * but not the updating of files. However, since it is the | |
102 | * creation and deletion that is the problem, this could be | |
103 | * a good thing to do. Unfortunately, if the directory (say ".") | |
104 | * were fairly large and changed fairly frequently, the constant | |
105 | * rehashing could seriously degrade performance. It might be | |
106 | * good in such cases to keep track of the number of rehashes | |
107 | * and if the number goes over a (small) limit, resort to using | |
108 | * stat in its place. | |
109 | * | |
110 | * An additional thing to consider is that pmake is used primarily | |
111 | * to create C programs and until recently pcc-based compilers refused | |
112 | * to allow you to specify where the resulting object file should be | |
113 | * placed. This forced all objects to be created in the current | |
114 | * directory. This isn't meant as a full excuse, just an explanation of | |
115 | * some of the reasons for the caching used here. | |
116 | * | |
117 | * One more note: the location of a target's file is only performed | |
118 | * on the downward traversal of the graph and then only for terminal | |
119 | * nodes in the graph. This could be construed as wrong in some cases, | |
120 | * but prevents inadvertent modification of files when the "installed" | |
121 | * directory for a file is provided in the search path. | |
122 | * | |
123 | * Another data structure maintained by this module is an mtime | |
124 | * cache used when the searching of cached directories fails to find | |
125 | * a file. In the past, Dir_FindFile would simply perform an access() | |
126 | * call in such a case to determine if the file could be found using | |
127 | * just the name given. When this hit, however, all that was gained | |
128 | * was the knowledge that the file existed. Given that an access() is | |
129 | * essentially a stat() without the copyout() call, and that the same | |
130 | * filesystem overhead would have to be incurred in Dir_MTime, it made | |
131 | * sense to replace the access() with a stat() and record the mtime | |
132 | * in a cache for when Dir_MTime was actually called. | |
133 | */ | |
134 | ||
135 | Lst dirSearchPath; /* main search path */ | |
136 | ||
137 | static Lst openDirectories; /* the list of all open directories */ | |
138 | ||
139 | /* | |
140 | * Variables for gathering statistics on the efficiency of the hashing | |
141 | * mechanism. | |
142 | */ | |
143 | static int hits, /* Found in directory cache */ | |
144 | misses, /* Sad, but not evil misses */ | |
145 | nearmisses, /* Found under search path */ | |
146 | bigmisses; /* Sought by itself */ | |
147 | ||
148 | typedef struct Path { | |
149 | char *name; /* Name of directory */ | |
150 | int refCount; /* Number of paths with this directory */ | |
151 | int hits; /* the number of times a file in this | |
152 | * directory has been found */ | |
153 | Hash_Table files; /* Hash table of files in directory */ | |
154 | } Path; | |
155 | ||
156 | static Path *dot; /* contents of current directory */ | |
157 | static Hash_Table mtimes; /* Results of doing a last-resort stat in | |
158 | * Dir_FindFile -- if we have to go to the | |
159 | * system to find the file, we might as well | |
160 | * have its mtime on record. XXX: If this is done | |
161 | * way early, there's a chance other rules will | |
162 | * have already updated the file, in which case | |
163 | * we'll update it again. Generally, there won't | |
164 | * be two rules to update a single file, so this | |
165 | * should be ok, but... */ | |
166 | ||
182ca07d | 167 | |
ab950546 KB |
168 | /*- |
169 | *----------------------------------------------------------------------- | |
170 | * Dir_Init -- | |
171 | * initialize things for this module | |
172 | * | |
173 | * Results: | |
174 | * none | |
175 | * | |
176 | * Side Effects: | |
177 | * some directories may be opened. | |
178 | *----------------------------------------------------------------------- | |
179 | */ | |
180 | void | |
181 | Dir_Init () | |
182 | { | |
183 | dirSearchPath = Lst_Init (FALSE); | |
184 | openDirectories = Lst_Init (FALSE); | |
fc46faab | 185 | Hash_InitTable(&mtimes, 0); |
ab950546 KB |
186 | |
187 | /* | |
188 | * Since the Path structure is placed on both openDirectories and | |
189 | * the path we give Dir_AddDir (which in this case is openDirectories), | |
190 | * we need to remove "." from openDirectories and what better time to | |
191 | * do it than when we have to fetch the thing anyway? | |
192 | */ | |
193 | Dir_AddDir (openDirectories, "."); | |
194 | dot = (Path *) Lst_DeQueue (openDirectories); | |
195 | ||
196 | /* | |
197 | * We always need to have dot around, so we increment its reference count | |
198 | * to make sure it's not destroyed. | |
199 | */ | |
200 | dot->refCount += 1; | |
201 | } | |
182ca07d | 202 | |
ab950546 KB |
203 | /*- |
204 | *----------------------------------------------------------------------- | |
205 | * DirFindName -- | |
206 | * See if the Path structure describes the same directory as the | |
207 | * given one by comparing their names. Called from Dir_AddDir via | |
208 | * Lst_Find when searching the list of open directories. | |
209 | * | |
210 | * Results: | |
211 | * 0 if it is the same. Non-zero otherwise | |
212 | * | |
213 | * Side Effects: | |
214 | * None | |
215 | *----------------------------------------------------------------------- | |
216 | */ | |
217 | static int | |
218 | DirFindName (p, dname) | |
219 | Path *p; /* Current name */ | |
220 | char *dname; /* Desired name */ | |
221 | { | |
222 | return (strcmp (p->name, dname)); | |
223 | } | |
182ca07d | 224 | |
ab950546 KB |
225 | /*- |
226 | *----------------------------------------------------------------------- | |
227 | * Dir_HasWildcards -- | |
228 | * see if the given name has any wildcard characters in it | |
229 | * | |
230 | * Results: | |
231 | * returns TRUE if the word should be expanded, FALSE otherwise | |
232 | * | |
233 | * Side Effects: | |
234 | * none | |
235 | *----------------------------------------------------------------------- | |
236 | */ | |
237 | Boolean | |
238 | Dir_HasWildcards (name) | |
239 | char *name; /* name to check */ | |
240 | { | |
241 | register char *cp; | |
242 | ||
243 | for (cp = name; *cp; cp++) { | |
244 | switch(*cp) { | |
245 | case '{': | |
246 | case '[': | |
247 | case '?': | |
248 | case '*': | |
249 | return (TRUE); | |
250 | } | |
251 | } | |
252 | return (FALSE); | |
253 | } | |
182ca07d | 254 | |
ab950546 KB |
255 | /*- |
256 | *----------------------------------------------------------------------- | |
257 | * DirMatchFiles -- | |
258 | * Given a pattern and a Path structure, see if any files | |
259 | * match the pattern and add their names to the 'expansions' list if | |
260 | * any do. This is incomplete -- it doesn't take care of patterns like | |
261 | * src/*src/*.c properly (just *.c on any of the directories), but it | |
262 | * will do for now. | |
263 | * | |
264 | * Results: | |
265 | * Always returns 0 | |
266 | * | |
267 | * Side Effects: | |
268 | * File names are added to the expansions lst. The directory will be | |
269 | * fully hashed when this is done. | |
270 | *----------------------------------------------------------------------- | |
271 | */ | |
272 | static int | |
273 | DirMatchFiles (pattern, p, expansions) | |
274 | char *pattern; /* Pattern to look for */ | |
275 | Path *p; /* Directory to search */ | |
276 | Lst expansions; /* Place to store the results */ | |
277 | { | |
278 | Hash_Search search; /* Index into the directory's table */ | |
279 | Hash_Entry *entry; /* Current entry in the table */ | |
280 | char *f; /* Current entry in the directory */ | |
281 | Boolean isDot; /* TRUE if the directory being searched is . */ | |
282 | ||
283 | isDot = (*p->name == '.' && p->name[1] == '\0'); | |
284 | ||
285 | for (entry = Hash_EnumFirst(&p->files, &search); | |
286 | entry != (Hash_Entry *)NULL; | |
287 | entry = Hash_EnumNext(&search)) | |
288 | { | |
289 | /* | |
290 | * See if the file matches the given pattern. Note we follow the UNIX | |
291 | * convention that dot files will only be found if the pattern | |
292 | * begins with a dot (note also that as a side effect of the hashing | |
293 | * scheme, .* won't match . or .. since they aren't hashed). | |
294 | */ | |
fc46faab KB |
295 | if (Str_Match(entry->name, pattern) && |
296 | ((entry->name[0] != '.') || | |
ab950546 KB |
297 | (pattern[0] == '.'))) |
298 | { | |
299 | (void)Lst_AtEnd(expansions, | |
fc46faab KB |
300 | (isDot ? strdup(entry->name) : |
301 | str_concat(p->name, entry->name, | |
ab950546 KB |
302 | STR_ADDSLASH))); |
303 | } | |
304 | } | |
305 | return (0); | |
306 | } | |
182ca07d | 307 | |
ab950546 KB |
308 | /*- |
309 | *----------------------------------------------------------------------- | |
310 | * DirExpandCurly -- | |
311 | * Expand curly braces like the C shell. Does this recursively. | |
312 | * Note the special case: if after the piece of the curly brace is | |
313 | * done there are no wildcard characters in the result, the result is | |
314 | * placed on the list WITHOUT CHECKING FOR ITS EXISTENCE. | |
315 | * | |
316 | * Results: | |
317 | * None. | |
318 | * | |
319 | * Side Effects: | |
320 | * The given list is filled with the expansions... | |
321 | * | |
322 | *----------------------------------------------------------------------- | |
323 | */ | |
324 | static void | |
325 | DirExpandCurly(word, brace, path, expansions) | |
326 | char *word; /* Entire word to expand */ | |
327 | char *brace; /* First curly brace in it */ | |
328 | Lst path; /* Search path to use */ | |
329 | Lst expansions; /* Place to store the expansions */ | |
330 | { | |
331 | char *end; /* Character after the closing brace */ | |
332 | char *cp; /* Current position in brace clause */ | |
333 | char *start; /* Start of current piece of brace clause */ | |
334 | int bracelevel; /* Number of braces we've seen. If we see a | |
335 | * right brace when this is 0, we've hit the | |
336 | * end of the clause. */ | |
337 | char *file; /* Current expansion */ | |
338 | int otherLen; /* The length of the other pieces of the | |
339 | * expansion (chars before and after the | |
340 | * clause in 'word') */ | |
341 | char *cp2; /* Pointer for checking for wildcards in | |
342 | * expansion before calling Dir_Expand */ | |
343 | ||
344 | start = brace+1; | |
345 | ||
346 | /* | |
347 | * Find the end of the brace clause first, being wary of nested brace | |
348 | * clauses. | |
349 | */ | |
350 | for (end = start, bracelevel = 0; *end != '\0'; end++) { | |
351 | if (*end == '{') { | |
352 | bracelevel++; | |
353 | } else if ((*end == '}') && (bracelevel-- == 0)) { | |
354 | break; | |
355 | } | |
356 | } | |
357 | if (*end == '\0') { | |
358 | Error("Unterminated {} clause \"%s\"", start); | |
359 | return; | |
360 | } else { | |
361 | end++; | |
362 | } | |
363 | otherLen = brace - word + strlen(end); | |
364 | ||
365 | for (cp = start; cp < end; cp++) { | |
366 | /* | |
367 | * Find the end of this piece of the clause. | |
368 | */ | |
369 | bracelevel = 0; | |
370 | while (*cp != ',') { | |
371 | if (*cp == '{') { | |
372 | bracelevel++; | |
373 | } else if ((*cp == '}') && (bracelevel-- <= 0)) { | |
374 | break; | |
375 | } | |
376 | cp++; | |
377 | } | |
378 | /* | |
379 | * Allocate room for the combination and install the three pieces. | |
380 | */ | |
b24a6c68 | 381 | file = emalloc(otherLen + cp - start + 1); |
ab950546 KB |
382 | if (brace != word) { |
383 | strncpy(file, word, brace-word); | |
384 | } | |
385 | if (cp != start) { | |
386 | strncpy(&file[brace-word], start, cp-start); | |
387 | } | |
388 | strcpy(&file[(brace-word)+(cp-start)], end); | |
389 | ||
390 | /* | |
391 | * See if the result has any wildcards in it. If we find one, call | |
392 | * Dir_Expand right away, telling it to place the result on our list | |
393 | * of expansions. | |
394 | */ | |
395 | for (cp2 = file; *cp2 != '\0'; cp2++) { | |
396 | switch(*cp2) { | |
397 | case '*': | |
398 | case '?': | |
399 | case '{': | |
400 | case '[': | |
401 | Dir_Expand(file, path, expansions); | |
402 | goto next; | |
403 | } | |
404 | } | |
405 | if (*cp2 == '\0') { | |
406 | /* | |
407 | * Hit the end w/o finding any wildcards, so stick the expansion | |
408 | * on the end of the list. | |
409 | */ | |
410 | (void)Lst_AtEnd(expansions, file); | |
411 | } else { | |
412 | next: | |
413 | free(file); | |
414 | } | |
415 | start = cp+1; | |
416 | } | |
417 | } | |
418 | ||
182ca07d | 419 | |
ab950546 KB |
420 | /*- |
421 | *----------------------------------------------------------------------- | |
422 | * DirExpandInt -- | |
423 | * Internal expand routine. Passes through the directories in the | |
424 | * path one by one, calling DirMatchFiles for each. NOTE: This still | |
425 | * doesn't handle patterns in directories... | |
426 | * | |
427 | * Results: | |
428 | * None. | |
429 | * | |
430 | * Side Effects: | |
431 | * Things are added to the expansions list. | |
432 | * | |
433 | *----------------------------------------------------------------------- | |
434 | */ | |
435 | static void | |
436 | DirExpandInt(word, path, expansions) | |
437 | char *word; /* Word to expand */ | |
438 | Lst path; /* Path on which to look */ | |
439 | Lst expansions; /* Place to store the result */ | |
440 | { | |
441 | LstNode ln; /* Current node */ | |
442 | Path *p; /* Directory in the node */ | |
443 | ||
444 | if (Lst_Open(path) == SUCCESS) { | |
445 | while ((ln = Lst_Next(path)) != NILLNODE) { | |
446 | p = (Path *)Lst_Datum(ln); | |
447 | DirMatchFiles(word, p, expansions); | |
448 | } | |
449 | Lst_Close(path); | |
450 | } | |
451 | } | |
182ca07d | 452 | |
ab950546 KB |
453 | /*- |
454 | *----------------------------------------------------------------------- | |
455 | * DirPrintWord -- | |
456 | * Print a word in the list of expansions. Callback for Dir_Expand | |
457 | * when DEBUG(DIR), via Lst_ForEach. | |
458 | * | |
459 | * Results: | |
460 | * === 0 | |
461 | * | |
462 | * Side Effects: | |
463 | * The passed word is printed, followed by a space. | |
464 | * | |
465 | *----------------------------------------------------------------------- | |
466 | */ | |
467 | static int | |
468 | DirPrintWord(word) | |
469 | char *word; | |
470 | { | |
471 | printf("%s ", word); | |
472 | ||
473 | return(0); | |
474 | } | |
182ca07d | 475 | |
ab950546 KB |
476 | /*- |
477 | *----------------------------------------------------------------------- | |
478 | * Dir_Expand -- | |
479 | * Expand the given word into a list of words by globbing it looking | |
480 | * in the directories on the given search path. | |
481 | * | |
482 | * Results: | |
483 | * A list of words consisting of the files which exist along the search | |
484 | * path matching the given pattern. | |
485 | * | |
486 | * Side Effects: | |
487 | * Directories may be opened. Who knows? | |
488 | *----------------------------------------------------------------------- | |
489 | */ | |
490 | void | |
491 | Dir_Expand (word, path, expansions) | |
492 | char *word; /* the word to expand */ | |
493 | Lst path; /* the list of directories in which to find | |
494 | * the resulting files */ | |
495 | Lst expansions; /* the list on which to place the results */ | |
496 | { | |
497 | char *cp; | |
498 | ||
499 | if (DEBUG(DIR)) { | |
500 | printf("expanding \"%s\"...", word); | |
501 | } | |
502 | ||
503 | cp = index(word, '{'); | |
504 | if (cp) { | |
505 | DirExpandCurly(word, cp, path, expansions); | |
506 | } else { | |
507 | cp = index(word, '/'); | |
508 | if (cp) { | |
509 | /* | |
510 | * The thing has a directory component -- find the first wildcard | |
511 | * in the string. | |
512 | */ | |
513 | for (cp = word; *cp; cp++) { | |
514 | if (*cp == '?' || *cp == '[' || *cp == '*' || *cp == '{') { | |
515 | break; | |
516 | } | |
517 | } | |
518 | if (*cp == '{') { | |
519 | /* | |
520 | * This one will be fun. | |
521 | */ | |
522 | DirExpandCurly(word, cp, path, expansions); | |
523 | return; | |
524 | } else if (*cp != '\0') { | |
525 | /* | |
526 | * Back up to the start of the component | |
527 | */ | |
528 | char *dirpath; | |
529 | ||
530 | while (cp > word && *cp != '/') { | |
531 | cp--; | |
532 | } | |
533 | if (cp != word) { | |
b243757d | 534 | char sc; |
ab950546 KB |
535 | /* |
536 | * If the glob isn't in the first component, try and find | |
537 | * all the components up to the one with a wildcard. | |
538 | */ | |
b243757d KB |
539 | sc = cp[1]; |
540 | cp[1] = '\0'; | |
ab950546 | 541 | dirpath = Dir_FindFile(word, path); |
b243757d | 542 | cp[1] = sc; |
ab950546 KB |
543 | /* |
544 | * dirpath is null if can't find the leading component | |
545 | * XXX: Dir_FindFile won't find internal components. | |
546 | * i.e. if the path contains ../Etc/Object and we're | |
547 | * looking for Etc, it won't be found. Ah well. | |
548 | * Probably not important. | |
549 | */ | |
550 | if (dirpath != (char *)NULL) { | |
b243757d KB |
551 | char *dp = &dirpath[strlen(dirpath) - 1]; |
552 | if (*dp == '/') | |
553 | *dp = '\0'; | |
ab950546 KB |
554 | path = Lst_Init(FALSE); |
555 | Dir_AddDir(path, dirpath); | |
556 | DirExpandInt(cp+1, path, expansions); | |
557 | Lst_Destroy(path, NOFREE); | |
558 | } | |
559 | } else { | |
560 | /* | |
561 | * Start the search from the local directory | |
562 | */ | |
563 | DirExpandInt(word, path, expansions); | |
564 | } | |
565 | } else { | |
566 | /* | |
567 | * Return the file -- this should never happen. | |
568 | */ | |
569 | DirExpandInt(word, path, expansions); | |
570 | } | |
571 | } else { | |
572 | /* | |
573 | * First the files in dot | |
574 | */ | |
575 | DirMatchFiles(word, dot, expansions); | |
576 | ||
577 | /* | |
578 | * Then the files in every other directory on the path. | |
579 | */ | |
580 | DirExpandInt(word, path, expansions); | |
581 | } | |
582 | } | |
583 | if (DEBUG(DIR)) { | |
584 | Lst_ForEach(expansions, DirPrintWord, NULL); | |
585 | putchar('\n'); | |
586 | } | |
587 | } | |
182ca07d | 588 | |
ab950546 KB |
589 | /*- |
590 | *----------------------------------------------------------------------- | |
591 | * Dir_FindFile -- | |
592 | * Find the file with the given name along the given search path. | |
593 | * | |
594 | * Results: | |
595 | * The path to the file or NULL. This path is guaranteed to be in a | |
596 | * different part of memory than name and so may be safely free'd. | |
597 | * | |
598 | * Side Effects: | |
599 | * If the file is found in a directory which is not on the path | |
600 | * already (either 'name' is absolute or it is a relative path | |
601 | * [ dir1/.../dirn/file ] which exists below one of the directories | |
602 | * already on the search path), its directory is added to the end | |
603 | * of the path on the assumption that there will be more files in | |
604 | * that directory later on. Sometimes this is true. Sometimes not. | |
605 | *----------------------------------------------------------------------- | |
606 | */ | |
607 | char * | |
608 | Dir_FindFile (name, path) | |
609 | char *name; /* the file to find */ | |
610 | Lst path; /* the Lst of directories to search */ | |
611 | { | |
612 | register char *p1; /* pointer into p->name */ | |
613 | register char *p2; /* pointer into name */ | |
614 | LstNode ln; /* a list element */ | |
615 | register char *file; /* the current filename to check */ | |
616 | register Path *p; /* current path member */ | |
617 | register char *cp; /* index of first slash, if any */ | |
618 | Boolean hasSlash; /* true if 'name' contains a / */ | |
619 | struct stat stb; /* Buffer for stat, if necessary */ | |
620 | Hash_Entry *entry; /* Entry for mtimes table */ | |
621 | ||
622 | /* | |
623 | * Find the final component of the name and note whether it has a | |
624 | * slash in it (the name, I mean) | |
625 | */ | |
626 | cp = rindex (name, '/'); | |
627 | if (cp) { | |
628 | hasSlash = TRUE; | |
629 | cp += 1; | |
630 | } else { | |
631 | hasSlash = FALSE; | |
632 | cp = name; | |
633 | } | |
634 | ||
635 | if (DEBUG(DIR)) { | |
636 | printf("Searching for %s...", name); | |
637 | } | |
638 | /* | |
639 | * No matter what, we always look for the file in the current directory | |
640 | * before anywhere else and we *do not* add the ./ to it if it exists. | |
641 | * This is so there are no conflicts between what the user specifies | |
642 | * (fish.c) and what pmake finds (./fish.c). | |
643 | */ | |
644 | if ((!hasSlash || (cp - name == 2 && *name == '.')) && | |
fc46faab | 645 | (Hash_FindEntry (&dot->files, cp) != (Hash_Entry *)NULL)) { |
ab950546 KB |
646 | if (DEBUG(DIR)) { |
647 | printf("in '.'\n"); | |
648 | } | |
649 | hits += 1; | |
650 | dot->hits += 1; | |
182ca07d | 651 | return (strdup (name)); |
ab950546 KB |
652 | } |
653 | ||
654 | if (Lst_Open (path) == FAILURE) { | |
655 | if (DEBUG(DIR)) { | |
656 | printf("couldn't open path, file not found\n"); | |
657 | } | |
658 | misses += 1; | |
659 | return ((char *) NULL); | |
660 | } | |
661 | ||
662 | /* | |
663 | * We look through all the directories on the path seeking one which | |
664 | * contains the final component of the given name and whose final | |
665 | * component(s) match the name's initial component(s). If such a beast | |
666 | * is found, we concatenate the directory name and the final component | |
667 | * and return the resulting string. If we don't find any such thing, | |
668 | * we go on to phase two... | |
669 | */ | |
670 | while ((ln = Lst_Next (path)) != NILLNODE) { | |
671 | p = (Path *) Lst_Datum (ln); | |
672 | if (DEBUG(DIR)) { | |
673 | printf("%s...", p->name); | |
674 | } | |
fc46faab | 675 | if (Hash_FindEntry (&p->files, cp) != (Hash_Entry *)NULL) { |
ab950546 KB |
676 | if (DEBUG(DIR)) { |
677 | printf("here..."); | |
678 | } | |
679 | if (hasSlash) { | |
680 | /* | |
681 | * If the name had a slash, its initial components and p's | |
682 | * final components must match. This is false if a mismatch | |
683 | * is encountered before all of the initial components | |
684 | * have been checked (p2 > name at the end of the loop), or | |
685 | * we matched only part of one of the components of p | |
686 | * along with all the rest of them (*p1 != '/'). | |
687 | */ | |
688 | p1 = p->name + strlen (p->name) - 1; | |
689 | p2 = cp - 2; | |
690 | while (p2 >= name && *p1 == *p2) { | |
691 | p1 -= 1; p2 -= 1; | |
692 | } | |
693 | if (p2 >= name || (p1 >= p->name && *p1 != '/')) { | |
694 | if (DEBUG(DIR)) { | |
695 | printf("component mismatch -- continuing..."); | |
696 | } | |
697 | continue; | |
698 | } | |
699 | } | |
b24a6c68 | 700 | file = str_concat (p->name, cp, STR_ADDSLASH); |
ab950546 KB |
701 | if (DEBUG(DIR)) { |
702 | printf("returning %s\n", file); | |
703 | } | |
704 | Lst_Close (path); | |
705 | p->hits += 1; | |
706 | hits += 1; | |
707 | return (file); | |
708 | } else if (hasSlash) { | |
709 | /* | |
710 | * If the file has a leading path component and that component | |
711 | * exactly matches the entire name of the current search | |
712 | * directory, we assume the file doesn't exist and return NULL. | |
713 | */ | |
714 | for (p1 = p->name, p2 = name; *p1 && *p1 == *p2; p1++, p2++) { | |
715 | continue; | |
716 | } | |
717 | if (*p1 == '\0' && p2 == cp - 1) { | |
718 | if (DEBUG(DIR)) { | |
719 | printf("must be here but isn't -- returing NULL\n"); | |
720 | } | |
721 | Lst_Close (path); | |
722 | return ((char *) NULL); | |
723 | } | |
724 | } | |
725 | } | |
726 | ||
727 | /* | |
728 | * We didn't find the file on any existing members of the directory. | |
729 | * If the name doesn't contain a slash, that means it doesn't exist. | |
730 | * If it *does* contain a slash, however, there is still hope: it | |
731 | * could be in a subdirectory of one of the members of the search | |
732 | * path. (eg. /usr/include and sys/types.h. The above search would | |
733 | * fail to turn up types.h in /usr/include, but it *is* in | |
734 | * /usr/include/sys/types.h) If we find such a beast, we assume there | |
735 | * will be more (what else can we assume?) and add all but the last | |
736 | * component of the resulting name onto the search path (at the | |
737 | * end). This phase is only performed if the file is *not* absolute. | |
738 | */ | |
739 | if (!hasSlash) { | |
740 | if (DEBUG(DIR)) { | |
741 | printf("failed.\n"); | |
742 | } | |
743 | misses += 1; | |
744 | return ((char *) NULL); | |
745 | } | |
746 | ||
747 | if (*name != '/') { | |
748 | Boolean checkedDot = FALSE; | |
749 | ||
750 | if (DEBUG(DIR)) { | |
751 | printf("failed. Trying subdirectories..."); | |
752 | } | |
753 | (void) Lst_Open (path); | |
754 | while ((ln = Lst_Next (path)) != NILLNODE) { | |
755 | p = (Path *) Lst_Datum (ln); | |
756 | if (p != dot) { | |
b24a6c68 | 757 | file = str_concat (p->name, name, STR_ADDSLASH); |
ab950546 KB |
758 | } else { |
759 | /* | |
760 | * Checking in dot -- DON'T put a leading ./ on the thing. | |
761 | */ | |
182ca07d | 762 | file = strdup(name); |
ab950546 KB |
763 | checkedDot = TRUE; |
764 | } | |
765 | if (DEBUG(DIR)) { | |
766 | printf("checking %s...", file); | |
767 | } | |
768 | ||
769 | ||
770 | if (stat (file, &stb) == 0) { | |
771 | if (DEBUG(DIR)) { | |
772 | printf("got it.\n"); | |
773 | } | |
774 | ||
775 | Lst_Close (path); | |
776 | ||
777 | /* | |
778 | * We've found another directory to search. We know there's | |
779 | * a slash in 'file' because we put one there. We nuke it after | |
780 | * finding it and call Dir_AddDir to add this new directory | |
781 | * onto the existing search path. Once that's done, we restore | |
782 | * the slash and triumphantly return the file name, knowing | |
783 | * that should a file in this directory every be referenced | |
784 | * again in such a manner, we will find it without having to do | |
785 | * numerous numbers of access calls. Hurrah! | |
786 | */ | |
787 | cp = rindex (file, '/'); | |
788 | *cp = '\0'; | |
789 | Dir_AddDir (path, file); | |
790 | *cp = '/'; | |
791 | ||
792 | /* | |
793 | * Save the modification time so if it's needed, we don't have | |
794 | * to fetch it again. | |
795 | */ | |
796 | if (DEBUG(DIR)) { | |
797 | printf("Caching %s for %s\n", Targ_FmtTime(stb.st_mtime), | |
798 | file); | |
799 | } | |
800 | entry = Hash_CreateEntry(&mtimes, (ClientData)file, | |
801 | (Boolean *)NULL); | |
802 | Hash_SetValue(entry, stb.st_mtime); | |
803 | nearmisses += 1; | |
804 | return (file); | |
805 | } else { | |
806 | free (file); | |
807 | } | |
808 | } | |
809 | ||
810 | if (DEBUG(DIR)) { | |
811 | printf("failed. "); | |
812 | } | |
813 | Lst_Close (path); | |
814 | ||
815 | if (checkedDot) { | |
816 | /* | |
817 | * Already checked by the given name, since . was in the path, | |
818 | * so no point in proceeding... | |
819 | */ | |
820 | if (DEBUG(DIR)) { | |
821 | printf("Checked . already, returning NULL\n"); | |
822 | } | |
823 | return(NULL); | |
824 | } | |
825 | } | |
826 | ||
827 | /* | |
828 | * Didn't find it that way, either. Sigh. Phase 3. Add its directory | |
829 | * onto the search path in any case, just in case, then look for the | |
830 | * thing in the hash table. If we find it, grand. We return a new | |
831 | * copy of the name. Otherwise we sadly return a NULL pointer. Sigh. | |
832 | * Note that if the directory holding the file doesn't exist, this will | |
833 | * do an extra search of the final directory on the path. Unless something | |
834 | * weird happens, this search won't succeed and life will be groovy. | |
835 | * | |
836 | * Sigh. We cannot add the directory onto the search path because | |
837 | * of this amusing case: | |
838 | * $(INSTALLDIR)/$(FILE): $(FILE) | |
839 | * | |
840 | * $(FILE) exists in $(INSTALLDIR) but not in the current one. | |
841 | * When searching for $(FILE), we will find it in $(INSTALLDIR) | |
842 | * b/c we added it here. This is not good... | |
843 | */ | |
844 | #ifdef notdef | |
845 | cp[-1] = '\0'; | |
846 | Dir_AddDir (path, name); | |
847 | cp[-1] = '/'; | |
848 | ||
849 | bigmisses += 1; | |
850 | ln = Lst_Last (path); | |
851 | if (ln == NILLNODE) { | |
852 | return ((char *) NULL); | |
853 | } else { | |
854 | p = (Path *) Lst_Datum (ln); | |
855 | } | |
856 | ||
fc46faab | 857 | if (Hash_FindEntry (&p->files, cp) != (Hash_Entry *)NULL) { |
182ca07d | 858 | return (strdup (name)); |
ab950546 KB |
859 | } else { |
860 | return ((char *) NULL); | |
861 | } | |
862 | #else /* !notdef */ | |
863 | if (DEBUG(DIR)) { | |
864 | printf("Looking for \"%s\"...", name); | |
865 | } | |
866 | ||
867 | bigmisses += 1; | |
868 | entry = Hash_FindEntry(&mtimes, name); | |
869 | if (entry != (Hash_Entry *)NULL) { | |
870 | if (DEBUG(DIR)) { | |
871 | printf("got it (in mtime cache)\n"); | |
872 | } | |
182ca07d | 873 | return(strdup(name)); |
ab950546 KB |
874 | } else if (stat (name, &stb) == 0) { |
875 | entry = Hash_CreateEntry(&mtimes, name, (Boolean *)NULL); | |
876 | if (DEBUG(DIR)) { | |
877 | printf("Caching %s for %s\n", Targ_FmtTime(stb.st_mtime), | |
878 | name); | |
879 | } | |
880 | Hash_SetValue(entry, stb.st_mtime); | |
182ca07d | 881 | return (strdup (name)); |
ab950546 KB |
882 | } else { |
883 | if (DEBUG(DIR)) { | |
884 | printf("failed. Returning NULL\n"); | |
885 | } | |
886 | return ((char *)NULL); | |
887 | } | |
888 | #endif /* notdef */ | |
889 | } | |
182ca07d | 890 | |
ab950546 KB |
891 | /*- |
892 | *----------------------------------------------------------------------- | |
893 | * Dir_MTime -- | |
894 | * Find the modification time of the file described by gn along the | |
895 | * search path dirSearchPath. | |
896 | * | |
897 | * Results: | |
898 | * The modification time or 0 if it doesn't exist | |
899 | * | |
900 | * Side Effects: | |
901 | * The modification time is placed in the node's mtime slot. | |
902 | * If the node didn't have a path entry before, and Dir_FindFile | |
903 | * found one for it, the full name is placed in the path slot. | |
904 | *----------------------------------------------------------------------- | |
905 | */ | |
906 | int | |
907 | Dir_MTime (gn) | |
908 | GNode *gn; /* the file whose modification time is | |
909 | * desired */ | |
910 | { | |
911 | char *fullName; /* the full pathname of name */ | |
912 | struct stat stb; /* buffer for finding the mod time */ | |
913 | Hash_Entry *entry; | |
914 | ||
915 | if (gn->type & OP_ARCHV) { | |
916 | return Arch_MTime (gn); | |
917 | } else if (gn->path == (char *)NULL) { | |
918 | fullName = Dir_FindFile (gn->name, dirSearchPath); | |
919 | } else { | |
920 | fullName = gn->path; | |
921 | } | |
922 | ||
923 | if (fullName == (char *)NULL) { | |
924 | fullName = gn->name; | |
925 | } | |
926 | ||
927 | entry = Hash_FindEntry(&mtimes, fullName); | |
928 | if (entry != (Hash_Entry *)NULL) { | |
929 | /* | |
930 | * Only do this once -- the second time folks are checking to | |
931 | * see if the file was actually updated, so we need to actually go | |
932 | * to the file system. | |
933 | */ | |
934 | if (DEBUG(DIR)) { | |
935 | printf("Using cached time %s for %s\n", | |
936 | Targ_FmtTime(Hash_GetValue(entry)), fullName); | |
937 | } | |
938 | stb.st_mtime = (time_t)Hash_GetValue(entry); | |
939 | Hash_DeleteEntry(&mtimes, entry); | |
940 | } else if (stat (fullName, &stb) < 0) { | |
941 | if (gn->type & OP_MEMBER) { | |
942 | return Arch_MemMTime (gn); | |
943 | } else { | |
944 | stb.st_mtime = 0; | |
945 | } | |
946 | } | |
947 | if (fullName && gn->path == (char *)NULL) { | |
948 | gn->path = fullName; | |
949 | } | |
950 | ||
951 | gn->mtime = stb.st_mtime; | |
952 | return (gn->mtime); | |
953 | } | |
182ca07d | 954 | |
ab950546 KB |
955 | /*- |
956 | *----------------------------------------------------------------------- | |
957 | * Dir_AddDir -- | |
958 | * Add the given name to the end of the given path. The order of | |
959 | * the arguments is backwards so ParseDoDependency can do a | |
960 | * Lst_ForEach of its list of paths... | |
961 | * | |
962 | * Results: | |
963 | * none | |
964 | * | |
965 | * Side Effects: | |
966 | * A structure is added to the list and the directory is | |
967 | * read and hashed. | |
968 | *----------------------------------------------------------------------- | |
969 | */ | |
970 | void | |
971 | Dir_AddDir (path, name) | |
972 | Lst path; /* the path to which the directory should be | |
973 | * added */ | |
974 | char *name; /* the name of the directory to add */ | |
975 | { | |
976 | LstNode ln; /* node in case Path structure is found */ | |
977 | register Path *p; /* pointer to new Path structure */ | |
978 | DIR *d; /* for reading directory */ | |
979 | register struct direct *dp; /* entry in directory */ | |
980 | Hash_Entry *he; | |
981 | char *fName; | |
982 | ||
983 | ln = Lst_Find (openDirectories, (ClientData)name, DirFindName); | |
984 | if (ln != NILLNODE) { | |
985 | p = (Path *)Lst_Datum (ln); | |
986 | if (Lst_Member(path, (ClientData)p) == NILLNODE) { | |
987 | p->refCount += 1; | |
988 | (void)Lst_AtEnd (path, (ClientData)p); | |
989 | } | |
990 | } else { | |
991 | if (DEBUG(DIR)) { | |
992 | printf("Caching %s...", name); | |
993 | fflush(stdout); | |
994 | } | |
995 | ||
996 | if ((d = opendir (name)) != (DIR *) NULL) { | |
b24a6c68 | 997 | p = (Path *) emalloc (sizeof (Path)); |
182ca07d | 998 | p->name = strdup (name); |
ab950546 KB |
999 | p->hits = 0; |
1000 | p->refCount = 1; | |
fc46faab | 1001 | Hash_InitTable (&p->files, -1); |
ab950546 KB |
1002 | |
1003 | /* | |
1004 | * Skip the first two entries -- these will *always* be . and .. | |
1005 | */ | |
1006 | (void)readdir(d); | |
1007 | (void)readdir(d); | |
1008 | ||
1009 | while ((dp = readdir (d)) != (struct direct *) NULL) { | |
1010 | #ifdef sun | |
1011 | /* | |
1012 | * The sun directory library doesn't check for a 0 inode | |
1013 | * (0-inode slots just take up space), so we have to do | |
1014 | * it ourselves. | |
1015 | */ | |
1016 | if (dp->d_fileno == 0) { | |
1017 | continue; | |
1018 | } | |
1019 | #endif sun | |
1020 | (void)Hash_CreateEntry(&p->files, dp->d_name, (Boolean *)NULL); | |
1021 | } | |
1022 | (void) closedir (d); | |
1023 | (void)Lst_AtEnd (openDirectories, (ClientData)p); | |
1024 | (void)Lst_AtEnd (path, (ClientData)p); | |
1025 | } | |
1026 | if (DEBUG(DIR)) { | |
1027 | printf("done\n"); | |
1028 | } | |
1029 | } | |
1030 | } | |
182ca07d | 1031 | |
ab950546 KB |
1032 | /*- |
1033 | *----------------------------------------------------------------------- | |
1034 | * Dir_CopyDir -- | |
1035 | * Callback function for duplicating a search path via Lst_Duplicate. | |
1036 | * Ups the reference count for the directory. | |
1037 | * | |
1038 | * Results: | |
1039 | * Returns the Path it was given. | |
1040 | * | |
1041 | * Side Effects: | |
1042 | * The refCount of the path is incremented. | |
1043 | * | |
1044 | *----------------------------------------------------------------------- | |
1045 | */ | |
1046 | ClientData | |
1047 | Dir_CopyDir(p) | |
1048 | Path *p; /* Directory descriptor to copy */ | |
1049 | { | |
1050 | p->refCount += 1; | |
1051 | ||
1052 | return ((ClientData)p); | |
1053 | } | |
182ca07d | 1054 | |
ab950546 KB |
1055 | /*- |
1056 | *----------------------------------------------------------------------- | |
1057 | * Dir_MakeFlags -- | |
1058 | * Make a string by taking all the directories in the given search | |
1059 | * path and preceding them by the given flag. Used by the suffix | |
1060 | * module to create variables for compilers based on suffix search | |
1061 | * paths. | |
1062 | * | |
1063 | * Results: | |
1064 | * The string mentioned above. Note that there is no space between | |
1065 | * the given flag and each directory. The empty string is returned if | |
1066 | * Things don't go well. | |
1067 | * | |
1068 | * Side Effects: | |
1069 | * None | |
1070 | *----------------------------------------------------------------------- | |
1071 | */ | |
1072 | char * | |
1073 | Dir_MakeFlags (flag, path) | |
1074 | char *flag; /* flag which should precede each directory */ | |
1075 | Lst path; /* list of directories */ | |
1076 | { | |
1077 | char *str; /* the string which will be returned */ | |
1078 | char *tstr; /* the current directory preceded by 'flag' */ | |
1079 | LstNode ln; /* the node of the current directory */ | |
1080 | Path *p; /* the structure describing the current directory */ | |
1081 | ||
182ca07d | 1082 | str = strdup (""); |
ab950546 KB |
1083 | |
1084 | if (Lst_Open (path) == SUCCESS) { | |
1085 | while ((ln = Lst_Next (path)) != NILLNODE) { | |
1086 | p = (Path *) Lst_Datum (ln); | |
b24a6c68 KB |
1087 | tstr = str_concat (flag, p->name, 0); |
1088 | str = str_concat (str, tstr, STR_ADDSPACE | STR_DOFREE); | |
ab950546 KB |
1089 | } |
1090 | Lst_Close (path); | |
1091 | } | |
1092 | ||
1093 | return (str); | |
1094 | } | |
182ca07d | 1095 | |
ab950546 KB |
1096 | /*- |
1097 | *----------------------------------------------------------------------- | |
1098 | * Dir_Destroy -- | |
1099 | * Nuke a directory descriptor, if possible. Callback procedure | |
1100 | * for the suffixes module when destroying a search path. | |
1101 | * | |
1102 | * Results: | |
1103 | * None. | |
1104 | * | |
1105 | * Side Effects: | |
1106 | * If no other path references this directory (refCount == 0), | |
1107 | * the Path and all its data are freed. | |
1108 | * | |
1109 | *----------------------------------------------------------------------- | |
1110 | */ | |
1111 | void | |
1112 | Dir_Destroy (p) | |
1113 | Path *p; /* The directory descriptor to nuke */ | |
1114 | { | |
1115 | Hash_Search thing1; | |
1116 | Hash_Entry *thing2; | |
1117 | ||
1118 | p->refCount -= 1; | |
1119 | ||
1120 | if (p->refCount == 0) { | |
1121 | LstNode ln; | |
1122 | ||
1123 | ln = Lst_Member (openDirectories, (ClientData)p); | |
1124 | (void) Lst_Remove (openDirectories, ln); | |
1125 | ||
1126 | Hash_DeleteTable (&p->files); | |
1127 | free((Address)p->name); | |
1128 | free((Address)p); | |
1129 | } | |
1130 | } | |
182ca07d | 1131 | |
ab950546 KB |
1132 | /*- |
1133 | *----------------------------------------------------------------------- | |
1134 | * Dir_ClearPath -- | |
1135 | * Clear out all elements of the given search path. This is different | |
1136 | * from destroying the list, notice. | |
1137 | * | |
1138 | * Results: | |
1139 | * None. | |
1140 | * | |
1141 | * Side Effects: | |
1142 | * The path is set to the empty list. | |
1143 | * | |
1144 | *----------------------------------------------------------------------- | |
1145 | */ | |
1146 | void | |
1147 | Dir_ClearPath(path) | |
1148 | Lst path; /* Path to clear */ | |
1149 | { | |
1150 | Path *p; | |
1151 | while (!Lst_IsEmpty(path)) { | |
1152 | p = (Path *)Lst_DeQueue(path); | |
1153 | Dir_Destroy(p); | |
1154 | } | |
1155 | } | |
1156 | ||
182ca07d | 1157 | |
ab950546 KB |
1158 | /*- |
1159 | *----------------------------------------------------------------------- | |
1160 | * Dir_Concat -- | |
1161 | * Concatenate two paths, adding the second to the end of the first. | |
1162 | * Makes sure to avoid duplicates. | |
1163 | * | |
1164 | * Results: | |
1165 | * None | |
1166 | * | |
1167 | * Side Effects: | |
1168 | * Reference counts for added dirs are upped. | |
1169 | * | |
1170 | *----------------------------------------------------------------------- | |
1171 | */ | |
1172 | void | |
1173 | Dir_Concat(path1, path2) | |
1174 | Lst path1; /* Dest */ | |
1175 | Lst path2; /* Source */ | |
1176 | { | |
1177 | LstNode ln; | |
1178 | Path *p; | |
1179 | ||
1180 | for (ln = Lst_First(path2); ln != NILLNODE; ln = Lst_Succ(ln)) { | |
1181 | p = (Path *)Lst_Datum(ln); | |
1182 | if (Lst_Member(path1, (ClientData)p) == NILLNODE) { | |
1183 | p->refCount += 1; | |
1184 | (void)Lst_AtEnd(path1, (ClientData)p); | |
1185 | } | |
1186 | } | |
1187 | } | |
1188 | ||
1189 | /********** DEBUG INFO **********/ | |
1190 | Dir_PrintDirectories() | |
1191 | { | |
1192 | LstNode ln; | |
1193 | Path *p; | |
1194 | ||
1195 | printf ("#*** Directory Cache:\n"); | |
1196 | printf ("# Stats: %d hits %d misses %d near misses %d losers (%d%%)\n", | |
1197 | hits, misses, nearmisses, bigmisses, | |
1198 | (hits+bigmisses+nearmisses ? | |
1199 | hits * 100 / (hits + bigmisses + nearmisses) : 0)); | |
1200 | printf ("# %-20s referenced\thits\n", "directory"); | |
1201 | if (Lst_Open (openDirectories) == SUCCESS) { | |
1202 | while ((ln = Lst_Next (openDirectories)) != NILLNODE) { | |
1203 | p = (Path *) Lst_Datum (ln); | |
1204 | printf ("# %-20s %10d\t%4d\n", p->name, p->refCount, p->hits); | |
1205 | } | |
1206 | Lst_Close (openDirectories); | |
1207 | } | |
1208 | } | |
1209 | ||
1210 | static int DirPrintDir (p) Path *p; { printf ("%s ", p->name); return (0); } | |
1211 | ||
1212 | Dir_PrintPath (path) | |
1213 | Lst path; | |
1214 | { | |
1215 | Lst_ForEach (path, DirPrintDir, (ClientData)0); | |
1216 | } |