Annotation of 43BSDReno/usr.bin/make/compat.c, revision 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 provided
        !            11:  * that: (1) source distributions retain this entire copyright notice and
        !            12:  * comment, and (2) distributions including binaries display the following
        !            13:  * acknowledgement:  ``This product includes software developed by the
        !            14:  * University of California, Berkeley and its contributors'' in the
        !            15:  * documentation or other materials provided with the distribution and in
        !            16:  * all advertising materials mentioning features or use of this software.
        !            17:  * Neither the name of the University nor the names of its contributors may
        !            18:  * be used to endorse or promote products derived from this software without
        !            19:  * specific prior written permission.
        !            20:  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
        !            21:  * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
        !            22:  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
        !            23:  */
        !            24: 
        !            25: #ifndef lint
        !            26: static char sccsid[] = "@(#)compat.c   5.6 (Berkeley) 6/1/90";
        !            27: #endif /* not lint */
        !            28: 
        !            29: /*-
        !            30:  * compat.c --
        !            31:  *     The routines in this file implement the full-compatibility
        !            32:  *     mode of PMake. Most of the special functionality of PMake
        !            33:  *     is available in this mode. Things not supported:
        !            34:  *         - different shells.
        !            35:  *         - friendly variable substitution.
        !            36:  *
        !            37:  * Interface:
        !            38:  *     Compat_Run          Initialize things for this module and recreate
        !            39:  *                         thems as need creatin'
        !            40:  */
        !            41: 
        !            42: #include    <stdio.h>
        !            43: #include    <sys/types.h>
        !            44: #include    <sys/signal.h>
        !            45: #include    <sys/wait.h>
        !            46: #include    <sys/errno.h>
        !            47: #include    <ctype.h>
        !            48: #include    "make.h"
        !            49: extern int errno;
        !            50: 
        !            51: /*
        !            52:  * The following array is used to make a fast determination of which
        !            53:  * characters are interpreted specially by the shell.  If a command
        !            54:  * contains any of these characters, it is executed by the shell, not
        !            55:  * directly by us.
        !            56:  */
        !            57: 
        !            58: static char        meta[256];
        !            59: 
        !            60: static GNode       *curTarg = NILGNODE;
        !            61: static GNode       *ENDNode;
        !            62: static int         CompatRunCommand();
        !            63: 
        !            64: /*-
        !            65:  *-----------------------------------------------------------------------
        !            66:  * CompatInterrupt --
        !            67:  *     Interrupt the creation of the current target and remove it if
        !            68:  *     it ain't precious.
        !            69:  *
        !            70:  * Results:
        !            71:  *     None.
        !            72:  *
        !            73:  * Side Effects:
        !            74:  *     The target is removed and the process exits. If .INTERRUPT exists,
        !            75:  *     its commands are run first WITH INTERRUPTS IGNORED..
        !            76:  *
        !            77:  *-----------------------------------------------------------------------
        !            78:  */
        !            79: static int
        !            80: CompatInterrupt (signo)
        !            81:     int            signo;
        !            82: {
        !            83:     GNode   *gn;
        !            84:     
        !            85:     if ((curTarg != NILGNODE) && !Targ_Precious (curTarg)) {
        !            86:        char      *file = Var_Value (TARGET, curTarg);
        !            87: 
        !            88:        if (unlink (file) == SUCCESS) {
        !            89:            printf ("*** %s removed\n", file);
        !            90:        }
        !            91: 
        !            92:        /*
        !            93:         * Run .INTERRUPT only if hit with interrupt signal
        !            94:         */
        !            95:        if (signo == SIGINT) {
        !            96:            gn = Targ_FindNode(".INTERRUPT", TARG_NOCREATE);
        !            97:            if (gn != NILGNODE) {
        !            98:                Lst_ForEach(gn->commands, CompatRunCommand, (ClientData)gn);
        !            99:            }
        !           100:        }
        !           101:     }
        !           102:     exit (0);
        !           103: }
        !           104: 
        !           105: /*-
        !           106:  *-----------------------------------------------------------------------
        !           107:  * CompatRunCommand --
        !           108:  *     Execute the next command for a target. If the command returns an
        !           109:  *     error, the node's made field is set to ERROR and creation stops.
        !           110:  *
        !           111:  * Results:
        !           112:  *     0 if the command succeeded, 1 if an error occurred.
        !           113:  *
        !           114:  * Side Effects:
        !           115:  *     The node's 'made' field may be set to ERROR.
        !           116:  *
        !           117:  *-----------------------------------------------------------------------
        !           118:  */
        !           119: static int
        !           120: CompatRunCommand (cmd, gn)
        !           121:     char         *cmd;         /* Command to execute */
        !           122:     GNode        *gn;          /* Node from which the command came */
        !           123: {
        !           124:     char         *cmdStart;    /* Start of expanded command */
        !           125:     register char *cp;
        !           126:     Boolean      silent,       /* Don't print command */
        !           127:                  errCheck;     /* Check errors */
        !           128:     union wait           reason;       /* Reason for child's death */
        !           129:     int                  status;       /* Description of child's death */
        !           130:     int                  cpid;         /* Child actually found */
        !           131:     int                  numWritten;   /* Number of bytes written for error message */
        !           132:     ReturnStatus  stat;                /* Status of fork */
        !           133:     LstNode      cmdNode;      /* Node where current command is located */
        !           134:     char         **av;         /* Argument vector for thing to exec */
        !           135:     int                  argc;         /* Number of arguments in av or 0 if not
        !           136:                                 * dynamically allocated */
        !           137:     Boolean      local;        /* TRUE if command should be executed
        !           138:                                 * locally */
        !           139: 
        !           140:     silent = gn->type & OP_SILENT;
        !           141:     errCheck = !(gn->type & OP_IGNORE);
        !           142: 
        !           143:     cmdNode = Lst_Member (gn->commands, (ClientData)cmd);
        !           144:     cmdStart = Var_Subst (cmd, gn, FALSE);
        !           145: 
        !           146:     /*
        !           147:      * brk_string will return an argv with a NULL in av[1], thus causing
        !           148:      * execvp to choke and die horribly. Besides, how can we execute a null
        !           149:      * command? In any case, we warn the user that the command expanded to
        !           150:      * nothing (is this the right thing to do?).
        !           151:      */
        !           152:      
        !           153:     if (*cmdStart == '\0') {
        !           154:        Error("%s expands to empty string", cmd);
        !           155:        return(0);
        !           156:     } else {
        !           157:        cmd = cmdStart;
        !           158:     }
        !           159:     Lst_Replace (cmdNode, (ClientData)cmdStart);
        !           160: 
        !           161:     if ((gn->type & OP_SAVE_CMDS) && (gn != ENDNode)) {
        !           162:        (void)Lst_AtEnd(ENDNode->commands, (ClientData)cmdStart);
        !           163:        return(0);
        !           164:     } else if (strcmp(cmdStart, "...") == 0) {
        !           165:        gn->type |= OP_SAVE_CMDS;
        !           166:        return(0);
        !           167:     }
        !           168: 
        !           169:     while ((*cmd == '@') || (*cmd == '-')) {
        !           170:        if (*cmd == '@') {
        !           171:            silent = TRUE;
        !           172:        } else {
        !           173:            errCheck = FALSE;
        !           174:        }
        !           175:        cmd++;
        !           176:     }
        !           177:     
        !           178:     /*
        !           179:      * Search for meta characters in the command. If there are no meta
        !           180:      * characters, there's no need to execute a shell to execute the
        !           181:      * command.
        !           182:      */
        !           183:     for (cp = cmd; !meta[*cp]; cp++) {
        !           184:        continue;
        !           185:     }
        !           186: 
        !           187:     /*
        !           188:      * Print the command before echoing if we're not supposed to be quiet for
        !           189:      * this one. We also print the command if -n given.
        !           190:      */
        !           191:     if (!silent || noExecute) {
        !           192:        printf ("%s\n", cmd);
        !           193:        fflush(stdout);
        !           194:     }
        !           195: 
        !           196:     /*
        !           197:      * If we're not supposed to execute any commands, this is as far as
        !           198:      * we go...
        !           199:      */
        !           200:     if (noExecute) {
        !           201:        return (0);
        !           202:     }
        !           203:     
        !           204:     if (*cp != '\0') {
        !           205:        /*
        !           206:         * If *cp isn't the null character, we hit a "meta" character and
        !           207:         * need to pass the command off to the shell. We give the shell the
        !           208:         * -e flag as well as -c if it's supposed to exit when it hits an
        !           209:         * error.
        !           210:         */
        !           211:        static char     *shargv[4] = { "/bin/sh" };
        !           212: 
        !           213:        shargv[1] = (errCheck ? "-ec" : "-c");
        !           214:        shargv[2] = cmd;
        !           215:        shargv[3] = (char *)NULL;
        !           216:        av = shargv;
        !           217:        argc = 0;
        !           218:     } else {
        !           219:        /*
        !           220:         * No meta-characters, so no need to exec a shell. Break the command
        !           221:         * into words to form an argument vector we can execute.
        !           222:         * brk_string sticks our name in av[0], so we have to
        !           223:         * skip over it...
        !           224:         */
        !           225:        av = brk_string(cmd, &argc);
        !           226:        av += 1;
        !           227:     }
        !           228:     
        !           229:     local = TRUE;
        !           230: 
        !           231:     /*
        !           232:      * Fork and execute the single command. If the fork fails, we abort.
        !           233:      */
        !           234:     cpid = vfork();
        !           235:     if (cpid < 0) {
        !           236:        Fatal("Could not fork");
        !           237:     }
        !           238:     if (cpid == 0) {
        !           239:        if (local) {
        !           240:            execvp(av[0], av);
        !           241:            numWritten = write (2, av[0], strlen (av[0]));
        !           242:            numWritten = write (2, ": not found\n", sizeof(": not found"));
        !           243:        } else {
        !           244:            (void)execv(av[0], av);
        !           245:        }
        !           246:        exit(1);
        !           247:     }
        !           248:     
        !           249:     /*
        !           250:      * The child is off and running. Now all we can do is wait...
        !           251:      */
        !           252:     while (1) {
        !           253:        int       id;
        !           254: 
        !           255:        if (!local) {
        !           256:            id = 0;
        !           257:        }
        !           258: 
        !           259:        while ((stat = wait(&reason)) != cpid) {
        !           260:            if (stat == -1 && errno != EINTR) {
        !           261:                break;
        !           262:            }
        !           263:        }
        !           264:        
        !           265:        if (stat > -1) {
        !           266:            if (WIFSTOPPED(reason)) {
        !           267:                status = reason.w_stopval;              /* stopped */
        !           268:            } else if (WIFEXITED(reason)) {
        !           269:                status = reason.w_retcode;              /* exited */
        !           270:                if (status != 0) {
        !           271:                    printf ("*** Error code %d", status);
        !           272:                }
        !           273:            } else {
        !           274:                status = reason.w_termsig;              /* signaled */
        !           275:                printf ("*** Signal %d", status);
        !           276:            } 
        !           277: 
        !           278:            
        !           279:            if (!WIFEXITED(reason) || (status != 0)) {
        !           280:                if (errCheck) {
        !           281:                    gn->made = ERROR;
        !           282:                    if (keepgoing) {
        !           283:                        /*
        !           284:                         * Abort the current target, but let others
        !           285:                         * continue.
        !           286:                         */
        !           287:                        printf (" (continuing)\n");
        !           288:                    }
        !           289:                } else {
        !           290:                    /*
        !           291:                     * Continue executing commands for this target.
        !           292:                     * If we return 0, this will happen...
        !           293:                     */
        !           294:                    printf (" (ignored)\n");
        !           295:                    status = 0;
        !           296:                }
        !           297:            }
        !           298:            break;
        !           299:        } else {
        !           300:            Fatal ("error in wait: %d", stat);
        !           301:            /*NOTREACHED*/
        !           302:        }
        !           303:     }
        !           304: 
        !           305:     return (status);
        !           306: }
        !           307: 
        !           308: /*-
        !           309:  *-----------------------------------------------------------------------
        !           310:  * CompatMake --
        !           311:  *     Make a target.
        !           312:  *
        !           313:  * Results:
        !           314:  *     0
        !           315:  *
        !           316:  * Side Effects:
        !           317:  *     If an error is detected and not being ignored, the process exits.
        !           318:  *
        !           319:  *-----------------------------------------------------------------------
        !           320:  */
        !           321: static int
        !           322: CompatMake (gn, pgn)
        !           323:     GNode        *gn;      /* The node to make */
        !           324:     GNode        *pgn;     /* Parent to abort if necessary */
        !           325: {
        !           326:     if (gn->type & OP_USE) {
        !           327:        Make_HandleUse(gn, pgn);
        !           328:     } else if (gn->made == UNMADE) {
        !           329:        /*
        !           330:         * First mark ourselves to be made, then apply whatever transformations
        !           331:         * the suffix module thinks are necessary. Once that's done, we can
        !           332:         * descend and make all our children. If any of them has an error
        !           333:         * but the -k flag was given, our 'make' field will be set FALSE again.
        !           334:         * This is our signal to not attempt to do anything but abort our
        !           335:         * parent as well.
        !           336:         */
        !           337:        gn->make = TRUE;
        !           338:        gn->made = BEINGMADE;
        !           339:        Suff_FindDeps (gn);
        !           340:        Lst_ForEach (gn->children, CompatMake, (ClientData)gn);
        !           341:        if (!gn->make) {
        !           342:            gn->made = ABORTED;
        !           343:            pgn->make = FALSE;
        !           344:            return (0);
        !           345:        }
        !           346: 
        !           347:        if (Lst_Member (gn->iParents, pgn) != NILLNODE) {
        !           348:            Var_Set (IMPSRC, Var_Value(TARGET, gn), pgn);
        !           349:        }
        !           350:        
        !           351:        /*
        !           352:         * All the children were made ok. Now cmtime contains the modification
        !           353:         * time of the newest child, we need to find out if we exist and when
        !           354:         * we were modified last. The criteria for datedness are defined by the
        !           355:         * Make_OODate function.
        !           356:         */
        !           357:        if (DEBUG(MAKE)) {
        !           358:            printf("Examining %s...", gn->name);
        !           359:        }
        !           360:        if (! Make_OODate(gn)) {
        !           361:            gn->made = UPTODATE;
        !           362:            if (DEBUG(MAKE)) {
        !           363:                printf("up-to-date.\n");
        !           364:            }
        !           365:            return (0);
        !           366:        } else if (DEBUG(MAKE)) {
        !           367:            printf("out-of-date.\n");
        !           368:        }
        !           369: 
        !           370:        /*
        !           371:         * If the user is just seeing if something is out-of-date, exit now
        !           372:         * to tell him/her "yes".
        !           373:         */
        !           374:        if (queryFlag) {
        !           375:            exit (-1);
        !           376:        }
        !           377: 
        !           378:        /*
        !           379:         * We need to be re-made. We also have to make sure we've got a $?
        !           380:         * variable. To be nice, we also define the $> variable using
        !           381:         * Make_DoAllVar().
        !           382:         */
        !           383:        Make_DoAllVar(gn);
        !           384:                    
        !           385:        /*
        !           386:         * Alter our type to tell if errors should be ignored or things
        !           387:         * should not be printed so CompatRunCommand knows what to do.
        !           388:         */
        !           389:        if (Targ_Ignore (gn)) {
        !           390:            gn->type |= OP_IGNORE;
        !           391:        }
        !           392:        if (Targ_Silent (gn)) {
        !           393:            gn->type |= OP_SILENT;
        !           394:        }
        !           395: 
        !           396:        if (Job_CheckCommands (gn, Fatal)) {
        !           397:            /*
        !           398:             * Our commands are ok, but we still have to worry about the -t
        !           399:             * flag...
        !           400:             */
        !           401:            if (!touchFlag) {
        !           402:                curTarg = gn;
        !           403:                Lst_ForEach (gn->commands, CompatRunCommand, (ClientData)gn);
        !           404:                curTarg = NILGNODE;
        !           405:            } else {
        !           406:                Job_Touch (gn, gn->type & OP_SILENT);
        !           407:            }
        !           408:        } else {
        !           409:            gn->made = ERROR;
        !           410:        }
        !           411: 
        !           412:        if (gn->made != ERROR) {
        !           413:            /*
        !           414:             * If the node was made successfully, mark it so, update
        !           415:             * its modification time and timestamp all its parents. Note
        !           416:             * that for .ZEROTIME targets, the timestamping isn't done.
        !           417:             * This is to keep its state from affecting that of its parent.
        !           418:             */
        !           419:            gn->made = MADE;
        !           420: #ifndef RECHECK
        !           421:            /*
        !           422:             * We can't re-stat the thing, but we can at least take care of
        !           423:             * rules where a target depends on a source that actually creates
        !           424:             * the target, but only if it has changed, e.g.
        !           425:             *
        !           426:             * parse.h : parse.o
        !           427:             *
        !           428:             * parse.o : parse.y
        !           429:             *          yacc -d parse.y
        !           430:             *          cc -c y.tab.c
        !           431:             *          mv y.tab.o parse.o
        !           432:             *          cmp -s y.tab.h parse.h || mv y.tab.h parse.h
        !           433:             *
        !           434:             * In this case, if the definitions produced by yacc haven't
        !           435:             * changed from before, parse.h won't have been updated and
        !           436:             * gn->mtime will reflect the current modification time for
        !           437:             * parse.h. This is something of a kludge, I admit, but it's a
        !           438:             * useful one..
        !           439:             *
        !           440:             * XXX: People like to use a rule like
        !           441:             *
        !           442:             * FRC:
        !           443:             *
        !           444:             * To force things that depend on FRC to be made, so we have to
        !           445:             * check for gn->children being empty as well...
        !           446:             */
        !           447:            if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) {
        !           448:                gn->mtime = now;
        !           449:            }
        !           450: #else
        !           451:            /*
        !           452:             * This is what Make does and it's actually a good thing, as it
        !           453:             * allows rules like
        !           454:             *
        !           455:             *  cmp -s y.tab.h parse.h || cp y.tab.h parse.h
        !           456:             *
        !           457:             * to function as intended. Unfortunately, thanks to the stateless
        !           458:             * nature of NFS (and the speed of this program), there are times
        !           459:             * when the modification time of a file created on a remote
        !           460:             * machine will not be modified before the stat() implied by
        !           461:             * the Dir_MTime occurs, thus leading us to believe that the file
        !           462:             * is unchanged, wreaking havoc with files that depend on this one.
        !           463:             *
        !           464:             * I have decided it is better to make too much than to make too
        !           465:             * little, so this stuff is commented out unless you're sure it's
        !           466:             * ok.
        !           467:             * -- ardeb 1/12/88
        !           468:             */
        !           469:            if (noExecute || Dir_MTime(gn) == 0) {
        !           470:                gn->mtime = now;
        !           471:            }
        !           472:            if (DEBUG(MAKE)) {
        !           473:                printf("update time: %s\n", Targ_FmtTime(gn->mtime));
        !           474:            }
        !           475: #endif
        !           476:            if (!(gn->type & OP_EXEC)) {
        !           477:                pgn->childMade = TRUE;
        !           478:                Make_TimeStamp(pgn, gn);
        !           479:            }
        !           480:        } else if (keepgoing) {
        !           481:            pgn->make = FALSE;
        !           482:        } else {
        !           483:            printf ("\n\nStop.\n");
        !           484:            exit (1);
        !           485:        }
        !           486:     } else if (gn->made == ERROR) {
        !           487:        /*
        !           488:         * Already had an error when making this beastie. Tell the parent
        !           489:         * to abort.
        !           490:         */
        !           491:        pgn->make = FALSE;
        !           492:     } else {
        !           493:        if (Lst_Member (gn->iParents, pgn) != NILLNODE) {
        !           494:            Var_Set (IMPSRC, Var_Value(TARGET, gn), pgn);
        !           495:        }
        !           496:        switch(gn->made) {
        !           497:            case BEINGMADE:
        !           498:                Error("Graph cycles through %s\n", gn->name);
        !           499:                gn->made = ERROR;
        !           500:                pgn->make = FALSE;
        !           501:                break;
        !           502:            case MADE:
        !           503:                if ((gn->type & OP_EXEC) == 0) {
        !           504:                    pgn->childMade = TRUE;
        !           505:                    Make_TimeStamp(pgn, gn);
        !           506:                }
        !           507:                break;
        !           508:            case UPTODATE:
        !           509:                if ((gn->type & OP_EXEC) == 0) {
        !           510:                    Make_TimeStamp(pgn, gn);
        !           511:                }
        !           512:                break;
        !           513:        }
        !           514:     }
        !           515: 
        !           516:     return (0);
        !           517: }
        !           518:       
        !           519: /*-
        !           520:  *-----------------------------------------------------------------------
        !           521:  * Compat_Run --
        !           522:  *     Initialize this mode and start making.
        !           523:  *
        !           524:  * Results:
        !           525:  *     None.
        !           526:  *
        !           527:  * Side Effects:
        !           528:  *     Guess what?
        !           529:  *
        !           530:  *-----------------------------------------------------------------------
        !           531:  */
        !           532: void
        !           533: Compat_Run(targs)
        !           534:     Lst                  targs;    /* List of target nodes to re-create */
        !           535: {
        !           536:     char         *cp;      /* Pointer to string of shell meta-characters */
        !           537:     GNode        *gn;      /* Current root target */
        !           538:     int                  errors;   /* Number of targets not remade due to errors */
        !           539: 
        !           540:     if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
        !           541:        signal(SIGINT, CompatInterrupt);
        !           542:     }
        !           543:     if (signal(SIGTERM, SIG_IGN) != SIG_IGN) {
        !           544:        signal(SIGTERM, CompatInterrupt);
        !           545:     }
        !           546:     if (signal(SIGHUP, SIG_IGN) != SIG_IGN) {
        !           547:        signal(SIGHUP, CompatInterrupt);
        !           548:     }
        !           549:     if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) {
        !           550:        signal(SIGQUIT, CompatInterrupt);
        !           551:     }
        !           552: 
        !           553:     for (cp = "#=|^(){};&<>*?[]:$`\\\n"; *cp != '\0'; cp++) {
        !           554:        meta[*cp] = 1;
        !           555:     }
        !           556:     /*
        !           557:      * The null character serves as a sentinel in the string.
        !           558:      */
        !           559:     meta[0] = 1;
        !           560: 
        !           561:     ENDNode = Targ_FindNode(".END", TARG_CREATE);
        !           562:     /*
        !           563:      * If the user has defined a .BEGIN target, execute the commands attached
        !           564:      * to it.
        !           565:      */
        !           566:     if (!queryFlag) {
        !           567:        gn = Targ_FindNode(".BEGIN", TARG_NOCREATE);
        !           568:        if (gn != NILGNODE) {
        !           569:            Lst_ForEach(gn->commands, CompatRunCommand, (ClientData)gn);
        !           570:        }
        !           571:     }
        !           572: 
        !           573:     /*
        !           574:      * For each entry in the list of targets to create, call CompatMake on
        !           575:      * it to create the thing. CompatMake will leave the 'made' field of gn
        !           576:      * in one of several states:
        !           577:      *     UPTODATE        gn was already up-to-date
        !           578:      *     MADE            gn was recreated successfully
        !           579:      *     ERROR           An error occurred while gn was being created
        !           580:      *     ABORTED         gn was not remade because one of its inferiors
        !           581:      *                     could not be made due to errors.
        !           582:      */
        !           583:     errors = 0;
        !           584:     while (!Lst_IsEmpty (targs)) {
        !           585:        gn = (GNode *) Lst_DeQueue (targs);
        !           586:        CompatMake (gn, gn);
        !           587: 
        !           588:        if (gn->made == UPTODATE) {
        !           589:            printf ("`%s' is up to date.\n", gn->name);
        !           590:        } else if (gn->made == ABORTED) {
        !           591:            printf ("`%s' not remade because of errors.\n", gn->name);
        !           592:            errors += 1;
        !           593:        }
        !           594:     }
        !           595: 
        !           596:     /*
        !           597:      * If the user has defined a .END target, run its commands.
        !           598:      */
        !           599:     if (errors == 0) {
        !           600:        Lst_ForEach(ENDNode->commands, CompatRunCommand, (ClientData)gn);
        !           601:     }
        !           602: }

unix.superglobalmegacorp.com

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