Annotation of 43BSDReno/usr.bin/make/dir.c, revision 1.1.1.1

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

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.