Index: head/usr.bin/make/arch.c =================================================================== --- head/usr.bin/make/arch.c (revision 138511) +++ head/usr.bin/make/arch.c (revision 138512) @@ -1,1217 +1,1217 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)arch.c 8.2 (Berkeley) 1/2/94 */ #include __FBSDID("$FreeBSD$"); /*- * arch.c -- * Functions to manipulate libraries, archives and their members. * * Once again, cacheing/hashing comes into play in the manipulation * of archives. The first time an archive is referenced, all of its members' * headers are read and hashed and the archive closed again. All hashed * archives are kept on a list which is searched each time an archive member * is referenced. * * The interface to this module is: * Arch_ParseArchive Given an archive specification, return a list * of GNode's, one for each member in the spec. * FAILURE is returned if the specification is * invalid for some reason. * * Arch_Touch Alter the modification time of the archive * member described by the given node to be * the current time. * * Arch_TouchLib Update the modification time of the library * described by the given node. This is special * because it also updates the modification time * of the library's table of contents. * * Arch_MTime Find the modification time of a member of * an archive *in the archive*. The time is also * placed in the member's GNode. Returns the * modification time. * * Arch_MemTime Find the modification time of a member of * an archive. Called when the member doesn't * already exist. Looks in the archive for the * modification time. Returns the modification * time. * * Arch_FindLib Search for a library along a path. The * library name in the GNode should be in * -l format. * * Arch_LibOODate Special function to decide if a library node * is out-of-date. * * Arch_Init Initialize this module. * * Arch_End Cleanup this module. */ #include #include #include #include #include #include #include #include #include #include "make.h" #include "hash.h" #include "dir.h" #include "config.h" -static Lst archives; /* Lst of archives we've already examined */ +static Lst *archives; /* Lst of archives we've already examined */ typedef struct Arch { char *name; /* Name of archive */ Hash_Table members; /* All the members of the archive described * by key/value pairs */ char *fnametab; /* Extended name table strings */ size_t fnamesize; /* Size of the string table */ } Arch; static int ArchFindArchive(void *, void *); static void ArchFree(void *); static struct ar_hdr *ArchStatMember(char *, char *, Boolean); static FILE *ArchFindMember(char *, char *, struct ar_hdr *, char *); #if defined(__svr4__) || defined(__SVR4) || defined(__ELF__) #define SVR4ARCHIVES static int ArchSVR4Entry(Arch *, char *, size_t, FILE *); #endif /*- *----------------------------------------------------------------------- * ArchFree -- * Free memory used by an archive * * Results: * None. * * Side Effects: * None. * *----------------------------------------------------------------------- */ static void ArchFree(void *ap) { Arch *a = ap; Hash_Search search; Hash_Entry *entry; /* Free memory from hash entries */ for (entry = Hash_EnumFirst(&a->members, &search); entry != NULL; entry = Hash_EnumNext(&search)) free(Hash_GetValue(entry)); free(a->name); free(a->fnametab); Hash_DeleteTable(&a->members); free(a); } /*- *----------------------------------------------------------------------- * Arch_ParseArchive -- * Parse the archive specification in the given line and find/create * the nodes for the specified archive members, placing their nodes * on the given list, given the pointer to the start of the * specification, a Lst on which to place the nodes, and a context * in which to expand variables. * * Results: * SUCCESS if it was a valid specification. The linePtr is updated * to point to the first non-space after the archive spec. The * nodes for the members are placed on the given list. * * Side Effects: * Some nodes may be created. The given list is extended. * *----------------------------------------------------------------------- */ ReturnStatus -Arch_ParseArchive(char **linePtr, Lst nodeLst, GNode *ctxt) +Arch_ParseArchive(char **linePtr, Lst *nodeLst, GNode *ctxt) { char *cp; /* Pointer into line */ GNode *gn; /* New node */ char *libName; /* Library-part of specification */ char *memName; /* Member-part of specification */ char *nameBuf; /* temporary place for node name */ char saveChar; /* Ending delimiter of member-name */ Boolean subLibName; /* TRUE if libName should have/had * variable substitution performed on it */ libName = *linePtr; subLibName = FALSE; for (cp = libName; *cp != '(' && *cp != '\0'; cp++) { if (*cp == '$') { /* * Variable spec, so call the Var module to parse the puppy * so we can safely advance beyond it... */ size_t length; Boolean freeIt; char *result; result = Var_Parse(cp, ctxt, TRUE, &length, &freeIt); if (result == var_Error) { return (FAILURE); } else { subLibName = TRUE; } if (freeIt) { free(result); } cp += length - 1; } } *cp++ = '\0'; if (subLibName) { libName = Var_Subst(NULL, libName, ctxt, TRUE); } for (;;) { /* * First skip to the start of the member's name, mark that * place and skip to the end of it (either white-space or * a close paren). */ Boolean doSubst = FALSE; /* TRUE if need to substitute in memName */ while (*cp != '\0' && *cp != ')' && isspace((unsigned char)*cp)) { cp++; } memName = cp; while (*cp != '\0' && *cp != ')' && !isspace((unsigned char)*cp)) { if (*cp == '$') { /* * Variable spec, so call the Var module to parse the puppy * so we can safely advance beyond it... */ size_t length; Boolean freeIt; char *result; result = Var_Parse(cp, ctxt, TRUE, &length, &freeIt); if (result == var_Error) { return (FAILURE); } else { doSubst = TRUE; } if (freeIt) { free(result); } cp += length; } else { cp++; } } /* * If the specification ends without a closing parenthesis, * chances are there's something wrong (like a missing backslash), * so it's better to return failure than allow such things to happen */ if (*cp == '\0') { printf("No closing parenthesis in archive specification\n"); return (FAILURE); } /* * If we didn't move anywhere, we must be done */ if (cp == memName) { break; } saveChar = *cp; *cp = '\0'; /* * XXX: This should be taken care of intelligently by * SuffExpandChildren, both for the archive and the member portions. */ /* * If member contains variables, try and substitute for them. * This will slow down archive specs with dynamic sources, of course, * since we'll be (non-)substituting them three times, but them's * the breaks -- we need to do this since SuffExpandChildren calls * us, otherwise we could assume the thing would be taken care of * later. */ if (doSubst) { char *buf; char *sacrifice; char *oldMemName = memName; size_t sz; memName = Var_Subst(NULL, memName, ctxt, TRUE); /* * Now form an archive spec and recurse to deal with nested * variables and multi-word variable values.... The results * are just placed at the end of the nodeLst we're returning. */ sz = strlen(memName) + strlen(libName) + 3; buf = sacrifice = emalloc(sz); snprintf(buf, sz, "%s(%s)", libName, memName); if (strchr(memName, '$') && strcmp(memName, oldMemName) == 0) { /* * Must contain dynamic sources, so we can't deal with it now. * Just create an ARCHV node for the thing and let * SuffExpandChildren handle it... */ gn = Targ_FindNode(buf, TARG_CREATE); if (gn == NULL) { free(buf); return (FAILURE); } else { gn->type |= OP_ARCHV; Lst_AtEnd(nodeLst, (void *)gn); } } else if (Arch_ParseArchive(&sacrifice, nodeLst, ctxt) != SUCCESS) { /* * Error in nested call -- free buffer and return FAILURE * ourselves. */ free(buf); return (FAILURE); } /* * Free buffer and continue with our work. */ free(buf); } else if (Dir_HasWildcards(memName)) { - Lst members = Lst_Init(); + Lst *members = Lst_Init(); char *member; size_t sz = MAXPATHLEN; size_t nsz; nameBuf = emalloc(sz); Dir_Expand(memName, dirSearchPath, members); while (!Lst_IsEmpty(members)) { member = Lst_DeQueue(members); nsz = strlen(libName) + strlen(member) + 3; if (nsz > sz) { sz = nsz * 2; nameBuf = erealloc(nameBuf, sz); } snprintf(nameBuf, sz, "%s(%s)", libName, member); free(member); gn = Targ_FindNode(nameBuf, TARG_CREATE); if (gn == NULL) { free(nameBuf); return (FAILURE); } else { /* * We've found the node, but have to make sure the rest of * the world knows it's an archive member, without having * to constantly check for parentheses, so we type the * thing with the OP_ARCHV bit before we place it on the * end of the provided list. */ gn->type |= OP_ARCHV; Lst_AtEnd(nodeLst, (void *)gn); } } Lst_Destroy(members, NOFREE); free(nameBuf); } else { size_t sz = strlen(libName) + strlen(memName) + 3; nameBuf = emalloc(sz); snprintf(nameBuf, sz, "%s(%s)", libName, memName); gn = Targ_FindNode(nameBuf, TARG_CREATE); free(nameBuf); if (gn == NULL) { return (FAILURE); } else { /* * We've found the node, but have to make sure the rest of the * world knows it's an archive member, without having to * constantly check for parentheses, so we type the thing with * the OP_ARCHV bit before we place it on the end of the * provided list. */ gn->type |= OP_ARCHV; Lst_AtEnd(nodeLst, gn); } } if (doSubst) { free(memName); } *cp = saveChar; } /* * If substituted libName, free it now, since we need it no longer. */ if (subLibName) { free(libName); } /* * We promised the pointer would be set up at the next non-space, so * we must advance cp there before setting *linePtr... (note that on * entrance to the loop, cp is guaranteed to point at a ')') */ do { cp++; } while (*cp != '\0' && isspace((unsigned char)*cp)); *linePtr = cp; return (SUCCESS); } /*- *----------------------------------------------------------------------- * ArchFindArchive -- * See if the given archive is the one we are looking for. Called * From ArchStatMember and ArchFindMember via Lst_Find with the * current list element and the name we want. * * Results: * 0 if it is, non-zero if it isn't. * * Side Effects: * None. * *----------------------------------------------------------------------- */ static int ArchFindArchive(void *ar, void *archName) { return (strcmp((char *)archName, ((Arch *)ar)->name)); } /*- *----------------------------------------------------------------------- * ArchStatMember -- * Locate a member of an archive, given the path of the archive and * the path of the desired member, and a boolean representing whether * or not the archive should be hashed (if not already hashed). * * Results: * A pointer to the current struct ar_hdr structure for the member. Note * That no position is returned, so this is not useful for touching * archive members. This is mostly because we have no assurances that * The archive will remain constant after we read all the headers, so * there's not much point in remembering the position... * * Side Effects: * *----------------------------------------------------------------------- */ static struct ar_hdr * ArchStatMember(char *archive, char *member, Boolean hash) { #define AR_MAX_NAME_LEN (sizeof(arh.ar_name) - 1) FILE * arch; /* Stream to archive */ int size; /* Size of archive member */ char *cp; /* Useful character pointer */ char magic[SARMAG]; - LstNode ln; /* Lst member containing archive descriptor */ + LstNode *ln; /* Lst member containing archive descriptor */ Arch *ar; /* Archive descriptor */ Hash_Entry *he; /* Entry containing member's description */ struct ar_hdr arh; /* archive-member header for reading archive */ char memName[MAXPATHLEN + 1]; /* Current member name while hashing. */ /* * Because of space constraints and similar things, files are archived * using their final path components, not the entire thing, so we need * to point 'member' to the final component, if there is one, to make * the comparisons easier... */ cp = strrchr(member, '/'); if ((cp != NULL) && (strcmp(member, RANLIBMAG) != 0)) member = cp + 1; ln = Lst_Find(archives, archive, ArchFindArchive); if (ln != NULL) { ar = Lst_Datum(ln); he = Hash_FindEntry(&ar->members, member); if (he != NULL) { return ((struct ar_hdr *)Hash_GetValue (he)); } else { /* Try truncated name */ char copy[AR_MAX_NAME_LEN + 1]; size_t len = strlen(member); if (len > AR_MAX_NAME_LEN) { len = AR_MAX_NAME_LEN; strncpy(copy, member, AR_MAX_NAME_LEN); copy[AR_MAX_NAME_LEN] = '\0'; } if ((he = Hash_FindEntry(&ar->members, copy)) != NULL) return (Hash_GetValue(he)); return (NULL); } } if (!hash) { /* * Caller doesn't want the thing hashed, just use ArchFindMember * to read the header for the member out and close down the stream * again. Since the archive is not to be hashed, we assume there's * no need to allocate extra room for the header we're returning, * so just declare it static. */ static struct ar_hdr sarh; arch = ArchFindMember(archive, member, &sarh, "r"); if (arch == NULL) { return (NULL); } else { fclose(arch); return (&sarh); } } /* * We don't have this archive on the list yet, so we want to find out * everything that's in it and cache it so we can get at it quickly. */ arch = fopen(archive, "r"); if (arch == NULL) { return (NULL); } /* * We use the ARMAG string to make sure this is an archive we * can handle... */ if ((fread(magic, SARMAG, 1, arch) != 1) || (strncmp(magic, ARMAG, SARMAG) != 0)) { fclose(arch); return (NULL); } ar = emalloc(sizeof(Arch)); ar->name = estrdup(archive); ar->fnametab = NULL; ar->fnamesize = 0; Hash_InitTable(&ar->members, -1); memName[AR_MAX_NAME_LEN] = '\0'; while (fread(&arh, sizeof(struct ar_hdr), 1, arch) == 1) { if (strncmp(arh.ar_fmag, ARFMAG, sizeof(arh.ar_fmag)) != 0) { /* * The header is bogus, so the archive is bad * and there's no way we can recover... */ goto badarch; } else { /* * We need to advance the stream's pointer to the start of the * next header. Files are padded with newlines to an even-byte * boundary, so we need to extract the size of the file from the * 'size' field of the header and round it up during the seek. */ arh.ar_size[sizeof(arh.ar_size) - 1] = '\0'; size = (int)strtol(arh.ar_size, NULL, 10); strncpy(memName, arh.ar_name, sizeof(arh.ar_name)); for (cp = &memName[AR_MAX_NAME_LEN]; *cp == ' '; cp--) { continue; } cp[1] = '\0'; #ifdef SVR4ARCHIVES /* * svr4 names are slash terminated. Also svr4 extended AR format. */ if (memName[0] == '/') { /* * svr4 magic mode; handle it */ switch (ArchSVR4Entry(ar, memName, size, arch)) { case -1: /* Invalid data */ goto badarch; case 0: /* List of files entry */ continue; default: /* Got the entry */ break; } } else { if (cp[0] == '/') cp[0] = '\0'; } #endif #ifdef AR_EFMT1 /* * BSD 4.4 extended AR format: #1/, with name as the * first bytes of the file */ if (strncmp(memName, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 && isdigit(memName[sizeof(AR_EFMT1) - 1])) { unsigned int elen = atoi(&memName[sizeof(AR_EFMT1)-1]); if (elen > MAXPATHLEN) goto badarch; if (fread(memName, elen, 1, arch) != 1) goto badarch; memName[elen] = '\0'; fseek(arch, -elen, SEEK_CUR); /* XXX Multiple levels may be asked for, make this conditional * on one, and use DEBUGF. */ if (DEBUG(ARCH) || DEBUG(MAKE)) { fprintf(stderr, "ArchStat: Extended format entry for %s\n", memName); } } #endif he = Hash_CreateEntry(&ar->members, memName, NULL); Hash_SetValue(he, emalloc(sizeof(struct ar_hdr))); memcpy(Hash_GetValue(he), &arh, sizeof(struct ar_hdr)); } fseek(arch, (size + 1) & ~1, SEEK_CUR); } fclose(arch); Lst_AtEnd(archives, ar); /* * Now that the archive has been read and cached, we can look into * the hash table to find the desired member's header. */ he = Hash_FindEntry(&ar->members, member); if (he != NULL) { return (Hash_GetValue (he)); } else { return (NULL); } badarch: fclose(arch); Hash_DeleteTable(&ar->members); free(ar->fnametab); free(ar); return (NULL); } #ifdef SVR4ARCHIVES /*- *----------------------------------------------------------------------- * ArchSVR4Entry -- * Parse an SVR4 style entry that begins with a slash. * If it is "//", then load the table of filenames * If it is "/", then try to substitute the long file name * from offset of a table previously read. * * Results: * -1: Bad data in archive * 0: A table was loaded from the file * 1: Name was successfully substituted from table * 2: Name was not successfully substituted from table * * Side Effects: * If a table is read, the file pointer is moved to the next archive * member * *----------------------------------------------------------------------- */ static int ArchSVR4Entry(Arch *ar, char *name, size_t size, FILE *arch) { #define ARLONGNAMES1 "//" #define ARLONGNAMES2 "/ARFILENAMES" size_t entry; char *ptr, *eptr; if (strncmp(name, ARLONGNAMES1, sizeof(ARLONGNAMES1) - 1) == 0 || strncmp(name, ARLONGNAMES2, sizeof(ARLONGNAMES2) - 1) == 0) { if (ar->fnametab != NULL) { DEBUGF(ARCH, ("Attempted to redefine an SVR4 name table\n")); return (-1); } /* * This is a table of archive names, so we build one for * ourselves */ ar->fnametab = emalloc(size); ar->fnamesize = size; if (fread(ar->fnametab, size, 1, arch) != 1) { DEBUGF(ARCH, ("Reading an SVR4 name table failed\n")); return (-1); } eptr = ar->fnametab + size; for (entry = 0, ptr = ar->fnametab; ptr < eptr; ptr++) switch (*ptr) { case '/': entry++; *ptr = '\0'; break; case '\n': break; default: break; } DEBUGF(ARCH, ("Found svr4 archive name table with %zu entries\n", entry)); return (0); } if (name[1] == ' ' || name[1] == '\0') return (2); entry = (size_t)strtol(&name[1], &eptr, 0); if ((*eptr != ' ' && *eptr != '\0') || eptr == &name[1]) { DEBUGF(ARCH, ("Could not parse SVR4 name %s\n", name)); return (2); } if (entry >= ar->fnamesize) { DEBUGF(ARCH, ("SVR4 entry offset %s is greater than %zu\n", name, ar->fnamesize)); return (2); } DEBUGF(ARCH, ("Replaced %s with %s\n", name, &ar->fnametab[entry])); strncpy(name, &ar->fnametab[entry], MAXPATHLEN); name[MAXPATHLEN] = '\0'; return (1); } #endif /*- *----------------------------------------------------------------------- * ArchFindMember -- * Locate a member of an archive, given the path of the archive and * the path of the desired member. If the archive is to be modified, * the mode should be "r+", if not, it should be "r". arhPtr is a * poitner to the header structure to fill in. * * Results: * An FILE *, opened for reading and writing, positioned at the * start of the member's struct ar_hdr, or NULL if the member was * nonexistent. The current struct ar_hdr for member. * * Side Effects: * The passed struct ar_hdr structure is filled in. * *----------------------------------------------------------------------- */ static FILE * ArchFindMember(char *archive, char *member, struct ar_hdr *arhPtr, char *mode) { FILE * arch; /* Stream to archive */ int size; /* Size of archive member */ char *cp; /* Useful character pointer */ char magic[SARMAG]; size_t len, tlen; arch = fopen(archive, mode); if (arch == NULL) { return (NULL); } /* * We use the ARMAG string to make sure this is an archive we * can handle... */ if ((fread(magic, SARMAG, 1, arch) != 1) || (strncmp(magic, ARMAG, SARMAG) != 0)) { fclose(arch); return (NULL); } /* * Because of space constraints and similar things, files are archived * using their final path components, not the entire thing, so we need * to point 'member' to the final component, if there is one, to make * the comparisons easier... */ cp = strrchr(member, '/'); if ((cp != NULL) && (strcmp(member, RANLIBMAG) != 0)) { member = cp + 1; } len = tlen = strlen(member); if (len > sizeof(arhPtr->ar_name)) { tlen = sizeof(arhPtr->ar_name); } while (fread(arhPtr, sizeof(struct ar_hdr), 1, arch) == 1) { if (strncmp(arhPtr->ar_fmag, ARFMAG, sizeof(arhPtr->ar_fmag) ) != 0) { /* * The header is bogus, so the archive is bad * and there's no way we can recover... */ fclose(arch); return (NULL); } else if (strncmp(member, arhPtr->ar_name, tlen) == 0) { /* * If the member's name doesn't take up the entire 'name' field, * we have to be careful of matching prefixes. Names are space- * padded to the right, so if the character in 'name' at the end * of the matched string is anything but a space, this isn't the * member we sought. */ if (tlen != sizeof(arhPtr->ar_name) && arhPtr->ar_name[tlen] != ' '){ goto skip; } else { /* * To make life easier, we reposition the file at the start * of the header we just read before we return the stream. * In a more general situation, it might be better to leave * the file at the actual member, rather than its header, but * not here... */ fseek(arch, -sizeof(struct ar_hdr), SEEK_CUR); return (arch); } } else #ifdef AR_EFMT1 /* * BSD 4.4 extended AR format: #1/, with name as the * first bytes of the file */ if (strncmp(arhPtr->ar_name, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 && isdigit(arhPtr->ar_name[sizeof(AR_EFMT1) - 1])) { unsigned int elen = atoi(&arhPtr->ar_name[sizeof(AR_EFMT1)-1]); char ename[MAXPATHLEN]; if (elen > MAXPATHLEN) { fclose(arch); return NULL; } if (fread(ename, elen, 1, arch) != 1) { fclose(arch); return NULL; } ename[elen] = '\0'; /* * XXX choose one. */ if (DEBUG(ARCH) || DEBUG(MAKE)) { printf("ArchFind: Extended format entry for %s\n", ename); } if (strncmp(ename, member, len) == 0) { /* Found as extended name */ fseek(arch, -sizeof(struct ar_hdr) - elen, SEEK_CUR); return (arch); } fseek(arch, -elen, SEEK_CUR); goto skip; } else #endif { skip: /* * This isn't the member we're after, so we need to advance the * stream's pointer to the start of the next header. Files are * padded with newlines to an even-byte boundary, so we need to * extract the size of the file from the 'size' field of the * header and round it up during the seek. */ arhPtr->ar_size[sizeof(arhPtr->ar_size) - 1] = '\0'; size = (int)strtol(arhPtr->ar_size, NULL, 10); fseek(arch, (size + 1) & ~1, SEEK_CUR); } } /* * We've looked everywhere, but the member is not to be found. Close the * archive and return NULL -- an error. */ fclose(arch); return (NULL); } /*- *----------------------------------------------------------------------- * Arch_Touch -- * Touch a member of an archive. * * Results: * The 'time' field of the member's header is updated. * * Side Effects: * The modification time of the entire archive is also changed. * For a library, this could necessitate the re-ranlib'ing of the * whole thing. * *----------------------------------------------------------------------- */ void Arch_Touch(GNode *gn) { FILE * arch; /* Stream open to archive, positioned properly */ struct ar_hdr arh; /* Current header describing member */ char *p1, *p2; arch = ArchFindMember(Var_Value(ARCHIVE, gn, &p1), Var_Value(TARGET, gn, &p2), &arh, "r+"); free(p1); free(p2); snprintf(arh.ar_date, sizeof(arh.ar_date), "%-12ld", (long)now); if (arch != NULL) { fwrite(&arh, sizeof(struct ar_hdr), 1, arch); fclose(arch); } } /*- *----------------------------------------------------------------------- * Arch_TouchLib -- * Given a node which represents a library, touch the thing, making * sure that the table of contents also is touched. * * Results: * None. * * Side Effects: * Both the modification time of the library and of the RANLIBMAG * member are set to 'now'. * *----------------------------------------------------------------------- */ void Arch_TouchLib(GNode *gn) { #ifdef RANLIBMAG FILE * arch; /* Stream open to archive */ struct ar_hdr arh; /* Header describing table of contents */ struct utimbuf times; /* Times for utime() call */ arch = ArchFindMember(gn->path, RANLIBMAG, &arh, "r+"); snprintf(arh.ar_date, sizeof(arh.ar_date), "%-12ld", (long) now); if (arch != NULL) { fwrite(&arh, sizeof(struct ar_hdr), 1, arch); fclose(arch); times.actime = times.modtime = now; utime(gn->path, ×); } #endif } /*- *----------------------------------------------------------------------- * Arch_MTime -- * Return the modification time of a member of an archive, given its * name. * * Results: * The modification time(seconds). * * Side Effects: * The mtime field of the given node is filled in with the value * returned by the function. * *----------------------------------------------------------------------- */ int Arch_MTime(GNode *gn) { struct ar_hdr *arhPtr; /* Header of desired member */ int modTime; /* Modification time as an integer */ char *p1, *p2; arhPtr = ArchStatMember(Var_Value(ARCHIVE, gn, &p1), Var_Value(TARGET, gn, &p2), TRUE); free(p1); free(p2); if (arhPtr != NULL) { modTime = (int)strtol(arhPtr->ar_date, NULL, 10); } else { modTime = 0; } gn->mtime = modTime; return (modTime); } /*- *----------------------------------------------------------------------- * Arch_MemMTime -- * Given a non-existent archive member's node, get its modification * time from its archived form, if it exists. * * Results: * The modification time. * * Side Effects: * The mtime field is filled in. * *----------------------------------------------------------------------- */ int Arch_MemMTime(GNode *gn) { - LstNode ln; + LstNode *ln; GNode *pgn; char *nameStart, *nameEnd; if (Lst_Open(gn->parents) != SUCCESS) { gn->mtime = 0; return (0); } while ((ln = Lst_Next(gn->parents)) != NULL) { pgn = Lst_Datum(ln); if (pgn->type & OP_ARCHV) { /* * If the parent is an archive specification and is being made * and its member's name matches the name of the node we were * given, record the modification time of the parent in the * child. We keep searching its parents in case some other * parent requires this child to exist... */ nameStart = strchr(pgn->name, '(') + 1; nameEnd = strchr(nameStart, ')'); if (pgn->make && strncmp(nameStart, gn->name, nameEnd - nameStart) == 0) { gn->mtime = Arch_MTime(pgn); } } else if (pgn->make) { /* * Something which isn't a library depends on the existence of * this target, so it needs to exist. */ gn->mtime = 0; break; } } Lst_Close(gn->parents); return (gn->mtime); } /*- *----------------------------------------------------------------------- * Arch_FindLib -- * Search for a named library along the given search path. * * Results: * None. * * Side Effects: * The node's 'path' field is set to the found path (including the * actual file name, not -l...). If the system can handle the -L * flag when linking (or we cannot find the library), we assume that * the user has placed the .LIBRARIES variable in the final linking * command (or the linker will know where to find it) and set the * TARGET variable for this node to be the node's name. Otherwise, * we set the TARGET variable to be the full path of the library, * as returned by Dir_FindFile. * *----------------------------------------------------------------------- */ void -Arch_FindLib(GNode *gn, Lst path) +Arch_FindLib(GNode *gn, Lst *path) { char *libName; /* file name for archive */ size_t sz; sz = strlen(gn->name) + 4; libName = emalloc(sz); snprintf(libName, sz, "lib%s.a", &gn->name[2]); gn->path = Dir_FindFile(libName, path); free(libName); #ifdef LIBRARIES Var_Set(TARGET, gn->name, gn); #else Var_Set(TARGET, gn->path == NULL ? gn->name : gn->path, gn); #endif /* LIBRARIES */ } /*- *----------------------------------------------------------------------- * Arch_LibOODate -- * Decide if a node with the OP_LIB attribute is out-of-date. Called * from Make_OODate to make its life easier, with the library's * graph node. * * There are several ways for a library to be out-of-date that are * not available to ordinary files. In addition, there are ways * that are open to regular files that are not available to * libraries. A library that is only used as a source is never * considered out-of-date by itself. This does not preclude the * library's modification time from making its parent be out-of-date. * A library will be considered out-of-date for any of these reasons, * given that it is a target on a dependency line somewhere: * Its modification time is less than that of one of its * sources (gn->mtime < gn->cmtime). * Its modification time is greater than the time at which the * make began (i.e. it's been modified in the course * of the make, probably by archiving). * The modification time of one of its sources is greater than * the one of its RANLIBMAG member (i.e. its table of contents * is out-of-date). We don't compare of the archive time * vs. TOC time because they can be too close. In my * opinion we should not bother with the TOC at all since * this is used by 'ar' rules that affect the data contents * of the archive, not by ranlib rules, which affect the * TOC. * * Results: * TRUE if the library is out-of-date. FALSE otherwise. * * Side Effects: * The library will be hashed if it hasn't been already. * *----------------------------------------------------------------------- */ Boolean Arch_LibOODate(GNode *gn) { Boolean oodate; if (OP_NOP(gn->type) && Lst_IsEmpty(gn->children)) { oodate = FALSE; } else if ((gn->mtime > now) || (gn->mtime < gn->cmtime)) { oodate = TRUE; } else { #ifdef RANLIBMAG struct ar_hdr *arhPtr; /* Header for __.SYMDEF */ int modTimeTOC; /* The table-of-contents's mod time */ arhPtr = ArchStatMember(gn->path, RANLIBMAG, FALSE); if (arhPtr != NULL) { modTimeTOC = (int)strtol(arhPtr->ar_date, NULL, 10); /* XXX choose one. */ if (DEBUG(ARCH) || DEBUG(MAKE)) { printf("%s modified %s...", RANLIBMAG, Targ_FmtTime(modTimeTOC)); } oodate = (gn->cmtime > modTimeTOC); } else { /* * A library w/o a table of contents is out-of-date */ if (DEBUG(ARCH) || DEBUG(MAKE)) { printf("No t.o.c...."); } oodate = TRUE; } #else oodate = (gn->mtime == 0); /* out-of-date if not present */ #endif } return (oodate); } /*- *----------------------------------------------------------------------- * Arch_Init -- * Initialize things for this module. * * Results: * None. * * Side Effects: * The 'archives' list is initialized. * *----------------------------------------------------------------------- */ void Arch_Init(void) { archives = Lst_Init(); } /*- *----------------------------------------------------------------------- * Arch_End -- * Cleanup things for this module. * * Results: * None. * * Side Effects: * The 'archives' list is freed * *----------------------------------------------------------------------- */ void Arch_End(void) { Lst_Destroy(archives, ArchFree); } Index: head/usr.bin/make/compat.c =================================================================== --- head/usr.bin/make/compat.c (revision 138511) +++ head/usr.bin/make/compat.c (revision 138512) @@ -1,748 +1,748 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 1988, 1989 by Adam de Boor * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)compat.c 8.2 (Berkeley) 3/19/94 */ #include __FBSDID("$FreeBSD$"); /*- * compat.c -- * The routines in this file implement the full-compatibility * mode of PMake. Most of the special functionality of PMake * is available in this mode. Things not supported: * - different shells. * - friendly variable substitution. * * Interface: * Compat_Run Initialize things for this module and recreate * thems as need creatin' */ #include #include #include #include #include #include #include #include #include "make.h" #include "hash.h" #include "dir.h" #include "job.h" /* * The following array is used to make a fast determination of which * characters are interpreted specially by the shell. If a command * contains any of these characters, it is executed by the shell, not * directly by us. */ static char meta[256]; static GNode *curTarg = NULL; static GNode *ENDNode; static sig_atomic_t interrupted; static void CompatInterrupt(int); static int CompatMake(void *, void *); static int shellneed(char *); static char *sh_builtin[] = { "alias", "cd", "eval", "exec", "exit", "read", "set", "ulimit", "unalias", "umask", "unset", "wait", ":", 0}; static void CompatInit(void) { char *cp; /* Pointer to string of shell meta-characters */ for (cp = "#=|^(){};&<>*?[]:$`\\\n"; *cp != '\0'; cp++) { meta[(unsigned char)*cp] = 1; } /* * The null character serves as a sentinel in the string. */ meta[0] = 1; } /* * Interrupt handler - set flag and defer handling to the main code */ static void CompatCatchSig(int signo) { interrupted = signo; } /*- *----------------------------------------------------------------------- * CompatInterrupt -- * Interrupt the creation of the current target and remove it if * it ain't precious. * * Results: * None. * * Side Effects: * The target is removed and the process exits. If .INTERRUPT exists, * its commands are run first WITH INTERRUPTS IGNORED.. * *----------------------------------------------------------------------- */ static void CompatInterrupt (int signo) { GNode *gn; sigset_t nmask, omask; sigemptyset(&nmask); sigaddset(&nmask, SIGINT); sigaddset(&nmask, SIGTERM); sigaddset(&nmask, SIGHUP); sigaddset(&nmask, SIGQUIT); sigprocmask(SIG_SETMASK, &nmask, &omask); /* prevent recursion in evaluation of .INTERRUPT */ interrupted = 0; if ((curTarg != NULL) && !Targ_Precious(curTarg)) { char *p1; char *file = Var_Value(TARGET, curTarg, &p1); if (!noExecute && eunlink(file) != -1) { printf("*** %s removed\n", file); } free(p1); } /* * Run .INTERRUPT only if hit with interrupt signal */ if (signo == SIGINT) { gn = Targ_FindNode(".INTERRUPT", TARG_NOCREATE); if (gn != NULL) { Lst_ForEach(gn->commands, Compat_RunCommand, (void *)gn); } } sigprocmask(SIG_SETMASK, &omask, NULL); if (signo == SIGQUIT) exit(signo); signal(signo, SIG_DFL); kill(getpid(), signo); } /*- *----------------------------------------------------------------------- * shellneed -- * * Results: * Returns 1 if a specified line must be executed by the shell, * 0 if it can be run via execve, and -1 if the command is a no-op. * * Side Effects: * None. * *----------------------------------------------------------------------- */ static int shellneed (char *cmd) { char **av, **p; int ac; av = brk_string(cmd, &ac, TRUE); for(p = sh_builtin; *p != 0; p++) if (strcmp(av[1], *p) == 0) return (1); return (0); } /*- *----------------------------------------------------------------------- * Compat_RunCommand -- * Execute the next command for a target. If the command returns an * error, the node's made field is set to ERROR and creation stops. * The node from which the command came is also given. * * Results: * 0 if the command succeeded, 1 if an error occurred. * * Side Effects: * The node's 'made' field may be set to ERROR. * *----------------------------------------------------------------------- */ int Compat_RunCommand(void *cmdp, void *gnp) { char *cmdStart; /* Start of expanded command */ char *cp; Boolean silent, /* Don't print command */ doit, /* Execute even in -n */ errCheck; /* Check errors */ int reason; /* Reason for child's death */ int status; /* Description of child's death */ int cpid; /* Child actually found */ ReturnStatus rstat; /* Status of fork */ - LstNode cmdNode; /* Node where current command is located */ + LstNode *cmdNode; /* Node where current command is located */ char **av; /* Argument vector for thing to exec */ int argc; /* Number of arguments in av or 0 if not * dynamically allocated */ int internal; /* Various values.. */ char *cmd = cmdp; GNode *gn = gnp; /* * Avoid clobbered variable warnings by forcing the compiler * to ``unregister'' variables */ #if __GNUC__ (void) &av; (void) &errCheck; #endif silent = gn->type & OP_SILENT; errCheck = !(gn->type & OP_IGNORE); doit = FALSE; cmdNode = Lst_Member(gn->commands, cmd); cmdStart = Var_Subst(NULL, cmd, gn, FALSE); /* * brk_string will return an argv with a NULL in av[0], thus causing * execvp to choke and die horribly. Besides, how can we execute a null * command? In any case, we warn the user that the command expanded to * nothing (is this the right thing to do?). */ if (*cmdStart == '\0') { free(cmdStart); Error("%s expands to empty string", cmd); return (0); } else { cmd = cmdStart; } Lst_Replace (cmdNode, cmdStart); if ((gn->type & OP_SAVE_CMDS) && (gn != ENDNode)) { Lst_AtEnd(ENDNode->commands, cmdStart); return (0); } else if (strcmp(cmdStart, "...") == 0) { gn->type |= OP_SAVE_CMDS; return (0); } while ((*cmd == '@') || (*cmd == '-') || (*cmd == '+')) { switch (*cmd) { case '@': silent = DEBUG(LOUD) ? FALSE : TRUE; break; case '-': errCheck = FALSE; break; case '+': doit = TRUE; if (!meta[0]) /* we came here from jobs */ CompatInit(); break; } cmd++; } while (isspace((unsigned char)*cmd)) cmd++; /* * Search for meta characters in the command. If there are no meta * characters, there's no need to execute a shell to execute the * command. */ for (cp = cmd; !meta[(unsigned char)*cp]; cp++) { continue; } /* * Print the command before echoing if we're not supposed to be quiet for * this one. We also print the command if -n given, but not if '+'. */ if (!silent || (noExecute && !doit)) { printf("%s\n", cmd); fflush(stdout); } /* * If we're not supposed to execute any commands, this is as far as * we go... */ if (!doit && noExecute) { return (0); } if (*cp != '\0') { /* * If *cp isn't the null character, we hit a "meta" character and * need to pass the command off to the shell. We give the shell the * -e flag as well as -c if it's supposed to exit when it hits an * error. */ static char *shargv[4]; shargv[0] = shellPath; shargv[1] = (errCheck ? "-ec" : "-c"); shargv[2] = cmd; shargv[3] = NULL; av = shargv; argc = 0; } else if ((internal = shellneed(cmd))) { /* * This command must be passed by the shell for other reasons.. * or.. possibly not at all. */ static char *shargv[4]; if (internal == -1) { /* Command does not need to be executed */ return (0); } shargv[0] = shellPath; shargv[1] = (errCheck ? "-ec" : "-c"); shargv[2] = cmd; shargv[3] = NULL; av = shargv; argc = 0; } else { /* * No meta-characters, so no need to exec a shell. Break the command * into words to form an argument vector we can execute. * brk_string sticks our name in av[0], so we have to * skip over it... */ av = brk_string(cmd, &argc, TRUE); av += 1; } /* * Fork and execute the single command. If the fork fails, we abort. */ cpid = vfork(); if (cpid < 0) { Fatal("Could not fork"); } if (cpid == 0) { execvp(av[0], av); write(STDERR_FILENO, av[0], strlen (av[0])); write(STDERR_FILENO, ":", 1); write(STDERR_FILENO, strerror(errno), strlen(strerror(errno))); write(STDERR_FILENO, "\n", 1); exit(1); } /* * we need to print out the command associated with this Gnode in * Targ_PrintCmd from Targ_PrintGraph when debugging at level g2, * in main(), Fatal() and DieHorribly(), therefore do not free it * when debugging. */ if (!DEBUG(GRAPH2)) { free(cmdStart); Lst_Replace(cmdNode, cmdp); } /* * The child is off and running. Now all we can do is wait... */ while (1) { while ((rstat = wait(&reason)) != cpid) { if (interrupted || (rstat == -1 && errno != EINTR)) { break; } } if (interrupted) CompatInterrupt(interrupted); if (rstat > -1) { if (WIFSTOPPED(reason)) { status = WSTOPSIG(reason); /* stopped */ } else if (WIFEXITED(reason)) { status = WEXITSTATUS(reason); /* exited */ if (status != 0) { printf("*** Error code %d", status); } } else { status = WTERMSIG(reason); /* signaled */ printf("*** Signal %d", status); } if (!WIFEXITED(reason) || (status != 0)) { if (errCheck) { gn->made = ERROR; if (keepgoing) { /* * Abort the current target, but let others * continue. */ printf(" (continuing)\n"); } } else { /* * Continue executing commands for this target. * If we return 0, this will happen... */ printf(" (ignored)\n"); status = 0; } } break; } else { Fatal("error in wait: %d", rstat); /*NOTREACHED*/ } } return (status); } /*- *----------------------------------------------------------------------- * CompatMake -- * Make a target, given the parent, to abort if necessary. * * Results: * 0 * * Side Effects: * If an error is detected and not being ignored, the process exits. * *----------------------------------------------------------------------- */ static int CompatMake(void *gnp, void *pgnp) { GNode *gn = gnp; GNode *pgn = pgnp; if (gn->type & OP_USE) { Make_HandleUse(gn, pgn); } else if (gn->made == UNMADE) { /* * First mark ourselves to be made, then apply whatever transformations * the suffix module thinks are necessary. Once that's done, we can * descend and make all our children. If any of them has an error * but the -k flag was given, our 'make' field will be set FALSE again. * This is our signal to not attempt to do anything but abort our * parent as well. */ gn->make = TRUE; gn->made = BEINGMADE; Suff_FindDeps(gn); Lst_ForEach(gn->children, CompatMake, gn); if (!gn->make) { gn->made = ABORTED; pgn->make = FALSE; return (0); } if (Lst_Member(gn->iParents, pgn) != NULL) { char *p1; Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn); free(p1); } /* * All the children were made ok. Now cmtime contains the modification * time of the newest child, we need to find out if we exist and when * we were modified last. The criteria for datedness are defined by the * Make_OODate function. */ DEBUGF(MAKE, ("Examining %s...", gn->name)); if (!Make_OODate(gn)) { gn->made = UPTODATE; DEBUGF(MAKE, ("up-to-date.\n")); return (0); } else { DEBUGF(MAKE, ("out-of-date.\n")); } /* * If the user is just seeing if something is out-of-date, exit now * to tell him/her "yes". */ if (queryFlag) { exit(1); } /* * We need to be re-made. We also have to make sure we've got a $? * variable. To be nice, we also define the $> variable using * Make_DoAllVar(). */ Make_DoAllVar(gn); /* * Alter our type to tell if errors should be ignored or things * should not be printed so Compat_RunCommand knows what to do. */ if (Targ_Ignore(gn)) { gn->type |= OP_IGNORE; } if (Targ_Silent(gn)) { gn->type |= OP_SILENT; } if (Job_CheckCommands(gn, Fatal)) { /* * Our commands are ok, but we still have to worry about the -t * flag... */ if (!touchFlag) { curTarg = gn; Lst_ForEach(gn->commands, Compat_RunCommand, (void *)gn); curTarg = NULL; } else { Job_Touch(gn, gn->type & OP_SILENT); } } else { gn->made = ERROR; } if (gn->made != ERROR) { /* * If the node was made successfully, mark it so, update * its modification time and timestamp all its parents. Note * that for .ZEROTIME targets, the timestamping isn't done. * This is to keep its state from affecting that of its parent. */ gn->made = MADE; #ifndef RECHECK /* * We can't re-stat the thing, but we can at least take care of * rules where a target depends on a source that actually creates * the target, but only if it has changed, e.g. * * parse.h : parse.o * * parse.o : parse.y * yacc -d parse.y * cc -c y.tab.c * mv y.tab.o parse.o * cmp -s y.tab.h parse.h || mv y.tab.h parse.h * * In this case, if the definitions produced by yacc haven't * changed from before, parse.h won't have been updated and * gn->mtime will reflect the current modification time for * parse.h. This is something of a kludge, I admit, but it's a * useful one.. * * XXX: People like to use a rule like * * FRC: * * To force things that depend on FRC to be made, so we have to * check for gn->children being empty as well... */ if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) { gn->mtime = now; } #else /* * This is what Make does and it's actually a good thing, as it * allows rules like * * cmp -s y.tab.h parse.h || cp y.tab.h parse.h * * to function as intended. Unfortunately, thanks to the stateless * nature of NFS (and the speed of this program), there are times * when the modification time of a file created on a remote * machine will not be modified before the stat() implied by * the Dir_MTime occurs, thus leading us to believe that the file * is unchanged, wreaking havoc with files that depend on this one. * * I have decided it is better to make too much than to make too * little, so this stuff is commented out unless you're sure it's * ok. * -- ardeb 1/12/88 */ if (noExecute || Dir_MTime(gn) == 0) { gn->mtime = now; } if (gn->cmtime > gn->mtime) gn->mtime = gn->cmtime; DEBUGF(MAKE, ("update time: %s\n", Targ_FmtTime(gn->mtime))); #endif if (!(gn->type & OP_EXEC)) { pgn->childMade = TRUE; Make_TimeStamp(pgn, gn); } } else if (keepgoing) { pgn->make = FALSE; } else { char *p1; printf("\n\nStop in %s.\n", Var_Value(".CURDIR", gn, &p1)); free(p1); exit(1); } } else if (gn->made == ERROR) { /* * Already had an error when making this beastie. Tell the parent * to abort. */ pgn->make = FALSE; } else { if (Lst_Member(gn->iParents, pgn) != NULL) { char *p1; Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn); free(p1); } switch(gn->made) { case BEINGMADE: Error("Graph cycles through %s\n", gn->name); gn->made = ERROR; pgn->make = FALSE; break; case MADE: if ((gn->type & OP_EXEC) == 0) { pgn->childMade = TRUE; Make_TimeStamp(pgn, gn); } break; case UPTODATE: if ((gn->type & OP_EXEC) == 0) { Make_TimeStamp(pgn, gn); } break; default: break; } } return (0); } /*- *----------------------------------------------------------------------- * Compat_Run -- * Start making again, given a list of target nodes. * * Results: * None. * * Side Effects: * Guess what? * *----------------------------------------------------------------------- */ void -Compat_Run(Lst targs) +Compat_Run(Lst *targs) { GNode *gn = NULL;/* Current root target */ int errors; /* Number of targets not remade due to errors */ CompatInit(); Shell_Init(); /* Set up shell. */ if (signal(SIGINT, SIG_IGN) != SIG_IGN) { signal(SIGINT, CompatCatchSig); } if (signal(SIGTERM, SIG_IGN) != SIG_IGN) { signal(SIGTERM, CompatCatchSig); } if (signal(SIGHUP, SIG_IGN) != SIG_IGN) { signal(SIGHUP, CompatCatchSig); } if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) { signal(SIGQUIT, CompatCatchSig); } ENDNode = Targ_FindNode(".END", TARG_CREATE); /* * If the user has defined a .BEGIN target, execute the commands attached * to it. */ if (!queryFlag) { gn = Targ_FindNode(".BEGIN", TARG_NOCREATE); if (gn != NULL) { Lst_ForEach(gn->commands, Compat_RunCommand, gn); if (gn->made == ERROR) { printf("\n\nStop.\n"); exit(1); } } } /* * For each entry in the list of targets to create, call CompatMake on * it to create the thing. CompatMake will leave the 'made' field of gn * in one of several states: * UPTODATE gn was already up-to-date * MADE gn was recreated successfully * ERROR An error occurred while gn was being created * ABORTED gn was not remade because one of its inferiors * could not be made due to errors. */ errors = 0; while (!Lst_IsEmpty(targs)) { gn = Lst_DeQueue(targs); CompatMake(gn, gn); if (gn->made == UPTODATE) { printf("`%s' is up to date.\n", gn->name); } else if (gn->made == ABORTED) { printf("`%s' not remade because of errors.\n", gn->name); errors += 1; } } /* * If the user has defined a .END target, run its commands. */ if (errors == 0) { Lst_ForEach(ENDNode->commands, Compat_RunCommand, gn); } } Index: head/usr.bin/make/dir.c =================================================================== --- head/usr.bin/make/dir.c (revision 138511) +++ head/usr.bin/make/dir.c (revision 138512) @@ -1,1261 +1,1261 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 1988, 1989 by Adam de Boor * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)dir.c 8.2 (Berkeley) 1/2/94 */ #include __FBSDID("$FreeBSD$"); /*- * dir.c -- * Directory searching using wildcards and/or normal names... * Used both for source wildcarding in the Makefile and for finding * implicit sources. * * The interface for this module is: * Dir_Init Initialize the module. * * Dir_End Cleanup the module. * * Dir_HasWildcards Returns TRUE if the name given it needs to * be wildcard-expanded. * * Dir_Expand Given a pattern and a path, return a Lst of names * which match the pattern on the search path. * * Dir_FindFile Searches for a file on a given search path. * If it exists, the entire path is returned. * Otherwise NULL is returned. * * Dir_MTime Return the modification time of a node. The file * is searched for along the default search path. * The path and mtime fields of the node are filled * in. * * Dir_AddDir Add a directory to a search path. * * Dir_MakeFlags Given a search path and a command flag, create * a string with each of the directories in the path * preceded by the command flag and all of them * separated by a space. * * Dir_Destroy Destroy an element of a search path. Frees up all * things that can be freed for the element as long * as the element is no longer referenced by any other * search path. * Dir_ClearPath Resets a search path to the empty list. * * For debugging: * Dir_PrintDirectories Print stats about the directory cache. */ #include #include #include #include #include #include "make.h" #include "hash.h" #include "dir.h" /* * A search path consists of a Lst of Path structures. A Path structure * has in it the name of the directory and a hash table of all the files * in the directory. This is used to cut down on the number of system * calls necessary to find implicit dependents and their like. Since * these searches are made before any actions are taken, we need not * worry about the directory changing due to creation commands. If this * hampers the style of some makefiles, they must be changed. * * A list of all previously-read directories is kept in the * openDirectories Lst. This list is checked first before a directory * is opened. * * The need for the caching of whole directories is brought about by * the multi-level transformation code in suff.c, which tends to search * for far more files than regular make does. In the initial * implementation, the amount of time spent performing "stat" calls was * truly astronomical. The problem with hashing at the start is, * of course, that pmake doesn't then detect changes to these directories * during the course of the make. Three possibilities suggest themselves: * * 1) just use stat to test for a file's existence. As mentioned * above, this is very inefficient due to the number of checks * engendered by the multi-level transformation code. * 2) use readdir() and company to search the directories, keeping * them open between checks. I have tried this and while it * didn't slow down the process too much, it could severely * affect the amount of parallelism available as each directory * open would take another file descriptor out of play for * handling I/O for another job. Given that it is only recently * that UNIX OS's have taken to allowing more than 20 or 32 * file descriptors for a process, this doesn't seem acceptable * to me. * 3) record the mtime of the directory in the Path structure and * verify the directory hasn't changed since the contents were * hashed. This will catch the creation or deletion of files, * but not the updating of files. However, since it is the * creation and deletion that is the problem, this could be * a good thing to do. Unfortunately, if the directory (say ".") * were fairly large and changed fairly frequently, the constant * rehashing could seriously degrade performance. It might be * good in such cases to keep track of the number of rehashes * and if the number goes over a (small) limit, resort to using * stat in its place. * * An additional thing to consider is that pmake is used primarily * to create C programs and until recently pcc-based compilers refused * to allow you to specify where the resulting object file should be * placed. This forced all objects to be created in the current * directory. This isn't meant as a full excuse, just an explanation of * some of the reasons for the caching used here. * * One more note: the location of a target's file is only performed * on the downward traversal of the graph and then only for terminal * nodes in the graph. This could be construed as wrong in some cases, * but prevents inadvertent modification of files when the "installed" * directory for a file is provided in the search path. * * Another data structure maintained by this module is an mtime * cache used when the searching of cached directories fails to find * a file. In the past, Dir_FindFile would simply perform an access() * call in such a case to determine if the file could be found using * just the name given. When this hit, however, all that was gained * was the knowledge that the file existed. Given that an access() is * essentially a stat() without the copyout() call, and that the same * filesystem overhead would have to be incurred in Dir_MTime, it made * sense to replace the access() with a stat() and record the mtime * in a cache for when Dir_MTime was actually called. */ -Lst dirSearchPath; /* main search path */ +Lst *dirSearchPath; /* main search path */ -static Lst openDirectories; /* the list of all open directories */ +static Lst *openDirectories; /* the list of all open directories */ /* * Variables for gathering statistics on the efficiency of the hashing * mechanism. */ static int hits; /* Found in directory cache */ static int misses; /* Sad, but not evil misses */ static int nearmisses; /* Found under search path */ static int bigmisses; /* Sought by itself */ static Path *dot; /* contents of current directory */ /* Results of doing a last-resort stat in Dir_FindFile -- * if we have to go to the system to find the file, we might as well * have its mtime on record. * XXX: If this is done way early, there's a chance other rules will * have already updated the file, in which case we'll update it again. * Generally, there won't be two rules to update a single file, so this * should be ok, but... */ static Hash_Table mtimes; static int DirFindName(void *, void *); static int DirPrintWord(void *, void *); static int DirPrintDir(void *, void *); /*- *----------------------------------------------------------------------- * Dir_Init -- * initialize things for this module * * Results: * none * * Side Effects: * none *----------------------------------------------------------------------- */ void Dir_Init(void) { dirSearchPath = Lst_Init(); openDirectories = Lst_Init(); Hash_InitTable(&mtimes, 0); } /*- *----------------------------------------------------------------------- * Dir_InitDot -- * initialize the "." directory * * Results: * none * * Side Effects: * some directories may be opened. *----------------------------------------------------------------------- */ void Dir_InitDot(void) { - LstNode ln; + LstNode *ln; Dir_AddDir(openDirectories, "."); if ((ln = Lst_Last(openDirectories)) == NULL) err(1, "cannot open current directory"); dot = Lst_Datum(ln); /* * We always need to have dot around, so we increment its * reference count to make sure it's not destroyed. */ dot->refCount += 1; } /*- *----------------------------------------------------------------------- * Dir_End -- * cleanup things for this module * * Results: * none * * Side Effects: * none *----------------------------------------------------------------------- */ void Dir_End(void) { dot->refCount -= 1; Dir_Destroy(dot); Dir_ClearPath(dirSearchPath); Lst_Destroy(dirSearchPath, NOFREE); Dir_ClearPath(openDirectories); Lst_Destroy(openDirectories, NOFREE); Hash_DeleteTable(&mtimes); } /*- *----------------------------------------------------------------------- * DirFindName -- * See if the Path structure describes the same directory as the * given one by comparing their names. Called from Dir_AddDir via * Lst_Find when searching the list of open directories. * * Results: * 0 if it is the same. Non-zero otherwise * * Side Effects: * None *----------------------------------------------------------------------- */ static int DirFindName(void *p, void *dname) { return (strcmp(((Path *)p)->name, dname)); } /*- *----------------------------------------------------------------------- * Dir_HasWildcards -- * See if the given name has any wildcard characters in it. * * Results: * returns TRUE if the word should be expanded, FALSE otherwise * * Side Effects: * none *----------------------------------------------------------------------- */ Boolean Dir_HasWildcards(const char *name) { const char *cp; int wild = 0, brace = 0, bracket = 0; for (cp = name; *cp; cp++) { switch (*cp) { case '{': brace++; wild = 1; break; case '}': brace--; break; case '[': bracket++; wild = 1; break; case ']': bracket--; break; case '?': case '*': wild = 1; break; default: break; } } return (wild && bracket == 0 && brace == 0); } /*- *----------------------------------------------------------------------- * DirMatchFiles -- * Given a pattern and a Path structure, see if any files * match the pattern and add their names to the 'expansions' list if * any do. This is incomplete -- it doesn't take care of patterns like * src / *src / *.c properly (just *.c on any of the directories), but it * will do for now. * * Results: * Always returns 0 * * Side Effects: * File names are added to the expansions lst. The directory will be * fully hashed when this is done. *----------------------------------------------------------------------- */ static int -DirMatchFiles(const char *pattern, const Path *p, Lst expansions) +DirMatchFiles(const char *pattern, const Path *p, Lst *expansions) { Hash_Search search; /* Index into the directory's table */ Hash_Entry *entry; /* Current entry in the table */ Boolean isDot; /* TRUE if the directory being searched is . */ isDot = (*p->name == '.' && p->name[1] == '\0'); for (entry = Hash_EnumFirst(&p->files, &search); entry != NULL; entry = Hash_EnumNext(&search)) { /* * See if the file matches the given pattern. Note we follow * the UNIX convention that dot files will only be found if * the pattern begins with a dot (note also that as a side * effect of the hashing scheme, .* won't match . or .. * since they aren't hashed). */ if (Str_Match(entry->name, pattern) && ((entry->name[0] != '.') || (pattern[0] == '.'))) { Lst_AtEnd(expansions, (isDot ? estrdup(entry->name) : str_concat(p->name, entry->name, STR_ADDSLASH))); } } return (0); } /*- *----------------------------------------------------------------------- * DirExpandCurly -- * Expand curly braces like the C shell. Does this recursively. * Note the special case: if after the piece of the curly brace is * done there are no wildcard characters in the result, the result is * placed on the list WITHOUT CHECKING FOR ITS EXISTENCE. The * given arguments are the entire word to expand, the first curly * brace in the word, the search path, and the list to store the * expansions in. * * Results: * None. * * Side Effects: * The given list is filled with the expansions... * *----------------------------------------------------------------------- */ static void -DirExpandCurly(const char *word, const char *brace, Lst path, Lst expansions) +DirExpandCurly(const char *word, const char *brace, Lst *path, Lst *expansions) { const char *end; /* Character after the closing brace */ const char *cp; /* Current position in brace clause */ const char *start; /* Start of current piece of brace clause */ int bracelevel; /* Number of braces we've seen. If we see a right brace * when this is 0, we've hit the end of the clause. */ char *file; /* Current expansion */ int otherLen; /* The length of the other pieces of the expansion * (chars before and after the clause in 'word') */ char *cp2; /* Pointer for checking for wildcards in * expansion before calling Dir_Expand */ start = brace + 1; /* * Find the end of the brace clause first, being wary of nested brace * clauses. */ for (end = start, bracelevel = 0; *end != '\0'; end++) { if (*end == '{') bracelevel++; else if ((*end == '}') && (bracelevel-- == 0)) break; } if (*end == '\0') { Error("Unterminated {} clause \"%s\"", start); return; } else end++; otherLen = brace - word + strlen(end); for (cp = start; cp < end; cp++) { /* * Find the end of this piece of the clause. */ bracelevel = 0; while (*cp != ',') { if (*cp == '{') bracelevel++; else if ((*cp == '}') && (bracelevel-- <= 0)) break; cp++; } /* * Allocate room for the combination and install the * three pieces. */ file = emalloc(otherLen + cp - start + 1); if (brace != word) strncpy(file, word, brace - word); if (cp != start) strncpy(&file[brace - word], start, cp - start); strcpy(&file[(brace - word) + (cp - start)], end); /* * See if the result has any wildcards in it. If we find one, * call Dir_Expand right away, telling it to place the result * on our list of expansions. */ for (cp2 = file; *cp2 != '\0'; cp2++) { switch (*cp2) { case '*': case '?': case '{': case '[': Dir_Expand(file, path, expansions); goto next; default: break; } } if (*cp2 == '\0') { /* * Hit the end w/o finding any wildcards, so stick * the expansion on the end of the list. */ Lst_AtEnd(expansions, file); } else { next: free(file); } start = cp + 1; } } /*- *----------------------------------------------------------------------- * DirExpandInt -- * Internal expand routine. Passes through the directories in the * path one by one, calling DirMatchFiles for each. NOTE: This still * doesn't handle patterns in directories... Works given a word to * expand, a path to look in, and a list to store expansions in. * * Results: * None. * * Side Effects: * Things are added to the expansions list. * *----------------------------------------------------------------------- */ static void -DirExpandInt(const char *word, Lst path, Lst expansions) +DirExpandInt(const char *word, Lst *path, Lst *expansions) { - LstNode ln; /* Current node */ + LstNode *ln; /* Current node */ Path *p; /* Directory in the node */ if (Lst_Open(path) == SUCCESS) { while ((ln = Lst_Next(path)) != NULL) { p = Lst_Datum(ln); DirMatchFiles(word, p, expansions); } Lst_Close(path); } } /*- *----------------------------------------------------------------------- * DirPrintWord -- * Print a word in the list of expansions. Callback for Dir_Expand * when DEBUG(DIR), via Lst_ForEach. * * Results: * === 0 * * Side Effects: * The passed word is printed, followed by a space. * *----------------------------------------------------------------------- */ static int DirPrintWord(void *word, void *dummy __unused) { DEBUGF(DIR, ("%s ", (char *)word)); return (0); } /*- *----------------------------------------------------------------------- * Dir_Expand -- * Expand the given word into a list of words by globbing it looking * in the directories on the given search path. * * Results: * A list of words consisting of the files which exist along the search * path matching the given pattern is placed in expansions. * * Side Effects: * Directories may be opened. Who knows? *----------------------------------------------------------------------- */ void -Dir_Expand(char *word, Lst path, Lst expansions) +Dir_Expand(char *word, Lst *path, Lst *expansions) { char *cp; DEBUGF(DIR, ("expanding \"%s\"...", word)); cp = strchr(word, '{'); if (cp != NULL) DirExpandCurly(word, cp, path, expansions); else { cp = strchr(word, '/'); if (cp != NULL) { /* * The thing has a directory component -- find the * first wildcard in the string. */ for (cp = word; *cp != '\0'; cp++) { if (*cp == '?' || *cp == '[' || *cp == '*' || *cp == '{') { break; } } if (*cp == '{') { /* * This one will be fun. */ DirExpandCurly(word, cp, path, expansions); return; } else if (*cp != '\0') { /* * Back up to the start of the component */ char *dirpath; while (cp > word && *cp != '/') cp--; if (cp != word) { char sc; /* * If the glob isn't in the first * component, try and find all the * components up to the one with a * wildcard. */ sc = cp[1]; cp[1] = '\0'; dirpath = Dir_FindFile(word, path); cp[1] = sc; /* * dirpath is null if can't find the * leading component * XXX: Dir_FindFile won't find internal * components. i.e. if the path contains * ../Etc/Object and we're looking for * Etc, * it won't be found. Ah well. * Probably not important. */ if (dirpath != NULL) { char *dp = &dirpath[strlen(dirpath) - 1]; if (*dp == '/') *dp = '\0'; path = Lst_Init(); Dir_AddDir(path, dirpath); DirExpandInt(cp + 1, path, expansions); Lst_Destroy(path, NOFREE); } } else { /* * Start the search from the local * directory */ DirExpandInt(word, path, expansions); } } else { /* * Return the file -- this should never happen. */ DirExpandInt(word, path, expansions); } } else { /* * First the files in dot */ DirMatchFiles(word, dot, expansions); /* * Then the files in every other directory on the path. */ DirExpandInt(word, path, expansions); } } if (DEBUG(DIR)) { Lst_ForEach(expansions, DirPrintWord, (void *)NULL); DEBUGF(DIR, ("\n")); } } /*- *----------------------------------------------------------------------- * Dir_FindFile -- * Find the file with the given name along the given search path. * * Results: * The path to the file or NULL. This path is guaranteed to be in a * different part of memory than name and so may be safely free'd. * * Side Effects: * If the file is found in a directory which is not on the path * already (either 'name' is absolute or it is a relative path * [ dir1/.../dirn/file ] which exists below one of the directories * already on the search path), its directory is added to the end * of the path on the assumption that there will be more files in * that directory later on. Sometimes this is true. Sometimes not. *----------------------------------------------------------------------- */ char * -Dir_FindFile(char *name, Lst path) +Dir_FindFile(char *name, Lst *path) { char *p1; /* pointer into p->name */ char *p2; /* pointer into name */ - LstNode ln; /* a list element */ + LstNode *ln; /* a list element */ char *file; /* the current filename to check */ Path *p; /* current path member */ char *cp; /* final component of the name */ Boolean hasSlash; /* true if 'name' contains a / */ struct stat stb; /* Buffer for stat, if necessary */ Hash_Entry *entry; /* Entry for mtimes table */ /* * Find the final component of the name and note whether it has a * slash in it (the name, I mean) */ cp = strrchr(name, '/'); if (cp != NULL) { hasSlash = TRUE; cp += 1; } else { hasSlash = FALSE; cp = name; } DEBUGF(DIR, ("Searching for %s...", name)); /* * No matter what, we always look for the file in the current directory * before anywhere else and we *do not* add the ./ to it if it exists. * This is so there are no conflicts between what the user specifies * (fish.c) and what pmake finds (./fish.c). */ if ((!hasSlash || (cp - name == 2 && *name == '.')) && (Hash_FindEntry(&dot->files, cp) != NULL)) { DEBUGF(DIR, ("in '.'\n")); hits += 1; dot->hits += 1; return (estrdup(name)); } if (Lst_Open(path) == FAILURE) { DEBUGF(DIR, ("couldn't open path, file not found\n")); misses += 1; return (NULL); } /* * We look through all the directories on the path seeking one which * contains the final component of the given name and whose final * component(s) match the name's initial component(s). If such a beast * is found, we concatenate the directory name and the final component * and return the resulting string. If we don't find any such thing, * we go on to phase two... */ while ((ln = Lst_Next(path)) != NULL) { p = Lst_Datum(ln); DEBUGF(DIR, ("%s...", p->name)); if (Hash_FindEntry(&p->files, cp) != NULL) { DEBUGF(DIR, ("here...")); if (hasSlash) { /* * If the name had a slash, its initial * components and p's final components must * match. This is false if a mismatch is * encountered before all of the initial * components have been checked (p2 > name at * the end of the loop), or we matched only * part of one of the components of p * along with all the rest of them (*p1 != '/'). */ p1 = p->name + strlen(p->name) - 1; p2 = cp - 2; while (p2 >= name && p1 >= p->name && *p1 == *p2) { p1 -= 1; p2 -= 1; } if (p2 >= name || (p1 >= p->name && *p1 != '/')) { DEBUGF(DIR, ("component mismatch -- " "continuing...")); continue; } } file = str_concat(p->name, cp, STR_ADDSLASH); DEBUGF(DIR, ("returning %s\n", file)); Lst_Close(path); p->hits += 1; hits += 1; return (file); } else if (hasSlash) { /* * If the file has a leading path component and that * component exactly matches the entire name of the * current search directory, we assume the file * doesn't exist and return NULL. */ for (p1 = p->name, p2 = name; *p1 && *p1 == *p2; p1++, p2++) continue; if (*p1 == '\0' && p2 == cp - 1) { Lst_Close(path); if (*cp == '\0' || ISDOT(cp) || ISDOTDOT(cp)) { DEBUGF(DIR, ("returning %s\n", name)); return (estrdup(name)); } else { DEBUGF(DIR, ("must be here but isn't --" " returning NULL\n")); return (NULL); } } } } /* * We didn't find the file on any existing members of the directory. * If the name doesn't contain a slash, that means it doesn't exist. * If it *does* contain a slash, however, there is still hope: it * could be in a subdirectory of one of the members of the search * path. (eg. /usr/include and sys/types.h. The above search would * fail to turn up types.h in /usr/include, but it *is* in * /usr/include/sys/types.h) If we find such a beast, we assume there * will be more (what else can we assume?) and add all but the last * component of the resulting name onto the search path (at the * end). This phase is only performed if the file is *not* absolute. */ if (!hasSlash) { DEBUGF(DIR, ("failed.\n")); misses += 1; return (NULL); } if (*name != '/') { Boolean checkedDot = FALSE; DEBUGF(DIR, ("failed. Trying subdirectories...")); Lst_Open(path); while ((ln = Lst_Next(path)) != NULL) { p = Lst_Datum(ln); if (p != dot) { file = str_concat(p->name, name, STR_ADDSLASH); } else { /* * Checking in dot -- DON'T put a leading ./ * on the thing. */ file = estrdup(name); checkedDot = TRUE; } DEBUGF(DIR, ("checking %s...", file)); if (stat(file, &stb) == 0) { DEBUGF(DIR, ("got it.\n")); Lst_Close(path); /* * We've found another directory to search. We * know there's a slash in 'file' because we put * one there. We nuke it after finding it and * call Dir_AddDir to add this new directory * onto the existing search path. Once that's * done, we restore the slash and triumphantly * return the file name, knowing that should a * file in this directory every be referenced * again in such a manner, we will find it * without having to do numerous numbers of * access calls. Hurrah! */ cp = strrchr(file, '/'); *cp = '\0'; Dir_AddDir(path, file); *cp = '/'; /* * Save the modification time so if * it's needed, we don't have to fetch it again. */ DEBUGF(DIR, ("Caching %s for %s\n", Targ_FmtTime(stb.st_mtime), file)); entry = Hash_CreateEntry(&mtimes, file, (Boolean *)NULL); Hash_SetValue(entry, (void *)(long)stb.st_mtime); nearmisses += 1; return (file); } else { free(file); } } DEBUGF(DIR, ("failed. ")); Lst_Close(path); if (checkedDot) { /* * Already checked by the given name, since . was in * the path, so no point in proceeding... */ DEBUGF(DIR, ("Checked . already, returning NULL\n")); return (NULL); } } /* * Didn't find it that way, either. Sigh. Phase 3. Add its directory * onto the search path in any case, just in case, then look for the * thing in the hash table. If we find it, grand. We return a new * copy of the name. Otherwise we sadly return a NULL pointer. Sigh. * Note that if the directory holding the file doesn't exist, this will * do an extra search of the final directory on the path. Unless * something weird happens, this search won't succeed and life will * be groovy. * * Sigh. We cannot add the directory onto the search path because * of this amusing case: * $(INSTALLDIR)/$(FILE): $(FILE) * * $(FILE) exists in $(INSTALLDIR) but not in the current one. * When searching for $(FILE), we will find it in $(INSTALLDIR) * b/c we added it here. This is not good... */ #ifdef notdef cp[-1] = '\0'; Dir_AddDir(path, name); cp[-1] = '/'; bigmisses += 1; ln = Lst_Last(path); if (ln == NULL) return (NULL); p = Lst_Datum(ln); if (Hash_FindEntry(&p->files, cp) != NULL) { return (estrdup(name)); return (NULL); #else /* !notdef */ DEBUGF(DIR, ("Looking for \"%s\"...", name)); bigmisses += 1; entry = Hash_FindEntry(&mtimes, name); if (entry != NULL) { DEBUGF(DIR, ("got it (in mtime cache)\n")); return (estrdup(name)); } else if (stat (name, &stb) == 0) { entry = Hash_CreateEntry(&mtimes, name, (Boolean *)NULL); DEBUGF(DIR, ("Caching %s for %s\n", Targ_FmtTime(stb.st_mtime), name)); Hash_SetValue(entry, (void *)(long)stb.st_mtime); return (estrdup(name)); } else { DEBUGF(DIR, ("failed. Returning NULL\n")); return (NULL); } #endif /* notdef */ } /*- *----------------------------------------------------------------------- * Dir_MTime -- * Find the modification time of the file described by gn along the * search path dirSearchPath. * * Results: * The modification time or 0 if it doesn't exist * * Side Effects: * The modification time is placed in the node's mtime slot. * If the node didn't have a path entry before, and Dir_FindFile * found one for it, the full name is placed in the path slot. *----------------------------------------------------------------------- */ int Dir_MTime(GNode *gn) { char *fullName; /* the full pathname of name */ struct stat stb; /* buffer for finding the mod time */ Hash_Entry *entry; if (gn->type & OP_ARCHV) return (Arch_MTime(gn)); else if (gn->path == NULL) fullName = Dir_FindFile(gn->name, dirSearchPath); else fullName = gn->path; if (fullName == NULL) fullName = estrdup(gn->name); entry = Hash_FindEntry(&mtimes, fullName); if (entry != NULL) { /* * Only do this once -- the second time folks are checking to * see if the file was actually updated, so we need to * actually go to the filesystem. */ DEBUGF(DIR, ("Using cached time %s for %s\n", Targ_FmtTime((time_t)(long)Hash_GetValue(entry)), fullName)); stb.st_mtime = (time_t)(long)Hash_GetValue(entry); Hash_DeleteEntry(&mtimes, entry); } else if (stat(fullName, &stb) < 0) { if (gn->type & OP_MEMBER) { if (fullName != gn->path) free(fullName); return (Arch_MemMTime(gn)); } else { stb.st_mtime = 0; } } if (fullName && gn->path == (char *)NULL) gn->path = fullName; gn->mtime = stb.st_mtime; return (gn->mtime); } /*- *----------------------------------------------------------------------- * Dir_AddDir -- * Add the given name to the end of the given path. The order of * the arguments is backwards so ParseDoDependency can do a * Lst_ForEach of its list of paths... * * Results: * none * * Side Effects: * A structure is added to the list and the directory is * read and hashed. *----------------------------------------------------------------------- */ void -Dir_AddDir(Lst path, char *name) +Dir_AddDir(Lst *path, char *name) { - LstNode ln; /* node in case Path structure is found */ + LstNode *ln; /* node in case Path structure is found */ Path *p; /* pointer to new Path structure */ DIR *d; /* for reading directory */ struct dirent *dp; /* entry in directory */ ln = Lst_Find(openDirectories, name, DirFindName); if (ln != NULL) { p = Lst_Datum(ln); if (Lst_Member(path, p) == NULL) { p->refCount += 1; Lst_AtEnd(path, p); } } else { DEBUGF(DIR, ("Caching %s...", name)); if ((d = opendir(name)) != NULL) { p = emalloc(sizeof(Path)); p->name = estrdup(name); p->hits = 0; p->refCount = 1; Hash_InitTable(&p->files, -1); while ((dp = readdir(d)) != NULL) { #if defined(sun) && defined(d_ino) /* d_ino is a sunos4 #define for d_fileno */ /* * The sun directory library doesn't check for * a 0 inode (0-inode slots just take up space), * so we have to do it ourselves. */ if (dp->d_fileno == 0) continue; #endif /* sun && d_ino */ /* Skip the '.' and '..' entries by checking * for them specifically instead of assuming * readdir() reuturns them in that order when * first going through a directory. This is * needed for XFS over NFS filesystems since * SGI does not guarantee that these are the * first two entries returned from readdir(). */ if (ISDOT(dp->d_name) || ISDOTDOT(dp->d_name)) continue; Hash_CreateEntry(&p->files, dp->d_name, (Boolean *)NULL); } closedir(d); Lst_AtEnd(openDirectories, p); if (path != openDirectories) Lst_AtEnd(path, p); } DEBUGF(DIR, ("done\n")); } } /*- *----------------------------------------------------------------------- * Dir_CopyDir -- * Callback function for duplicating a search path via Lst_Duplicate. * Ups the reference count for the directory. * * Results: * Returns the Path it was given. * * Side Effects: * The refCount of the path is incremented. * *----------------------------------------------------------------------- */ void * Dir_CopyDir(void *p) { ((Path *)p)->refCount += 1; return (p); } /*- *----------------------------------------------------------------------- * Dir_MakeFlags -- * Make a string by taking all the directories in the given search * path and preceding them by the given flag. Used by the suffix * module to create variables for compilers based on suffix search * paths. * * Results: * The string mentioned above. Note that there is no space between * the given flag and each directory. The empty string is returned if * Things don't go well. * * Side Effects: * None *----------------------------------------------------------------------- */ char * -Dir_MakeFlags(char *flag, Lst path) +Dir_MakeFlags(char *flag, Lst *path) { char *str; /* the string which will be returned */ char *tstr; /* the current directory preceded by 'flag' */ - LstNode ln; /* the node of the current directory */ + LstNode *ln; /* the node of the current directory */ Path *p; /* the structure describing the current directory */ str = estrdup(""); if (Lst_Open(path) == SUCCESS) { while ((ln = Lst_Next(path)) != NULL) { p = Lst_Datum(ln); tstr = str_concat(flag, p->name, 0); str = str_concat(str, tstr, STR_ADDSPACE | STR_DOFREE); } Lst_Close(path); } return (str); } /*- *----------------------------------------------------------------------- * Dir_Destroy -- * Nuke a directory descriptor, if possible. Callback procedure * for the suffixes module when destroying a search path. * * Results: * None. * * Side Effects: * If no other path references this directory (refCount == 0), * the Path and all its data are freed. * *----------------------------------------------------------------------- */ void Dir_Destroy(void *pp) { Path *p = pp; p->refCount -= 1; if (p->refCount == 0) { - LstNode ln; + LstNode *ln; ln = Lst_Member(openDirectories, p); Lst_Remove(openDirectories, ln); Hash_DeleteTable(&p->files); free(p->name); free(p); } } /*- *----------------------------------------------------------------------- * Dir_ClearPath -- * Clear out all elements of the given search path. This is different * from destroying the list, notice. * * Results: * None. * * Side Effects: * The path is set to the empty list. * *----------------------------------------------------------------------- */ void -Dir_ClearPath(Lst path) +Dir_ClearPath(Lst *path) { Path *p; while (!Lst_IsEmpty(path)) { p = Lst_DeQueue(path); Dir_Destroy(p); } } /*- *----------------------------------------------------------------------- * Dir_Concat -- * Concatenate two paths, adding the second to the end of the first. * Makes sure to avoid duplicates. * * Results: * None * * Side Effects: * Reference counts for added dirs are upped. * *----------------------------------------------------------------------- */ void -Dir_Concat(Lst path1, Lst path2) +Dir_Concat(Lst *path1, Lst *path2) { - LstNode ln; + LstNode *ln; Path *p; for (ln = Lst_First(path2); ln != NULL; ln = Lst_Succ(ln)) { p = Lst_Datum(ln); if (Lst_Member(path1, p) == NULL) { p->refCount += 1; Lst_AtEnd(path1, p); } } } /********** DEBUG INFO **********/ void Dir_PrintDirectories(void) { - LstNode ln; + LstNode *ln; Path *p; printf("#*** Directory Cache:\n"); printf("# Stats: %d hits %d misses %d near misses %d losers (%d%%)\n", hits, misses, nearmisses, bigmisses, (hits + bigmisses + nearmisses ? hits * 100 / (hits + bigmisses + nearmisses) : 0)); printf("# %-20s referenced\thits\n", "directory"); if (Lst_Open(openDirectories) == SUCCESS) { while ((ln = Lst_Next(openDirectories)) != NULL) { p = Lst_Datum(ln); printf("# %-20s %10d\t%4d\n", p->name, p->refCount, p->hits); } Lst_Close(openDirectories); } } static int DirPrintDir(void *p, void *dummy __unused) { printf("%s ", ((Path *)p)->name); return (0); } void -Dir_PrintPath(Lst path) +Dir_PrintPath(Lst *path) { Lst_ForEach(path, DirPrintDir, (void *)NULL); } Index: head/usr.bin/make/dir.h =================================================================== --- head/usr.bin/make/dir.h (revision 138511) +++ head/usr.bin/make/dir.h (revision 138512) @@ -1,73 +1,73 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 1988, 1989 by Adam de Boor * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)dir.h 8.2 (Berkeley) 4/28/95 * $FreeBSD$ */ /* dir.h -- */ #ifndef _DIR #define _DIR typedef struct Path { char *name; /* Name of directory */ int refCount; /* Number of paths with this directory */ int hits; /* Number of times a file in this dirextory has * been found */ Hash_Table files; /* Hash table of files in directory */ } Path; void Dir_Init(void); void Dir_InitDot(void); void Dir_End(void); Boolean Dir_HasWildcards(const char *); -void Dir_Expand(char *, Lst, Lst); -char *Dir_FindFile(char *, Lst); +void Dir_Expand(char *, Lst *, Lst *); +char *Dir_FindFile(char *, Lst *); int Dir_MTime(GNode *); -void Dir_AddDir(Lst, char *); -char *Dir_MakeFlags(char *, Lst); -void Dir_ClearPath(Lst); -void Dir_Concat(Lst, Lst); +void Dir_AddDir(Lst *, char *); +char *Dir_MakeFlags(char *, Lst *); +void Dir_ClearPath(Lst *); +void Dir_Concat(Lst *, Lst *); void Dir_PrintDirectories(void); -void Dir_PrintPath(Lst); +void Dir_PrintPath(Lst *); void Dir_Destroy(void *); void *Dir_CopyDir(void *); #endif /* _DIR */ Index: head/usr.bin/make/for.c =================================================================== --- head/usr.bin/make/for.c (revision 138511) +++ head/usr.bin/make/for.c (revision 138512) @@ -1,295 +1,295 @@ /* * Copyright (c) 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Christos Zoulas. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)for.c 8.1 (Berkeley) 6/6/93 */ #include __FBSDID("$FreeBSD$"); /*- * for.c -- * Functions to handle loops in a makefile. * * Interface: * For_Eval Evaluate the loop in the passed line. * For_Run Run accumulated loop * */ #include #include "make.h" #include "hash.h" #include "dir.h" #include "buf.h" /* * For statements are of the form: * * .for in * ... * .endfor * * The trick is to look for the matching end inside for for loop * To do that, we count the current nesting level of the for loops. * and the .endfor statements, accumulating all the statements between * the initial .for loop and the matching .endfor; * then we evaluate the for loop for each variable in the varlist. */ static int forLevel = 0; /* Nesting level */ static char *forVar; /* Iteration variable */ static Buffer forBuf; /* Commands in loop */ -static Lst forLst; /* List of items */ +static Lst *forLst; /* List of items */ /* * State of a for loop. */ typedef struct _For { Buffer buf; /* Unexpanded buffer */ char* var; /* Index name */ - Lst lst; /* List of variables */ + Lst *lst; /* List of variables */ int lineno; /* Line # */ } For; static int ForExec(void *, void *); /*- *----------------------------------------------------------------------- * For_Eval -- * Evaluate the for loop in the passed line. The line * looks like this: * .for in * * Results: * TRUE: We found a for loop, or we are inside a for loop * FALSE: We did not find a for loop, or we found the end of the for * for loop. * * Side Effects: * None. * *----------------------------------------------------------------------- */ int For_Eval(char *line) { char *ptr = line, *sub, *wrd; int level; /* Level at which to report errors. */ level = PARSE_FATAL; if (forLevel == 0) { Buffer buf; size_t varlen; for (ptr++; *ptr && isspace((unsigned char)*ptr); ptr++) continue; /* * If we are not in a for loop quickly determine if the statement is * a for. */ if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' || !isspace((unsigned char)ptr[3])) return (FALSE); ptr += 3; /* * we found a for loop, and now we are going to parse it. */ while (*ptr && isspace((unsigned char)*ptr)) ptr++; /* * Grab the variable */ buf = Buf_Init(0); for (wrd = ptr; *ptr && !isspace((unsigned char)*ptr); ptr++) continue; Buf_AddBytes(buf, ptr - wrd, (Byte *)wrd); forVar = (char *)Buf_GetAll(buf, &varlen); if (varlen == 0) { Parse_Error(level, "missing variable in for"); return (0); } Buf_Destroy(buf, FALSE); while (*ptr && isspace((unsigned char)*ptr)) ptr++; /* * Grab the `in' */ if (ptr[0] != 'i' || ptr[1] != 'n' || !isspace((unsigned char)ptr[2])) { Parse_Error(level, "missing `in' in for"); printf("%s\n", ptr); return (0); } ptr += 3; while (*ptr && isspace((unsigned char)*ptr)) ptr++; /* * Make a list with the remaining words */ forLst = Lst_Init(); buf = Buf_Init(0); sub = Var_Subst(NULL, ptr, VAR_CMD, FALSE); #define ADDWORD() \ Buf_AddBytes(buf, ptr - wrd, (Byte *)wrd), \ Buf_AddByte(buf, (Byte)'\0'), \ Lst_AtFront(forLst, Buf_GetAll(buf, &varlen)), \ Buf_Destroy(buf, FALSE) for (ptr = sub; *ptr && isspace((unsigned char)*ptr); ptr++) continue; for (wrd = ptr; *ptr; ptr++) if (isspace((unsigned char)*ptr)) { ADDWORD(); buf = Buf_Init(0); while (*ptr && isspace((unsigned char)*ptr)) ptr++; wrd = ptr--; } DEBUGF(FOR, ("For: Iterator %s List %s\n", forVar, sub)); if (ptr - wrd > 0) ADDWORD(); else Buf_Destroy(buf, TRUE); free(sub); forBuf = Buf_Init(0); forLevel++; return (1); } else if (*ptr == '.') { for (ptr++; *ptr && isspace((unsigned char)*ptr); ptr++) continue; if (strncmp(ptr, "endfor", 6) == 0 && (isspace((unsigned char)ptr[6]) || !ptr[6])) { DEBUGF(FOR, ("For: end for %d\n", forLevel)); if (--forLevel < 0) { Parse_Error(level, "for-less endfor"); return (0); } } else if (strncmp(ptr, "for", 3) == 0 && isspace((unsigned char)ptr[3])) { forLevel++; DEBUGF(FOR, ("For: new loop %d\n", forLevel)); } } if (forLevel != 0) { Buf_AddBytes(forBuf, strlen(line), (Byte *)line); Buf_AddByte(forBuf, (Byte)'\n'); return (1); } else { return (0); } } /*- *----------------------------------------------------------------------- * ForExec -- * Expand the for loop for this index and push it in the Makefile * * Results: * None. * * Side Effects: * None. * *----------------------------------------------------------------------- */ static int ForExec(void *namep, void *argp) { char *name = namep; For *arg = argp; size_t len; Var_Set(arg->var, name, VAR_GLOBAL); DEBUGF(FOR, ("--- %s = %s\n", arg->var, name)); Parse_FromString(Var_Subst(arg->var, (char *)Buf_GetAll(arg->buf, &len), VAR_GLOBAL, FALSE), arg->lineno); Var_Delete(arg->var, VAR_GLOBAL); return (0); } /*- *----------------------------------------------------------------------- * For_Run -- * Run the for loop, immitating the actions of an include file * * Results: * None. * * Side Effects: * None. * *----------------------------------------------------------------------- */ void For_Run(int lineno) { For arg; if (forVar == NULL || forBuf == NULL || forLst == NULL) return; arg.var = forVar; arg.buf = forBuf; arg.lst = forLst; arg.lineno = lineno; forVar = NULL; forBuf = NULL; forLst = NULL; Lst_ForEach(arg.lst, ForExec, &arg); free(arg.var); Lst_Destroy(arg.lst, free); Buf_Destroy(arg.buf, TRUE); } Index: head/usr.bin/make/job.c =================================================================== --- head/usr.bin/make/job.c (revision 138511) +++ head/usr.bin/make/job.c (revision 138512) @@ -1,2788 +1,2788 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 1988, 1989 by Adam de Boor * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)job.c 8.2 (Berkeley) 3/19/94 */ #include __FBSDID("$FreeBSD$"); #ifndef OLD_JOKE #define OLD_JOKE 0 #endif /* OLD_JOKE */ /*- * job.c -- * handle the creation etc. of our child processes. * * Interface: * Job_Make Start the creation of the given target. * * Job_CatchChildren Check for and handle the termination of any * children. This must be called reasonably * frequently to keep the whole make going at * a decent clip, since job table entries aren't * removed until their process is caught this way. * Its single argument is TRUE if the function * should block waiting for a child to terminate. * * Job_CatchOutput Print any output our children have produced. * Should also be called fairly frequently to * keep the user informed of what's going on. * If no output is waiting, it will block for * a time given by the SEL_* constants, below, * or until output is ready. * * Job_Init Called to intialize this module. in addition, * any commands attached to the .BEGIN target * are executed before this function returns. * Hence, the makefile must have been parsed * before this function is called. * * Job_Full Return TRUE if the job table is filled. * * Job_Empty Return TRUE if the job table is completely * empty. * * Job_ParseShell Given the line following a .SHELL target, parse * the line as a shell specification. Returns * FAILURE if the spec was incorrect. * * Job_Finish Perform any final processing which needs doing. * This includes the execution of any commands * which have been/were attached to the .END * target. It should only be called when the * job table is empty. * * Job_AbortAll Abort all currently running jobs. It doesn't * handle output or do anything for the jobs, * just kills them. It should only be called in * an emergency, as it were. * * Job_CheckCommands Verify that the commands for a target are * ok. Provide them if necessary and possible. * * Job_Touch Update a target without really updating it. * * Job_Wait Wait for all currently-running jobs to finish. */ #include #include #include #include #ifdef USE_KQUEUE #include #endif #include #include #include #include #include #include #include #include #include #include "make.h" #include "hash.h" #include "dir.h" #include "job.h" #include "pathnames.h" #define STATIC static /* * error handling variables */ static int errors = 0; /* number of errors reported */ static int aborting = 0; /* why is the make aborting? */ #define ABORT_ERROR 1 /* Because of an error */ #define ABORT_INTERRUPT 2 /* Because it was interrupted */ #define ABORT_WAIT 3 /* Waiting for jobs to finish */ /* * XXX: Avoid SunOS bug... FILENO() is fp->_file, and file * is a char! So when we go above 127 we turn negative! */ #define FILENO(a) ((unsigned)fileno(a)) /* * post-make command processing. The node postCommands is really just the * .END target but we keep it around to avoid having to search for it * all the time. */ static GNode *postCommands; /* node containing commands to execute when * everything else is done */ static int numCommands; /* The number of commands actually printed * for a target. Should this number be * 0, no shell will be executed. */ /* * Return values from JobStart. */ #define JOB_RUNNING 0 /* Job is running */ #define JOB_ERROR 1 /* Error in starting the job */ #define JOB_FINISHED 2 /* The job is already finished */ #define JOB_STOPPED 3 /* The job is stopped */ /* * tfile is used to build temp file names to store shell commands to * execute. */ static char tfile[sizeof(TMPPAT)]; /* * Descriptions for various shells. */ static const DEF_SHELL_STRUCT(CShell, const) shells[] = { /* * CSH description. The csh can do echo control by playing * with the setting of the 'echo' shell variable. Sadly, * however, it is unable to do error control nicely. */ { "csh", TRUE, "unset verbose", "set verbose", "unset verbose", 13, FALSE, "echo \"%s\"\n", "csh -c \"%s || exit 0\"", "v", "e", }, /* * SH description. Echo control is also possible and, under * sun UNIX anyway, one can even control error checking. */ { "sh", TRUE, "set -", "set -v", "set -", 5, TRUE, "set -e", "set +e", #ifdef OLDBOURNESHELL FALSE, "echo \"%s\"\n", "sh -c '%s || exit 0'\n", #endif "v", "e", }, /* * KSH description. The Korn shell has a superset of * the Bourne shell's functionality. */ { "ksh", TRUE, "set -", "set -v", "set -", 5, TRUE, "set -e", "set +e", "v", "e", }, }; static Shell *commandShell = NULL; /* this is the shell to which we pass * all commands in the Makefile. It is * set by the Job_ParseShell function */ char *shellPath = NULL, /* full pathname of executable image */ *shellName = NULL; /* last component of shell */ static int maxJobs; /* The most children we can run at once */ STATIC int nJobs; /* The number of children currently running */ -STATIC Lst jobs; /* The structures that describe them */ +STATIC Lst *jobs; /* The structures that describe them */ STATIC Boolean jobFull; /* Flag to tell when the job table is full. It * is set TRUE when (1) the total number of * running jobs equals the maximum allowed */ #ifdef USE_KQUEUE static int kqfd; /* File descriptor obtained by kqueue() */ #else static fd_set outputs; /* Set of descriptors of pipes connected to * the output channels of children */ #endif STATIC GNode *lastNode; /* The node for which output was most recently * produced. */ STATIC char *targFmt; /* Format string to use to head output from a * job when it's not the most-recent job heard * from */ #define TARG_FMT "--- %s ---\n" /* Default format */ #define MESSAGE(fp, gn) \ fprintf(fp, targFmt, gn->name); /* * When JobStart attempts to run a job but isn't allowed to * or when Job_CatchChildren detects a job that has * been stopped somehow, the job is placed on the stoppedJobs queue to be run * when the next job finishes. */ -STATIC Lst stoppedJobs; /* Lst of Job structures describing +STATIC Lst *stoppedJobs; /* Lst of Job structures describing * jobs that were stopped due to concurrency * limits or externally */ STATIC int fifoFd; /* Fd of our job fifo */ STATIC char fifoName[] = "/tmp/make_fifo_XXXXXXXXX"; STATIC int fifoMaster; static sig_atomic_t interrupted; #if defined(USE_PGRP) && defined(SYSV) # define KILL(pid, sig) killpg(-(pid), (sig)) #else # if defined(USE_PGRP) # define KILL(pid, sig) killpg((pid), (sig)) # else # define KILL(pid, sig) kill((pid), (sig)) # endif #endif /* * Grmpf... There is no way to set bits of the wait structure * anymore with the stupid W*() macros. I liked the union wait * stuff much more. So, we devise our own macros... This is * really ugly, use dramamine sparingly. You have been warned. */ #define W_SETMASKED(st, val, fun) \ { \ int sh = (int)~0; \ int mask = fun(sh); \ \ for (sh = 0; ((mask >> sh) & 1) == 0; sh++) \ continue; \ *(st) = (*(st) & ~mask) | ((val) << sh); \ } #define W_SETTERMSIG(st, val) W_SETMASKED(st, val, WTERMSIG) #define W_SETEXITSTATUS(st, val) W_SETMASKED(st, val, WEXITSTATUS) static int JobCondPassSig(void *, void *); static void JobPassSig(int); static int JobCmpPid(void *, void *); static int JobPrintCommand(void *, void *); static int JobSaveCommand(void *, void *); static void JobClose(Job *); static void JobFinish(Job *, int *); static void JobExec(Job *, char **); static void JobMakeArgv(Job *, char **); static void JobRestart(Job *); static int JobStart(GNode *, int, Job *); static char *JobOutput(Job *, char *, char *, int); static void JobDoOutput(Job *, Boolean); static Shell *JobMatchShell(const char *); static void JobInterrupt(int, int); static void JobRestartJobs(void); /* * JobCatchSignal * * Got a signal. Set global variables and hope that someone will * handle it. */ static void JobCatchSig(int signo) { interrupted = signo; } /*- *----------------------------------------------------------------------- * JobCondPassSig -- * Pass a signal to a job if USE_PGRP is defined. * * Results: * === 0 * * Side Effects: * None, except the job may bite it. * *----------------------------------------------------------------------- */ static int JobCondPassSig(void *jobp, void *signop) { Job *job = jobp; int signo = *(int *)signop; DEBUGF(JOB, ("JobCondPassSig passing signal %d to child %d.\n", signo, job->pid)); KILL(job->pid, signo); return (0); } /*- *----------------------------------------------------------------------- * JobPassSig -- * Pass a signal on to all local jobs if * USE_PGRP is defined, then die ourselves. * * Results: * None. * * Side Effects: * We die by the same signal. * *----------------------------------------------------------------------- */ static void JobPassSig(int signo) { sigset_t nmask, omask; struct sigaction act; sigemptyset(&nmask); sigaddset(&nmask, signo); sigprocmask(SIG_SETMASK, &nmask, &omask); DEBUGF(JOB, ("JobPassSig(%d) called.\n", signo)); Lst_ForEach(jobs, JobCondPassSig, &signo); /* * Deal with proper cleanup based on the signal received. We only run * the .INTERRUPT target if the signal was in fact an interrupt. The other * three termination signals are more of a "get out *now*" command. */ if (signo == SIGINT) { JobInterrupt(TRUE, signo); } else if ((signo == SIGHUP) || (signo == SIGTERM) || (signo == SIGQUIT)) { JobInterrupt(FALSE, signo); } /* * Leave gracefully if SIGQUIT, rather than core dumping. */ if (signo == SIGQUIT) { signo = SIGINT; } /* * Send ourselves the signal now we've given the message to everyone else. * Note we block everything else possible while we're getting the signal. * This ensures that all our jobs get continued when we wake up before * we take any other signal. * XXX this comment seems wrong. */ act.sa_handler = SIG_DFL; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(signo, &act, NULL); DEBUGF(JOB, ("JobPassSig passing signal to self, mask = %x.\n", ~0 & ~(1 << (signo - 1)))); signal(signo, SIG_DFL); KILL(getpid(), signo); signo = SIGCONT; Lst_ForEach(jobs, JobCondPassSig, &signo); sigprocmask(SIG_SETMASK, &omask, NULL); sigprocmask(SIG_SETMASK, &omask, NULL); act.sa_handler = JobPassSig; sigaction(signo, &act, NULL); } /*- *----------------------------------------------------------------------- * JobCmpPid -- * Compare the pid of the job with the given pid and return 0 if they * are equal. This function is called from Job_CatchChildren via * Lst_Find to find the job descriptor of the finished job. * * Results: * 0 if the pid's match * * Side Effects: * None *----------------------------------------------------------------------- */ static int JobCmpPid(void *job, void *pid) { return (*(int *)pid - ((Job *)job)->pid); } /*- *----------------------------------------------------------------------- * JobPrintCommand -- * Put out another command for the given job. If the command starts * with an @ or a - we process it specially. In the former case, * so long as the -s and -n flags weren't given to make, we stick * a shell-specific echoOff command in the script. In the latter, * we ignore errors for the entire job, unless the shell has error * control. * If the command is just "..." we take all future commands for this * job to be commands to be executed once the entire graph has been * made and return non-zero to signal that the end of the commands * was reached. These commands are later attached to the postCommands * node and executed by Job_Finish when all things are done. * This function is called from JobStart via Lst_ForEach. * * Results: * Always 0, unless the command was "..." * * Side Effects: * If the command begins with a '-' and the shell has no error control, * the JOB_IGNERR flag is set in the job descriptor. * If the command is "..." and we're not ignoring such things, * tailCmds is set to the successor node of the cmd. * numCommands is incremented if the command is actually printed. *----------------------------------------------------------------------- */ static int JobPrintCommand(void *cmdp, void *jobp) { Boolean noSpecials; /* true if we shouldn't worry about * inserting special commands into * the input stream. */ Boolean shutUp = FALSE; /* true if we put a no echo command * into the command file */ Boolean errOff = FALSE; /* true if we turned error checking * off before printing the command * and need to turn it back on */ char *cmdTemplate; /* Template to use when printing the * command */ char *cmdStart; /* Start of expanded command */ - LstNode cmdNode; /* Node for replacing the command */ + LstNode *cmdNode; /* Node for replacing the command */ char *cmd = cmdp; Job *job = jobp; noSpecials = (noExecute && !(job->node->type & OP_MAKE)); if (strcmp(cmd, "...") == 0) { job->node->type |= OP_SAVE_CMDS; if ((job->flags & JOB_IGNDOTS) == 0) { job->tailCmds = Lst_Succ(Lst_Member(job->node->commands, cmd)); return (1); } return (0); } #define DBPRINTF(fmt, arg) \ DEBUGF(JOB, (fmt, arg)); \ fprintf(job->cmdFILE, fmt, arg); \ fflush(job->cmdFILE); numCommands += 1; /* * For debugging, we replace each command with the result of expanding * the variables in the command. */ cmdNode = Lst_Member(job->node->commands, cmd); cmdStart = cmd = Var_Subst(NULL, cmd, job->node, FALSE); Lst_Replace(cmdNode, cmdStart); cmdTemplate = "%s\n"; /* * Check for leading @', -' or +'s to control echoing, error checking, * and execution on -n. */ while (*cmd == '@' || *cmd == '-' || *cmd == '+') { switch (*cmd) { case '@': shutUp = DEBUG(LOUD) ? FALSE : TRUE; break; case '-': errOff = TRUE; break; case '+': if (noSpecials) { /* * We're not actually exececuting anything... * but this one needs to be - use compat mode just for it. */ Compat_RunCommand(cmdp, job->node); return (0); } break; } cmd++; } while (isspace((unsigned char)*cmd)) cmd++; if (shutUp) { if (!(job->flags & JOB_SILENT) && !noSpecials && commandShell->hasEchoCtl) { DBPRINTF("%s\n", commandShell->echoOff); } else { shutUp = FALSE; } } if (errOff) { if ( !(job->flags & JOB_IGNERR) && !noSpecials) { if (commandShell->hasErrCtl) { /* * we don't want the error-control commands showing * up either, so we turn off echoing while executing * them. We could put another field in the shell * structure to tell JobDoOutput to look for this * string too, but why make it any more complex than * it already is? */ if (!(job->flags & JOB_SILENT) && !shutUp && commandShell->hasEchoCtl) { DBPRINTF("%s\n", commandShell->echoOff); DBPRINTF("%s\n", commandShell->ignErr); DBPRINTF("%s\n", commandShell->echoOn); } else { DBPRINTF("%s\n", commandShell->ignErr); } } else if (commandShell->ignErr && (*commandShell->ignErr != '\0')) { /* * The shell has no error control, so we need to be * weird to get it to ignore any errors from the command. * If echoing is turned on, we turn it off and use the * errCheck template to echo the command. Leave echoing * off so the user doesn't see the weirdness we go through * to ignore errors. Set cmdTemplate to use the weirdness * instead of the simple "%s\n" template. */ if (!(job->flags & JOB_SILENT) && !shutUp && commandShell->hasEchoCtl) { DBPRINTF("%s\n", commandShell->echoOff); DBPRINTF(commandShell->errCheck, cmd); shutUp = TRUE; } cmdTemplate = commandShell->ignErr; /* * The error ignoration (hee hee) is already taken care * of by the ignErr template, so pretend error checking * is still on. */ errOff = FALSE; } else { errOff = FALSE; } } else { errOff = FALSE; } } DBPRINTF(cmdTemplate, cmd); if (errOff) { /* * If echoing is already off, there's no point in issuing the * echoOff command. Otherwise we issue it and pretend it was on * for the whole command... */ if (!shutUp && !(job->flags & JOB_SILENT) && commandShell->hasEchoCtl) { DBPRINTF("%s\n", commandShell->echoOff); shutUp = TRUE; } DBPRINTF("%s\n", commandShell->errCheck); } if (shutUp) { DBPRINTF("%s\n", commandShell->echoOn); } return (0); } /*- *----------------------------------------------------------------------- * JobSaveCommand -- * Save a command to be executed when everything else is done. * Callback function for JobFinish... * * Results: * Always returns 0 * * Side Effects: * The command is tacked onto the end of postCommands's commands list. * *----------------------------------------------------------------------- */ static int JobSaveCommand(void *cmd, void *gn) { cmd = Var_Subst(NULL, cmd, gn, FALSE); Lst_AtEnd(postCommands->commands, cmd); return (0); } /*- *----------------------------------------------------------------------- * JobClose -- * Called to close both input and output pipes when a job is finished. * * Results: * Nada * * Side Effects: * The file descriptors associated with the job are closed. * *----------------------------------------------------------------------- */ static void JobClose(Job *job) { if (usePipes) { #if !defined(USE_KQUEUE) FD_CLR(job->inPipe, &outputs); #endif if (job->outPipe != job->inPipe) { close(job->outPipe); } JobDoOutput(job, TRUE); close(job->inPipe); } else { close(job->outFd); JobDoOutput(job, TRUE); } } /*- *----------------------------------------------------------------------- * JobFinish -- * Do final processing for the given job including updating * parents and starting new jobs as available/necessary. Note * that we pay no attention to the JOB_IGNERR flag here. * This is because when we're called because of a noexecute flag * or something, jstat.w_status is 0 and when called from * Job_CatchChildren, the status is zeroed if it s/b ignored. * * Results: * None * * Side Effects: * Some nodes may be put on the toBeMade queue. * Final commands for the job are placed on postCommands. * * If we got an error and are aborting (aborting == ABORT_ERROR) and * the job list is now empty, we are done for the day. * If we recognized an error (errors !=0), we set the aborting flag * to ABORT_ERROR so no more jobs will be started. *----------------------------------------------------------------------- */ /*ARGSUSED*/ static void JobFinish(Job *job, int *status) { Boolean done; if ((WIFEXITED(*status) && (((WEXITSTATUS(*status) != 0) && !(job->flags & JOB_IGNERR)))) || (WIFSIGNALED(*status) && (WTERMSIG(*status) != SIGCONT))) { /* * If it exited non-zero and either we're doing things our * way or we're not ignoring errors, the job is finished. * Similarly, if the shell died because of a signal * the job is also finished. In these * cases, finish out the job's output before printing the exit * status... */ JobClose(job); if (job->cmdFILE != NULL && job->cmdFILE != stdout) { fclose(job->cmdFILE); } done = TRUE; } else if (WIFEXITED(*status)) { /* * Deal with ignored errors in -B mode. We need to print a message * telling of the ignored error as well as setting status.w_status * to 0 so the next command gets run. To do this, we set done to be * TRUE if in -B mode and the job exited non-zero. */ done = WEXITSTATUS(*status) != 0; /* * Old comment said: "Note we don't * want to close down any of the streams until we know we're at the * end." * But we do. Otherwise when are we going to print the rest of the * stuff? */ JobClose(job); } else { /* * No need to close things down or anything. */ done = FALSE; } if (done || WIFSTOPPED(*status) || (WIFSIGNALED(*status) && (WTERMSIG(*status) == SIGCONT)) || DEBUG(JOB)) { FILE *out; if (compatMake && !usePipes && (job->flags & JOB_IGNERR)) { /* * If output is going to a file and this job is ignoring * errors, arrange to have the exit status sent to the * output file as well. */ out = fdopen(job->outFd, "w"); if (out == NULL) Punt("Cannot fdopen"); } else { out = stdout; } if (WIFEXITED(*status)) { DEBUGF(JOB, ("Process %d exited.\n", job->pid)); if (WEXITSTATUS(*status) != 0) { if (usePipes && job->node != lastNode) { MESSAGE(out, job->node); lastNode = job->node; } fprintf(out, "*** Error code %d%s\n", WEXITSTATUS(*status), (job->flags & JOB_IGNERR) ? "(ignored)" : ""); if (job->flags & JOB_IGNERR) { *status = 0; } } else if (DEBUG(JOB)) { if (usePipes && job->node != lastNode) { MESSAGE(out, job->node); lastNode = job->node; } fprintf(out, "*** Completed successfully\n"); } } else if (WIFSTOPPED(*status)) { DEBUGF(JOB, ("Process %d stopped.\n", job->pid)); if (usePipes && job->node != lastNode) { MESSAGE(out, job->node); lastNode = job->node; } fprintf(out, "*** Stopped -- signal %d\n", WSTOPSIG(*status)); job->flags |= JOB_RESUME; Lst_AtEnd(stoppedJobs, job); fflush(out); return; } else if (WTERMSIG(*status) == SIGCONT) { /* * If the beastie has continued, shift the Job from the stopped * list to the running one (or re-stop it if concurrency is * exceeded) and go and get another child. */ if (job->flags & (JOB_RESUME|JOB_RESTART)) { if (usePipes && job->node != lastNode) { MESSAGE(out, job->node); lastNode = job->node; } fprintf(out, "*** Continued\n"); } if (!(job->flags & JOB_CONTINUING)) { DEBUGF(JOB, ("Warning: process %d was not continuing.\n", job->pid)); #ifdef notdef /* * We don't really want to restart a job from scratch just * because it continued, especially not without killing the * continuing process! That's why this is ifdef'ed out. * FD - 9/17/90 */ JobRestart(job); #endif } job->flags &= ~JOB_CONTINUING; Lst_AtEnd(jobs, job); nJobs += 1; DEBUGF(JOB, ("Process %d is continuing locally.\n", job->pid)); if (nJobs == maxJobs) { jobFull = TRUE; DEBUGF(JOB, ("Job queue is full.\n")); } fflush(out); return; } else { if (usePipes && job->node != lastNode) { MESSAGE(out, job->node); lastNode = job->node; } fprintf(out, "*** Signal %d\n", WTERMSIG(*status)); } fflush(out); } /* * Now handle the -B-mode stuff. If the beast still isn't finished, * try and restart the job on the next command. If JobStart says it's * ok, it's ok. If there's an error, this puppy is done. */ if (compatMake && (WIFEXITED(*status) && !Lst_IsAtEnd(job->node->commands))) { switch (JobStart(job->node, job->flags & JOB_IGNDOTS, job)) { case JOB_RUNNING: done = FALSE; break; case JOB_ERROR: done = TRUE; W_SETEXITSTATUS(status, 1); break; case JOB_FINISHED: /* * If we got back a JOB_FINISHED code, JobStart has already * called Make_Update and freed the job descriptor. We set * done to false here to avoid fake cycles and double frees. * JobStart needs to do the update so we can proceed up the * graph when given the -n flag.. */ done = FALSE; break; default: break; } } else { done = TRUE; } if (done && (aborting != ABORT_ERROR) && (aborting != ABORT_INTERRUPT) && (*status == 0)) { /* * As long as we aren't aborting and the job didn't return a non-zero * status that we shouldn't ignore, we call Make_Update to update * the parents. In addition, any saved commands for the node are placed * on the .END target. */ if (job->tailCmds != NULL) { Lst_ForEachFrom(job->node->commands, job->tailCmds, JobSaveCommand, job->node); } job->node->made = MADE; Make_Update(job->node); free(job); } else if (*status != 0) { errors += 1; free(job); } JobRestartJobs(); /* * Set aborting if any error. */ if (errors && !keepgoing && (aborting != ABORT_INTERRUPT)) { /* * If we found any errors in this batch of children and the -k flag * wasn't given, we set the aborting flag so no more jobs get * started. */ aborting = ABORT_ERROR; } if ((aborting == ABORT_ERROR) && Job_Empty()) /* * If we are aborting and the job table is now empty, we finish. */ Finish(errors); } /*- *----------------------------------------------------------------------- * Job_Touch -- * Touch the given target. Called by JobStart when the -t flag was * given. Prints messages unless told to be silent. * * Results: * None * * Side Effects: * The data modification of the file is changed. In addition, if the * file did not exist, it is created. *----------------------------------------------------------------------- */ void Job_Touch(GNode *gn, Boolean silent) { int streamID; /* ID of stream opened to do the touch */ struct utimbuf times; /* Times for utime() call */ if (gn->type & (OP_JOIN | OP_USE | OP_EXEC | OP_OPTIONAL)) { /* * .JOIN, .USE, .ZEROTIME and .OPTIONAL targets are "virtual" targets * and, as such, shouldn't really be created. */ return; } if (!silent) { fprintf(stdout, "touch %s\n", gn->name); fflush(stdout); } if (noExecute) { return; } if (gn->type & OP_ARCHV) { Arch_Touch(gn); } else if (gn->type & OP_LIB) { Arch_TouchLib(gn); } else { char *file = gn->path ? gn->path : gn->name; times.actime = times.modtime = now; if (utime(file, ×) < 0){ streamID = open(file, O_RDWR | O_CREAT, 0666); if (streamID >= 0) { char c; /* * Read and write a byte to the file to change the * modification time, then close the file. */ if (read(streamID, &c, 1) == 1) { lseek(streamID, (off_t)0, SEEK_SET); write(streamID, &c, 1); } close(streamID); } else { fprintf(stdout, "*** couldn't touch %s: %s", file, strerror(errno)); fflush(stdout); } } } } /*- *----------------------------------------------------------------------- * Job_CheckCommands -- * Make sure the given node has all the commands it needs. * * Results: * TRUE if the commands list is/was ok. * * Side Effects: * The node will have commands from the .DEFAULT rule added to it * if it needs them. *----------------------------------------------------------------------- */ Boolean Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...)) { if (OP_NOP(gn->type) && Lst_IsEmpty(gn->commands) && (gn->type & OP_LIB) == 0) { /* * No commands. Look for .DEFAULT rule from which we might infer * commands */ if ((DEFAULT != NULL) && !Lst_IsEmpty(DEFAULT->commands)) { char *p1; /* * Make only looks for a .DEFAULT if the node was never the * target of an operator, so that's what we do too. If * a .DEFAULT was given, we substitute its commands for gn's * commands and set the IMPSRC variable to be the target's name * The DEFAULT node acts like a transformation rule, in that * gn also inherits any attributes or sources attached to * .DEFAULT itself. */ Make_HandleUse(DEFAULT, gn); Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), gn); free(p1); } else if (Dir_MTime(gn) == 0) { /* * The node wasn't the target of an operator we have no .DEFAULT * rule to go on and the target doesn't already exist. There's * nothing more we can do for this branch. If the -k flag wasn't * given, we stop in our tracks, otherwise we just don't update * this node's parents so they never get examined. */ static const char msg[] = "make: don't know how to make"; if (gn->type & OP_OPTIONAL) { fprintf(stdout, "%s %s(ignored)\n", msg, gn->name); fflush(stdout); } else if (keepgoing) { fprintf(stdout, "%s %s(continuing)\n", msg, gn->name); fflush(stdout); return (FALSE); } else { #if OLD_JOKE if (strcmp(gn->name,"love") == 0) (*abortProc)("Not war."); else #endif (*abortProc)("%s %s. Stop", msg, gn->name); return (FALSE); } } } return (TRUE); } /*- *----------------------------------------------------------------------- * JobExec -- * Execute the shell for the given job. Called from JobStart and * JobRestart. * * Results: * None. * * Side Effects: * A shell is executed, outputs is altered and the Job structure added * to the job table. * *----------------------------------------------------------------------- */ static void JobExec(Job *job, char **argv) { int cpid; /* ID of new child */ if (DEBUG(JOB)) { int i; DEBUGF(JOB, ("Running %s\n", job->node->name)); DEBUGF(JOB, ("\tCommand: ")); for (i = 0; argv[i] != NULL; i++) { DEBUGF(JOB, ("%s ", argv[i])); } DEBUGF(JOB, ("\n")); } /* * Some jobs produce no output and it's disconcerting to have * no feedback of their running (since they produce no output, the * banner with their name in it never appears). This is an attempt to * provide that feedback, even if nothing follows it. */ if ((lastNode != job->node) && (job->flags & JOB_FIRST) && !(job->flags & JOB_SILENT)) { MESSAGE(stdout, job->node); lastNode = job->node; } if ((cpid = vfork()) == -1) { Punt("Cannot fork"); } else if (cpid == 0) { if (fifoFd >= 0) close(fifoFd); /* * Must duplicate the input stream down to the child's input and * reset it to the beginning (again). Since the stream was marked * close-on-exec, we must clear that bit in the new input. */ if (dup2(FILENO(job->cmdFILE), 0) == -1) Punt("Cannot dup2: %s", strerror(errno)); fcntl(0, F_SETFD, 0); lseek(0, (off_t)0, SEEK_SET); if (usePipes) { /* * Set up the child's output to be routed through the pipe * we've created for it. */ if (dup2(job->outPipe, 1) == -1) Punt("Cannot dup2: %s", strerror(errno)); } else { /* * We're capturing output in a file, so we duplicate the * descriptor to the temporary file into the standard * output. */ if (dup2(job->outFd, 1) == -1) Punt("Cannot dup2: %s", strerror(errno)); } /* * The output channels are marked close on exec. This bit was * duplicated by the dup2 (on some systems), so we have to clear * it before routing the shell's error output to the same place as * its standard output. */ fcntl(1, F_SETFD, 0); if (dup2(1, 2) == -1) Punt("Cannot dup2: %s", strerror(errno)); #ifdef USE_PGRP /* * We want to switch the child into a different process family so * we can kill it and all its descendants in one fell swoop, * by killing its process family, but not commit suicide. */ # if defined(SYSV) setsid(); # else setpgid(0, getpid()); # endif #endif /* USE_PGRP */ execv(shellPath, argv); write(STDERR_FILENO, "Could not execute shell\n", sizeof("Could not execute shell")); _exit(1); } else { job->pid = cpid; if (usePipes && (job->flags & JOB_FIRST) ) { /* * The first time a job is run for a node, we set the current * position in the buffer to the beginning and mark another * stream to watch in the outputs mask */ #ifdef USE_KQUEUE struct kevent kev[2]; #endif job->curPos = 0; #if defined(USE_KQUEUE) EV_SET(&kev[0], job->inPipe, EVFILT_READ, EV_ADD, 0, 0, job); EV_SET(&kev[1], job->pid, EVFILT_PROC, EV_ADD | EV_ONESHOT, NOTE_EXIT, 0, NULL); if (kevent(kqfd, kev, 2, NULL, 0, NULL) != 0) { /* kevent() will fail if the job is already finished */ if (errno != EINTR && errno != EBADF && errno != ESRCH) Punt("kevent: %s", strerror(errno)); } #else FD_SET(job->inPipe, &outputs); #endif /* USE_KQUEUE */ } if (job->cmdFILE != NULL && job->cmdFILE != stdout) { fclose(job->cmdFILE); job->cmdFILE = NULL; } } /* * Now the job is actually running, add it to the table. */ nJobs += 1; Lst_AtEnd(jobs, job); if (nJobs == maxJobs) { jobFull = TRUE; } } /*- *----------------------------------------------------------------------- * JobMakeArgv -- * Create the argv needed to execute the shell for a given job. * * * Results: * * Side Effects: * *----------------------------------------------------------------------- */ static void JobMakeArgv(Job *job, char **argv) { int argc; static char args[10]; /* For merged arguments */ argv[0] = shellName; argc = 1; if ((commandShell->exit && (*commandShell->exit != '-')) || (commandShell->echo && (*commandShell->echo != '-'))) { /* * At least one of the flags doesn't have a minus before it, so * merge them together. Have to do this because the *(&(@*#*&#$# * Bourne shell thinks its second argument is a file to source. * Grrrr. Note the ten-character limitation on the combined arguments. */ sprintf(args, "-%s%s", ((job->flags & JOB_IGNERR) ? "" : (commandShell->exit ? commandShell->exit : "")), ((job->flags & JOB_SILENT) ? "" : (commandShell->echo ? commandShell->echo : ""))); if (args[1]) { argv[argc] = args; argc++; } } else { if (!(job->flags & JOB_IGNERR) && commandShell->exit) { argv[argc] = commandShell->exit; argc++; } if (!(job->flags & JOB_SILENT) && commandShell->echo) { argv[argc] = commandShell->echo; argc++; } } argv[argc] = NULL; } /*- *----------------------------------------------------------------------- * JobRestart -- * Restart a job that stopped for some reason. * * Results: * None. * * Side Effects: * jobFull will be set if the job couldn't be run. * *----------------------------------------------------------------------- */ static void JobRestart(Job *job) { if (job->flags & JOB_RESTART) { /* * Set up the control arguments to the shell. This is based on the * flags set earlier for this job. If the JOB_IGNERR flag is clear, * the 'exit' flag of the commandShell is used to cause it to exit * upon receiving an error. If the JOB_SILENT flag is clear, the * 'echo' flag of the commandShell is used to get it to start echoing * as soon as it starts processing commands. */ char *argv[4]; JobMakeArgv(job, argv); DEBUGF(JOB, ("Restarting %s...", job->node->name)); if (((nJobs >= maxJobs) && !(job->flags & JOB_SPECIAL))) { /* * Can't be exported and not allowed to run locally -- put it * back on the hold queue and mark the table full */ DEBUGF(JOB, ("holding\n")); Lst_AtFront(stoppedJobs, (void *)job); jobFull = TRUE; DEBUGF(JOB, ("Job queue is full.\n")); return; } else { /* * Job may be run locally. */ DEBUGF(JOB, ("running locally\n")); } JobExec(job, argv); } else { /* * The job has stopped and needs to be restarted. Why it stopped, * we don't know... */ DEBUGF(JOB, ("Resuming %s...", job->node->name)); if (((nJobs < maxJobs) || ((job->flags & JOB_SPECIAL) && (maxJobs == 0))) && (nJobs != maxJobs)) { /* * If we haven't reached the concurrency limit already (or the * job must be run and maxJobs is 0), it's ok to resume it. */ Boolean error; int status; error = (KILL(job->pid, SIGCONT) != 0); if (!error) { /* * Make sure the user knows we've continued the beast and * actually put the thing in the job table. */ job->flags |= JOB_CONTINUING; W_SETTERMSIG(&status, SIGCONT); JobFinish(job, &status); job->flags &= ~(JOB_RESUME|JOB_CONTINUING); DEBUGF(JOB, ("done\n")); } else { Error("couldn't resume %s: %s", job->node->name, strerror(errno)); status = 0; W_SETEXITSTATUS(&status, 1); JobFinish(job, &status); } } else { /* * Job cannot be restarted. Mark the table as full and * place the job back on the list of stopped jobs. */ DEBUGF(JOB, ("table full\n")); Lst_AtFront(stoppedJobs, (void *)job); jobFull = TRUE; DEBUGF(JOB, ("Job queue is full.\n")); } } } /*- *----------------------------------------------------------------------- * JobStart -- * Start a target-creation process going for the target described * by the graph node gn. * * Results: * JOB_ERROR if there was an error in the commands, JOB_FINISHED * if there isn't actually anything left to do for the job and * JOB_RUNNING if the job has been started. * * Side Effects: * A new Job node is created and added to the list of running * jobs. PMake is forked and a child shell created. *----------------------------------------------------------------------- */ static int JobStart(GNode *gn, int flags, Job *previous) { Job *job; /* new job descriptor */ char *argv[4]; /* Argument vector to shell */ Boolean cmdsOK; /* true if the nodes commands were all right */ Boolean noExec; /* Set true if we decide not to run the job */ int tfd; /* File descriptor for temp file */ if (interrupted) { JobPassSig(interrupted); return (JOB_ERROR); } if (previous != NULL) { previous->flags &= ~(JOB_FIRST|JOB_IGNERR|JOB_SILENT); job = previous; } else { job = emalloc(sizeof(Job)); flags |= JOB_FIRST; } job->node = gn; job->tailCmds = NULL; /* * Set the initial value of the flags for this job based on the global * ones and the node's attributes... Any flags supplied by the caller * are also added to the field. */ job->flags = 0; if (Targ_Ignore(gn)) { job->flags |= JOB_IGNERR; } if (Targ_Silent(gn)) { job->flags |= JOB_SILENT; } job->flags |= flags; /* * Check the commands now so any attributes from .DEFAULT have a chance * to migrate to the node */ if (!compatMake && job->flags & JOB_FIRST) { cmdsOK = Job_CheckCommands(gn, Error); } else { cmdsOK = TRUE; } /* * If the -n flag wasn't given, we open up OUR (not the child's) * temporary file to stuff commands in it. The thing is rd/wr so we don't * need to reopen it to feed it to the shell. If the -n flag *was* given, * we just set the file to be stdout. Cute, huh? */ if ((gn->type & OP_MAKE) || (!noExecute && !touchFlag)) { /* * We're serious here, but if the commands were bogus, we're * also dead... */ if (!cmdsOK) { DieHorribly(); } strcpy(tfile, TMPPAT); if ((tfd = mkstemp(tfile)) == -1) Punt("Cannot create temp file: %s", strerror(errno)); job->cmdFILE = fdopen(tfd, "w+"); eunlink(tfile); if (job->cmdFILE == NULL) { close(tfd); Punt("Could not open %s", tfile); } fcntl(FILENO(job->cmdFILE), F_SETFD, 1); /* * Send the commands to the command file, flush all its buffers then * rewind and remove the thing. */ noExec = FALSE; /* * used to be backwards; replace when start doing multiple commands * per shell. */ if (compatMake) { /* * Be compatible: If this is the first time for this node, * verify its commands are ok and open the commands list for * sequential access by later invocations of JobStart. * Once that is done, we take the next command off the list * and print it to the command file. If the command was an * ellipsis, note that there's nothing more to execute. */ if ((job->flags&JOB_FIRST) && (Lst_Open(gn->commands) != SUCCESS)){ cmdsOK = FALSE; } else { - LstNode ln = Lst_Next(gn->commands); + LstNode *ln = Lst_Next(gn->commands); if ((ln == NULL) || JobPrintCommand(Lst_Datum(ln), job)) { noExec = TRUE; Lst_Close(gn->commands); } if (noExec && !(job->flags & JOB_FIRST)) { /* * If we're not going to execute anything, the job * is done and we need to close down the various * file descriptors we've opened for output, then * call JobDoOutput to catch the final characters or * send the file to the screen... Note that the i/o streams * are only open if this isn't the first job. * Note also that this could not be done in * Job_CatchChildren b/c it wasn't clear if there were * more commands to execute or not... */ JobClose(job); } } } else { /* * We can do all the commands at once. hooray for sanity */ numCommands = 0; Lst_ForEach(gn->commands, JobPrintCommand, job); /* * If we didn't print out any commands to the shell script, * there's not much point in executing the shell, is there? */ if (numCommands == 0) { noExec = TRUE; } } } else if (noExecute) { /* * Not executing anything -- just print all the commands to stdout * in one fell swoop. This will still set up job->tailCmds correctly. */ if (lastNode != gn) { MESSAGE(stdout, gn); lastNode = gn; } job->cmdFILE = stdout; /* * Only print the commands if they're ok, but don't die if they're * not -- just let the user know they're bad and keep going. It * doesn't do any harm in this case and may do some good. */ if (cmdsOK) { Lst_ForEach(gn->commands, JobPrintCommand, job); } /* * Don't execute the shell, thank you. */ noExec = TRUE; } else { /* * Just touch the target and note that no shell should be executed. * Set cmdFILE to stdout to make life easier. Check the commands, too, * but don't die if they're no good -- it does no harm to keep working * up the graph. */ job->cmdFILE = stdout; Job_Touch(gn, job->flags & JOB_SILENT); noExec = TRUE; } /* * If we're not supposed to execute a shell, don't. */ if (noExec) { /* * Unlink and close the command file if we opened one */ if (job->cmdFILE != stdout) { if (job->cmdFILE != NULL) fclose(job->cmdFILE); } else { fflush(stdout); } /* * We only want to work our way up the graph if we aren't here because * the commands for the job were no good. */ if (cmdsOK) { if (aborting == 0) { if (job->tailCmds != NULL) { Lst_ForEachFrom(job->node->commands, job->tailCmds, JobSaveCommand, job->node); } job->node->made = MADE; Make_Update(job->node); } free(job); return(JOB_FINISHED); } else { free(job); return(JOB_ERROR); } } else { fflush(job->cmdFILE); } /* * Set up the control arguments to the shell. This is based on the flags * set earlier for this job. */ JobMakeArgv(job, argv); /* * If we're using pipes to catch output, create the pipe by which we'll * get the shell's output. If we're using files, print out that we're * starting a job and then set up its temporary-file name. */ if (!compatMake || (job->flags & JOB_FIRST)) { if (usePipes) { int fd[2]; if (pipe(fd) == -1) Punt("Cannot create pipe: %s", strerror(errno)); job->inPipe = fd[0]; job->outPipe = fd[1]; fcntl(job->inPipe, F_SETFD, 1); fcntl(job->outPipe, F_SETFD, 1); } else { fprintf(stdout, "Remaking `%s'\n", gn->name); fflush(stdout); strcpy(job->outFile, TMPPAT); if ((job->outFd = mkstemp(job->outFile)) == -1) Punt("cannot create temp file: %s", strerror(errno)); fcntl(job->outFd, F_SETFD, 1); } } if ((nJobs >= maxJobs) && !(job->flags & JOB_SPECIAL) && (maxJobs != 0)) { /* * We've hit the limit of concurrency, so put the job on hold until * some other job finishes. Note that the special jobs (.BEGIN, * .INTERRUPT and .END) may be run even when the limit has been reached * (e.g. when maxJobs == 0). */ jobFull = TRUE; DEBUGF(JOB, ("Can only run job locally.\n")); job->flags |= JOB_RESTART; Lst_AtEnd(stoppedJobs, job); } else { if (nJobs >= maxJobs) { /* * If we're running this job locally as a special case (see above), * at least say the table is full. */ jobFull = TRUE; DEBUGF(JOB, ("Local job queue is full.\n")); } JobExec(job, argv); } return (JOB_RUNNING); } static char * JobOutput(Job *job, char *cp, char *endp, int msg) { char *ecp; if (commandShell->noPrint) { ecp = strstr(cp, commandShell->noPrint); while (ecp != NULL) { if (cp != ecp) { *ecp = '\0'; if (msg && job->node != lastNode) { MESSAGE(stdout, job->node); lastNode = job->node; } /* * The only way there wouldn't be a newline after * this line is if it were the last in the buffer. * however, since the non-printable comes after it, * there must be a newline, so we don't print one. */ fprintf(stdout, "%s", cp); fflush(stdout); } cp = ecp + commandShell->noPLen; if (cp != endp) { /* * Still more to print, look again after skipping * the whitespace following the non-printable * command.... */ cp++; while (*cp == ' ' || *cp == '\t' || *cp == '\n') { cp++; } ecp = strstr(cp, commandShell->noPrint); } else { return (cp); } } } return (cp); } /*- *----------------------------------------------------------------------- * JobDoOutput -- * This function is called at different times depending on * whether the user has specified that output is to be collected * via pipes or temporary files. In the former case, we are called * whenever there is something to read on the pipe. We collect more * output from the given job and store it in the job's outBuf. If * this makes up a line, we print it tagged by the job's identifier, * as necessary. * If output has been collected in a temporary file, we open the * file and read it line by line, transfering it to our own * output channel until the file is empty. At which point we * remove the temporary file. * In both cases, however, we keep our figurative eye out for the * 'noPrint' line for the shell from which the output came. If * we recognize a line, we don't print it. If the command is not * alone on the line (the character after it is not \0 or \n), we * do print whatever follows it. * * Results: * None * * Side Effects: * curPos may be shifted as may the contents of outBuf. *----------------------------------------------------------------------- */ STATIC void JobDoOutput(Job *job, Boolean finish) { Boolean gotNL = FALSE; /* true if got a newline */ Boolean fbuf; /* true if our buffer filled up */ int nr; /* number of bytes read */ int i; /* auxiliary index into outBuf */ int max; /* limit for i (end of current data) */ int nRead; /* (Temporary) number of bytes read */ FILE *oFILE; /* Stream pointer to shell's output file */ char inLine[132]; if (usePipes) { /* * Read as many bytes as will fit in the buffer. */ end_loop: gotNL = FALSE; fbuf = FALSE; nRead = read(job->inPipe, &job->outBuf[job->curPos], JOB_BUFSIZE - job->curPos); /* * Check for interrupt here too, because the above read may block * when the child process is stopped. In this case the interrupt * will unblock it (we don't use SA_RESTART). */ if (interrupted) JobPassSig(interrupted); if (nRead < 0) { DEBUGF(JOB, ("JobDoOutput(piperead)")); nr = 0; } else { nr = nRead; } /* * If we hit the end-of-file (the job is dead), we must flush its * remaining output, so pretend we read a newline if there's any * output remaining in the buffer. * Also clear the 'finish' flag so we stop looping. */ if ((nr == 0) && (job->curPos != 0)) { job->outBuf[job->curPos] = '\n'; nr = 1; finish = FALSE; } else if (nr == 0) { finish = FALSE; } /* * Look for the last newline in the bytes we just got. If there is * one, break out of the loop with 'i' as its index and gotNL set * TRUE. */ max = job->curPos + nr; for (i = job->curPos + nr - 1; i >= job->curPos; i--) { if (job->outBuf[i] == '\n') { gotNL = TRUE; break; } else if (job->outBuf[i] == '\0') { /* * Why? */ job->outBuf[i] = ' '; } } if (!gotNL) { job->curPos += nr; if (job->curPos == JOB_BUFSIZE) { /* * If we've run out of buffer space, we have no choice * but to print the stuff. sigh. */ fbuf = TRUE; i = job->curPos; } } if (gotNL || fbuf) { /* * Need to send the output to the screen. Null terminate it * first, overwriting the newline character if there was one. * So long as the line isn't one we should filter (according * to the shell description), we print the line, preceded * by a target banner if this target isn't the same as the * one for which we last printed something. * The rest of the data in the buffer are then shifted down * to the start of the buffer and curPos is set accordingly. */ job->outBuf[i] = '\0'; if (i >= job->curPos) { char *cp; cp = JobOutput(job, job->outBuf, &job->outBuf[i], FALSE); /* * There's still more in that thar buffer. This time, though, * we know there's no newline at the end, so we add one of * our own free will. */ if (*cp != '\0') { if (job->node != lastNode) { MESSAGE(stdout, job->node); lastNode = job->node; } fprintf(stdout, "%s%s", cp, gotNL ? "\n" : ""); fflush(stdout); } } if (i < max - 1) { /* shift the remaining characters down */ memcpy(job->outBuf, &job->outBuf[i + 1], max - (i + 1)); job->curPos = max - (i + 1); } else { /* * We have written everything out, so we just start over * from the start of the buffer. No copying. No nothing. */ job->curPos = 0; } } if (finish) { /* * If the finish flag is true, we must loop until we hit * end-of-file on the pipe. This is guaranteed to happen * eventually since the other end of the pipe is now closed * (we closed it explicitly and the child has exited). When * we do get an EOF, finish will be set FALSE and we'll fall * through and out. */ goto end_loop; } } else { /* * We've been called to retrieve the output of the job from the * temporary file where it's been squirreled away. This consists of * opening the file, reading the output line by line, being sure not * to print the noPrint line for the shell we used, then close and * remove the temporary file. Very simple. * * Change to read in blocks and do FindSubString type things as for * pipes? That would allow for "@echo -n..." */ oFILE = fopen(job->outFile, "r"); if (oFILE != NULL) { fprintf(stdout, "Results of making %s:\n", job->node->name); fflush(stdout); while (fgets(inLine, sizeof(inLine), oFILE) != NULL) { char *cp, *endp, *oendp; cp = inLine; oendp = endp = inLine + strlen(inLine); if (endp[-1] == '\n') { *--endp = '\0'; } cp = JobOutput(job, inLine, endp, FALSE); /* * There's still more in that thar buffer. This time, though, * we know there's no newline at the end, so we add one of * our own free will. */ fprintf(stdout, "%s", cp); fflush(stdout); if (endp != oendp) { fprintf(stdout, "\n"); fflush(stdout); } } fclose(oFILE); eunlink(job->outFile); } } } /*- *----------------------------------------------------------------------- * Job_CatchChildren -- * Handle the exit of a child. Called from Make_Make. * * Results: * none. * * Side Effects: * The job descriptor is removed from the list of children. * * Notes: * We do waits, blocking or not, according to the wisdom of our * caller, until there are no more children to report. For each * job, call JobFinish to finish things off. This will take care of * putting jobs on the stoppedJobs queue. * *----------------------------------------------------------------------- */ void Job_CatchChildren(Boolean block) { int pid; /* pid of dead child */ Job *job; /* job descriptor for dead child */ - LstNode jnode; /* list element for finding job */ + LstNode *jnode; /* list element for finding job */ int status; /* Exit/termination status */ /* * Don't even bother if we know there's no one around. */ if (nJobs == 0) { return; } for (;;) { pid = waitpid((pid_t)-1, &status, (block ? 0 : WNOHANG) | WUNTRACED); if (pid <= 0) break; DEBUGF(JOB, ("Process %d exited or stopped.\n", pid)); jnode = Lst_Find(jobs, &pid, JobCmpPid); if (jnode == NULL) { if (WIFSIGNALED(status) && (WTERMSIG(status) == SIGCONT)) { jnode = Lst_Find(stoppedJobs, &pid, JobCmpPid); if (jnode == NULL) { Error("Resumed child (%d) not in table", pid); continue; } job = Lst_Datum(jnode); Lst_Remove(stoppedJobs, jnode); } else { Error("Child (%d) not in table?", pid); continue; } } else { job = Lst_Datum(jnode); Lst_Remove(jobs, jnode); nJobs -= 1; if (fifoFd >= 0 && maxJobs > 1) { write(fifoFd, "+", 1); maxJobs--; if (nJobs >= maxJobs) jobFull = TRUE; else jobFull = FALSE; } else { DEBUGF(JOB, ("Job queue is no longer full.\n")); jobFull = FALSE; } } JobFinish(job, &status); } if (interrupted) JobPassSig(interrupted); } /*- *----------------------------------------------------------------------- * Job_CatchOutput -- * Catch the output from our children, if we're using * pipes do so. Otherwise just block time until we get a * signal(most likely a SIGCHLD) since there's no point in * just spinning when there's nothing to do and the reaping * of a child can wait for a while. * * Results: * None * * Side Effects: * Output is read from pipes if we're piping. * ----------------------------------------------------------------------- */ void Job_CatchOutput(int flag) { int nfds; #ifdef USE_KQUEUE #define KEV_SIZE 4 struct kevent kev[KEV_SIZE]; int i; #else struct timeval timeout; fd_set readfds; - LstNode ln; + LstNode *ln; Job *job; #endif fflush(stdout); if (usePipes) { #ifdef USE_KQUEUE if ((nfds = kevent(kqfd, NULL, 0, kev, KEV_SIZE, NULL)) == -1) { if (errno != EINTR) Punt("kevent: %s", strerror(errno)); if (interrupted) JobPassSig(interrupted); } else { for (i = 0; i < nfds; i++) { if (kev[i].flags & EV_ERROR) { warnc(kev[i].data, "kevent"); continue; } switch (kev[i].filter) { case EVFILT_READ: JobDoOutput(kev[i].udata, FALSE); break; case EVFILT_PROC: /* Just wake up and let Job_CatchChildren() collect the * terminated job. */ break; } } } #else readfds = outputs; timeout.tv_sec = SEL_SEC; timeout.tv_usec = SEL_USEC; if (flag && jobFull && fifoFd >= 0) FD_SET(fifoFd, &readfds); nfds = select(FD_SETSIZE, &readfds, (fd_set *)NULL, (fd_set *)NULL, &timeout); if (nfds <= 0) { if (interrupted) JobPassSig(interrupted); return; } if (fifoFd >= 0 && FD_ISSET(fifoFd, &readfds)) { if (--nfds <= 0) return; } if (Lst_Open(jobs) == FAILURE) { Punt("Cannot open job table"); } while (nfds && (ln = Lst_Next(jobs)) != NULL) { job = Lst_Datum(ln); if (FD_ISSET(job->inPipe, &readfds)) { JobDoOutput(job, FALSE); nfds -= 1; } } Lst_Close(jobs); #endif /* !USE_KQUEUE */ } } /*- *----------------------------------------------------------------------- * Job_Make -- * Start the creation of a target. Basically a front-end for * JobStart used by the Make module. * * Results: * None. * * Side Effects: * Another job is started. * *----------------------------------------------------------------------- */ void Job_Make(GNode *gn) { JobStart(gn, 0, NULL); } /* * JobCopyShell: * * Make a new copy of the shell structure including a copy of the strings * in it. This also defaults some fields in case they are NULL. * * The function returns a pointer to the new shell structure otherwise. */ static Shell * JobCopyShell(const Shell *osh) { Shell *nsh; nsh = emalloc(sizeof(*nsh)); nsh->name = estrdup(osh->name); if (osh->echoOff != NULL) nsh->echoOff = estrdup(osh->echoOff); else nsh->echoOff = NULL; if (osh->echoOn != NULL) nsh->echoOn = estrdup(osh->echoOn); else nsh->echoOn = NULL; nsh->hasEchoCtl = osh->hasEchoCtl; if (osh->noPrint != NULL) nsh->noPrint = estrdup(osh->noPrint); else nsh->noPrint = NULL; nsh->noPLen = osh->noPLen; nsh->hasErrCtl = osh->hasErrCtl; if (osh->errCheck == NULL) nsh->errCheck = estrdup(""); else nsh->errCheck = estrdup(osh->errCheck); if (osh->ignErr == NULL) nsh->ignErr = estrdup("%s"); else nsh->ignErr = estrdup(osh->ignErr); if (osh->echo == NULL) nsh->echo = estrdup(""); else nsh->echo = estrdup(osh->echo); if (osh->exit == NULL) nsh->exit = estrdup(""); else nsh->exit = estrdup(osh->exit); return (nsh); } /* * JobFreeShell: * * Free a shell structure and all associated strings. */ static void JobFreeShell(Shell *sh) { if (sh != NULL) { free(sh->name); free(sh->echoOff); free(sh->echoOn); free(sh->noPrint); free(sh->errCheck); free(sh->ignErr); free(sh->echo); free(sh->exit); free(sh); } } void Shell_Init(void) { if (commandShell == NULL) commandShell = JobMatchShell(shells[DEFSHELL].name); if (shellPath == NULL) { /* * The user didn't specify a shell to use, so we are using the * default one... Both the absolute path and the last component * must be set. The last component is taken from the 'name' field * of the default shell description pointed-to by commandShell. * All default shells are located in _PATH_DEFSHELLDIR. */ shellName = commandShell->name; shellPath = str_concat(_PATH_DEFSHELLDIR, shellName, STR_ADDSLASH); } } /*- *----------------------------------------------------------------------- * Job_Init -- * Initialize the process module, given a maximum number of jobs. * * Results: * none * * Side Effects: * lists and counters are initialized *----------------------------------------------------------------------- */ void Job_Init(int maxproc) { GNode *begin; /* node for commands to do at the very start */ const char *env; struct sigaction sa; fifoFd = -1; jobs = Lst_Init(); stoppedJobs = Lst_Init(); env = getenv("MAKE_JOBS_FIFO"); if (env == NULL && maxproc > 1) { /* * We did not find the environment variable so we are the leader. * Create the fifo, open it, write one char per allowed job into * the pipe. */ mktemp(fifoName); if (!mkfifo(fifoName, 0600)) { fifoFd = open(fifoName, O_RDWR | O_NONBLOCK, 0); if (fifoFd >= 0) { fifoMaster = 1; fcntl(fifoFd, F_SETFL, O_NONBLOCK); env = fifoName; setenv("MAKE_JOBS_FIFO", env, 1); while (maxproc-- > 0) { write(fifoFd, "+", 1); } /* The master make does not get a magic token */ jobFull = TRUE; maxJobs = 0; } else { unlink(fifoName); env = NULL; } } } else if (env != NULL) { /* * We had the environment variable so we are a slave. * Open fifo and give ourselves a magic token which represents * the token our parent make has grabbed to start his make process. * Otherwise the sub-makes would gobble up tokens and the proper * number of tokens to specify to -j would depend on the depth of * the tree and the order of execution. */ fifoFd = open(env, O_RDWR, 0); if (fifoFd >= 0) { fcntl(fifoFd, F_SETFL, O_NONBLOCK); maxJobs = 1; jobFull = FALSE; } } if (fifoFd <= 0) { maxJobs = maxproc; jobFull = FALSE; } else { } nJobs = 0; aborting = 0; errors = 0; lastNode = NULL; if ((maxJobs == 1 && fifoFd < 0) || beVerbose == 0) { /* * If only one job can run at a time, there's no need for a banner, * no is there? */ targFmt = ""; } else { targFmt = TARG_FMT; } Shell_Init(); /* * Catch the four signals that POSIX specifies if they aren't ignored. * JobCatchSignal will just set global variables and hope someone * else is going to handle the interrupt. */ sa.sa_handler = JobCatchSig; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; if (signal(SIGINT, SIG_IGN) != SIG_IGN) { sigaction(SIGINT, &sa, NULL); } if (signal(SIGHUP, SIG_IGN) != SIG_IGN) { sigaction(SIGHUP, &sa, NULL); } if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) { sigaction(SIGQUIT, &sa, NULL); } if (signal(SIGTERM, SIG_IGN) != SIG_IGN) { sigaction(SIGTERM, &sa, NULL); } /* * There are additional signals that need to be caught and passed if * either the export system wants to be told directly of signals or if * we're giving each job its own process group (since then it won't get * signals from the terminal driver as we own the terminal) */ #if defined(USE_PGRP) if (signal(SIGTSTP, SIG_IGN) != SIG_IGN) { sigaction(SIGTSTP, &sa, NULL); } if (signal(SIGTTOU, SIG_IGN) != SIG_IGN) { sigaction(SIGTTOU, &sa, NULL); } if (signal(SIGTTIN, SIG_IGN) != SIG_IGN) { sigaction(SIGTTIN, &sa, NULL); } if (signal(SIGWINCH, SIG_IGN) != SIG_IGN) { sigaction(SIGWINCH, &sa, NULL); } #endif #ifdef USE_KQUEUE if ((kqfd = kqueue()) == -1) { Punt("kqueue: %s", strerror(errno)); } #endif begin = Targ_FindNode(".BEGIN", TARG_NOCREATE); if (begin != NULL) { JobStart(begin, JOB_SPECIAL, (Job *)NULL); while (nJobs) { Job_CatchOutput(0); Job_CatchChildren(!usePipes); } } postCommands = Targ_FindNode(".END", TARG_CREATE); } /*- *----------------------------------------------------------------------- * Job_Full -- * See if the job table is full. It is considered full if it is OR * if we are in the process of aborting OR if we have * reached/exceeded our local quota. This prevents any more jobs * from starting up. * * Results: * TRUE if the job table is full, FALSE otherwise * Side Effects: * None. *----------------------------------------------------------------------- */ Boolean Job_Full(void) { char c; int i; if (aborting) return (aborting); if (fifoFd >= 0 && jobFull) { i = read(fifoFd, &c, 1); if (i > 0) { maxJobs++; jobFull = FALSE; } } return (jobFull); } /*- *----------------------------------------------------------------------- * Job_Empty -- * See if the job table is empty. Because the local concurrency may * be set to 0, it is possible for the job table to become empty, * while the list of stoppedJobs remains non-empty. In such a case, * we want to restart as many jobs as we can. * * Results: * TRUE if it is. FALSE if it ain't. * * Side Effects: * None. * * ----------------------------------------------------------------------- */ Boolean Job_Empty(void) { if (nJobs == 0) { if (!Lst_IsEmpty(stoppedJobs) && !aborting) { /* * The job table is obviously not full if it has no jobs in * it...Try and restart the stopped jobs. */ jobFull = FALSE; JobRestartJobs(); return (FALSE); } else { return (TRUE); } } else { return (FALSE); } } /*- *----------------------------------------------------------------------- * JobMatchShell -- * Find a matching shell in 'shells' given its final component. * * Results: * A pointer to a freshly allocated Shell structure with a copy * of the static structure or NULL if no shell with the given name * is found. * * Side Effects: * None. * *----------------------------------------------------------------------- */ static Shell * JobMatchShell(const char *name) { const struct CShell *sh; /* Pointer into shells table */ struct Shell *nsh; for (sh = shells; sh < shells + sizeof(shells) / sizeof(shells[0]); sh++) if (strcmp(sh->name, name) == 0) break; if (sh == shells + sizeof(shells) / sizeof(shells[0])) return (NULL); /* make a copy */ nsh = emalloc(sizeof(*nsh)); nsh->name = estrdup(sh->name); nsh->echoOff = estrdup(sh->echoOff); nsh->echoOn = estrdup(sh->echoOn); nsh->hasEchoCtl = sh->hasEchoCtl; nsh->noPrint = estrdup(sh->noPrint); nsh->noPLen = sh->noPLen; nsh->hasErrCtl = sh->hasErrCtl; nsh->errCheck = estrdup(sh->errCheck); nsh->ignErr = estrdup(sh->ignErr); nsh->echo = estrdup(sh->echo); nsh->exit = estrdup(sh->exit); return (nsh); } /*- *----------------------------------------------------------------------- * Job_ParseShell -- * Parse a shell specification and set up commandShell, shellPath * and shellName appropriately. * * Results: * FAILURE if the specification was incorrect. * * Side Effects: * commandShell points to a Shell structure (either predefined or * created from the shell spec), shellPath is the full path of the * shell described by commandShell, while shellName is just the * final component of shellPath. * * Notes: * A shell specification consists of a .SHELL target, with dependency * operator, followed by a series of blank-separated words. Double * quotes can be used to use blanks in words. A backslash escapes * anything (most notably a double-quote and a space) and * provides the functionality it does in C. Each word consists of * keyword and value separated by an equal sign. There should be no * unnecessary spaces in the word. The keywords are as follows: * name Name of shell. * path Location of shell. Overrides "name" if given * quiet Command to turn off echoing. * echo Command to turn echoing on * filter Result of turning off echoing that shouldn't be * printed. * echoFlag Flag to turn echoing on at the start * errFlag Flag to turn error checking on at the start * hasErrCtl True if shell has error checking control * check Command to turn on error checking if hasErrCtl * is TRUE or template of command to echo a command * for which error checking is off if hasErrCtl is * FALSE. * ignore Command to turn off error checking if hasErrCtl * is TRUE or template of command to execute a * command so as to ignore any errors it returns if * hasErrCtl is FALSE. * *----------------------------------------------------------------------- */ ReturnStatus Job_ParseShell(char *line) { char **words; int wordCount; char **argv; int argc; char *path; Shell newShell; Shell *sh; Boolean fullSpec = FALSE; while (isspace((unsigned char)*line)) { line++; } words = brk_string(line, &wordCount, TRUE); memset(&newShell, 0, sizeof(newShell)); /* * Parse the specification by keyword */ for (path = NULL, argc = wordCount - 1, argv = words + 1; argc != 0; argc--, argv++) { if (strncmp(*argv, "path=", 5) == 0) { path = &argv[0][5]; } else if (strncmp(*argv, "name=", 5) == 0) { newShell.name = &argv[0][5]; } else { if (strncmp(*argv, "quiet=", 6) == 0) { newShell.echoOff = &argv[0][6]; } else if (strncmp(*argv, "echo=", 5) == 0) { newShell.echoOn = &argv[0][5]; } else if (strncmp(*argv, "filter=", 7) == 0) { newShell.noPrint = &argv[0][7]; newShell.noPLen = strlen(newShell.noPrint); } else if (strncmp(*argv, "echoFlag=", 9) == 0) { newShell.echo = &argv[0][9]; } else if (strncmp(*argv, "errFlag=", 8) == 0) { newShell.exit = &argv[0][8]; } else if (strncmp(*argv, "hasErrCtl=", 10) == 0) { char c = argv[0][10]; newShell.hasErrCtl = !((c != 'Y') && (c != 'y') && (c != 'T') && (c != 't')); } else if (strncmp(*argv, "check=", 6) == 0) { newShell.errCheck = &argv[0][6]; } else if (strncmp(*argv, "ignore=", 7) == 0) { newShell.ignErr = &argv[0][7]; } else { Parse_Error(PARSE_FATAL, "Unknown keyword \"%s\"", *argv); return (FAILURE); } fullSpec = TRUE; } } /* * Some checks (could be more) */ if (fullSpec) { if ((newShell.echoOn != NULL) ^ (newShell.echoOff != NULL)) Parse_Error(PARSE_FATAL, "Shell must have either both echoOff and " "echoOn or none of them"); if (newShell.echoOn != NULL && newShell.echoOff) newShell.hasEchoCtl = TRUE; } if (path == NULL) { /* * If no path was given, the user wants one of the pre-defined shells, * yes? So we find the one s/he wants with the help of JobMatchShell * and set things up the right way. shellPath will be set up by * Job_Init. */ if (newShell.name == NULL) { Parse_Error(PARSE_FATAL, "Neither path nor name specified"); return (FAILURE); } if ((sh = JobMatchShell(newShell.name)) == NULL) { Parse_Error(PARSE_FATAL, "%s: no matching shell", newShell.name); return (FAILURE); } } else { /* * The user provided a path. If s/he gave nothing else (fullSpec is * FALSE), try and find a matching shell in the ones we know of. * Else we just take the specification at its word and copy it * to a new location. In either case, we need to record the * path the user gave for the shell. */ free(shellPath); shellPath = estrdup(path); if (newShell.name == NULL) { /* get the base name as the name */ path = strrchr(path, '/'); if (path == NULL) { path = shellPath; } else { path += 1; } newShell.name = path; } if (!fullSpec) { if ((sh = JobMatchShell(newShell.name)) == NULL) { Parse_Error(PARSE_FATAL, "%s: no matching shell", newShell.name); return (FAILURE); } } else { sh = JobCopyShell(&newShell); } } /* set the new shell */ JobFreeShell(commandShell); commandShell = sh; shellName = commandShell->name; return (SUCCESS); } /*- *----------------------------------------------------------------------- * JobInterrupt -- * Handle the receipt of an interrupt. * * Results: * None * * Side Effects: * All children are killed. Another job will be started if the * .INTERRUPT target was given. *----------------------------------------------------------------------- */ static void JobInterrupt(int runINTERRUPT, int signo) { - LstNode ln; /* element in job table */ + LstNode *ln; /* element in job table */ Job *job = NULL; /* job descriptor in that element */ GNode *interrupt; /* the node describing the .INTERRUPT target */ aborting = ABORT_INTERRUPT; Lst_Open(jobs); while ((ln = Lst_Next(jobs)) != NULL) { job = Lst_Datum(ln); if (!Targ_Precious(job->node)) { char *file = (job->node->path == NULL ? job->node->name : job->node->path); if (!noExecute && eunlink(file) != -1) { Error("*** %s removed", file); } } if (job->pid) { DEBUGF(JOB, ("JobInterrupt passing signal to child %d.\n", job->pid)); KILL(job->pid, signo); } } if (runINTERRUPT && !touchFlag) { /* clear the interrupted flag because we would get an * infinite loop otherwise */ interrupted = 0; interrupt = Targ_FindNode(".INTERRUPT", TARG_NOCREATE); if (interrupt != NULL) { ignoreErrors = FALSE; JobStart(interrupt, JOB_IGNDOTS, (Job *)NULL); while (nJobs) { Job_CatchOutput(0); Job_CatchChildren(!usePipes); } } } } /* *----------------------------------------------------------------------- * Job_Finish -- * Do final processing such as the running of the commands * attached to the .END target. * * Results: * Number of errors reported. *----------------------------------------------------------------------- */ int Job_Finish(void) { if (postCommands != NULL && !Lst_IsEmpty(postCommands->commands)) { if (errors) { Error("Errors reported so .END ignored"); } else { JobStart(postCommands, JOB_SPECIAL | JOB_IGNDOTS, NULL); while (nJobs) { Job_CatchOutput(0); Job_CatchChildren(!usePipes); } } } if (fifoFd >= 0) { close(fifoFd); fifoFd = -1; if (fifoMaster) unlink(fifoName); } return (errors); } /*- *----------------------------------------------------------------------- * Job_Wait -- * Waits for all running jobs to finish and returns. Sets 'aborting' * to ABORT_WAIT to prevent other jobs from starting. * * Results: * None. * * Side Effects: * Currently running jobs finish. * *----------------------------------------------------------------------- */ void Job_Wait(void) { aborting = ABORT_WAIT; while (nJobs != 0) { Job_CatchOutput(0); Job_CatchChildren(!usePipes); } aborting = 0; } /*- *----------------------------------------------------------------------- * Job_AbortAll -- * Abort all currently running jobs without handling output or anything. * This function is to be called only in the event of a major * error. Most definitely NOT to be called from JobInterrupt. * * Results: * None * * Side Effects: * All children are killed, not just the firstborn *----------------------------------------------------------------------- */ void Job_AbortAll(void) { - LstNode ln; /* element in job table */ + LstNode *ln; /* element in job table */ Job *job; /* the job descriptor in that element */ int foo; aborting = ABORT_ERROR; if (nJobs) { Lst_Open(jobs); while ((ln = Lst_Next(jobs)) != NULL) { job = Lst_Datum(ln); /* * kill the child process with increasingly drastic signals to make * darn sure it's dead. */ KILL(job->pid, SIGINT); KILL(job->pid, SIGKILL); } } /* * Catch as many children as want to report in at first, then give up */ while (waitpid((pid_t)-1, &foo, WNOHANG) > 0) continue; } /*- *----------------------------------------------------------------------- * JobRestartJobs -- * Tries to restart stopped jobs if there are slots available. * Note that this tries to restart them regardless of pending errors. * It's not good to leave stopped jobs lying around! * * Results: * None. * * Side Effects: * Resumes(and possibly migrates) jobs. * *----------------------------------------------------------------------- */ static void JobRestartJobs(void) { while (!jobFull && !Lst_IsEmpty(stoppedJobs)) { DEBUGF(JOB, ("Job queue is not full. Restarting a stopped job.\n")); JobRestart(Lst_DeQueue(stoppedJobs)); } } Index: head/usr.bin/make/job.h =================================================================== --- head/usr.bin/make/job.h (revision 138511) +++ head/usr.bin/make/job.h (revision 138512) @@ -1,223 +1,223 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 1988, 1989 by Adam de Boor * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)job.h 8.1 (Berkeley) 6/6/93 * $FreeBSD$ */ /*- * job.h -- * Definitions pertaining to the running of jobs in parallel mode. * Exported from job.c for the use of remote-execution modules. */ #ifndef _JOB_H_ #define _JOB_H_ #define TMPPAT "/tmp/makeXXXXXXXXXX" #ifndef USE_KQUEUE /* * The SEL_ constants determine the maximum amount of time spent in select * before coming out to see if a child has finished. SEL_SEC is the number of * seconds and SEL_USEC is the number of micro-seconds */ #define SEL_SEC 2 #define SEL_USEC 0 #endif /* !USE_KQUEUE */ /*- * Job Table definitions. * * Each job has several things associated with it: * 1) The process id of the child shell * 2) The graph node describing the target being made by this job * 3) A LstNode for the first command to be saved after the job * completes. This is NULL if there was no "..." in the job's * commands. * 4) An FILE* for writing out the commands. This is only * used before the job is actually started. * 5) A union of things used for handling the shell's output. Different * parts of the union are used based on the value of the usePipes * flag. If it is true, the output is being caught via a pipe and * the descriptors of our pipe, an array in which output is line * buffered and the current position in that buffer are all * maintained for each job. If, on the other hand, usePipes is false, * the output is routed to a temporary file and all that is kept * is the name of the file and the descriptor open to the file. * 6) A word of flags which determine how the module handles errors, * echoing, etc. for the job * * The job "table" is kept as a linked Lst in 'jobs', with the number of * active jobs maintained in the 'nJobs' variable. At no time will this * exceed the value of 'maxJobs', initialized by the Job_Init function. * * When a job is finished, the Make_Update function is called on each of the * parents of the node which was just remade. This takes care of the upward * traversal of the dependency graph. */ #define JOB_BUFSIZE 1024 typedef struct Job { int pid; /* The child's process ID */ char tfile[sizeof(TMPPAT)]; /* Temporary file to use for job */ GNode *node; /* The target the child is making */ - LstNode tailCmds; /* The node of the first command to be + LstNode *tailCmds; /* The node of the first command to be * saved when the job has been run */ FILE *cmdFILE; /* When creating the shell script, this is * where the commands go */ short flags; /* Flags to control treatment of job */ #define JOB_IGNERR 0x001 /* Ignore non-zero exits */ #define JOB_SILENT 0x002 /* no output */ #define JOB_SPECIAL 0x004 /* Target is a special one. i.e. run it locally * if we can't export it and maxLocal is 0 */ #define JOB_IGNDOTS 0x008 /* Ignore "..." lines when processing * commands */ #define JOB_FIRST 0x020 /* Job is first job for the node */ #define JOB_RESTART 0x080 /* Job needs to be completely restarted */ #define JOB_RESUME 0x100 /* Job needs to be resumed b/c it stopped, * for some reason */ #define JOB_CONTINUING 0x200 /* We are in the process of resuming this job. * Used to avoid infinite recursion between * JobFinish and JobRestart */ union { struct { int op_inPipe; /* Input side of pipe associated * with job's output channel */ int op_outPipe; /* Output side of pipe associated with * job's output channel */ char op_outBuf[JOB_BUFSIZE + 1]; /* Buffer for storing the output of the * job, line by line */ int op_curPos; /* Current position in op_outBuf */ } o_pipe; /* data used when catching the output via * a pipe */ struct { char of_outFile[sizeof(TMPPAT)]; /* Name of file to which shell output * was rerouted */ int of_outFd; /* Stream open to the output * file. Used to funnel all * from a single job to one file * while still allowing * multiple shell invocations */ } o_file; /* Data used when catching the output in * a temporary file */ } output; /* Data for tracking a shell's output */ } Job; #define outPipe output.o_pipe.op_outPipe #define inPipe output.o_pipe.op_inPipe #define outBuf output.o_pipe.op_outBuf #define curPos output.o_pipe.op_curPos #define outFile output.o_file.of_outFile #define outFd output.o_file.of_outFd /*- * Shell Specifications: * Each shell type has associated with it the following information: * 1) The string which must match the last character of the shell name * for the shell to be considered of this type. The longest match * wins. * 2) A command to issue to turn off echoing of command lines * 3) A command to issue to turn echoing back on again * 4) What the shell prints, and its length, when given the echo-off * command. This line will not be printed when received from the shell * 5) A boolean to tell if the shell has the ability to control * error checking for individual commands. * 6) The string to turn this checking on. * 7) The string to turn it off. * 8) The command-flag to give to cause the shell to start echoing * commands right away. * 9) The command-flag to cause the shell to Lib_Exit when an error is * detected in one of the commands. * * Some special stuff goes on if a shell doesn't have error control. In such * a case, errCheck becomes a printf template for echoing the command, * should echoing be on and ignErr becomes another printf template for * executing the command while ignoring the return status. If either of these * strings is empty when hasErrCtl is FALSE, the command will be executed * anyway as is and if it causes an error, so be it. */ #define DEF_SHELL_STRUCT(TAG, CONST) \ struct TAG { \ CONST char *name; /* the name of the shell. For Bourne and C \ * shells, this is used only to find the \ * shell description when used as the single \ * source of a .SHELL target. For user-defined \ * shells, this is the full path of the shell. \ */ \ Boolean hasEchoCtl; /* True if both echoOff and echoOn defined */ \ CONST char *echoOff; /* command to turn off echo */ \ CONST char *echoOn; /* command to turn it back on again */ \ CONST char *noPrint; /* command to skip when printing output from \ * shell. This is usually the command which \ * was executed to turn off echoing */ \ int noPLen; /* length of noPrint command */ \ Boolean hasErrCtl; /* set if can control error checking for \ * individual commands */ \ CONST char *errCheck; /* string to turn error checking on */ \ CONST char *ignErr; /* string to turn off error checking */ \ /* \ * command-line flags \ */ \ CONST char *echo; /* echo commands */ \ CONST char *exit; /* exit on error */ \ } typedef DEF_SHELL_STRUCT(Shell,) Shell; extern char *shellPath; extern char *shellName; extern int maxJobs; /* Number of jobs that may run */ void Shell_Init(void); void Job_Touch(GNode *, Boolean); Boolean Job_CheckCommands(GNode *, void (*abortProc)(const char *, ...)); void Job_CatchChildren(Boolean); void Job_CatchOutput(int flag); void Job_Make(GNode *); void Job_Init(int); Boolean Job_Full(void); Boolean Job_Empty(void); ReturnStatus Job_ParseShell(char *); int Job_Finish(void); void Job_Wait(void); void Job_AbortAll(void); #endif /* _JOB_H_ */ Index: head/usr.bin/make/lst.h =================================================================== --- head/usr.bin/make/lst.h (revision 138511) +++ head/usr.bin/make/lst.h (revision 138512) @@ -1,218 +1,218 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 1988, 1989 by Adam de Boor * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)lst.h 8.2 (Berkeley) 4/28/95 * $FreeBSD$ */ /*- * lst.h -- * Header for using the list library */ #ifndef _LST_H_ #define _LST_H_ #include #include #include "sprite.h" /* * Structure of a list node. */ struct LstNode { struct LstNode *prevPtr; /* previous element in list */ struct LstNode *nextPtr; /* next in list */ int useCount:8; /* Count of functions using the node. Node may not * be deleted until count goes to 0 */ int flags:8; /* Node status flags */ void *datum; /* datum associated with this element */ }; -typedef struct LstNode *LstNode; +typedef struct LstNode LstNode; /* * Flags required for synchronization */ #define LN_DELETED 0x0001 /* List node should be removed when done */ typedef enum { LstHead, LstMiddle, LstTail, LstUnknown } LstWhere; /* * The list itself */ struct Lst { - LstNode firstPtr; /* first node in list */ - LstNode lastPtr; /* last node in list */ + LstNode *firstPtr; /* first node in list */ + LstNode *lastPtr; /* last node in list */ /* * fields for sequential access */ LstWhere atEnd; /* Where in the list the last access was */ Boolean isOpen; /* true if list has been Lst_Open'ed */ - LstNode curPtr; /* current node, if open. NULL if + LstNode *curPtr; /* current node, if open. NULL if * *just* opened */ - LstNode prevPtr; /* Previous node, if open. Used by + LstNode *prevPtr; /* Previous node, if open. Used by * Lst_Remove */ }; -typedef struct Lst *Lst; +typedef struct Lst Lst; typedef int CompareProc(void *, void *); typedef int DoProc(void *, void *); typedef void *DuplicateProc(void *); typedef void FreeProc(void *); /* * NOFREE can be used as the freeProc to Lst_Destroy when the elements are * not to be freed. * NOCOPY performs similarly when given as the copyProc to Lst_Duplicate. */ #define NOFREE ((FreeProc *)NULL) #define NOCOPY ((DuplicateProc *)NULL) #define LST_CONCNEW 0 /* create new LstNode's when using Lst_Concat */ #define LST_CONCLINK 1 /* relink LstNode's when using Lst_Concat */ /* * Creation/destruction functions */ /* Create a new list */ -Lst Lst_Init(void); +Lst *Lst_Init(void); /* Duplicate an existing list */ -Lst Lst_Duplicate(Lst, DuplicateProc *); +Lst *Lst_Duplicate(Lst *, DuplicateProc *); /* Destroy an old one */ -void Lst_Destroy(Lst, FreeProc *); +void Lst_Destroy(Lst *, FreeProc *); /* * Functions to modify a list */ /* Insert an element before another */ -ReturnStatus Lst_Insert(Lst, LstNode, void *); +ReturnStatus Lst_Insert(Lst *, LstNode *, void *); /* Insert an element after another */ -ReturnStatus Lst_Append(Lst, LstNode, void *); +ReturnStatus Lst_Append(Lst *, LstNode *, void *); /* Place an element at the front of a lst. */ #define Lst_AtFront(LST, D) (Lst_Insert((LST), Lst_First(LST), (D))) /* Place an element at the end of a lst. */ #define Lst_AtEnd(LST, D) (Lst_Append((LST), Lst_Last(LST), (D))) /* Remove an element */ -ReturnStatus Lst_Remove(Lst, LstNode); +ReturnStatus Lst_Remove(Lst *, LstNode *); /* Replace a node with a new value */ #define Lst_Replace(NODE, D) (((NODE) == NULL) ? FAILURE : \ (((NODE)->datum = (D)), SUCCESS)) /* Concatenate two lists */ -ReturnStatus Lst_Concat(Lst, Lst, int); +ReturnStatus Lst_Concat(Lst *, Lst *, int); /* * Node-specific functions */ /* Return first element in list */ #define Lst_First(LST) ((Lst_Valid(LST) && !Lst_IsEmpty(LST)) \ ? (LST)->firstPtr : NULL) /* Return last element in list */ #define Lst_Last(LST) ((Lst_Valid(LST) && !Lst_IsEmpty(LST)) \ ? (LST)->lastPtr : NULL) /* Return successor to given element */ #define Lst_Succ(NODE) (((NODE) == NULL) ? NULL : (NODE)->nextPtr) /* Get datum from LstNode */ #define Lst_Datum(NODE) ((NODE)->datum) /* * Functions for entire lists */ /* Find an element in a list */ #define Lst_Find(LST, D, FN) (Lst_FindFrom((LST), Lst_First(LST), (D), (FN))) /* Find an element starting from somewhere */ -LstNode Lst_FindFrom(Lst, LstNode, void *, CompareProc *); +LstNode *Lst_FindFrom(Lst *, LstNode *, void *, CompareProc *); /* * See if the given datum is on the list. Returns the LstNode containing * the datum */ -LstNode Lst_Member(Lst, void *); +LstNode *Lst_Member(Lst *, void *); /* Apply a function to all elements of a lst */ -void Lst_ForEach(Lst, DoProc *, void *); +void Lst_ForEach(Lst *, DoProc *, void *); #define Lst_ForEach(LST, FN, D) (Lst_ForEachFrom((LST), Lst_First(LST), \ (FN), (D))) /* * Apply a function to all elements of a lst starting from a certain point. * If the list is circular, the application will wrap around to the * beginning of the list again. */ -void Lst_ForEachFrom(Lst, LstNode, DoProc *, void *); +void Lst_ForEachFrom(Lst *, LstNode *, DoProc *, void *); /* * these functions are for dealing with a list as a table, of sorts. * An idea of the "current element" is kept and used by all the functions * between Lst_Open() and Lst_Close(). */ /* Open the list */ -ReturnStatus Lst_Open(Lst); +ReturnStatus Lst_Open(Lst *); /* Next element please */ -LstNode Lst_Next(Lst); +LstNode *Lst_Next(Lst *); /* Done yet? */ -Boolean Lst_IsAtEnd(Lst); +Boolean Lst_IsAtEnd(Lst *); /* Finish table access */ -void Lst_Close(Lst); +void Lst_Close(Lst *); /* * for using the list as a queue */ /* Place an element at tail of queue */ #define Lst_EnQueue(LST, D) (Lst_Valid(LST) \ ? Lst_Append((LST), Lst_Last(LST), (D)) \ : FAILURE) /* Remove an element from head of queue */ -void * Lst_DeQueue(Lst); +void *Lst_DeQueue(Lst *); /* * LstValid (L) -- * Return TRUE if the list L is valid */ #define Lst_Valid(L) (((L) == NULL) ? FALSE : TRUE) /* * LstNodeValid (LN, L) -- * Return TRUE if the LstNode LN is valid with respect to L */ #define Lst_NodeValid(LN, L) (((LN) == NULL) ? FALSE : TRUE) /* * Lst_IsEmpty(L) -- * TRUE if the list L is empty. */ #define Lst_IsEmpty(L) (!Lst_Valid(L) || (L)->firstPtr == NULL) #endif /* _LST_H_ */ Index: head/usr.bin/make/lst.lib/lstAppend.c =================================================================== --- head/usr.bin/make/lst.lib/lstAppend.c (revision 138511) +++ head/usr.bin/make/lst.lib/lstAppend.c (revision 138512) @@ -1,109 +1,109 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)lstAppend.c 8.1 (Berkeley) 6/6/93 */ #ifndef lint #include __FBSDID("$FreeBSD$"); #endif /* not lint */ /*- * LstAppend.c -- * Add a new node with a new datum after an existing node */ #include "make.h" #include "lst.h" /*- *----------------------------------------------------------------------- * Lst_Append -- * Create a new node and add it to the given list after the given node. * * Results: * SUCCESS if all went well. * * Arguments: * l affected list * ln node after which to append the datum * d said datum * * Side Effects: - * A new ListNode is created and linked in to the List. The lastPtr + * A new LstNode is created and linked in to the List. The lastPtr * field of the List will be altered if ln is the last node in the * list. lastPtr and firstPtr will alter if the list was empty and * ln was NULL. * *----------------------------------------------------------------------- */ ReturnStatus -Lst_Append(Lst list, LstNode ln, void *d) +Lst_Append(Lst *list, LstNode *ln, void *d) { - LstNode nLNode; + LstNode *nLNode; if (Lst_Valid(list) && (ln == NULL && Lst_IsEmpty(list))) { goto ok; } if (!Lst_Valid(list) || Lst_IsEmpty(list) || ! Lst_NodeValid(ln, list)) { return (FAILURE); } ok: nLNode = emalloc(sizeof(*nLNode)); nLNode->datum = d; nLNode->useCount = nLNode->flags = 0; if (ln == NULL) { nLNode->nextPtr = nLNode->prevPtr = NULL; list->firstPtr = list->lastPtr = nLNode; } else { nLNode->prevPtr = ln; nLNode->nextPtr = ln->nextPtr; ln->nextPtr = nLNode; if (nLNode->nextPtr != NULL) { nLNode->nextPtr->prevPtr = nLNode; } if (ln == list->lastPtr) { list->lastPtr = nLNode; } } return (SUCCESS); } Index: head/usr.bin/make/lst.lib/lstClose.c =================================================================== --- head/usr.bin/make/lst.lib/lstClose.c (revision 138511) +++ head/usr.bin/make/lst.lib/lstClose.c (revision 138512) @@ -1,80 +1,80 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)lstClose.c 8.1 (Berkeley) 6/6/93 */ #ifndef lint #include __FBSDID("$FreeBSD$"); #endif /* not lint */ /*- * LstClose.c -- * Close a list for sequential access. * The sequential functions access the list in a slightly different way. * CurPtr points to their idea of the current node in the list and they * access the list based on it. Lst_IsAtEnd must be used to determine * when to stop. */ #include "make.h" #include "lst.h" /*- *----------------------------------------------------------------------- * Lst_Close -- * Close a list which was opened for sequential access. * * Results: * None. * * Arguments: * l The list to close * * Side Effects: * The list is closed. * *----------------------------------------------------------------------- */ void -Lst_Close(Lst list) +Lst_Close(Lst *list) { if (Lst_Valid(list) == TRUE) { list->isOpen = FALSE; list->atEnd = LstUnknown; } } Index: head/usr.bin/make/lst.lib/lstConcat.c =================================================================== --- head/usr.bin/make/lst.lib/lstConcat.c (revision 138511) +++ head/usr.bin/make/lst.lib/lstConcat.c (revision 138512) @@ -1,153 +1,153 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)lstConcat.c 8.1 (Berkeley) 6/6/93 */ #ifndef lint #include __FBSDID("$FreeBSD$"); #endif /* not lint */ /*- * listConcat.c -- * Function to concatentate two lists. */ #include "make.h" #include "lst.h" /*- *----------------------------------------------------------------------- * Lst_Concat -- * Concatenate two lists. New elements are created to hold the data * elements, if specified, but the elements themselves are not copied. * If the elements should be duplicated to avoid confusion with another * list, the Lst_Duplicate function should be called first. * If LST_CONCLINK is specified, the second list is destroyed since * its pointers have been corrupted and the list is no longer useable. * * Results: * SUCCESS if all went well. FAILURE otherwise. * * Arguments: * list1 The list to which list2 is to be appended * list2 The list to append to list1 * flags LST_CONCNEW if LstNode's should be duplicated * LST_CONCLINK if should just be relinked * * Side Effects: * New elements are created and appended the the first list. *----------------------------------------------------------------------- */ ReturnStatus -Lst_Concat(Lst list1, Lst list2, int flags) +Lst_Concat(Lst *list1, Lst *list2, int flags) { - LstNode ln; /* original LstNode */ - LstNode nln; /* new LstNode */ - LstNode last; /* the last element in the list. Keeps - * bookkeeping until the end */ + LstNode *ln; /* original LstNode */ + LstNode *nln; /* new LstNode */ + LstNode *last; /* the last element in the list. Keeps + * bookkeeping until the end */ if (!Lst_Valid(list1) || !Lst_Valid(list2)) { return (FAILURE); } if (flags == LST_CONCLINK) { if (list2->firstPtr != NULL) { /* * We set the nextPtr of the last element of list two to be NULL * to make the loop easier and so we don't need an extra case -- * the final element will already point to NULL space and the first * element will be untouched if it existed before and will also * point to NULL space if it didn't. */ list2->lastPtr->nextPtr = NULL; /* * So long as the second list isn't empty, we just link the * first element of the second list to the last element of the * first list. If the first list isn't empty, we then link the * last element of the list to the first element of the second list * The last element of the second list, if it exists, then becomes * the last element of the first list. */ list2->firstPtr->prevPtr = list1->lastPtr; if (list1->lastPtr != NULL) { list1->lastPtr->nextPtr = list2->firstPtr; } else { list1->firstPtr = list2->firstPtr; } list1->lastPtr = list2->lastPtr; } free(list2); } else if (list2->firstPtr != NULL) { /* * We set the nextPtr of the last element of list 2 to be NULL to make * the loop less difficult. The loop simply goes through the entire * second list creating new LstNodes and filling in the nextPtr, and * prevPtr to fit into list1 and its datum field from the * datum field of the corresponding element in list2. The 'last' node * follows the last of the new nodes along until the entire list2 has * been appended. Only then does the bookkeeping catch up with the * changes. During the first iteration of the loop, if 'last' is NULL, * the first list must have been empty so the newly-created node is * made the first node of the list. */ list2->lastPtr->nextPtr = NULL; for (last = list1->lastPtr, ln = list2->firstPtr; ln != NULL; ln = ln->nextPtr) { nln = emalloc(sizeof(*nln)); nln->datum = ln->datum; if (last != NULL) { last->nextPtr = nln; } else { list1->firstPtr = nln; } nln->prevPtr = last; nln->flags = nln->useCount = 0; last = nln; } /* * Finish bookkeeping. The last new element becomes the last element * of list one. */ list1->lastPtr = last; last->nextPtr = NULL; } return (SUCCESS); } Index: head/usr.bin/make/lst.lib/lstDeQueue.c =================================================================== --- head/usr.bin/make/lst.lib/lstDeQueue.c (revision 138511) +++ head/usr.bin/make/lst.lib/lstDeQueue.c (revision 138512) @@ -1,83 +1,83 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)lstDeQueue.c 8.1 (Berkeley) 6/6/93 */ #ifndef lint #include __FBSDID("$FreeBSD$"); #endif /* not lint */ /*- * LstDeQueue.c -- * Remove the node and return its datum from the head of the list */ #include "make.h" #include "lst.h" /*- *----------------------------------------------------------------------- * Lst_DeQueue -- * Remove and return the datum at the head of the given list. * * Results: * The datum in the node at the head or (ick) NULL if the list * is empty. * * Side Effects: * The head node is removed from the list. * *----------------------------------------------------------------------- */ void * -Lst_DeQueue(Lst l) +Lst_DeQueue(Lst *l) { - void * rd; - LstNode tln; + void *rd; + LstNode *tln; tln = Lst_First(l); if (tln == NULL) { return (NULL); } rd = tln->datum; if (Lst_Remove(l, tln) == FAILURE) { return (NULL); } else { return (rd); } } Index: head/usr.bin/make/lst.lib/lstDestroy.c =================================================================== --- head/usr.bin/make/lst.lib/lstDestroy.c (revision 138511) +++ head/usr.bin/make/lst.lib/lstDestroy.c (revision 138512) @@ -1,102 +1,102 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)lstDestroy.c 8.1 (Berkeley) 6/6/93 */ #ifndef lint #include __FBSDID("$FreeBSD$"); #endif /* not lint */ /*- * LstDestroy.c -- * Nuke a list and all its resources */ #include "make.h" #include "lst.h" /*- *----------------------------------------------------------------------- * Lst_Destroy -- * Destroy a list and free all its resources. If the freeProc is * given, it is called with the datum from each node in turn before * the node is freed. * * Results: * None. * * Side Effects: * The given list is freed in its entirety. * *----------------------------------------------------------------------- */ void -Lst_Destroy(Lst list, FreeProc *freeProc) +Lst_Destroy(Lst *list, FreeProc *freeProc) { - LstNode ln; - LstNode tln; + LstNode *ln; + LstNode *tln; if (!Lst_Valid(list)) { /* * Note the check to catch uninitialized static Lst's. * Gross, but useful. */ return; } if (list->lastPtr == NULL) { free(list); return; } /* To ease scanning */ list->lastPtr->nextPtr = NULL; if (freeProc) { for (ln = list->firstPtr; ln != NULL; ln = tln) { tln = ln->nextPtr; (*freeProc)(ln->datum); free(ln); } } else { for (ln = list->firstPtr; ln != NULL; ln = tln) { tln = ln->nextPtr; free(ln); } } free(list); } Index: head/usr.bin/make/lst.lib/lstDupl.c =================================================================== --- head/usr.bin/make/lst.lib/lstDupl.c (revision 138511) +++ head/usr.bin/make/lst.lib/lstDupl.c (revision 138512) @@ -1,99 +1,99 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)lstDupl.c 8.1 (Berkeley) 6/6/93 */ #ifndef lint #include __FBSDID("$FreeBSD$"); #endif /* not lint */ /*- * listDupl.c -- * Duplicate a list. This includes duplicating the individual * elements. */ #include "make.h" #include "lst.h" /*- *----------------------------------------------------------------------- * Lst_Duplicate -- * Duplicate an entire list. If a function to copy a void * is * given, the individual client elements will be duplicated as well. * * Results: * The new Lst structure or NULL if failure. * * Arguments: * l the list to duplicate * copyProc A function to duplicate each void * * Side Effects: * A new list is created. *----------------------------------------------------------------------- */ -Lst -Lst_Duplicate(Lst list, DuplicateProc *copyProc) +Lst * +Lst_Duplicate(Lst *list, DuplicateProc *copyProc) { - Lst nl; - LstNode ln; + Lst *nl; + LstNode *ln; if (!Lst_Valid(list)) { return (NULL); } nl = Lst_Init(); if (nl == NULL) { return (NULL); } ln = list->firstPtr; while (ln != NULL) { if (copyProc != NOCOPY) { if (Lst_AtEnd(nl, (*copyProc)(ln->datum)) == FAILURE) { return (NULL); } } else if (Lst_AtEnd (nl, ln->datum) == FAILURE) { return (NULL); } ln = ln->nextPtr; } return (nl); } Index: head/usr.bin/make/lst.lib/lstFindFrom.c =================================================================== --- head/usr.bin/make/lst.lib/lstFindFrom.c (revision 138511) +++ head/usr.bin/make/lst.lib/lstFindFrom.c (revision 138512) @@ -1,93 +1,93 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)lstFindFrom.c 8.1 (Berkeley) 6/6/93 */ #ifndef lint #include __FBSDID("$FreeBSD$"); #endif /* not lint */ /*- * LstFindFrom.c -- * Find a node on a list from a given starting point. Used by Lst_Find. */ #include "make.h" #include "lst.h" /*- *----------------------------------------------------------------------- * Lst_FindFrom -- * Search for a node starting and ending with the given one on the * given list using the passed datum and comparison function to * determine when it has been found. * * Results: * The found node or NULL * * Side Effects: * None. * *----------------------------------------------------------------------- */ -LstNode -Lst_FindFrom(Lst l, LstNode ln, void *d, CompareProc *cProc) +LstNode * +Lst_FindFrom(Lst *l, LstNode *ln, void *d, CompareProc *cProc) { - LstNode tln; + LstNode *tln; Boolean found = FALSE; if (!Lst_Valid(l) || Lst_IsEmpty(l) || !Lst_NodeValid(ln, l)) { return (NULL); } tln = ln; do { if ((*cProc)(tln->datum, d) == 0) { found = TRUE; break; } else { tln = tln->nextPtr; } } while (tln != ln && tln != NULL); if (found) { return (tln); } else { return (NULL); } } Index: head/usr.bin/make/lst.lib/lstForEachFrom.c =================================================================== --- head/usr.bin/make/lst.lib/lstForEachFrom.c (revision 138511) +++ head/usr.bin/make/lst.lib/lstForEachFrom.c (revision 138512) @@ -1,107 +1,107 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)lstForEachFrom.c 8.1 (Berkeley) 6/6/93 */ #ifndef lint #include __FBSDID("$FreeBSD$"); #endif /* not lint */ /*- * lstForEachFrom.c -- * Perform a given function on all elements of a list starting from * a given point. */ #include "make.h" #include "lst.h" /*- *----------------------------------------------------------------------- * Lst_ForEachFrom -- * Apply the given function to each element of the given list. The * function should return 0 if traversal should continue and non- * zero if it should abort. * * Results: * None. * * Side Effects: * Only those created by the passed-in function. * *----------------------------------------------------------------------- */ void -Lst_ForEachFrom(Lst list, LstNode ln, DoProc *proc, void *d) +Lst_ForEachFrom(Lst *list, LstNode *ln, DoProc *proc, void *d) { - LstNode next; + LstNode *next; Boolean done; int result; if (!Lst_Valid(list) || Lst_IsEmpty(list)) { return; } do { /* * Take care of having the current element deleted out from under * us. */ next = ln->nextPtr; ln->useCount++; result = (*proc)(ln->datum, d); ln->useCount--; /* * We're done with the traversal if * - nothing's been added after the current node and * - the next node to examine is the first in the queue or * doesn't exist. */ done = (next == ln->nextPtr && (next == NULL || next == list->firstPtr)); next = ln->nextPtr; if (ln->flags & LN_DELETED) { free(ln); } ln = next; } while (!result && !Lst_IsEmpty(list) && !done); } Index: head/usr.bin/make/lst.lib/lstInit.c =================================================================== --- head/usr.bin/make/lst.lib/lstInit.c (revision 138511) +++ head/usr.bin/make/lst.lib/lstInit.c (revision 138512) @@ -1,78 +1,78 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)lstInit.c 8.1 (Berkeley) 6/6/93 */ #ifndef lint #include __FBSDID("$FreeBSD$"); #endif /* not lint */ /*- * init.c -- * Initialize a new linked list. */ #include "make.h" #include "lst.h" /*- *----------------------------------------------------------------------- * Lst_Init -- * Create and initialize a new list. * * Results: * The created list. * * Side Effects: * A list is created, what else? * *----------------------------------------------------------------------- */ -Lst +Lst * Lst_Init(void) { - Lst nList; + Lst *nList; nList = emalloc(sizeof(*nList)); nList->firstPtr = NULL; nList->lastPtr = NULL; nList->isOpen = FALSE; nList->atEnd = LstUnknown; return (nList); } Index: head/usr.bin/make/lst.lib/lstInsert.c =================================================================== --- head/usr.bin/make/lst.lib/lstInsert.c (revision 138511) +++ head/usr.bin/make/lst.lib/lstInsert.c (revision 138512) @@ -1,110 +1,110 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)lstInsert.c 8.1 (Berkeley) 6/6/93 */ #ifndef lint #include __FBSDID("$FreeBSD$"); #endif /* not lint */ /*- * LstInsert.c -- * Insert a new datum before an old one */ #include "make.h" #include "lst.h" /*- *----------------------------------------------------------------------- * Lst_Insert -- * Insert a new node with the given piece of data before the given * node in the given list. * * Results: * SUCCESS or FAILURE. * * l list to manipulate * ln node before which to insert d * d datum to be inserted * * Side Effects: * the firstPtr field will be changed if ln is the first node in the * list. * *----------------------------------------------------------------------- */ ReturnStatus -Lst_Insert(Lst list, LstNode ln, void *d) +Lst_Insert(Lst *list, LstNode *ln, void *d) { - LstNode nLNode; /* new lnode for d */ + LstNode *nLNode; /* new lnode for d */ /* * check validity of arguments */ if (Lst_Valid(list) && (Lst_IsEmpty(list) && ln == NULL)) goto ok; if (!Lst_Valid(list) || Lst_IsEmpty(list) || !Lst_NodeValid(ln, list)) { return (FAILURE); } ok: nLNode = emalloc(sizeof(*nLNode)); nLNode->datum = d; nLNode->useCount = nLNode->flags = 0; if (ln == NULL) { nLNode->prevPtr = nLNode->nextPtr = NULL; list->firstPtr = list->lastPtr = nLNode; } else { nLNode->prevPtr = ln->prevPtr; nLNode->nextPtr = ln; if (nLNode->prevPtr != NULL) { nLNode->prevPtr->nextPtr = nLNode; } ln->prevPtr = nLNode; if (ln == list->firstPtr) { list->firstPtr = nLNode; } } return (SUCCESS); } Index: head/usr.bin/make/lst.lib/lstIsAtEnd.c =================================================================== --- head/usr.bin/make/lst.lib/lstIsAtEnd.c (revision 138511) +++ head/usr.bin/make/lst.lib/lstIsAtEnd.c (revision 138512) @@ -1,81 +1,81 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)lstIsAtEnd.c 8.1 (Berkeley) 6/6/93 */ #ifndef lint #include __FBSDID("$FreeBSD$"); #endif /* not lint */ /*- * LstIsAtEnd.c -- * Tell if the current node is at the end of the list. * The sequential functions access the list in a slightly different way. * CurPtr points to their idea of the current node in the list and they * access the list based on it. Lst_IsAtEnd must be used to determine * when to stop. */ #include "make.h" #include "lst.h" /*- *----------------------------------------------------------------------- * Lst_IsAtEnd -- * Return true if have reached the end of the given list. * * Results: * TRUE if at the end of the list (this includes the list not being * open or being invalid) or FALSE if not. We return TRUE if the list * is invalid or unopend so as to cause the caller to exit its loop * asap, the assumption being that the loop is of the form * while (!Lst_IsAtEnd (l)) { * ... * } * * Side Effects: * None. * *----------------------------------------------------------------------- */ Boolean -Lst_IsAtEnd(Lst list) +Lst_IsAtEnd(Lst *list) { return (!Lst_Valid(list) || !list->isOpen || (list->atEnd == LstHead) || (list->atEnd == LstTail)); } Index: head/usr.bin/make/lst.lib/lstMember.c =================================================================== --- head/usr.bin/make/lst.lib/lstMember.c (revision 138511) +++ head/usr.bin/make/lst.lib/lstMember.c (revision 138512) @@ -1,70 +1,70 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)lstMember.c 8.1 (Berkeley) 6/6/93 */ #ifndef lint #include __FBSDID("$FreeBSD$"); #endif /* not lint */ /*- * lstMember.c -- * See if a given datum is on a given list. */ #include "make.h" #include "lst.h" -LstNode -Lst_Member(Lst list, void *d) +LstNode * +Lst_Member(Lst *list, void *d) { - LstNode lNode; + LstNode *lNode; lNode = list->firstPtr; if (lNode == NULL) { return (NULL); } do { if (lNode->datum == d) { return (lNode); } lNode = lNode->nextPtr; } while (lNode != NULL && lNode != list->firstPtr); return (NULL); } Index: head/usr.bin/make/lst.lib/lstNext.c =================================================================== --- head/usr.bin/make/lst.lib/lstNext.c (revision 138511) +++ head/usr.bin/make/lst.lib/lstNext.c (revision 138512) @@ -1,106 +1,106 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)lstNext.c 8.1 (Berkeley) 6/6/93 */ #ifndef lint #include __FBSDID("$FreeBSD$"); #endif /* not lint */ /*- * LstNext.c -- * Return the next node for a list. * The sequential functions access the list in a slightly different way. * CurPtr points to their idea of the current node in the list and they * access the list based on it. Lst_IsAtEnd must be used to determine * when to stop. */ #include "make.h" #include "lst.h" /*- *----------------------------------------------------------------------- * Lst_Next -- * Return the next node for the given list. * * Results: * The next node or NULL if the list has yet to be opened or the end * has been reached. * * Side Effects: * the curPtr field is updated. * *----------------------------------------------------------------------- */ -LstNode -Lst_Next(Lst list) +LstNode * +Lst_Next(Lst *list) { - LstNode tln; + LstNode *tln; if ((Lst_Valid(list) == FALSE) || (list->isOpen == FALSE)) { return (NULL); } list->prevPtr = list->curPtr; if (list->curPtr == NULL) { if (list->atEnd == LstUnknown) { /* * If we're just starting out, atEnd will be Unknown. * Then we want to start this thing off in the right * direction -- at the start with atEnd being Middle. */ list->curPtr = tln = list->firstPtr; list->atEnd = LstMiddle; } else { tln = NULL; list->atEnd = LstTail; } } else { tln = list->curPtr->nextPtr; list->curPtr = tln; if (tln == NULL) { list->atEnd = LstTail; } else { list->atEnd = LstMiddle; } } return (tln); } Index: head/usr.bin/make/lst.lib/lstOpen.c =================================================================== --- head/usr.bin/make/lst.lib/lstOpen.c (revision 138511) +++ head/usr.bin/make/lst.lib/lstOpen.c (revision 138512) @@ -1,83 +1,83 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)lstOpen.c 8.1 (Berkeley) 6/6/93 */ #ifndef lint #include __FBSDID("$FreeBSD$"); #endif /* not lint */ /*- * LstOpen.c -- * Open a list for sequential access. The sequential functions access the * list in a slightly different way. CurPtr points to their idea of the * current node in the list and they access the list based on it. * Lst_IsAtEnd must be used to determine when to stop. */ #include "make.h" #include "lst.h" /*- *----------------------------------------------------------------------- * Lst_Open -- * Open a list for sequential access. A list can still be searched, * etc., without confusing these functions. * * Results: * SUCCESS or FAILURE. * * Side Effects: * isOpen is set TRUE and curPtr is set to NULL so the * other sequential functions no it was just opened and can choose * the first element accessed based on this. * *----------------------------------------------------------------------- */ ReturnStatus -Lst_Open(Lst l) +Lst_Open(Lst *l) { if (Lst_Valid(l) == FALSE) { return (FAILURE); } l->isOpen = TRUE; l->atEnd = Lst_IsEmpty(l) ? LstHead : LstUnknown; l->curPtr = NULL; return (SUCCESS); } Index: head/usr.bin/make/lst.lib/lstRemove.c =================================================================== --- head/usr.bin/make/lst.lib/lstRemove.c (revision 138511) +++ head/usr.bin/make/lst.lib/lstRemove.c (revision 138512) @@ -1,128 +1,128 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)lstRemove.c 8.1 (Berkeley) 6/6/93 */ #ifndef lint #include __FBSDID("$FreeBSD$"); #endif /* not lint */ /*- * LstRemove.c -- * Remove an element from a list */ #include "make.h" #include "lst.h" /*- *----------------------------------------------------------------------- * Lst_Remove -- * Remove the given node from the given list. * * Results: * SUCCESS or FAILURE. * * Side Effects: * The list's firstPtr will be set to NULL if ln is the last * node on the list. firsPtr and lastPtr will be altered if ln is * either the first or last node, respectively, on the list. * *----------------------------------------------------------------------- */ ReturnStatus -Lst_Remove(Lst list, LstNode ln) +Lst_Remove(Lst *list, LstNode *ln) { if (!Lst_Valid(list) || !Lst_NodeValid(ln, list)) { return (FAILURE); } /* * unlink it from the list */ if (ln->nextPtr != NULL) { ln->nextPtr->prevPtr = ln->prevPtr; } if (ln->prevPtr != NULL) { ln->prevPtr->nextPtr = ln->nextPtr; } /* * if either the firstPtr or lastPtr of the list point to this node, * adjust them accordingly */ if (list->firstPtr == ln) { list->firstPtr = ln->nextPtr; } if (list->lastPtr == ln) { list->lastPtr = ln->prevPtr; } /* * Sequential access stuff. If the node we're removing is the current * node in the list, reset the current node to the previous one. If the * previous one was non-existent (prevPtr == NULL), we set the * end to be Unknown, since it is. */ if (list->isOpen && (list->curPtr == ln)) { list->curPtr = list->prevPtr; if (list->curPtr == NULL) { list->atEnd = LstUnknown; } } /* * the only way firstPtr can still point to ln is if ln is the last * node on the list. The list is, therefore, empty and is marked as such */ if (list->firstPtr == ln) { list->firstPtr = NULL; } /* * note that the datum is unmolested. The caller must free it as * necessary and as expected. */ if (ln->useCount == 0) { free(ln); } else { ln->flags |= LN_DELETED; } return (SUCCESS); } Index: head/usr.bin/make/main.c =================================================================== --- head/usr.bin/make/main.c (revision 138511) +++ head/usr.bin/make/main.c (revision 138512) @@ -1,1133 +1,1133 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)main.c 8.3 (Berkeley) 3/19/94 */ #ifndef lint #if 0 static char copyright[] = "@(#) Copyright (c) 1988, 1989, 1990, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); /*- * main.c -- * The main file for this entire program. Exit routines etc * reside here. * * Utility functions defined in this file: * Main_ParseArgLine Takes a line of arguments, breaks them and * treats them as if they were given when first * invoked. Used by the parse module to implement * the .MFLAGS target. */ #include #include #include #include #include #include #ifndef MACHINE #include #endif #include #include #include #include #include #include #include #include #include #include #include "make.h" #include "hash.h" #include "dir.h" #include "job.h" #include "pathnames.h" #define WANT_ENV_MKLVL 1 #define MKLVL_MAXVAL 500 #define MKLVL_ENVVAR "__MKLVL__" #define MAKEFLAGS ".MAKEFLAGS" -Lst create; /* Targets to be made */ +Lst *create; /* Targets to be made */ time_t now; /* Time at start of make */ GNode *DEFAULT; /* .DEFAULT node */ Boolean allPrecious; /* .PRECIOUS given on line by itself */ static Boolean noBuiltins; /* -r flag */ -static Lst makefiles; /* ordered list of makefiles to read */ +static Lst *makefiles; /* ordered list of makefiles to read */ static Boolean expandVars; /* fully expand printed variables */ -static Lst variables; /* list of variables to print */ +static Lst *variables; /* list of variables to print */ int maxJobs; /* -j argument */ static Boolean forceJobs; /* -j argument given */ Boolean compatMake; /* -B argument */ Boolean debug; /* -d flag */ Boolean noExecute; /* -n flag */ Boolean keepgoing; /* -k flag */ Boolean queryFlag; /* -q flag */ Boolean touchFlag; /* -t flag */ Boolean usePipes; /* !-P flag */ Boolean ignoreErrors; /* -i flag */ Boolean beSilent; /* -s flag */ Boolean beVerbose; /* -v flag */ Boolean oldVars; /* variable substitution style */ Boolean checkEnvFirst; /* -e flag */ -Lst envFirstVars; /* (-E) vars to override from env */ +Lst *envFirstVars; /* (-E) vars to override from env */ Boolean jobsRunning; /* TRUE if the jobs might be running */ static void MainParseArgs(int, char **); char * chdir_verify_path(char *, char *); static int ReadMakefile(void *, void *); static void usage(void); static char *curdir; /* startup directory */ static char *objdir; /* where we chdir'ed to */ /* * Append a flag with an optional argument to MAKEFLAGS and MFLAGS */ static void MFLAGS_append(char *flag, char *arg) { Var_Append(MAKEFLAGS, flag, VAR_GLOBAL); if (arg != NULL) Var_Append(MAKEFLAGS, arg, VAR_GLOBAL); Var_Append("MFLAGS", flag, VAR_GLOBAL); if (arg != NULL) Var_Append("MFLAGS", arg, VAR_GLOBAL); } /*- * MainParseArgs -- * Parse a given argument vector. Called from main() and from * Main_ParseArgLine() when the .MAKEFLAGS target is used. * * XXX: Deal with command line overriding .MAKEFLAGS in makefile * * Results: * None * * Side Effects: * Various global and local flags will be set depending on the flags * given */ static void MainParseArgs(int argc, char **argv) { char *p; int c; optind = 1; /* since we're called more than once */ #define OPTFLAGS "BC:D:E:I:PSV:Xd:ef:ij:km:nqrstv" rearg: while((c = getopt(argc, argv, OPTFLAGS)) != -1) { switch(c) { case 'C': if (chdir(optarg) == -1) err(1, "chdir %s", optarg); break; case 'D': Var_Set(optarg, "1", VAR_GLOBAL); MFLAGS_append("-D", optarg); break; case 'I': Parse_AddIncludeDir(optarg); MFLAGS_append("-I", optarg); break; case 'V': (void)Lst_AtEnd(variables, (void *)optarg); MFLAGS_append("-V", optarg); break; case 'X': expandVars = FALSE; break; case 'B': compatMake = TRUE; MFLAGS_append("-B", NULL); unsetenv("MAKE_JOBS_FIFO"); break; case 'P': usePipes = FALSE; MFLAGS_append("-P", NULL); break; case 'S': keepgoing = FALSE; MFLAGS_append("-S", NULL); break; case 'd': { char *modules = optarg; for (; *modules; ++modules) switch (*modules) { case 'A': debug = ~0; break; case 'a': debug |= DEBUG_ARCH; break; case 'c': debug |= DEBUG_COND; break; case 'd': debug |= DEBUG_DIR; break; case 'f': debug |= DEBUG_FOR; break; case 'g': if (modules[1] == '1') { debug |= DEBUG_GRAPH1; ++modules; } else if (modules[1] == '2') { debug |= DEBUG_GRAPH2; ++modules; } break; case 'j': debug |= DEBUG_JOB; break; case 'l': debug |= DEBUG_LOUD; break; case 'm': debug |= DEBUG_MAKE; break; case 's': debug |= DEBUG_SUFF; break; case 't': debug |= DEBUG_TARG; break; case 'v': debug |= DEBUG_VAR; break; default: warnx("illegal argument to d option -- %c", *modules); usage(); } MFLAGS_append("-d", optarg); break; } case 'E': p = emalloc(strlen(optarg) + 1); strcpy(p, optarg); Lst_AtEnd(envFirstVars, p); MFLAGS_append("-E", optarg); break; case 'e': checkEnvFirst = TRUE; MFLAGS_append("-e", NULL); break; case 'f': Lst_AtEnd(makefiles, optarg); break; case 'i': ignoreErrors = TRUE; MFLAGS_append("-i", NULL); break; case 'j': { char *endptr; forceJobs = TRUE; maxJobs = strtol(optarg, &endptr, 10); if (maxJobs <= 0 || *endptr != '\0') { warnx("illegal number, -j argument -- %s", optarg); usage(); } MFLAGS_append("-j", optarg); break; } case 'k': keepgoing = TRUE; MFLAGS_append("-k", NULL); break; case 'm': Dir_AddDir(sysIncPath, optarg); MFLAGS_append("-m", optarg); break; case 'n': noExecute = TRUE; MFLAGS_append("-n", NULL); break; case 'q': queryFlag = TRUE; /* Kind of nonsensical, wot? */ MFLAGS_append("-q", NULL); break; case 'r': noBuiltins = TRUE; MFLAGS_append("-r", NULL); break; case 's': beSilent = TRUE; MFLAGS_append("-s", NULL); break; case 't': touchFlag = TRUE; MFLAGS_append("-t", NULL); break; case 'v': beVerbose = TRUE; MFLAGS_append("-v", NULL); break; default: case '?': usage(); } } oldVars = TRUE; /* * See if the rest of the arguments are variable assignments and * perform them if so. Else take them to be targets and stuff them * on the end of the "create" list. */ for (argv += optind, argc -= optind; *argv; ++argv, --argc) if (Parse_IsVar(*argv)) { char *ptr = Var_Quote(*argv); Var_Append(MAKEFLAGS, ptr, VAR_GLOBAL); free(ptr); Parse_DoVar(*argv, VAR_CMD); } else { if (!**argv) Punt("illegal (null) argument."); if (**argv == '-') { if ((*argv)[1]) optind = 0; /* -flag... */ else optind = 1; /* - */ goto rearg; } Lst_AtEnd(create, estrdup(*argv)); } } /*- * Main_ParseArgLine -- * Used by the parse module when a .MFLAGS or .MAKEFLAGS target * is encountered and by main() when reading the .MAKEFLAGS envariable. * Takes a line of arguments and breaks it into its * component words and passes those words and the number of them to the * MainParseArgs function. * The line should have all its leading whitespace removed. * * Results: * None * * Side Effects: * Only those that come from the various arguments. */ void Main_ParseArgLine(char *line) { char **argv; /* Manufactured argument vector */ int argc; /* Number of arguments in argv */ if (line == NULL) return; for (; *line == ' '; ++line) continue; if (!*line) return; argv = brk_string(line, &argc, TRUE); MainParseArgs(argc, argv); } char * chdir_verify_path(char *path, char *obpath) { struct stat sb; if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { if (chdir(path) == -1 || getcwd(obpath, MAXPATHLEN) == NULL) { warn("warning: %s", path); return (0); } return (obpath); } return (0); } static void catch_child(int sig __unused) { } /* * In lieu of a good way to prevent every possible looping in * make(1), stop there from being more than MKLVL_MAXVAL processes forked * by make(1), to prevent a forkbomb from happening, in a dumb and * mechanical way. */ static void check_make_level(void) { #ifdef WANT_ENV_MKLVL char *value = getenv(MKLVL_ENVVAR); int level = (value == NULL) ? 0 : atoi(value); if (level < 0) { errc(2, EAGAIN, "Invalid value for recursion level (%d).", level); } else if (level > MKLVL_MAXVAL) { errc(2, EAGAIN, "Max recursion level (%d) exceeded.", MKLVL_MAXVAL); } else { char new_value[32]; sprintf(new_value, "%d", level + 1); setenv(MKLVL_ENVVAR, new_value, 1); } #endif /* WANT_ENV_MKLVL */ } /*- * main -- * The main function, for obvious reasons. Initializes variables * and a few modules, then parses the arguments give it in the * environment and on the command line. Reads the system makefile * followed by either Makefile, makefile or the file given by the * -f argument. Sets the .MAKEFLAGS PMake variable based on all the * flags it has received by then uses either the Make or the Compat * module to create the initial list of targets. * * Results: * If -q was given, exits -1 if anything was out-of-date. Else it exits * 0. * * Side Effects: * The program exits when done. Targets are created. etc. etc. etc. */ int main(int argc, char **argv) { - Lst targs; /* target nodes to create -- passed to Make_Init */ + Lst *targs; /* target nodes to create -- passed to Make_Init */ Boolean outOfDate = TRUE; /* FALSE if all targets up to date */ struct stat sa; char *p, *p1, *path, *pathp; char mdpath[MAXPATHLEN]; char obpath[MAXPATHLEN]; char cdpath[MAXPATHLEN]; char *machine = getenv("MACHINE"); char *machine_arch = getenv("MACHINE_ARCH"); char *machine_cpu = getenv("MACHINE_CPU"); - Lst sysMkPath; /* Path of sys.mk */ + Lst *sysMkPath; /* Path of sys.mk */ char *cp = NULL, *start; /* avoid faults on read-only strings */ static char syspath[] = _PATH_DEFSYSPATH; { /* * Catch SIGCHLD so that we get kicked out of select() when we * need to look at a child. This is only known to matter for the * -j case (perhaps without -P). * * XXX this is intentionally misplaced. */ struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; sa.sa_handler = catch_child; sigaction(SIGCHLD, &sa, NULL); } check_make_level(); #if DEFSHELL == 2 /* * Turn off ENV to make ksh happier. */ unsetenv("ENV"); #endif #ifdef RLIMIT_NOFILE /* * get rid of resource limit on file descriptors */ { struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) != -1 && rl.rlim_cur != rl.rlim_max) { rl.rlim_cur = rl.rlim_max; setrlimit(RLIMIT_NOFILE, &rl); } } #endif /* * PC-98 kernel sets the `i386' string to the utsname.machine and * it cannot be distinguished from IBM-PC by uname(3). Therefore, * we check machine.ispc98 and adjust the machine variable before * using usname(3) below. * NOTE: machdep.ispc98 was defined on 1998/8/31. At that time, * __FreeBSD_version was defined as 300003. So, this check can * safely be done with any kernel with version > 300003. */ if (!machine) { int ispc98; size_t len; len = sizeof(ispc98); if (!sysctlbyname("machdep.ispc98", &ispc98, &len, NULL, 0)) { if (ispc98) machine = "pc98"; } } /* * Get the name of this type of MACHINE from utsname * so we can share an executable for similar machines. * (i.e. m68k: amiga hp300, mac68k, sun3, ...) * * Note that while MACHINE is decided at run-time, * MACHINE_ARCH is always known at compile time. */ if (!machine) { #ifndef MACHINE struct utsname utsname; if (uname(&utsname) == -1) err(2, "uname"); machine = utsname.machine; #else machine = MACHINE; #endif } if (!machine_arch) { #ifndef MACHINE_ARCH machine_arch = "unknown"; #else machine_arch = MACHINE_ARCH; #endif } /* * Set machine_cpu to the minumum supported CPU revision based * on the target architecture, if not already set. */ if (!machine_cpu) { if (!strcmp(machine_arch, "i386")) machine_cpu = "i386"; else if (!strcmp(machine_arch, "alpha")) machine_cpu = "ev4"; else machine_cpu = "unknown"; } create = Lst_Init(); makefiles = Lst_Init(); envFirstVars = Lst_Init(); expandVars = TRUE; variables = Lst_Init(); beSilent = FALSE; /* Print commands as executed */ ignoreErrors = FALSE; /* Pay attention to non-zero returns */ noExecute = FALSE; /* Execute all commands */ keepgoing = FALSE; /* Stop on error */ allPrecious = FALSE; /* Remove targets when interrupted */ queryFlag = FALSE; /* This is not just a check-run */ noBuiltins = FALSE; /* Read the built-in rules */ touchFlag = FALSE; /* Actually update targets */ usePipes = TRUE; /* Catch child output in pipes */ debug = 0; /* No debug verbosity, please. */ jobsRunning = FALSE; maxJobs = DEFMAXJOBS; forceJobs = FALSE; /* No -j flag */ compatMake = FALSE; /* No compat mode */ /* * Initialize the parsing, directory and variable modules to prepare * for the reading of inclusion paths and variable settings on the * command line */ Dir_Init(); /* Initialize directory structures so -I flags * can be processed correctly */ Parse_Init(); /* Need to initialize the paths of #include * directories */ Var_Init(); /* As well as the lists of variables for * parsing arguments */ str_init(); /* * Initialize various variables. * MAKE also gets this name, for compatibility * .MAKEFLAGS gets set to the empty string just in case. * MFLAGS also gets initialized empty, for compatibility. */ Var_Set("MAKE", argv[0], VAR_GLOBAL); Var_Set(MAKEFLAGS, "", VAR_GLOBAL); Var_Set("MFLAGS", "", VAR_GLOBAL); Var_Set("MACHINE", machine, VAR_GLOBAL); Var_Set("MACHINE_ARCH", machine_arch, VAR_GLOBAL); Var_Set("MACHINE_CPU", machine_cpu, VAR_GLOBAL); #ifdef MAKE_VERSION Var_Set("MAKE_VERSION", MAKE_VERSION, VAR_GLOBAL); #endif /* * First snag any flags out of the MAKE environment variable. * (Note this is *not* MAKEFLAGS since /bin/make uses that and it's * in a different format). */ Main_ParseArgLine(getenv("MAKEFLAGS")); MainParseArgs(argc, argv); /* * Find where we are... * All this code is so that we know where we are when we start up * on a different machine with pmake. */ curdir = cdpath; if (getcwd(curdir, MAXPATHLEN) == NULL) err(2, NULL); if (stat(curdir, &sa) == -1) err(2, "%s", curdir); /* * The object directory location is determined using the * following order of preference: * * 1. MAKEOBJDIRPREFIX`cwd` * 2. MAKEOBJDIR * 3. _PATH_OBJDIR.${MACHINE} * 4. _PATH_OBJDIR * 5. _PATH_OBJDIRPREFIX`cwd` * * If one of the first two fails, use the current directory. * If the remaining three all fail, use the current directory. * * Once things are initted, * have to add the original directory to the search path, * and modify the paths for the Makefiles apropriately. The * current directory is also placed as a variable for make scripts. */ if (!(pathp = getenv("MAKEOBJDIRPREFIX"))) { if (!(path = getenv("MAKEOBJDIR"))) { path = _PATH_OBJDIR; pathp = _PATH_OBJDIRPREFIX; snprintf(mdpath, MAXPATHLEN, "%s.%s", path, machine); if (!(objdir = chdir_verify_path(mdpath, obpath))) if (!(objdir=chdir_verify_path(path, obpath))) { snprintf(mdpath, MAXPATHLEN, "%s%s", pathp, curdir); if (!(objdir=chdir_verify_path(mdpath, obpath))) objdir = curdir; } } else if (!(objdir = chdir_verify_path(path, obpath))) objdir = curdir; } else { snprintf(mdpath, MAXPATHLEN, "%s%s", pathp, curdir); if (!(objdir = chdir_verify_path(mdpath, obpath))) objdir = curdir; } Dir_InitDot(); /* Initialize the "." directory */ if (objdir != curdir) Dir_AddDir(dirSearchPath, curdir); Var_Set(".CURDIR", curdir, VAR_GLOBAL); Var_Set(".OBJDIR", objdir, VAR_GLOBAL); if (getenv("MAKE_JOBS_FIFO") != NULL) forceJobs = TRUE; /* * Be compatible if user did not specify -j and did not explicitly * turned compatibility on */ if (!compatMake && !forceJobs) compatMake = TRUE; /* * Initialize archive, target and suffix modules in preparation for * parsing the makefile(s) */ Arch_Init(); Targ_Init(); Suff_Init(); DEFAULT = NULL; time(&now); /* * Set up the .TARGETS variable to contain the list of targets to be * created. If none specified, make the variable empty -- the parser * will fill the thing in with the default or .MAIN target. */ if (!Lst_IsEmpty(create)) { - LstNode ln; + LstNode *ln; for (ln = Lst_First(create); ln != NULL; ln = Lst_Succ(ln)) { char *name = Lst_Datum(ln); Var_Append(".TARGETS", name, VAR_GLOBAL); } } else Var_Set(".TARGETS", "", VAR_GLOBAL); /* * If no user-supplied system path was given (through the -m option) * add the directories from the DEFSYSPATH (more than one may be given * as dir1:...:dirn) to the system include path. */ if (Lst_IsEmpty(sysIncPath)) { for (start = syspath; *start != '\0'; start = cp) { for (cp = start; *cp != '\0' && *cp != ':'; cp++) continue; if (*cp == '\0') { Dir_AddDir(sysIncPath, start); } else { *cp++ = '\0'; Dir_AddDir(sysIncPath, start); } } } /* * Read in the built-in rules first, followed by the specified * makefile, if it was (makefile != (char *) NULL), or the default * Makefile and makefile, in that order, if it wasn't. */ if (!noBuiltins) { - LstNode ln; + LstNode *ln; sysMkPath = Lst_Init(); Dir_Expand(_PATH_DEFSYSMK, sysIncPath, sysMkPath); if (Lst_IsEmpty(sysMkPath)) Fatal("make: no system rules (%s).", _PATH_DEFSYSMK); ln = Lst_Find(sysMkPath, NULL, ReadMakefile); if (ln != NULL) Fatal("make: cannot open %s.", (char *)Lst_Datum(ln)); } if (!Lst_IsEmpty(makefiles)) { - LstNode ln; + LstNode *ln; ln = Lst_Find(makefiles, NULL, ReadMakefile); if (ln != NULL) Fatal("make: cannot open %s.", (char *)Lst_Datum(ln)); } else if (!ReadMakefile("BSDmakefile", NULL)) if (!ReadMakefile("makefile", NULL)) ReadMakefile("Makefile", NULL); ReadMakefile(".depend", NULL); /* Install all the flags into the MAKE envariable. */ if (((p = Var_Value(MAKEFLAGS, VAR_GLOBAL, &p1)) != NULL) && *p) setenv("MAKEFLAGS", p, 1); free(p1); /* * For compatibility, look at the directories in the VPATH variable * and add them to the search path, if the variable is defined. The * variable's value is in the same format as the PATH envariable, i.e. * ::... */ if (Var_Exists("VPATH", VAR_CMD)) { char *vpath, savec; /* * GCC stores string constants in read-only memory, but * Var_Subst will want to write this thing, so store it * in an array */ static char VPATH[] = "${VPATH}"; vpath = Var_Subst(NULL, VPATH, VAR_CMD, FALSE); path = vpath; do { /* skip to end of directory */ for (cp = path; *cp != ':' && *cp != '\0'; cp++) continue; /* Save terminator character so know when to stop */ savec = *cp; *cp = '\0'; /* Add directory to search path */ Dir_AddDir(dirSearchPath, path); *cp = savec; path = cp + 1; } while (savec == ':'); free(vpath); } /* * Now that all search paths have been read for suffixes et al, it's * time to add the default search path to their lists... */ Suff_DoPaths(); /* print the initial graph, if the user requested it */ if (DEBUG(GRAPH1)) Targ_PrintGraph(1); /* print the values of any variables requested by the user */ if (!Lst_IsEmpty(variables)) { - LstNode ln; + LstNode *ln; for (ln = Lst_First(variables); ln != NULL; ln = Lst_Succ(ln)) { char *value; if (expandVars) { p1 = emalloc(strlen(Lst_Datum(ln)) + 1 + 3); /* This sprintf is safe, because of the malloc above */ sprintf(p1, "${%s}", (char *)Lst_Datum(ln)); value = Var_Subst(NULL, p1, VAR_GLOBAL, FALSE); } else { value = Var_Value(Lst_Datum(ln), VAR_GLOBAL, &p1); } printf("%s\n", value ? value : ""); if (p1) free(p1); } } else { /* * Have now read the entire graph and need to make a list of targets * to create. If none was given on the command line, we consult the * parsing module to find the main target(s) to create. */ if (Lst_IsEmpty(create)) targs = Parse_MainName(); else targs = Targ_FindList(create, TARG_CREATE); if (!compatMake) { /* * Initialize job module before traversing the graph, now that * any .BEGIN and .END targets have been read. This is done * only if the -q flag wasn't given (to prevent the .BEGIN from * being executed should it exist). */ if (!queryFlag) { Job_Init(maxJobs); jobsRunning = TRUE; } /* Traverse the graph, checking on all the targets */ outOfDate = Make_Run(targs); } else { /* * Compat_Init will take care of creating all the targets as * well as initializing the module. */ Compat_Run(targs); outOfDate = 0; } Lst_Destroy(targs, NOFREE); } Lst_Destroy(variables, NOFREE); Lst_Destroy(makefiles, NOFREE); Lst_Destroy(create, free); /* print the graph now it's been processed if the user requested it */ if (DEBUG(GRAPH2)) Targ_PrintGraph(2); Suff_End(); Targ_End(); Arch_End(); str_end(); Var_End(); Parse_End(); Dir_End(); if (queryFlag && outOfDate) return (1); else return (0); } /*- * ReadMakefile -- * Open and parse the given makefile. * * Results: * TRUE if ok. FALSE if couldn't open file. * * Side Effects: * lots */ static Boolean ReadMakefile(void *p, void *q __unused) { char *fname; /* makefile to read */ FILE *stream; char *name, path[MAXPATHLEN]; char *MAKEFILE; int setMAKEFILE; fname = p; if (!strcmp(fname, "-")) { Parse_File("(stdin)", stdin); Var_Set("MAKEFILE", "", VAR_GLOBAL); } else { setMAKEFILE = strcmp(fname, ".depend"); /* if we've chdir'd, rebuild the path name */ if (curdir != objdir && *fname != '/') { snprintf(path, MAXPATHLEN, "%s/%s", curdir, fname); /* * XXX The realpath stuff breaks relative includes * XXX in some cases. The problem likely is in * XXX parse.c where it does special things in * XXX ParseDoInclude if the file is relateive * XXX or absolute and not a system file. There * XXX it assumes that if the current file that's * XXX being included is absolute, that any files * XXX that it includes shouldn't do the -I path * XXX stuff, which is inconsistant with historical * XXX behavior. However, I can't pentrate the mists * XXX further, so I'm putting this workaround in * XXX here until such time as the underlying bug * XXX can be fixed. */ #if THIS_BREAKS_THINGS if (realpath(path, path) != NULL && (stream = fopen(path, "r")) != NULL) { MAKEFILE = fname; fname = path; goto found; } } else if (realpath(fname, path) != NULL) { MAKEFILE = fname; fname = path; if ((stream = fopen(fname, "r")) != NULL) goto found; } #else if ((stream = fopen(path, "r")) != NULL) { MAKEFILE = fname; fname = path; goto found; } } else { MAKEFILE = fname; if ((stream = fopen(fname, "r")) != NULL) goto found; } #endif /* look in -I and system include directories. */ name = Dir_FindFile(fname, parseIncPath); if (!name) name = Dir_FindFile(fname, sysIncPath); if (!name || !(stream = fopen(name, "r"))) return (FALSE); MAKEFILE = fname = name; /* * set the MAKEFILE variable desired by System V fans -- the * placement of the setting here means it gets set to the last * makefile specified, as it is set by SysV make. */ found: if (setMAKEFILE) Var_Set("MAKEFILE", MAKEFILE, VAR_GLOBAL); Parse_File(fname, stream); fclose(stream); } return (TRUE); } /*- * Cmd_Exec -- * Execute the command in cmd, and return the output of that command * in a string. * * Results: * A string containing the output of the command, or the empty string * If error is not NULL, it contains the reason for the command failure * * Side Effects: * The string must be freed by the caller. */ char * Cmd_Exec(char *cmd, char **error) { char *args[4]; /* Args for invoking the shell */ int fds[2]; /* Pipe streams */ int cpid; /* Child PID */ int pid; /* PID from wait() */ char *res; /* result */ int status; /* command exit status */ Buffer buf; /* buffer to store the result */ char *cp; size_t blen; ssize_t rcnt; *error = NULL; if (shellPath == NULL) Shell_Init(); /* * Set up arguments for shell */ args[0] = shellName; args[1] = "-c"; args[2] = cmd; args[3] = NULL; /* * Open a pipe for fetching its output */ if (pipe(fds) == -1) { *error = "Couldn't create pipe for \"%s\""; goto bad; } /* * Fork */ switch (cpid = vfork()) { case 0: /* * Close input side of pipe */ close(fds[0]); /* * Duplicate the output stream to the shell's output, then * shut the extra thing down. Note we don't fetch the error * stream...why not? Why? */ dup2(fds[1], 1); close(fds[1]); execv(shellPath, args); _exit(1); /*NOTREACHED*/ case -1: *error = "Couldn't exec \"%s\""; goto bad; default: /* * No need for the writing half */ close(fds[1]); buf = Buf_Init(MAKE_BSIZE); do { char result[BUFSIZ]; rcnt = read(fds[0], result, sizeof(result)); if (rcnt != -1) Buf_AddBytes(buf, (size_t)rcnt, (Byte *)result); } while (rcnt > 0 || (rcnt == -1 && errno == EINTR)); /* * Close the input side of the pipe. */ close(fds[0]); /* * Wait for the process to exit. */ while (((pid = wait(&status)) != cpid) && (pid >= 0)) continue; if (rcnt == -1) *error = "Error reading shell's output for \"%s\""; res = (char *)Buf_GetAll(buf, &blen); Buf_Destroy(buf, FALSE); if (status) *error = "\"%s\" returned non-zero status"; /* * Null-terminate the result, convert newlines to spaces and * install it in the variable. */ res[blen] = '\0'; cp = &res[blen] - 1; if (*cp == '\n') { /* * A final newline is just stripped */ *cp-- = '\0'; } while (cp >= res) { if (*cp == '\n') { *cp = ' '; } cp--; } break; } return (res); bad: res = emalloc(1); *res = '\0'; return (res); } /* * usage -- * exit with usage message */ static void usage(void) { fprintf(stderr, "%s\n%s\n%s\n", "usage: make [-BPSXeiknqrstv] [-C directory] [-D variable] [-d flags]", " [-E variable] [-f makefile] [-I directory] [-j max_jobs]", " [-m directory] [-V variable] [variable=value] [target ...]"); exit(2); } Index: head/usr.bin/make/make.c =================================================================== --- head/usr.bin/make/make.c (revision 138511) +++ head/usr.bin/make/make.c (revision 138512) @@ -1,867 +1,867 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)make.c 8.1 (Berkeley) 6/6/93 */ #include __FBSDID("$FreeBSD$"); /*- * make.c -- * The functions which perform the examination of targets and * their suitability for creation * * Interface: * Make_Run Initialize things for the module and recreate * whatever needs recreating. Returns TRUE if * work was (or would have been) done and FALSE * otherwise. * * Make_Update Update all parents of a given child. Performs * various bookkeeping chores like the updating * of the cmtime field of the parent, filling * of the IMPSRC context variable, etc. It will * place the parent on the toBeMade queue if it * should be. * * Make_TimeStamp Function to set the parent's cmtime field * based on a child's modification time. * * Make_DoAllVar Set up the various local variables for a * target, including the .ALLSRC variable, making * sure that any variable that needs to exist * at the very least has the empty value. * * Make_OODate Determine if a target is out-of-date. * * Make_HandleUse See if a child is a .USE node for a parent * and perform the .USE actions if so. */ #include "make.h" #include "hash.h" #include "dir.h" #include "job.h" -static Lst toBeMade; /* The current fringe of the graph. These +static Lst *toBeMade; /* The current fringe of the graph. These * are nodes which await examination by * MakeOODate. It is added to by * Make_Update and subtracted from by * MakeStartJobs */ static int numNodes; /* Number of nodes to be processed. If this * is non-zero when Job_Empty() returns * TRUE, there's a cycle in the graph */ static int MakeAddChild(void *, void *); static int MakeAddAllSrc(void *, void *); static int MakeTimeStamp(void *, void *); static int MakeHandleUse(void *, void *); static Boolean MakeStartJobs(void); static int MakePrintStatus(void *, void *); /*- *----------------------------------------------------------------------- * Make_TimeStamp -- * Set the cmtime field of a parent node based on the mtime stamp in its * child. Called from MakeOODate via Lst_ForEach. * * Results: * Always returns 0. * * Side Effects: * The cmtime of the parent node will be changed if the mtime * field of the child is greater than it. *----------------------------------------------------------------------- */ int Make_TimeStamp(GNode *pgn, GNode *cgn) { if (cgn->mtime > pgn->cmtime) { pgn->cmtime = cgn->mtime; } return (0); } static int MakeTimeStamp(void *pgn, void *cgn) { return (Make_TimeStamp(pgn, cgn)); } /*- *----------------------------------------------------------------------- * Make_OODate -- * See if a given node is out of date with respect to its sources. * Used by Make_Run when deciding which nodes to place on the * toBeMade queue initially and by Make_Update to screen out USE and * EXEC nodes. In the latter case, however, any other sort of node * must be considered out-of-date since at least one of its children * will have been recreated. * * Results: * TRUE if the node is out of date. FALSE otherwise. * * Side Effects: * The mtime field of the node and the cmtime field of its parents * will/may be changed. *----------------------------------------------------------------------- */ Boolean Make_OODate(GNode *gn) { Boolean oodate; /* * Certain types of targets needn't even be sought as their datedness * doesn't depend on their modification time... */ if ((gn->type & (OP_JOIN | OP_USE | OP_EXEC)) == 0) { Dir_MTime(gn); if (gn->mtime != 0) { DEBUGF(MAKE, ("modified %s...", Targ_FmtTime(gn->mtime))); } else { DEBUGF(MAKE, ("non-existent...")); } } /* * A target is remade in one of the following circumstances: * its modification time is smaller than that of its youngest child * and it would actually be run (has commands or type OP_NOP) * it's the object of a force operator * it has no children, was on the lhs of an operator and doesn't exist * already. * * Libraries are only considered out-of-date if the archive module says * they are. * * These weird rules are brought to you by Backward-Compatability and * the strange people who wrote 'Make'. */ if (gn->type & OP_USE) { /* * If the node is a USE node it is *never* out of date * no matter *what*. */ DEBUGF(MAKE, (".USE node...")); oodate = FALSE; } else if (gn->type & OP_LIB) { DEBUGF(MAKE, ("library...")); /* * always out of date if no children and :: target */ oodate = Arch_LibOODate(gn) || ((gn->cmtime == 0) && (gn->type & OP_DOUBLEDEP)); } else if (gn->type & OP_JOIN) { /* * A target with the .JOIN attribute is only considered * out-of-date if any of its children was out-of-date. */ DEBUGF(MAKE, (".JOIN node...")); oodate = gn->childMade; } else if (gn->type & (OP_FORCE|OP_EXEC|OP_PHONY)) { /* * A node which is the object of the force (!) operator or which has * the .EXEC attribute is always considered out-of-date. */ if (gn->type & OP_FORCE) { DEBUGF(MAKE, ("! operator...")); } else if (gn->type & OP_PHONY) { DEBUGF(MAKE, (".PHONY node...")); } else { DEBUGF(MAKE, (".EXEC node...")); } oodate = TRUE; } else if ((gn->mtime < gn->cmtime) || ((gn->cmtime == 0) && ((gn->mtime==0) || (gn->type & OP_DOUBLEDEP)))) { /* * A node whose modification time is less than that of its * youngest child or that has no children (cmtime == 0) and * either doesn't exist (mtime == 0) or was the object of a * :: operator is out-of-date. Why? Because that's the way Make does * it. */ if (gn->mtime < gn->cmtime) { DEBUGF(MAKE, ("modified before source...")); } else if (gn->mtime == 0) { DEBUGF(MAKE, ("non-existent and no sources...")); } else { DEBUGF(MAKE, (":: operator and no sources...")); } oodate = TRUE; } else oodate = FALSE; /* * If the target isn't out-of-date, the parents need to know its * modification time. Note that targets that appear to be out-of-date * but aren't, because they have no commands and aren't of type OP_NOP, * have their mtime stay below their children's mtime to keep parents from * thinking they're out-of-date. */ if (!oodate) { Lst_ForEach(gn->parents, MakeTimeStamp, gn); } return (oodate); } /*- *----------------------------------------------------------------------- * MakeAddChild -- * Function used by Make_Run to add a child to the list l. * It will only add the child if its make field is FALSE. * * Results: * Always returns 0 * * Side Effects: * The given list is extended *----------------------------------------------------------------------- */ static int MakeAddChild(void *gnp, void *lp) { GNode *gn = gnp; - Lst l = lp; + Lst *l = lp; if (!gn->make && !(gn->type & OP_USE)) { Lst_EnQueue(l, gn); } return (0); } /*- *----------------------------------------------------------------------- * Make_HandleUse -- * Function called by Make_Run and SuffApplyTransform on the downward * pass to handle .USE and transformation nodes. A callback function * for Lst_ForEach, it implements the .USE and transformation * functionality by copying the node's commands, type flags * and children to the parent node. Should be called before the * children are enqueued to be looked at by MakeAddChild. * * A .USE node is much like an explicit transformation rule, except * its commands are always added to the target node, even if the * target already has commands. * * Results: * returns 0. * * Side Effects: * Children and commands may be added to the parent and the parent's * type may be changed. * *----------------------------------------------------------------------- */ int Make_HandleUse(GNode *cgn, GNode *pgn) { GNode *gn; /* A child of the .USE node */ - LstNode ln; /* An element in the children list */ + LstNode *ln; /* An element in the children list */ if (cgn->type & (OP_USE | OP_TRANSFORM)) { if ((cgn->type & OP_USE) || Lst_IsEmpty(pgn->commands)) { /* * .USE or transformation and target has no commands -- append * the child's commands to the parent. */ Lst_Concat(pgn->commands, cgn->commands, LST_CONCNEW); } if (Lst_Open(cgn->children) == SUCCESS) { while ((ln = Lst_Next(cgn->children)) != NULL) { gn = Lst_Datum(ln); if (Lst_Member(pgn->children, gn) == NULL) { Lst_AtEnd(pgn->children, gn); Lst_AtEnd(gn->parents, pgn); pgn->unmade += 1; } } Lst_Close(cgn->children); } pgn->type |= cgn->type & ~(OP_OPMASK | OP_USE | OP_TRANSFORM); /* * This child node is now "made", so we decrement the count of * unmade children in the parent... We also remove the child * from the parent's list to accurately reflect the number of decent * children the parent has. This is used by Make_Run to decide * whether to queue the parent or examine its children... */ if (cgn->type & OP_USE) { pgn->unmade--; } } return (0); } static int MakeHandleUse(void *pgn, void *cgn) { return (Make_HandleUse(pgn, cgn)); } /*- *----------------------------------------------------------------------- * Make_Update -- * Perform update on the parents of a node. Used by JobFinish once * a node has been dealt with and by MakeStartJobs if it finds an * up-to-date node. * * Results: * Always returns 0 * * Side Effects: * The unmade field of pgn is decremented and pgn may be placed on * the toBeMade queue if this field becomes 0. * * If the child was made, the parent's childMade field will be set true * and its cmtime set to now. * * If the child wasn't made, the cmtime field of the parent will be * altered if the child's mtime is big enough. * * Finally, if the child is the implied source for the parent, the * parent's IMPSRC variable is set appropriately. * *----------------------------------------------------------------------- */ void Make_Update(GNode *cgn) { GNode *pgn; /* the parent node */ char *cname; /* the child's name */ - LstNode ln; /* Element in parents and iParents lists */ + LstNode *ln; /* Element in parents and iParents lists */ char *p1; cname = Var_Value(TARGET, cgn, &p1); free(p1); /* * If the child was actually made, see what its modification time is * now -- some rules won't actually update the file. If the file still * doesn't exist, make its mtime now. */ if (cgn->made != UPTODATE) { #ifndef RECHECK /* * We can't re-stat the thing, but we can at least take care of rules * where a target depends on a source that actually creates the * target, but only if it has changed, e.g. * * parse.h : parse.o * * parse.o : parse.y * yacc -d parse.y * cc -c y.tab.c * mv y.tab.o parse.o * cmp -s y.tab.h parse.h || mv y.tab.h parse.h * * In this case, if the definitions produced by yacc haven't changed * from before, parse.h won't have been updated and cgn->mtime will * reflect the current modification time for parse.h. This is * something of a kludge, I admit, but it's a useful one.. * XXX: People like to use a rule like * * FRC: * * To force things that depend on FRC to be made, so we have to * check for gn->children being empty as well... */ if (!Lst_IsEmpty(cgn->commands) || Lst_IsEmpty(cgn->children)) { cgn->mtime = now; } #else /* * This is what Make does and it's actually a good thing, as it * allows rules like * * cmp -s y.tab.h parse.h || cp y.tab.h parse.h * * to function as intended. Unfortunately, thanks to the stateless * nature of NFS (by which I mean the loose coupling of two clients * using the same file from a common server), there are times * when the modification time of a file created on a remote * machine will not be modified before the local stat() implied by * the Dir_MTime occurs, thus leading us to believe that the file * is unchanged, wreaking havoc with files that depend on this one. * * I have decided it is better to make too much than to make too * little, so this stuff is commented out unless you're sure it's ok. * -- ardeb 1/12/88 */ /* * Christos, 4/9/92: If we are saving commands pretend that * the target is made now. Otherwise archives with ... rules * don't work! */ if (noExecute || (cgn->type & OP_SAVE_CMDS) || Dir_MTime(cgn) == 0) { cgn->mtime = now; } DEBUGF(MAKE, ("update time: %s\n", Targ_FmtTime(cgn->mtime))); #endif } if (Lst_Open(cgn->parents) == SUCCESS) { while ((ln = Lst_Next(cgn->parents)) != NULL) { pgn = Lst_Datum(ln); if (pgn->make) { pgn->unmade -= 1; if (!(cgn->type & (OP_EXEC | OP_USE))) { if (cgn->made == MADE) { pgn->childMade = TRUE; if (pgn->cmtime < cgn->mtime) { pgn->cmtime = cgn->mtime; } } else { Make_TimeStamp(pgn, cgn); } } if (pgn->unmade == 0) { /* * Queue the node up -- any unmade predecessors will * be dealt with in MakeStartJobs. */ Lst_EnQueue(toBeMade, pgn); } else if (pgn->unmade < 0) { Error("Graph cycles through %s", pgn->name); } } } Lst_Close(cgn->parents); } /* * Deal with successor nodes. If any is marked for making and has an unmade * count of 0, has not been made and isn't in the examination queue, * it means we need to place it in the queue as it restrained itself * before. */ for (ln = Lst_First(cgn->successors); ln != NULL; ln = Lst_Succ(ln)) { GNode *succ = Lst_Datum(ln); if (succ->make && succ->unmade == 0 && succ->made == UNMADE && Lst_Member(toBeMade, succ) == NULL) { Lst_EnQueue(toBeMade, succ); } } /* * Set the .PREFIX and .IMPSRC variables for all the implied parents * of this node. */ if (Lst_Open(cgn->iParents) == SUCCESS) { char *ptr; char *cpref = Var_Value(PREFIX, cgn, &ptr); while ((ln = Lst_Next(cgn->iParents)) != NULL) { pgn = Lst_Datum (ln); if (pgn->make) { Var_Set(IMPSRC, cname, pgn); Var_Set(PREFIX, cpref, pgn); } } free(ptr); Lst_Close(cgn->iParents); } } /*- *----------------------------------------------------------------------- * MakeAddAllSrc -- * Add a child's name to the ALLSRC and OODATE variables of the given * node. Called from Make_DoAllVar via Lst_ForEach. A child is added only * if it has not been given the .EXEC, .USE or .INVISIBLE attributes. * .EXEC and .USE children are very rarely going to be files, so... * A child is added to the OODATE variable if its modification time is * later than that of its parent, as defined by Make, except if the * parent is a .JOIN node. In that case, it is only added to the OODATE * variable if it was actually made (since .JOIN nodes don't have * modification times, the comparison is rather unfair...).. * * Results: * Always returns 0 * * Side Effects: * The ALLSRC variable for the given node is extended. *----------------------------------------------------------------------- */ static int MakeAddAllSrc(void *cgnp, void *pgnp) { GNode *cgn = (GNode *) cgnp; GNode *pgn = (GNode *) pgnp; if ((cgn->type & (OP_EXEC | OP_USE | OP_INVISIBLE)) == 0) { char *child; char *p1 = NULL; if (OP_NOP(cgn->type)) { /* * this node is only source; use the specific pathname for it */ child = cgn->path ? cgn->path : cgn->name; } else child = Var_Value(TARGET, cgn, &p1); Var_Append(ALLSRC, child, pgn); if (pgn->type & OP_JOIN) { if (cgn->made == MADE) { Var_Append(OODATE, child, pgn); } } else if ((pgn->mtime < cgn->mtime) || (cgn->mtime >= now && cgn->made == MADE)) { /* * It goes in the OODATE variable if the parent is younger than the * child or if the child has been modified more recently than * the start of the make. This is to keep pmake from getting * confused if something else updates the parent after the * make starts (shouldn't happen, I know, but sometimes it * does). In such a case, if we've updated the kid, the parent * is likely to have a modification time later than that of * the kid and anything that relies on the OODATE variable will * be hosed. * * XXX: This will cause all made children to go in the OODATE * variable, even if they're not touched, if RECHECK isn't defined, * since cgn->mtime is set to now in Make_Update. According to * some people, this is good... */ Var_Append(OODATE, child, pgn); } free(p1); } return (0); } /*- *----------------------------------------------------------------------- * Make_DoAllVar -- * Set up the ALLSRC and OODATE variables. Sad to say, it must be * done separately, rather than while traversing the graph. This is * because Make defined OODATE to contain all sources whose modification * times were later than that of the target, *not* those sources that * were out-of-date. Since in both compatibility and native modes, * the modification time of the parent isn't found until the child * has been dealt with, we have to wait until now to fill in the * variable. As for ALLSRC, the ordering is important and not * guaranteed when in native mode, so it must be set here, too. * * Results: * None * * Side Effects: * The ALLSRC and OODATE variables of the given node is filled in. * If the node is a .JOIN node, its TARGET variable will be set to * match its ALLSRC variable. *----------------------------------------------------------------------- */ void Make_DoAllVar(GNode *gn) { Lst_ForEach(gn->children, MakeAddAllSrc, gn); if (!Var_Exists (OODATE, gn)) { Var_Set(OODATE, "", gn); } if (!Var_Exists (ALLSRC, gn)) { Var_Set(ALLSRC, "", gn); } if (gn->type & OP_JOIN) { char *p1; Var_Set(TARGET, Var_Value(ALLSRC, gn, &p1), gn); free(p1); } } /*- *----------------------------------------------------------------------- * MakeStartJobs -- * Start as many jobs as possible. * * Results: * If the query flag was given to pmake, no job will be started, * but as soon as an out-of-date target is found, this function * returns TRUE. At all other times, this function returns FALSE. * * Side Effects: * Nodes are removed from the toBeMade queue and job table slots * are filled. * *----------------------------------------------------------------------- */ static Boolean MakeStartJobs(void) { GNode *gn; while (!Lst_IsEmpty(toBeMade) && !Job_Full()) { gn = Lst_DeQueue(toBeMade); DEBUGF(MAKE, ("Examining %s...", gn->name)); /* * Make sure any and all predecessors that are going to be made, * have been. */ if (!Lst_IsEmpty(gn->preds)) { - LstNode ln; + LstNode *ln; for (ln = Lst_First(gn->preds); ln != NULL; ln = Lst_Succ(ln)){ GNode *pgn = Lst_Datum(ln); if (pgn->make && pgn->made == UNMADE) { DEBUGF(MAKE, ("predecessor %s not made yet.\n", pgn->name)); break; } } /* * If ln isn't NULL, there's a predecessor as yet unmade, so we * just drop this node on the floor. When the node in question * has been made, it will notice this node as being ready to * make but as yet unmade and will place the node on the queue. */ if (ln != NULL) { continue; } } numNodes--; if (Make_OODate(gn)) { DEBUGF(MAKE, ("out-of-date\n")); if (queryFlag) { return (TRUE); } Make_DoAllVar(gn); Job_Make(gn); } else { DEBUGF(MAKE, ("up-to-date\n")); gn->made = UPTODATE; if (gn->type & OP_JOIN) { /* * Even for an up-to-date .JOIN node, we need it to have its * context variables so references to it get the correct * value for .TARGET when building up the context variables * of its parent(s)... */ Make_DoAllVar(gn); } Make_Update(gn); } } return (FALSE); } /*- *----------------------------------------------------------------------- * MakePrintStatus -- * Print the status of a top-level node, viz. it being up-to-date * already or not created due to an error in a lower level. * Callback function for Make_Run via Lst_ForEach. If gn->unmade is * nonzero and that is meant to imply a cycle in the graph, then * cycle is TRUE. * * Results: * Always returns 0. * * Side Effects: * A message may be printed. * *----------------------------------------------------------------------- */ static int MakePrintStatus(void *gnp, void *cyclep) { GNode *gn = gnp; Boolean cycle = *(Boolean *)cyclep; if (gn->made == UPTODATE) { printf("`%s' is up to date.\n", gn->name); } else if (gn->unmade != 0) { if (cycle) { Boolean t = TRUE; /* * If printing cycles and came to one that has unmade children, * print out the cycle by recursing on its children. Note a * cycle like: * a : b * b : c * c : b * will cause this to erroneously complain about a being in * the cycle, but this is a good approximation. */ if (gn->made == CYCLE) { Error("Graph cycles through `%s'", gn->name); gn->made = ENDCYCLE; Lst_ForEach(gn->children, MakePrintStatus, &t); gn->made = UNMADE; } else if (gn->made != ENDCYCLE) { gn->made = CYCLE; Lst_ForEach(gn->children, MakePrintStatus, &t); } } else { printf("`%s' not remade because of errors.\n", gn->name); } } return (0); } /*- *----------------------------------------------------------------------- * Make_Run -- * Initialize the nodes to remake and the list of nodes which are * ready to be made by doing a breadth-first traversal of the graph * starting from the nodes in the given list. Once this traversal * is finished, all the 'leaves' of the graph are in the toBeMade * queue. * Using this queue and the Job module, work back up the graph, * calling on MakeStartJobs to keep the job table as full as * possible. * * Results: * TRUE if work was done. FALSE otherwise. * * Side Effects: * The make field of all nodes involved in the creation of the given * targets is set to 1. The toBeMade list is set to contain all the * 'leaves' of these subgraphs. *----------------------------------------------------------------------- */ Boolean -Make_Run(Lst targs) +Make_Run(Lst *targs) { GNode *gn; /* a temporary pointer */ - Lst examine; /* List of targets to examine */ + Lst *examine; /* List of targets to examine */ int errors; /* Number of errors the Job module reports */ toBeMade = Lst_Init(); examine = Lst_Duplicate(targs, NOCOPY); numNodes = 0; /* * Make an initial downward pass over the graph, marking nodes to be made * as we go down. We call Suff_FindDeps to find where a node is and * to get some children for it if it has none and also has no commands. * If the node is a leaf, we stick it on the toBeMade queue to * be looked at in a minute, otherwise we add its children to our queue * and go on about our business. */ while (!Lst_IsEmpty(examine)) { gn = Lst_DeQueue(examine); if (!gn->make) { gn->make = TRUE; numNodes++; /* * Apply any .USE rules before looking for implicit dependencies * to make sure everything has commands that should... */ Lst_ForEach(gn->children, MakeHandleUse, gn); Suff_FindDeps(gn); if (gn->unmade != 0) { Lst_ForEach(gn->children, MakeAddChild, examine); } else { Lst_EnQueue(toBeMade, gn); } } } Lst_Destroy(examine, NOFREE); if (queryFlag) { /* * We wouldn't do any work unless we could start some jobs in the * next loop... (we won't actually start any, of course, this is just * to see if any of the targets was out of date) */ return (MakeStartJobs()); } else { /* * Initialization. At the moment, no jobs are running and until some * get started, nothing will happen since the remaining upward * traversal of the graph is performed by the routines in job.c upon * the finishing of a job. So we fill the Job table as much as we can * before going into our loop. */ MakeStartJobs(); } /* * Main Loop: The idea here is that the ending of jobs will take * care of the maintenance of data structures and the waiting for output * will cause us to be idle most of the time while our children run as * much as possible. Because the job table is kept as full as possible, * the only time when it will be empty is when all the jobs which need * running have been run, so that is the end condition of this loop. * Note that the Job module will exit if there were any errors unless the * keepgoing flag was given. */ while (!Job_Empty ()) { Job_CatchOutput(!Lst_IsEmpty(toBeMade)); Job_CatchChildren(!usePipes); MakeStartJobs(); } errors = Job_Finish(); /* * Print the final status of each target. E.g. if it wasn't made * because some inferior reported an error. */ errors = ((errors == 0) && (numNodes != 0)); Lst_ForEach(targs, MakePrintStatus, &errors); return (TRUE); } Index: head/usr.bin/make/make.h =================================================================== --- head/usr.bin/make/make.h (revision 138511) +++ head/usr.bin/make/make.h (revision 138512) @@ -1,382 +1,382 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)make.h 8.3 (Berkeley) 6/13/95 * $FreeBSD$ */ /*- * make.h -- * The global definitions for pmake */ #ifndef _MAKE_H_ #define _MAKE_H_ #include #include #include #include #include #include #include "sprite.h" #include "lst.h" #include "config.h" #include "buf.h" /*- * The structure for an individual graph node. Each node has several * pieces of data associated with it. * 1) the name of the target it describes * 2) the location of the target file in the filesystem. * 3) the type of operator used to define its sources (qv. parse.c) * 4) whether it is involved in this invocation of make * 5) whether the target has been remade * 6) whether any of its children has been remade * 7) the number of its children that are, as yet, unmade * 8) its modification time * 9) the modification time of its youngest child (qv. make.c) * 10) a list of nodes for which this is a source * 11) a list of nodes on which this depends * 12) a list of nodes that depend on this, as gleaned from the * transformation rules. * 13) a list of nodes of the same name created by the :: operator * 14) a list of nodes that must be made (if they're made) before * this node can be, but that do no enter into the datedness of * this node. * 15) a list of nodes that must be made (if they're made) after * this node is, but that do not depend on this node, in the * normal sense. * 16) a Lst of ``local'' variables that are specific to this target * and this target only (qv. var.c [$@ $< $?, etc.]) * 17) a Lst of strings that are commands to be given to a shell * to create this target. */ typedef struct GNode { char *name; /* The target's name */ char *path; /* The full pathname of the file */ int type; /* Its type (see the OP flags, below) */ int order; /* Its wait weight */ Boolean make; /* TRUE if this target needs to be remade */ enum { UNMADE, BEINGMADE, MADE, UPTODATE, ERROR, ABORTED, CYCLE, ENDCYCLE } made; /* Set to reflect the state of processing * on this node: * UNMADE - Not examined yet * BEINGMADE - Target is already being made. * Indicates a cycle in the graph. (compat * mode only) * MADE - Was out-of-date and has been made * UPTODATE - Was already up-to-date * ERROR - An error occured while it was being * made (used only in compat mode) * ABORTED - The target was aborted due to * an error making an inferior (compat). * CYCLE - Marked as potentially being part of * a graph cycle. If we come back to a * node marked this way, it is printed * and 'made' is changed to ENDCYCLE. * ENDCYCLE - the cycle has been completely * printed. Go back and unmark all its * members. */ Boolean childMade; /* TRUE if one of this target's children was * made */ int unmade; /* The number of unmade children */ int mtime; /* Its modification time */ int cmtime; /* The modification time of its youngest * child */ - Lst iParents; /* Links to parents for which this is an + Lst *iParents; /* Links to parents for which this is an * implied source, if any */ - Lst cohorts; /* Other nodes for the :: operator */ - Lst parents; /* Nodes that depend on this one */ - Lst children; /* Nodes on which this one depends */ - Lst successors; /* Nodes that must be made after this one */ - Lst preds; /* Nodes that must be made before this one */ + Lst *cohorts; /* Other nodes for the :: operator */ + Lst *parents; /* Nodes that depend on this one */ + Lst *children; /* Nodes on which this one depends */ + Lst *successors;/* Nodes that must be made after this one */ + Lst *preds; /* Nodes that must be made before this one */ - Lst context; /* The local variables */ - Lst commands; /* Creation commands */ + Lst *context; /* The local variables */ + Lst *commands; /* Creation commands */ struct _Suff *suffix; /* Suffix for the node (determined by * Suff_FindDeps and opaque to everyone * but the Suff module) */ } GNode; /* * Definitions for handling #include specifications */ typedef struct { char *str; char *ptr; } PTR; typedef struct IFile { char *fname; /* name of previous file */ int lineno; /* saved line number */ FILE *F; /* the open stream */ PTR *p; /* the char pointer */ } IFile; /* * The OP_ constants are used when parsing a dependency line as a way of * communicating to other parts of the program the way in which a target * should be made. These constants are bitwise-OR'ed together and * placed in the 'type' field of each node. Any node that has * a 'type' field which satisfies the OP_NOP function was never never on * the lefthand side of an operator, though it may have been on the * righthand side... */ #define OP_DEPENDS 0x00000001 /* Execution of commands depends on * kids (:) */ #define OP_FORCE 0x00000002 /* Always execute commands (!) */ #define OP_DOUBLEDEP 0x00000004 /* Execution of commands depends on kids * per line (::) */ #define OP_OPMASK (OP_DEPENDS|OP_FORCE|OP_DOUBLEDEP) #define OP_OPTIONAL 0x00000008 /* Don't care if the target doesn't * exist and can't be created */ #define OP_USE 0x00000010 /* Use associated commands for parents */ #define OP_EXEC 0x00000020 /* Target is never out of date, but always * execute commands anyway. Its time * doesn't matter, so it has none...sort * of */ #define OP_IGNORE 0x00000040 /* Ignore errors when creating the node */ #define OP_PRECIOUS 0x00000080 /* Don't remove the target when * interrupted */ #define OP_SILENT 0x00000100 /* Don't echo commands when executed */ #define OP_MAKE 0x00000200 /* Target is a recurrsive make so its * commands should always be executed when * it is out of date, regardless of the * state of the -n or -t flags */ #define OP_JOIN 0x00000400 /* Target is out-of-date only if any of its * children was out-of-date */ #define OP_INVISIBLE 0x00004000 /* The node is invisible to its parents. * I.e. it doesn't show up in the parents's * local variables. */ #define OP_NOTMAIN 0x00008000 /* The node is exempt from normal 'main * target' processing in parse.c */ #define OP_PHONY 0x00010000 /* Not a file target; run always */ /* Attributes applied by PMake */ #define OP_TRANSFORM 0x80000000 /* The node is a transformation rule */ #define OP_MEMBER 0x40000000 /* Target is a member of an archive */ #define OP_LIB 0x20000000 /* Target is a library */ #define OP_ARCHV 0x10000000 /* Target is an archive construct */ #define OP_HAS_COMMANDS 0x08000000 /* Target has all the commands it should. * Used when parsing to catch multiple * commands for a target */ #define OP_SAVE_CMDS 0x04000000 /* Saving commands on .END (Compat) */ #define OP_DEPS_FOUND 0x02000000 /* Already processed by Suff_FindDeps */ /* * OP_NOP will return TRUE if the node with the given type was not the * object of a dependency operator */ #define OP_NOP(t) (((t) & OP_OPMASK) == 0x00000000) /* * The TARG_ constants are used when calling the Targ_FindNode and * Targ_FindList functions in targ.c. They simply tell the functions what to * do if the desired node(s) is (are) not found. If the TARG_CREATE constant * is given, a new, empty node will be created for the target, placed in the * table of all targets and its address returned. If TARG_NOCREATE is given, * a NULL pointer will be returned. */ #define TARG_CREATE 0x01 /* create node if not found */ #define TARG_NOCREATE 0x00 /* don't create it */ /* * There are several places where expandable buffers are used (parse.c and * var.c). This constant is merely the starting point for those buffers. If * lines tend to be much shorter than this, it would be best to reduce BSIZE. * If longer, it should be increased. Reducing it will cause more copying to * be done for longer lines, but will save space for shorter ones. In any * case, it ought to be a power of two simply because most storage allocation * schemes allocate in powers of two. */ #define MAKE_BSIZE 256 /* starting size for expandable buffers */ /* * These constants are all used by the Str_Concat function to decide how the * final string should look. If STR_ADDSPACE is given, a space will be * placed between the two strings. If STR_ADDSLASH is given, a '/' will * be used instead of a space. If neither is given, no intervening characters * will be placed between the two strings in the final output. If the * STR_DOFREE bit is set, the two input strings will be freed before * Str_Concat returns. */ #define STR_ADDSPACE 0x01 /* add a space when Str_Concat'ing */ #define STR_DOFREE 0x02 /* free source strings after concatenation */ #define STR_ADDSLASH 0x04 /* add a slash when Str_Concat'ing */ /* * Error levels for parsing. PARSE_FATAL means the process cannot continue * once the makefile has been parsed. PARSE_WARNING means it can. Passed * as the first argument to Parse_Error. */ #define PARSE_WARNING 2 #define PARSE_FATAL 1 /* * Values returned by Cond_Eval. */ #define COND_PARSE 0 /* Parse the next lines */ #define COND_SKIP 1 /* Skip the next lines */ #define COND_INVALID 2 /* Not a conditional statement */ /* * Definitions for the "local" variables. Used only for clarity. */ #define TARGET "@" /* Target of dependency */ #define OODATE "?" /* All out-of-date sources */ #define ALLSRC ">" /* All sources */ #define IMPSRC "<" /* Source implied by transformation */ #define PREFIX "*" /* Common prefix */ #define ARCHIVE "!" /* Archive in "archive(member)" syntax */ #define MEMBER "%" /* Member in "archive(member)" syntax */ #define FTARGET "@F" /* file part of TARGET */ #define DTARGET "@D" /* directory part of TARGET */ #define FIMPSRC " __FBSDID("$FreeBSD$"); /*- * parse.c -- * Functions to parse a makefile. * * One function, Parse_Init, must be called before any functions * in this module are used. After that, the function Parse_File is the * main entry point and controls most of the other functions in this * module. * * Most important structures are kept in Lsts. Directories for * the #include "..." function are kept in the 'parseIncPath' Lst, while * those for the #include <...> are kept in the 'sysIncPath' Lst. The * targets currently being defined are kept in the 'targets' Lst. * * The variables 'curFile.fname' and 'curFile.lineno' are used to track * the name of the current file and the line number in that file so that * error messages can be more meaningful. * * Interface: * Parse_Init Initialization function which must be * called before anything else in this module * is used. * * Parse_End Cleanup the module * * Parse_File Function used to parse a makefile. It must * be given the name of the file, which should * already have been opened, and a function * to call to read a character from the file. * * Parse_IsVar Returns TRUE if the given line is a * variable assignment. Used by MainParseArgs * to determine if an argument is a target * or a variable assignment. Used internally * for pretty much the same thing... * * Parse_Error Function called when an error occurs in * parsing. Used by the variable and * conditional modules. * Parse_MainName Returns a Lst of the main target to create. */ #include #include #include #include #include "make.h" #include "hash.h" #include "dir.h" #include "job.h" #include "buf.h" #include "pathnames.h" /* * These values are returned by ParseEOF to tell Parse_File whether to * CONTINUE parsing, i.e. it had only reached the end of an include file, * or if it's DONE. */ #define CONTINUE 1 #define DONE 0 -static Lst targets; /* targets we're working on */ -static Lst targCmds; /* command lines for targets */ +static Lst *targets; /* targets we're working on */ +static Lst *targCmds; /* command lines for targets */ static Boolean inLine; /* true if currently in a dependency * line or its commands */ static int fatals = 0; static GNode *mainNode; /* The main target to create. This is the * first target on the first dependency * line in the first makefile */ IFile curFile; /* current makefile */ -static Lst includes; /* stack of IFiles generated by +static Lst *includes; /* stack of IFiles generated by * #includes */ -Lst parseIncPath; /* list of directories for "..." includes */ -Lst sysIncPath; /* list of directories for <...> includes */ +Lst *parseIncPath; /* list of directories for "..." includes */ +Lst *sysIncPath; /* list of directories for <...> includes */ /*- * specType contains the SPECial TYPE of the current target. It is * Not if the target is unspecial. If it *is* special, however, the children * are linked as children of the parent but not vice versa. This variable is * set in ParseDoDependency */ typedef enum { Begin, /* .BEGIN */ Default, /* .DEFAULT */ End, /* .END */ Ignore, /* .IGNORE */ Includes, /* .INCLUDES */ Interrupt, /* .INTERRUPT */ Libs, /* .LIBS */ MFlags, /* .MFLAGS or .MAKEFLAGS */ Main, /* .MAIN and we don't have anything user-specified to * make */ NoExport, /* .NOEXPORT */ Not, /* Not special */ NotParallel, /* .NOTPARALELL */ Null, /* .NULL */ Order, /* .ORDER */ Parallel, /* .PARALLEL */ ExPath, /* .PATH */ Phony, /* .PHONY */ Posix, /* .POSIX */ Precious, /* .PRECIOUS */ ExShell, /* .SHELL */ Silent, /* .SILENT */ SingleShell, /* .SINGLESHELL */ Suffixes, /* .SUFFIXES */ Wait, /* .WAIT */ Attribute /* Generic attribute */ } ParseSpecial; static ParseSpecial specType; static int waiting; /* * Predecessor node for handling .ORDER. Initialized to NULL when .ORDER * seen, then set to each successive source on the line. */ static GNode *predecessor; /* * The parseKeywords table is searched using binary search when deciding * if a target or source is special. The 'spec' field is the ParseSpecial * type of the keyword ("Not" if the keyword isn't special as a target) while * the 'op' field is the operator to apply to the list of targets if the * keyword is used as a source ("0" if the keyword isn't special as a source) */ static struct { char *name; /* Name of keyword */ ParseSpecial spec; /* Type when used as a target */ int op; /* Operator when used as a source */ } parseKeywords[] = { { ".BEGIN", Begin, 0 }, { ".DEFAULT", Default, 0 }, { ".END", End, 0 }, { ".EXEC", Attribute, OP_EXEC }, { ".IGNORE", Ignore, OP_IGNORE }, { ".INCLUDES", Includes, 0 }, { ".INTERRUPT", Interrupt, 0 }, { ".INVISIBLE", Attribute, OP_INVISIBLE }, { ".JOIN", Attribute, OP_JOIN }, { ".LIBS", Libs, 0 }, { ".MAIN", Main, 0 }, { ".MAKE", Attribute, OP_MAKE }, { ".MAKEFLAGS", MFlags, 0 }, { ".MFLAGS", MFlags, 0 }, { ".NOTMAIN", Attribute, OP_NOTMAIN }, { ".NOTPARALLEL", NotParallel, 0 }, { ".NO_PARALLEL", NotParallel, 0 }, { ".NULL", Null, 0 }, { ".OPTIONAL", Attribute, OP_OPTIONAL }, { ".ORDER", Order, 0 }, { ".PARALLEL", Parallel, 0 }, { ".PATH", ExPath, 0 }, { ".PHONY", Phony, OP_PHONY }, { ".POSIX", Posix, 0 }, { ".PRECIOUS", Precious, OP_PRECIOUS }, { ".RECURSIVE", Attribute, OP_MAKE }, { ".SHELL", ExShell, 0 }, { ".SILENT", Silent, OP_SILENT }, { ".SINGLESHELL", SingleShell, 0 }, { ".SUFFIXES", Suffixes, 0 }, { ".USE", Attribute, OP_USE }, { ".WAIT", Wait, 0 }, }; static int ParseFindKeyword(char *); static int ParseLinkSrc(void *, void *); static int ParseDoOp(void *, void *); static int ParseAddDep(void *, void *); -static void ParseDoSrc(int, char *, Lst); +static void ParseDoSrc(int, char *, Lst *); static int ParseFindMain(void *, void *); static int ParseAddDir(void *, void *); static int ParseClearPath(void *, void *); static void ParseDoDependency(char *); static int ParseAddCmd(void *, void *); static int ParseReadc(void); static void ParseUnreadc(int); static void ParseHasCommands(void *); static void ParseDoInclude(char *); static void ParseDoError(char *); static void ParseDoWarning(char *); #ifdef SYSVINCLUDE static void ParseTraditionalInclude(char *); #endif static int ParseEOF(int); static char *ParseReadLine(void); static char *ParseSkipLine(int, int); static void ParseFinishLine(void); /*- *---------------------------------------------------------------------- * ParseFindKeyword -- * Look in the table of keywords for one matching the given string. * * Results: * The index of the keyword, or -1 if it isn't there. * * Side Effects: * None *---------------------------------------------------------------------- */ static int ParseFindKeyword(char *str) { int start, end, cur; int diff; start = 0; end = (sizeof(parseKeywords) / sizeof(parseKeywords[0])) - 1; do { cur = start + ((end - start) / 2); diff = strcmp(str, parseKeywords[cur].name); if (diff == 0) { return (cur); } else if (diff < 0) { end = cur - 1; } else { start = cur + 1; } } while (start <= end); return (-1); } /*- * Parse_Error -- * Error message abort function for parsing. Prints out the context * of the error (line number and file) as well as the message with * two optional arguments. * * Results: * None * * Side Effects: * "fatals" is incremented if the level is PARSE_FATAL. */ /* VARARGS */ void Parse_Error(int type, const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "\"%s\", line %d: ", curFile.fname, curFile.lineno); if (type == PARSE_WARNING) fprintf(stderr, "warning: "); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\n"); fflush(stderr); if (type == PARSE_FATAL) fatals += 1; } /*- *--------------------------------------------------------------------- * ParseLinkSrc -- * Link the parent node to its new child. Used in a Lst_ForEach by * ParseDoDependency. If the specType isn't 'Not', the parent * isn't linked as a parent of the child. * * Results: * Always = 0 * * Side Effects: * New elements are added to the parents list of cgn and the * children list of cgn. the unmade field of pgn is updated * to reflect the additional child. *--------------------------------------------------------------------- */ static int ParseLinkSrc(void *pgnp, void *cgnp) { GNode *pgn = pgnp; GNode *cgn = cgnp; if (Lst_Member(pgn->children, cgn) == NULL) { Lst_AtEnd(pgn->children, cgn); if (specType == Not) { Lst_AtEnd(cgn->parents, pgn); } pgn->unmade += 1; } return (0); } /*- *--------------------------------------------------------------------- * ParseDoOp -- * Apply the parsed operator to the given target node. Used in a * Lst_ForEach call by ParseDoDependency once all targets have * been found and their operator parsed. If the previous and new * operators are incompatible, a major error is taken. * * Results: * Always 0 * * Side Effects: * The type field of the node is altered to reflect any new bits in * the op. *--------------------------------------------------------------------- */ static int ParseDoOp(void *gnp, void *opp) { GNode *gn = gnp; int op = *(int *)opp; /* * If the dependency mask of the operator and the node don't match and * the node has actually had an operator applied to it before, and * the operator actually has some dependency information in it, complain. */ if (((op & OP_OPMASK) != (gn->type & OP_OPMASK)) && !OP_NOP(gn->type) && !OP_NOP(op)) { Parse_Error(PARSE_FATAL, "Inconsistent operator for %s", gn->name); return (1); } if ((op == OP_DOUBLEDEP) && ((gn->type & OP_OPMASK) == OP_DOUBLEDEP)) { /* * If the node was the object of a :: operator, we need to create a * new instance of it for the children and commands on this dependency * line. The new instance is placed on the 'cohorts' list of the * initial one (note the initial one is not on its own cohorts list) * and the new instance is linked to all parents of the initial * instance. */ - GNode *cohort; - LstNode ln; + GNode *cohort; + LstNode *ln; cohort = Targ_NewGN(gn->name); /* * Duplicate links to parents so graph traversal is simple. Perhaps * some type bits should be duplicated? * * Make the cohort invisible as well to avoid duplicating it into * other variables. True, parents of this target won't tend to do * anything with their local variables, but better safe than * sorry. */ Lst_ForEach(gn->parents, ParseLinkSrc, cohort); cohort->type = OP_DOUBLEDEP|OP_INVISIBLE; Lst_AtEnd(gn->cohorts, cohort); /* * Replace the node in the targets list with the new copy */ ln = Lst_Member(targets, gn); Lst_Replace(ln, cohort); gn = cohort; } /* * We don't want to nuke any previous flags (whatever they were) so we * just OR the new operator into the old */ gn->type |= op; return (0); } /*- *--------------------------------------------------------------------- * ParseAddDep -- * Check if the pair of GNodes given needs to be synchronized. * This has to be when two nodes are on different sides of a * .WAIT directive. * * Results: * Returns 1 if the two targets need to be ordered, 0 otherwise. * If it returns 1, the search can stop * * Side Effects: * A dependency can be added between the two nodes. * *--------------------------------------------------------------------- */ static int ParseAddDep(void *pp, void *sp) { GNode *p = pp; GNode *s = sp; if (p->order < s->order) { /* * XXX: This can cause loops, and loops can cause unmade targets, * but checking is tedious, and the debugging output can show the * problem */ Lst_AtEnd(p->successors, s); Lst_AtEnd(s->preds, p); return (0); } else return (1); } /*- *--------------------------------------------------------------------- * ParseDoSrc -- * Given the name of a source, figure out if it is an attribute * and apply it to the targets if it is. Else decide if there is * some attribute which should be applied *to* the source because * of some special target and apply it if so. Otherwise, make the * source be a child of the targets in the list 'targets' * * Results: * None * * Side Effects: * Operator bits may be added to the list of targets or to the source. * The targets may have a new source added to their lists of children. *--------------------------------------------------------------------- */ static void -ParseDoSrc(int tOp, char *src, Lst allsrc) +ParseDoSrc(int tOp, char *src, Lst *allsrc) { GNode *gn = NULL; if (*src == '.' && isupper ((unsigned char) src[1])) { int keywd = ParseFindKeyword(src); if (keywd != -1) { int op = parseKeywords[keywd].op; if (op != 0) { Lst_ForEach(targets, ParseDoOp, &op); return; } if (parseKeywords[keywd].spec == Wait) { waiting++; return; } } } switch (specType) { case Main: /* * If we have noted the existence of a .MAIN, it means we need * to add the sources of said target to the list of things * to create. The string 'src' is likely to be free, so we * must make a new copy of it. Note that this will only be * invoked if the user didn't specify a target on the command * line. This is to allow #ifmake's to succeed, or something... */ Lst_AtEnd(create, estrdup(src)); /* * Add the name to the .TARGETS variable as well, so the user cna * employ that, if desired. */ Var_Append(".TARGETS", src, VAR_GLOBAL); return; case Order: /* * Create proper predecessor/successor links between the previous * source and the current one. */ gn = Targ_FindNode(src, TARG_CREATE); if (predecessor != NULL) { Lst_AtEnd(predecessor->successors, gn); Lst_AtEnd(gn->preds, predecessor); } /* * The current source now becomes the predecessor for the next one. */ predecessor = gn; break; default: /* * If the source is not an attribute, we need to find/create * a node for it. After that we can apply any operator to it * from a special target or link it to its parents, as * appropriate. * * In the case of a source that was the object of a :: operator, * the attribute is applied to all of its instances (as kept in * the 'cohorts' list of the node) or all the cohorts are linked * to all the targets. */ gn = Targ_FindNode(src, TARG_CREATE); if (tOp) { gn->type |= tOp; } else { Lst_ForEach(targets, ParseLinkSrc, gn); } if ((gn->type & OP_OPMASK) == OP_DOUBLEDEP) { GNode *cohort; - LstNode ln; + LstNode *ln; for (ln=Lst_First(gn->cohorts); ln != NULL; ln = Lst_Succ(ln)){ cohort = Lst_Datum(ln); if (tOp) { cohort->type |= tOp; } else { Lst_ForEach(targets, ParseLinkSrc, cohort); } } } break; } gn->order = waiting; Lst_AtEnd(allsrc, gn); if (waiting) { Lst_ForEach(allsrc, ParseAddDep, gn); } } /*- *----------------------------------------------------------------------- * ParseFindMain -- * Find a real target in the list and set it to be the main one. * Called by ParseDoDependency when a main target hasn't been found * yet. * * Results: * 0 if main not found yet, 1 if it is. * * Side Effects: * mainNode is changed and Targ_SetMain is called. * *----------------------------------------------------------------------- */ static int ParseFindMain(void *gnp, void *dummy __unused) { GNode *gn = gnp; if ((gn->type & (OP_NOTMAIN | OP_USE | OP_EXEC | OP_TRANSFORM)) == 0) { mainNode = gn; Targ_SetMain(gn); return (1); } else { return (0); } } /*- *----------------------------------------------------------------------- * ParseAddDir -- * Front-end for Dir_AddDir to make sure Lst_ForEach keeps going * * Results: * === 0 * * Side Effects: * See Dir_AddDir. * *----------------------------------------------------------------------- */ static int ParseAddDir(void *path, void *name) { Dir_AddDir(path, name); return(0); } /*- *----------------------------------------------------------------------- * ParseClearPath -- * Front-end for Dir_ClearPath to make sure Lst_ForEach keeps going * * Results: * === 0 * * Side Effects: * See Dir_ClearPath * *----------------------------------------------------------------------- */ static int ParseClearPath(void *path, void *dummy __unused) { Dir_ClearPath(path); return (0); } /*- *--------------------------------------------------------------------- * ParseDoDependency -- * Parse the dependency line in line. * * Results: * None * * Side Effects: * The nodes of the sources are linked as children to the nodes of the * targets. Some nodes may be created. * * We parse a dependency line by first extracting words from the line and * finding nodes in the list of all targets with that name. This is done * until a character is encountered which is an operator character. Currently * these are only ! and :. At this point the operator is parsed and the * pointer into the line advanced until the first source is encountered. * The parsed operator is applied to each node in the 'targets' list, * which is where the nodes found for the targets are kept, by means of * the ParseDoOp function. * The sources are read in much the same way as the targets were except * that now they are expanded using the wildcarding scheme of the C-Shell * and all instances of the resulting words in the list of all targets * are found. Each of the resulting nodes is then linked to each of the * targets as one of its children. * Certain targets are handled specially. These are the ones detailed * by the specType variable. * The storing of transformation rules is also taken care of here. * A target is recognized as a transformation rule by calling * Suff_IsTransform. If it is a transformation rule, its node is gotten * from the suffix module via Suff_AddTransform rather than the standard * Targ_FindNode in the target module. *--------------------------------------------------------------------- */ static void ParseDoDependency (char *line) { char *cp; /* our current position */ GNode *gn; /* a general purpose temporary node */ int op; /* the operator on the line */ char savec; /* a place to save a character */ - Lst paths; /* List of search paths to alter when parsing + Lst *paths; /* List of search paths to alter when parsing * a list of .PATH targets */ int tOp; /* operator from special target */ - Lst sources; /* list of archive source names after + Lst *sources; /* list of archive source names after * expansion */ - Lst curTargs; /* list of target names to be found and added + Lst *curTargs; /* list of target names to be found and added * to the targets list */ - Lst curSrcs; /* list of sources in order */ + Lst *curSrcs; /* list of sources in order */ tOp = 0; specType = Not; waiting = 0; paths = NULL; curTargs = Lst_Init(); curSrcs = Lst_Init(); do { for (cp = line; *cp && !isspace((unsigned char)*cp) && *cp != '('; cp++) { if (*cp == '$') { /* * Must be a dynamic source (would have been expanded * otherwise), so call the Var module to parse the puppy * so we can safely advance beyond it...There should be * no errors in this, as they would have been discovered * in the initial Var_Subst and we wouldn't be here. */ size_t length; Boolean freeIt; char *result; result = Var_Parse(cp, VAR_CMD, TRUE, &length, &freeIt); if (freeIt) { free(result); } cp += length - 1; } else if (*cp == '!' || *cp == ':') { /* * We don't want to end a word on ':' or '!' if there is a * better match later on in the string (greedy matching). * This allows the user to have targets like: * fie::fi:fo: fum * foo::bar: * where "fie::fi:fo" and "foo::bar" are the targets. In * real life this is used for perl5 library man pages where * "::" separates an object from its class. * Ie: "File::Spec::Unix". This behaviour is also consistent * with other versions of make. */ char *p = cp + 1; if (*cp == ':' && *p == ':') p++; /* Found the best match already. */ if (*p == '\0' || isspace(*p)) break; p += strcspn(p, "!:"); /* No better match later on... */ if (*p == '\0') break; } continue; } if (*cp == '(') { /* * Archives must be handled specially to make sure the OP_ARCHV * flag is set in their 'type' field, for one thing, and because * things like "archive(file1.o file2.o file3.o)" are permissible. * Arch_ParseArchive will set 'line' to be the first non-blank * after the archive-spec. It creates/finds nodes for the members * and places them on the given list, returning SUCCESS if all * went well and FAILURE if there was an error in the * specification. On error, line should remain untouched. */ if (Arch_ParseArchive(&line, targets, VAR_CMD) != SUCCESS) { Parse_Error(PARSE_FATAL, "Error in archive specification: \"%s\"", line); return; } else { continue; } } savec = *cp; if (!*cp) { /* * Ending a dependency line without an operator is a Bozo * no-no. As a heuristic, this is also often triggered by * undetected conflicts from cvs/rcs merges. */ if ((strncmp(line, "<<<<<<", 6) == 0) || (strncmp(line, "======", 6) == 0) || (strncmp(line, ">>>>>>", 6) == 0)) Parse_Error(PARSE_FATAL, "Makefile appears to contain unresolved cvs/rcs/??? merge conflicts"); else Parse_Error(PARSE_FATAL, "Need an operator"); return; } *cp = '\0'; /* * Have a word in line. See if it's a special target and set * specType to match it. */ if (*line == '.' && isupper((unsigned char)line[1])) { /* * See if the target is a special target that must have it * or its sources handled specially. */ int keywd = ParseFindKeyword(line); if (keywd != -1) { if (specType == ExPath && parseKeywords[keywd].spec != ExPath) { Parse_Error(PARSE_FATAL, "Mismatched special targets"); return; } specType = parseKeywords[keywd].spec; tOp = parseKeywords[keywd].op; /* * Certain special targets have special semantics: * .PATH Have to set the dirSearchPath * variable too * .MAIN Its sources are only used if * nothing has been specified to * create. * .DEFAULT Need to create a node to hang * commands on, but we don't want * it in the graph, nor do we want * it to be the Main Target, so we * create it, set OP_NOTMAIN and * add it to the list, setting * DEFAULT to the new node for * later use. We claim the node is * A transformation rule to make * life easier later, when we'll * use Make_HandleUse to actually * apply the .DEFAULT commands. * .PHONY The list of targets * .BEGIN * .END * .INTERRUPT Are not to be considered the * main target. * .NOTPARALLEL Make only one target at a time. * .SINGLESHELL Create a shell for each command. * .ORDER Must set initial predecessor to NULL */ switch (specType) { case ExPath: if (paths == NULL) { paths = Lst_Init(); } Lst_AtEnd(paths, dirSearchPath); break; case Main: if (!Lst_IsEmpty(create)) { specType = Not; } break; case Begin: case End: case Interrupt: gn = Targ_FindNode(line, TARG_CREATE); gn->type |= OP_NOTMAIN; Lst_AtEnd(targets, gn); break; case Default: gn = Targ_NewGN(".DEFAULT"); gn->type |= (OP_NOTMAIN|OP_TRANSFORM); Lst_AtEnd(targets, gn); DEFAULT = gn; break; case NotParallel: { maxJobs = 1; break; } case SingleShell: compatMake = 1; break; case Order: predecessor = NULL; break; default: break; } } else if (strncmp(line, ".PATH", 5) == 0) { /* * .PATH has to be handled specially. * Call on the suffix module to give us a path to * modify. */ - Lst path; + Lst *path; specType = ExPath; path = Suff_GetPath(&line[5]); if (path == NULL) { Parse_Error(PARSE_FATAL, "Suffix '%s' not defined (yet)", &line[5]); return; } else { - if (paths == (Lst)NULL) { + if (paths == NULL) { paths = Lst_Init(); } Lst_AtEnd(paths, path); } } } /* * Have word in line. Get or create its node and stick it at * the end of the targets list */ if ((specType == Not) && (*line != '\0')) { if (Dir_HasWildcards(line)) { /* * Targets are to be sought only in the current directory, * so create an empty path for the thing. Note we need to * use Dir_Destroy in the destruction of the path as the * Dir module could have added a directory to the path... */ - Lst emptyPath = Lst_Init(); + Lst *emptyPath = Lst_Init(); Dir_Expand(line, emptyPath, curTargs); Lst_Destroy(emptyPath, Dir_Destroy); } else { /* * No wildcards, but we want to avoid code duplication, * so create a list with the word on it. */ Lst_AtEnd(curTargs, line); } while(!Lst_IsEmpty(curTargs)) { char *targName = Lst_DeQueue(curTargs); if (!Suff_IsTransform (targName)) { gn = Targ_FindNode(targName, TARG_CREATE); } else { gn = Suff_AddTransform(targName); } Lst_AtEnd(targets, gn); } } else if (specType == ExPath && *line != '.' && *line != '\0') { Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line); } *cp = savec; /* * If it is a special type and not .PATH, it's the only target we * allow on this line... */ if (specType != Not && specType != ExPath) { Boolean warn = FALSE; while ((*cp != '!') && (*cp != ':') && *cp) { if (*cp != ' ' && *cp != '\t') { warn = TRUE; } cp++; } if (warn) { Parse_Error(PARSE_WARNING, "Extra target ignored"); } } else { while (*cp && isspace((unsigned char)*cp)) { cp++; } } line = cp; } while ((*line != '!') && (*line != ':') && *line); /* * Don't need the list of target names anymore... */ Lst_Destroy(curTargs, NOFREE); if (!Lst_IsEmpty(targets)) { switch (specType) { default: Parse_Error(PARSE_WARNING, "Special and mundane targets don't mix. Mundane ones ignored"); break; case Default: case Begin: case End: case Interrupt: /* * These four create nodes on which to hang commands, so * targets shouldn't be empty... */ case Not: /* * Nothing special here -- targets can be empty if it wants. */ break; } } /* * Have now parsed all the target names. Must parse the operator next. The * result is left in op . */ if (*cp == '!') { op = OP_FORCE; } else if (*cp == ':') { if (cp[1] == ':') { op = OP_DOUBLEDEP; cp++; } else { op = OP_DEPENDS; } } else { Parse_Error(PARSE_FATAL, "Missing dependency operator"); return; } cp++; /* Advance beyond operator */ Lst_ForEach(targets, ParseDoOp, &op); /* * Get to the first source */ while (*cp && isspace((unsigned char)*cp)) { cp++; } line = cp; /* * Several special targets take different actions if present with no * sources: * a .SUFFIXES line with no sources clears out all old suffixes * a .PRECIOUS line makes all targets precious * a .IGNORE line ignores errors for all targets * a .SILENT line creates silence when making all targets * a .PATH removes all directories from the search path(s). */ if (!*line) { switch (specType) { case Suffixes: Suff_ClearSuffixes(); break; case Precious: allPrecious = TRUE; break; case Ignore: ignoreErrors = TRUE; break; case Silent: beSilent = TRUE; break; case ExPath: Lst_ForEach(paths, ParseClearPath, NULL); break; case Posix: Var_Set("%POSIX", "1003.2", VAR_GLOBAL); break; default: break; } } else if (specType == MFlags) { /* * Call on functions in main.c to deal with these arguments and * set the initial character to a null-character so the loop to * get sources won't get anything */ Main_ParseArgLine(line); *line = '\0'; } else if (specType == ExShell) { if (Job_ParseShell(line) != SUCCESS) { Parse_Error(PARSE_FATAL, "improper shell specification"); return; } *line = '\0'; } else if ((specType == NotParallel) || (specType == SingleShell)) { *line = '\0'; } /* * NOW GO FOR THE SOURCES */ if ((specType == Suffixes) || (specType == ExPath) || (specType == Includes) || (specType == Libs) || (specType == Null)) { while (*line) { /* * If the target was one that doesn't take files as its sources * but takes something like suffixes, we take each * space-separated word on the line as a something and deal * with it accordingly. * * If the target was .SUFFIXES, we take each source as a * suffix and add it to the list of suffixes maintained by the * Suff module. * * If the target was a .PATH, we add the source as a directory * to search on the search path. * * If it was .INCLUDES, the source is taken to be the suffix of * files which will be #included and whose search path should * be present in the .INCLUDES variable. * * If it was .LIBS, the source is taken to be the suffix of * files which are considered libraries and whose search path * should be present in the .LIBS variable. * * If it was .NULL, the source is the suffix to use when a file * has no valid suffix. */ char savech; while (*cp && !isspace((unsigned char)*cp)) { cp++; } savech = *cp; *cp = '\0'; switch (specType) { case Suffixes: Suff_AddSuffix(line); break; case ExPath: Lst_ForEach(paths, ParseAddDir, line); break; case Includes: Suff_AddInclude(line); break; case Libs: Suff_AddLib(line); break; case Null: Suff_SetNull(line); break; default: break; } *cp = savech; if (savech != '\0') { cp++; } while (*cp && isspace((unsigned char)*cp)) { cp++; } line = cp; } if (paths) { Lst_Destroy(paths, NOFREE); } } else { while (*line) { /* * The targets take real sources, so we must beware of archive * specifications (i.e. things with left parentheses in them) * and handle them accordingly. */ while (*cp && !isspace((unsigned char)*cp)) { if ((*cp == '(') && (cp > line) && (cp[-1] != '$')) { /* * Only stop for a left parenthesis if it isn't at the * start of a word (that'll be for variable changes * later) and isn't preceded by a dollar sign (a dynamic * source). */ break; } else { cp++; } } if (*cp == '(') { GNode *gnp; sources = Lst_Init(); if (Arch_ParseArchive(&line, sources, VAR_CMD) != SUCCESS) { Parse_Error(PARSE_FATAL, "Error in source archive spec \"%s\"", line); return; } while (!Lst_IsEmpty (sources)) { gnp = Lst_DeQueue(sources); ParseDoSrc(tOp, gnp->name, curSrcs); } Lst_Destroy(sources, NOFREE); cp = line; } else { if (*cp) { *cp = '\0'; cp += 1; } ParseDoSrc(tOp, line, curSrcs); } while (*cp && isspace((unsigned char)*cp)) { cp++; } line = cp; } } if (mainNode == NULL) { /* * If we have yet to decide on a main target to make, in the * absence of any user input, we want the first target on * the first dependency line that is actually a real target * (i.e. isn't a .USE or .EXEC rule) to be made. */ Lst_ForEach(targets, ParseFindMain, NULL); } /* * Finally, destroy the list of sources */ Lst_Destroy(curSrcs, NOFREE); } /*- *--------------------------------------------------------------------- * Parse_IsVar -- * Return TRUE if the passed line is a variable assignment. A variable * assignment consists of a single word followed by optional whitespace * followed by either a += or an = operator. * This function is used both by the Parse_File function and main when * parsing the command-line arguments. * * Results: * TRUE if it is. FALSE if it ain't * * Side Effects: * none *--------------------------------------------------------------------- */ Boolean Parse_IsVar(char *line) { Boolean wasSpace = FALSE; /* set TRUE if found a space */ Boolean haveName = FALSE; /* Set TRUE if have a variable name */ int level = 0; #define ISEQOPERATOR(c) \ (((c) == '+') || ((c) == ':') || ((c) == '?') || ((c) == '!')) /* * Skip to variable name */ for (; (*line == ' ') || (*line == '\t'); line++) continue; for (; *line != '=' || level != 0; line++) switch (*line) { case '\0': /* * end-of-line -- can't be a variable assignment. */ return (FALSE); case ' ': case '\t': /* * there can be as much white space as desired so long as there is * only one word before the operator */ wasSpace = TRUE; break; case '(': case '{': level++; break; case '}': case ')': level--; break; default: if (wasSpace && haveName) { if (ISEQOPERATOR(*line)) { /* * We must have a finished word */ if (level != 0) return (FALSE); /* * When an = operator [+?!:] is found, the next * character must be an = or it ain't a valid * assignment. */ if (line[1] == '=') return (haveName); #ifdef SUNSHCMD /* * This is a shell command */ if (strncmp(line, ":sh", 3) == 0) return (haveName); #endif } /* * This is the start of another word, so not assignment. */ return (FALSE); } else { haveName = TRUE; wasSpace = FALSE; } break; } return (haveName); } /*- *--------------------------------------------------------------------- * Parse_DoVar -- * Take the variable assignment in the passed line and do it in the * global context. * * Note: There is a lexical ambiguity with assignment modifier characters * in variable names. This routine interprets the character before the = * as a modifier. Therefore, an assignment like * C++=/usr/bin/CC * is interpreted as "C+ +=" instead of "C++ =". * * Results: * none * * Side Effects: * the variable structure of the given variable name is altered in the * global context. *--------------------------------------------------------------------- */ void Parse_DoVar(char *line, GNode *ctxt) { char *cp; /* pointer into line */ enum { VAR_SUBST, VAR_APPEND, VAR_SHELL, VAR_NORMAL } type; /* Type of assignment */ char *opc; /* ptr to operator character to * null-terminate the variable name */ /* * Avoid clobbered variable warnings by forcing the compiler * to ``unregister'' variables */ #if __GNUC__ (void)&cp; (void)&line; #endif /* * Skip to variable name */ while ((*line == ' ') || (*line == '\t')) { line++; } /* * Skip to operator character, nulling out whitespace as we go */ for (cp = line + 1; *cp != '='; cp++) { if (isspace((unsigned char)*cp)) { *cp = '\0'; } } opc = cp - 1; /* operator is the previous character */ *cp++ = '\0'; /* nuke the = */ /* * Check operator type */ switch (*opc) { case '+': type = VAR_APPEND; *opc = '\0'; break; case '?': /* * If the variable already has a value, we don't do anything. */ *opc = '\0'; if (Var_Exists(line, ctxt)) { return; } else { type = VAR_NORMAL; } break; case ':': type = VAR_SUBST; *opc = '\0'; break; case '!': type = VAR_SHELL; *opc = '\0'; break; default: #ifdef SUNSHCMD while (*opc != ':') if (opc == line) break; else --opc; if (strncmp(opc, ":sh", 3) == 0) { type = VAR_SHELL; *opc = '\0'; break; } #endif type = VAR_NORMAL; break; } while (isspace((unsigned char)*cp)) { cp++; } if (type == VAR_APPEND) { Var_Append(line, cp, ctxt); } else if (type == VAR_SUBST) { /* * Allow variables in the old value to be undefined, but leave their * invocation alone -- this is done by forcing oldVars to be false. * XXX: This can cause recursive variables, but that's not hard to do, * and this allows someone to do something like * * CFLAGS = $(.INCLUDES) * CFLAGS := -I.. $(CFLAGS) * * And not get an error. */ Boolean oldOldVars = oldVars; oldVars = FALSE; /* * make sure that we set the variable the first time to nothing * so that it gets substituted! */ if (!Var_Exists(line, ctxt)) Var_Set(line, "", ctxt); cp = Var_Subst(NULL, cp, ctxt, FALSE); oldVars = oldOldVars; Var_Set(line, cp, ctxt); free(cp); } else if (type == VAR_SHELL) { Boolean freeCmd = FALSE; /* TRUE if the command needs to be freed, i.e. * if any variable expansion was performed */ char *res, *error; if (strchr(cp, '$') != NULL) { /* * There's a dollar sign in the command, so perform variable * expansion on the whole thing. The resulting string will need * freeing when we're done, so set freeCmd to TRUE. */ cp = Var_Subst(NULL, cp, VAR_CMD, TRUE); freeCmd = TRUE; } res = Cmd_Exec(cp, &error); Var_Set(line, res, ctxt); free(res); if (error) Parse_Error(PARSE_WARNING, error, cp); if (freeCmd) free(cp); } else { /* * Normal assignment -- just do it. */ Var_Set(line, cp, ctxt); } } /*- * ParseAddCmd -- * Lst_ForEach function to add a command line to all targets * * Results: * Always 0 * * Side Effects: * A new element is added to the commands list of the node. */ static int ParseAddCmd(void *gnp, void *cmd) { GNode *gn = gnp; /* if target already supplied, ignore commands */ if (!(gn->type & OP_HAS_COMMANDS)) Lst_AtEnd(gn->commands, cmd); else Parse_Error(PARSE_WARNING, "duplicate script for target \"%s\" ignored", gn->name); return (0); } /*- *----------------------------------------------------------------------- * ParseHasCommands -- * Callback procedure for Parse_File when destroying the list of * targets on the last dependency line. Marks a target as already * having commands if it does, to keep from having shell commands * on multiple dependency lines. * * Results: * None * * Side Effects: * OP_HAS_COMMANDS may be set for the target. * *----------------------------------------------------------------------- */ static void ParseHasCommands(void *gnp) { GNode *gn = gnp; if (!Lst_IsEmpty(gn->commands)) { gn->type |= OP_HAS_COMMANDS; } } /*- *----------------------------------------------------------------------- * Parse_AddIncludeDir -- * Add a directory to the path searched for included makefiles * bracketed by double-quotes. Used by functions in main.c * * Results: * None. * * Side Effects: * The directory is appended to the list. * *----------------------------------------------------------------------- */ void Parse_AddIncludeDir(char *dir) { Dir_AddDir(parseIncPath, dir); } /*--------------------------------------------------------------------- * ParseDoError -- * Handle error directive * * The input is the line minus the ".error". We substitute variables, * print the message and exit(1) or just print a warning if the ".error" * directive is malformed. * *--------------------------------------------------------------------- */ static void ParseDoError(char *errmsg) { if (!isspace((unsigned char)*errmsg)) { Parse_Error(PARSE_WARNING, "invalid syntax: .error%s", errmsg); return; } while (isspace((unsigned char)*errmsg)) errmsg++; errmsg = Var_Subst(NULL, errmsg, VAR_GLOBAL, FALSE); Parse_Error(PARSE_FATAL, "%s", errmsg); /* Terminate immediately. */ exit(1); } /*--------------------------------------------------------------------- * ParseDoWarning -- * Handle warning directive * * The input is the line minus the ".warning". We substitute variables * and print the message or just print a warning if the ".warning" * directive is malformed. * *--------------------------------------------------------------------- */ static void ParseDoWarning(char *warnmsg) { if (!isspace((unsigned char)*warnmsg)) { Parse_Error(PARSE_WARNING, "invalid syntax: .warning%s", warnmsg); return; } while (isspace((unsigned char)*warnmsg)) warnmsg++; warnmsg = Var_Subst(NULL, warnmsg, VAR_GLOBAL, FALSE); Parse_Error(PARSE_WARNING, "%s", warnmsg); } /*- *--------------------------------------------------------------------- * ParseDoInclude -- * Push to another file. * * The input is the line minus the #include. A file spec is a string * enclosed in <> or "". The former is looked for only in sysIncPath. * The latter in . and the directories specified by -I command line * options * * Results: * None * * Side Effects: * A structure is added to the includes Lst and readProc, curFile.lineno, * curFile.fname and curFile.F are altered for the new file *--------------------------------------------------------------------- */ static void ParseDoInclude (char *file) { char *fullname; /* full pathname of file */ IFile *oldFile; /* state associated with current file */ char endc; /* the character which ends the file spec */ char *cp; /* current position in file spec */ Boolean isSystem; /* TRUE if makefile is a system makefile */ /* * Skip to delimiter character so we know where to look */ while ((*file == ' ') || (*file == '\t')) { file++; } if ((*file != '"') && (*file != '<')) { Parse_Error(PARSE_FATAL, ".include filename must be delimited by '\"' or '<'"); return; } /* * Set the search path on which to find the include file based on the * characters which bracket its name. Angle-brackets imply it's * a system Makefile while double-quotes imply it's a user makefile */ if (*file == '<') { isSystem = TRUE; endc = '>'; } else { isSystem = FALSE; endc = '"'; } /* * Skip to matching delimiter */ for (cp = ++file; *cp && *cp != endc; cp++) { continue; } if (*cp != endc) { Parse_Error(PARSE_FATAL, "Unclosed %cinclude filename. '%c' expected", '.', endc); return; } *cp = '\0'; /* * Substitute for any variables in the file name before trying to * find the thing. */ file = Var_Subst(NULL, file, VAR_CMD, FALSE); /* * Now we know the file's name and its search path, we attempt to * find the durn thing. A return of NULL indicates the file don't * exist. */ if (!isSystem) { /* * Include files contained in double-quotes are first searched for * relative to the including file's location. We don't want to * cd there, of course, so we just tack on the old file's * leading path components and call Dir_FindFile to see if * we can locate the beast. */ char *prefEnd, *Fname; /* Make a temporary copy of this, to be safe. */ Fname = estrdup(curFile.fname); prefEnd = strrchr(Fname, '/'); if (prefEnd != (char *)NULL) { char *newName; *prefEnd = '\0'; if (file[0] == '/') newName = estrdup(file); else newName = str_concat(Fname, file, STR_ADDSLASH); fullname = Dir_FindFile(newName, parseIncPath); if (fullname == NULL) { fullname = Dir_FindFile(newName, dirSearchPath); } free(newName); *prefEnd = '/'; } else { fullname = NULL; } free(Fname); } else { fullname = NULL; } if (fullname == NULL) { /* * System makefile or makefile wasn't found in same directory as * included makefile. Search for it first on the -I search path, * then on the .PATH search path, if not found in a -I directory. * XXX: Suffix specific? */ fullname = Dir_FindFile(file, parseIncPath); if (fullname == NULL) { fullname = Dir_FindFile(file, dirSearchPath); } } if (fullname == NULL) { /* * Still haven't found the makefile. Look for it on the system * path as a last resort. */ fullname = Dir_FindFile(file, sysIncPath); } if (fullname == NULL) { *cp = endc; Parse_Error(PARSE_FATAL, "Could not find %s", file); return; } free(file); /* * Once we find the absolute path to the file, we get to save all the * state from the current file before we can start reading this * include file. The state is stored in an IFile structure which * is placed on a list with other IFile structures. The list makes * a very nice stack to track how we got here... */ oldFile = emalloc(sizeof (IFile)); memcpy(oldFile, &curFile, sizeof(IFile)); Lst_AtFront(includes, oldFile); /* * Once the previous state has been saved, we can get down to reading * the new file. We set up the name of the file to be the absolute * name of the include file so error messages refer to the right * place. Naturally enough, we start reading at line number 0. */ curFile.fname = fullname; curFile.lineno = 0; curFile.F = fopen(fullname, "r"); curFile.p = NULL; if (curFile.F == NULL) { Parse_Error(PARSE_FATAL, "Cannot open %s", fullname); /* * Pop to previous file */ ParseEOF(0); } else { Var_Append(".MAKEFILE_LIST", fullname, VAR_GLOBAL); } } /*- *--------------------------------------------------------------------- * Parse_FromString -- * Start Parsing from the given string * * Results: * None * * Side Effects: * A structure is added to the includes Lst and readProc, curFile.lineno, * curFile.fname and curFile.F are altered for the new file *--------------------------------------------------------------------- */ void Parse_FromString(char *str, int lineno) { IFile *oldFile; /* state associated with this file */ DEBUGF(FOR, ("%s\n---- at line %d\n", str, lineno)); oldFile = emalloc(sizeof(IFile)); memcpy(oldFile, &curFile, sizeof(IFile)); Lst_AtFront(includes, oldFile); curFile.F = NULL; curFile.p = emalloc(sizeof (PTR)); curFile.p->str = curFile.p->ptr = str; curFile.lineno = lineno; curFile.fname = estrdup(curFile.fname); } #ifdef SYSVINCLUDE /*- *--------------------------------------------------------------------- * ParseTraditionalInclude -- * Push to another file. * * The input is the line minus the "include". The file name is * the string following the "include". * * Results: * None * * Side Effects: * A structure is added to the includes Lst and readProc, curFile.lineno, * curFile.fname and curFile.F are altered for the new file *--------------------------------------------------------------------- */ static void ParseTraditionalInclude (char *file) { char *fullname; /* full pathname of file */ IFile *oldFile; /* state associated with current file */ char *cp; /* current position in file spec */ /* * Skip over whitespace */ while ((*file == ' ') || (*file == '\t')) { file++; } if (*file == '\0') { Parse_Error(PARSE_FATAL, "Filename missing from \"include\""); return; } /* * Skip to end of line or next whitespace */ for (cp = file; *cp && *cp != '\n' && *cp != '\t' && *cp != ' '; cp++) { continue; } *cp = '\0'; /* * Substitute for any variables in the file name before trying to * find the thing. */ file = Var_Subst(NULL, file, VAR_CMD, FALSE); /* * Now we know the file's name, we attempt to find the durn thing. * Search for it first on the -I search path, then on the .PATH * search path, if not found in a -I directory. */ fullname = Dir_FindFile(file, parseIncPath); if (fullname == NULL) { fullname = Dir_FindFile(file, dirSearchPath); } if (fullname == NULL) { /* * Still haven't found the makefile. Look for it on the system * path as a last resort. */ fullname = Dir_FindFile(file, sysIncPath); } if (fullname == NULL) { Parse_Error(PARSE_FATAL, "Could not find %s", file); return; } /* * Once we find the absolute path to the file, we get to save all the * state from the current file before we can start reading this * include file. The state is stored in an IFile structure which * is placed on a list with other IFile structures. The list makes * a very nice stack to track how we got here... */ oldFile = emalloc(sizeof(IFile)); memcpy(oldFile, &curFile, sizeof(IFile)); Lst_AtFront(includes, oldFile); /* * Once the previous state has been saved, we can get down to reading * the new file. We set up the name of the file to be the absolute * name of the include file so error messages refer to the right * place. Naturally enough, we start reading at line number 0. */ curFile.fname = fullname; curFile.lineno = 0; curFile.F = fopen(fullname, "r"); curFile.p = NULL; if (curFile.F == NULL) { Parse_Error(PARSE_FATAL, "Cannot open %s", fullname); /* * Pop to previous file */ ParseEOF(1); } else { Var_Append(".MAKEFILE_LIST", fullname, VAR_GLOBAL); } } #endif /*- *--------------------------------------------------------------------- * ParseEOF -- * Called when EOF is reached in the current file. If we were reading * an include file, the includes stack is popped and things set up * to go back to reading the previous file at the previous location. * * Results: * CONTINUE if there's more to do. DONE if not. * * Side Effects: * The old curFile.F is closed. The includes list is shortened. * curFile.lineno, curFile.F, and curFile.fname are changed if * CONTINUE is returned. *--------------------------------------------------------------------- */ static int ParseEOF(int opened) { IFile *ifile; /* the state on the top of the includes stack */ if (Lst_IsEmpty (includes)) { Var_Append(".MAKEFILE_LIST", "..", VAR_GLOBAL); return (DONE); } ifile = Lst_DeQueue(includes); free(curFile.fname); if (opened && curFile.F) { fclose(curFile.F); Var_Append(".MAKEFILE_LIST", "..", VAR_GLOBAL); } if (curFile.p) { free(curFile.p->str); free(curFile.p); } memcpy(&curFile, ifile, sizeof(IFile)); free(ifile); return (CONTINUE); } /*- *--------------------------------------------------------------------- * ParseReadc -- * Read a character from the current file * * Results: * The character that was read * * Side Effects: *--------------------------------------------------------------------- */ static int ParseReadc(void) { if (curFile.F) return (fgetc(curFile.F)); if (curFile.p && *curFile.p->ptr) return (*curFile.p->ptr++); return (EOF); } /*- *--------------------------------------------------------------------- * ParseUnreadc -- * Put back a character to the current file * * Results: * None. * * Side Effects: *--------------------------------------------------------------------- */ static void ParseUnreadc(int c) { if (curFile.F) { ungetc(c, curFile.F); return; } if (curFile.p) { *--(curFile.p->ptr) = c; return; } } /* ParseSkipLine(): * Grab the next line unless it begins with a dot (`.') and we're told to * ignore such lines. */ static char * ParseSkipLine(int skip, int keep_newline) { char *line; int c, lastc; size_t lineLength = 0; Buffer buf; buf = Buf_Init(MAKE_BSIZE); do { Buf_Discard(buf, lineLength); lastc = '\0'; while (((c = ParseReadc()) != '\n' || lastc == '\\') && c != EOF) { if (skip && c == '#' && lastc != '\\') { /* let a comment be terminated even by an escaped \n. * This is consistent to comment handling in ParseReadLine */ while ((c = ParseReadc()) != '\n' && c != EOF) ; break; } if (c == '\n') { if (keep_newline) Buf_AddByte(buf, (Byte)c); else Buf_ReplaceLastByte(buf, (Byte)' '); curFile.lineno++; while ((c = ParseReadc()) == ' ' || c == '\t') continue; if (c == EOF) break; } Buf_AddByte(buf, (Byte)c); lastc = c; } if (c == EOF) { Parse_Error(PARSE_FATAL, "Unclosed conditional/for loop"); Buf_Destroy(buf, TRUE); return (NULL); } curFile.lineno++; Buf_AddByte(buf, (Byte)'\0'); line = (char *)Buf_GetAll(buf, &lineLength); } while (skip == 1 && line[0] != '.'); Buf_Destroy(buf, FALSE); return (line); } /*- *--------------------------------------------------------------------- * ParseReadLine -- * Read an entire line from the input file. Called only by Parse_File. * To facilitate escaped newlines and what have you, a character is * buffered in 'lastc', which is '\0' when no characters have been * read. When we break out of the loop, c holds the terminating * character and lastc holds a character that should be added to * the line (unless we don't read anything but a terminator). * * Results: * A line w/o its newline * * Side Effects: * Only those associated with reading a character *--------------------------------------------------------------------- */ static char * ParseReadLine(void) { Buffer buf; /* Buffer for current line */ int c; /* the current character */ int lastc; /* The most-recent character */ Boolean semiNL; /* treat semi-colons as newlines */ Boolean ignDepOp; /* TRUE if should ignore dependency operators * for the purposes of setting semiNL */ Boolean ignComment; /* TRUE if should ignore comments (in a * shell command */ char *line; /* Result */ char *ep; /* to strip trailing blanks */ size_t lineLength; /* Length of result */ int lineno; /* Saved line # */ semiNL = FALSE; ignDepOp = FALSE; ignComment = FALSE; /* * Handle special-characters at the beginning of the line. Either a * leading tab (shell command) or pound-sign (possible conditional) * forces us to ignore comments and dependency operators and treat * semi-colons as semi-colons (by leaving semiNL FALSE). This also * discards completely blank lines. */ for (;;) { c = ParseReadc(); if (c == '\t') { ignComment = ignDepOp = TRUE; break; } else if (c == '\n') { curFile.lineno++; } else if (c == '#') { ParseUnreadc(c); break; } else { /* * Anything else breaks out without doing anything */ break; } } if (c != EOF) { lastc = c; buf = Buf_Init(MAKE_BSIZE); while (((c = ParseReadc()) != '\n' || (lastc == '\\')) && (c != EOF)) { test_char: switch (c) { case '\n': /* * Escaped newline: read characters until a non-space or an * unescaped newline and replace them all by a single space. * This is done by storing the space over the backslash and * dropping through with the next nonspace. If it is a * semi-colon and semiNL is TRUE, it will be recognized as a * newline in the code below this... */ curFile.lineno++; lastc = ' '; while ((c = ParseReadc()) == ' ' || c == '\t') { continue; } if (c == EOF || c == '\n') { goto line_read; } else { /* * Check for comments, semiNL's, etc. -- easier than * ParseUnreadc(c); continue; */ goto test_char; } /*NOTREACHED*/ break; case ';': /* * Semi-colon: Need to see if it should be interpreted as a * newline */ if (semiNL) { /* * To make sure the command that may be following this * semi-colon begins with a tab, we push one back into the * input stream. This will overwrite the semi-colon in the * buffer. If there is no command following, this does no * harm, since the newline remains in the buffer and the * whole line is ignored. */ ParseUnreadc('\t'); goto line_read; } break; case '=': if (!semiNL) { /* * Haven't seen a dependency operator before this, so this * must be a variable assignment -- don't pay attention to * dependency operators after this. */ ignDepOp = TRUE; } else if (lastc == ':' || lastc == '!') { /* * Well, we've seen a dependency operator already, but it * was the previous character, so this is really just an * expanded variable assignment. Revert semi-colons to * being just semi-colons again and ignore any more * dependency operators. * * XXX: Note that a line like "foo : a:=b" will blow up, * but who'd write a line like that anyway? */ ignDepOp = TRUE; semiNL = FALSE; } break; case '#': if (!ignComment) { if (lastc != '\\') { /* * If the character is a hash mark and it isn't escaped * (or we're being compatible), the thing is a comment. * Skip to the end of the line. */ do { c = ParseReadc(); } while ((c != '\n') && (c != EOF)); goto line_read; } else { /* * Don't add the backslash. Just let the # get copied * over. */ lastc = c; continue; } } break; case ':': case '!': if (!ignDepOp && (c == ':' || c == '!')) { /* * A semi-colon is recognized as a newline only on * dependency lines. Dependency lines are lines with a * colon or an exclamation point. Ergo... */ semiNL = TRUE; } break; default: break; } /* * Copy in the previous character and save this one in lastc. */ Buf_AddByte(buf, (Byte)lastc); lastc = c; } line_read: curFile.lineno++; if (lastc != '\0') { Buf_AddByte(buf, (Byte)lastc); } Buf_AddByte(buf, (Byte)'\0'); line = (char *)Buf_GetAll(buf, &lineLength); Buf_Destroy(buf, FALSE); /* * Strip trailing blanks and tabs from the line. * Do not strip a blank or tab that is preceded by * a '\' */ ep = line; while (*ep) ++ep; while (ep > line + 1 && (ep[-1] == ' ' || ep[-1] == '\t')) { if (ep > line + 1 && ep[-2] == '\\') break; --ep; } *ep = 0; if (line[0] == '.') { /* * The line might be a conditional. Ask the conditional module * about it and act accordingly */ switch (Cond_Eval(line)) { case COND_SKIP: /* * Skip to next conditional that evaluates to COND_PARSE. */ do { free(line); line = ParseSkipLine(1, 0); } while (line && Cond_Eval(line) != COND_PARSE); if (line == NULL) break; /*FALLTHRU*/ case COND_PARSE: free(line); line = ParseReadLine(); break; case COND_INVALID: if (For_Eval(line)) { int ok; free(line); lineno = curFile.lineno; do { /* * Skip after the matching end */ line = ParseSkipLine(0, 1); if (line == NULL) { Parse_Error(PARSE_FATAL, "Unexpected end of file in for loop.\n"); break; } ok = For_Eval(line); free(line); } while (ok); if (line != NULL) For_Run(lineno); line = ParseReadLine(); } break; default: break; } } return (line); } else { /* * Hit end-of-file, so return a NULL line to indicate this. */ return (NULL); } } /*- *----------------------------------------------------------------------- * ParseFinishLine -- * Handle the end of a dependency group. * * Results: * Nothing. * * Side Effects: * inLine set FALSE. 'targets' list destroyed. * *----------------------------------------------------------------------- */ static void ParseFinishLine(void) { if (inLine) { Lst_ForEach(targets, Suff_EndTransform, NULL); Lst_Destroy(targets, ParseHasCommands); targets = NULL; inLine = FALSE; } } /*- *--------------------------------------------------------------------- * Parse_File -- * Parse a file into its component parts, incorporating it into the * current dependency graph. This is the main function and controls * almost every other function in this module * * Results: * None * * Side Effects: * Loads. Nodes are added to the list of all targets, nodes and links * are added to the dependency graph. etc. etc. etc. *--------------------------------------------------------------------- */ void Parse_File(char *name, FILE *stream) { char *cp, /* pointer into the line */ *line; /* the line we're working on */ inLine = FALSE; curFile.fname = name; curFile.F = stream; curFile.lineno = 0; fatals = 0; Var_Append(".MAKEFILE_LIST", name, VAR_GLOBAL); do { while ((line = ParseReadLine()) != NULL) { if (*line == '.') { /* * Lines that begin with the special character are either * include or undef directives. */ for (cp = line + 1; isspace((unsigned char)*cp); cp++) { continue; } if (strncmp(cp, "include", 7) == 0) { ParseDoInclude (cp + 7); goto nextLine; } else if (strncmp(cp, "error", 5) == 0) { ParseDoError(cp + 5); goto nextLine; } else if (strncmp(cp, "warning", 7) == 0) { ParseDoWarning(cp + 7); goto nextLine; } else if (strncmp(cp, "undef", 5) == 0) { char *cp2; for (cp += 5; isspace((unsigned char)*cp); cp++) { continue; } for (cp2 = cp; !isspace((unsigned char)*cp2) && (*cp2 != '\0'); cp2++) { continue; } *cp2 = '\0'; cp = Var_Subst(NULL, cp, VAR_CMD, FALSE); Var_Delete(cp, VAR_GLOBAL); goto nextLine; } } if (*line == '#') { /* If we're this far, the line must be a comment. */ goto nextLine; } if (*line == '\t') { /* * If a line starts with a tab, it can only hope to be * a creation command. */ for (cp = line + 1; isspace((unsigned char)*cp); cp++) { continue; } if (*cp) { if (inLine) { /* * So long as it's not a blank line and we're actually * in a dependency spec, add the command to the list of * commands of all targets in the dependency spec */ Lst_ForEach(targets, ParseAddCmd, cp); Lst_AtEnd(targCmds, line); continue; } else { Parse_Error(PARSE_FATAL, "Unassociated shell command \"%s\"", cp); } } #ifdef SYSVINCLUDE } else if (strncmp(line, "include", 7) == 0 && isspace((unsigned char)line[7]) && strchr(line, ':') == NULL) { /* * It's an S3/S5-style "include". */ ParseTraditionalInclude(line + 7); goto nextLine; #endif } else if (Parse_IsVar(line)) { ParseFinishLine(); Parse_DoVar(line, VAR_GLOBAL); } else { /* * We now know it's a dependency line so it needs to have all * variables expanded before being parsed. Tell the variable * module to complain if some variable is undefined... * To make life easier on novices, if the line is indented we * first make sure the line has a dependency operator in it. * If it doesn't have an operator and we're in a dependency * line's script, we assume it's actually a shell command * and add it to the current list of targets. */ cp = line; if (isspace((unsigned char)line[0])) { while ((*cp != '\0') && isspace((unsigned char)*cp)) { cp++; } if (*cp == '\0') { goto nextLine; } } ParseFinishLine(); cp = Var_Subst(NULL, line, VAR_CMD, TRUE); free(line); line = cp; /* * Need a non-circular list for the target nodes */ if (targets) Lst_Destroy(targets, NOFREE); targets = Lst_Init(); inLine = TRUE; ParseDoDependency (line); } nextLine: free(line); } /* * Reached EOF, but it may be just EOF of an include file... */ } while (ParseEOF(1) == CONTINUE); /* * Make sure conditionals are clean */ Cond_End(); if (fatals) errx(1, "fatal errors encountered -- cannot continue"); } /*- *--------------------------------------------------------------------- * Parse_Init -- * initialize the parsing module * * Results: * none * * Side Effects: * the parseIncPath list is initialized... *--------------------------------------------------------------------- */ void Parse_Init(void) { mainNode = NULL; parseIncPath = Lst_Init(); sysIncPath = Lst_Init(); includes = Lst_Init(); targCmds = Lst_Init(); } void Parse_End(void) { Lst_Destroy(targCmds, free); if (targets) Lst_Destroy(targets, NOFREE); Lst_Destroy(sysIncPath, Dir_Destroy); Lst_Destroy(parseIncPath, Dir_Destroy); Lst_Destroy(includes, NOFREE); /* Should be empty now */ } /*- *----------------------------------------------------------------------- * Parse_MainName -- * Return a Lst of the main target to create for main()'s sake. If * no such target exists, we Punt with an obnoxious error message. * * Results: * A Lst of the single node to create. * * Side Effects: * None. * *----------------------------------------------------------------------- */ -Lst +Lst * Parse_MainName(void) { - Lst listmain; /* result list */ + Lst *listmain; /* result list */ listmain = Lst_Init(); if (mainNode == NULL) { Punt("no target to make."); /*NOTREACHED*/ } else if (mainNode->type & OP_DOUBLEDEP) { Lst_AtEnd(listmain, mainNode); Lst_Concat(listmain, mainNode->cohorts, LST_CONCNEW); } else Lst_AtEnd(listmain, mainNode); return (listmain); } Index: head/usr.bin/make/suff.c =================================================================== --- head/usr.bin/make/suff.c (revision 138511) +++ head/usr.bin/make/suff.c (revision 138512) @@ -1,2356 +1,2356 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)suff.c 8.4 (Berkeley) 3/21/94 */ #include __FBSDID("$FreeBSD$"); /*- * suff.c -- * Functions to maintain suffix lists and find implicit dependents * using suffix transformation rules * * Interface: * Suff_Init Initialize all things to do with suffixes. * * Suff_End Cleanup the module * * Suff_DoPaths This function is used to make life easier * when searching for a file according to its * suffix. It takes the global search path, * as defined using the .PATH: target, and appends * its directories to the path of each of the * defined suffixes, as specified using * .PATH: targets. In addition, all * directories given for suffixes labeled as * include files or libraries, using the .INCLUDES * or .LIBS targets, are played with using * Dir_MakeFlags to create the .INCLUDES and * .LIBS global variables. * * Suff_ClearSuffixes Clear out all the suffixes and defined * transformations. * * Suff_IsTransform Return TRUE if the passed string is the lhs * of a transformation rule. * * Suff_AddSuffix Add the passed string as another known suffix. * * Suff_GetPath Return the search path for the given suffix. * * Suff_AddInclude Mark the given suffix as denoting an include * file. * * Suff_AddLib Mark the given suffix as denoting a library. * * Suff_AddTransform Add another transformation to the suffix * graph. Returns GNode suitable for framing, I * mean, tacking commands, attributes, etc. on. * * Suff_SetNull Define the suffix to consider the suffix of * any file that doesn't have a known one. * * Suff_FindDeps Find implicit sources for and the location of * a target based on its suffix. Returns the * bottom-most node added to the graph or NULL * if the target had no implicit sources. */ #include #include "make.h" #include "hash.h" #include "dir.h" -static Lst sufflist; /* Lst of suffixes */ -static Lst suffClean; /* Lst of suffixes to be cleaned */ -static Lst srclist; /* Lst of sources */ -static Lst transforms; /* Lst of transformation rules */ +static Lst *sufflist; /* Lst of suffixes */ +static Lst *suffClean; /* Lst of suffixes to be cleaned */ +static Lst *srclist; /* Lst of sources */ +static Lst *transforms; /* Lst of transformation rules */ static int sNum = 0; /* Counter for assigning suffix numbers */ /* * Structure describing an individual suffix. */ typedef struct _Suff { char *name; /* The suffix itself */ int nameLen; /* Length of the suffix */ short flags; /* Type of suffix */ #define SUFF_INCLUDE 0x01 /* One which is #include'd */ #define SUFF_LIBRARY 0x02 /* One which contains a library */ #define SUFF_NULL 0x04 /* The empty suffix */ - Lst searchPath; /* The path along which files of this suffix + Lst *searchPath; /* The path along which files of this suffix * may be found */ int sNum; /* The suffix number */ int refCount; /* Reference count of list membership */ - Lst parents; /* Suffixes we have a transformation to */ - Lst children; /* Suffixes we have a transformation from */ - Lst ref; /* List of lists this suffix is referenced */ + Lst *parents; /* Suffixes we have a transformation to */ + Lst *children; /* Suffixes we have a transformation from */ + Lst *ref; /* List of lists this suffix is referenced */ } Suff; /* * Structure used in the search for implied sources. */ typedef struct _Src { char *file; /* The file to look for */ char *pref; /* Prefix from which file was formed */ Suff *suff; /* The suffix on the file */ struct _Src *parent; /* The Src for which this is a source */ GNode *node; /* The node describing the file */ int children; /* Count of existing children (so we don't free * this thing too early or never nuke it) */ #ifdef DEBUG_SRC - Lst cp; /* Debug; children list */ + Lst *cp; /* Debug; children list */ #endif } Src; /* * A structure for passing more than one argument to the Lst-library-invoked * function... */ typedef struct { - Lst l; + Lst *l; Src *s; } LstSrc; static Suff *suffNull; /* The NULL suffix for this run */ static Suff *emptySuff; /* The empty suffix required for POSIX * single-suffix transformation rules */ static char *SuffStrIsPrefix(char *, char *); static char *SuffSuffIsSuffix(Suff *, char *); static int SuffSuffIsSuffixP(void *, void *); static int SuffSuffHasNameP(void *, void *); static int SuffSuffIsPrefix(void *, void *); static int SuffGNHasNameP(void *, void *); static void SuffFree(void *); -static void SuffInsert(Lst, Suff *); -static void SuffRemove(Lst, Suff *); +static void SuffInsert(Lst *, Suff *); +static void SuffRemove(Lst *, Suff *); static Boolean SuffParseTransform(char *, Suff **, Suff **); static int SuffRebuildGraph(void *, void *); static int SuffAddSrc(void *, void *); -static int SuffRemoveSrc(Lst); -static void SuffAddLevel(Lst, Src *); -static Src *SuffFindThem(Lst, Lst); -static Src *SuffFindCmds(Src *, Lst); +static int SuffRemoveSrc(Lst *); +static void SuffAddLevel(Lst *, Src *); +static Src *SuffFindThem(Lst *, Lst *); +static Src *SuffFindCmds(Src *, Lst *); static int SuffExpandChildren(void *, void *); static Boolean SuffApplyTransform(GNode *, GNode *, Suff *, Suff *); -static void SuffFindDeps(GNode *, Lst); -static void SuffFindArchiveDeps(GNode *, Lst); -static void SuffFindNormalDeps(GNode *, Lst); +static void SuffFindDeps(GNode *, Lst *); +static void SuffFindArchiveDeps(GNode *, Lst *); +static void SuffFindNormalDeps(GNode *, Lst *); static int SuffPrintName(void *, void *); static int SuffPrintSuff(void *, void *); static int SuffPrintTrans(void *, void *); /*************** Lst Predicates ****************/ /*- *----------------------------------------------------------------------- * SuffStrIsPrefix -- * See if pref is a prefix of str. * * Results: * NULL if it ain't, pointer to character in str after prefix if so * * Side Effects: * None *----------------------------------------------------------------------- */ static char * SuffStrIsPrefix(char *pref, char *str) { while (*str && *pref == *str) { pref++; str++; } return (*pref ? NULL : str); } /*- *----------------------------------------------------------------------- * SuffSuffIsSuffix -- * See if suff is a suffix of str. Str should point to THE END of the * string to check. (THE END == the null byte) * * Results: * NULL if it ain't, pointer to character in str before suffix if * it is. * * Side Effects: * None *----------------------------------------------------------------------- */ static char * SuffSuffIsSuffix(Suff *s, char *str) { char *p1; /* Pointer into suffix name */ char *p2; /* Pointer into string being examined */ p1 = s->name + s->nameLen; p2 = str; while (p1 >= s->name && *p1 == *p2) { p1--; p2--; } return (p1 == s->name - 1 ? p2 : NULL); } /*- *----------------------------------------------------------------------- * SuffSuffIsSuffixP -- * Predicate form of SuffSuffIsSuffix. Passed as the callback function * to Lst_Find. * * Results: * 0 if the suffix is the one desired, non-zero if not. * * Side Effects: * None. * *----------------------------------------------------------------------- */ static int SuffSuffIsSuffixP(void *s, void *str) { return (!SuffSuffIsSuffix(s, str)); } /*- *----------------------------------------------------------------------- * SuffSuffHasNameP -- * Callback procedure for finding a suffix based on its name. Used by * Suff_GetPath. * * Results: * 0 if the suffix is of the given name. non-zero otherwise. * * Side Effects: * None *----------------------------------------------------------------------- */ static int SuffSuffHasNameP(void *s, void *sname) { return (strcmp(sname, ((Suff *)s)->name)); } /*- *----------------------------------------------------------------------- * SuffSuffIsPrefix -- * See if the suffix described by s is a prefix of the string. Care * must be taken when using this to search for transformations and * what-not, since there could well be two suffixes, one of which * is a prefix of the other... * * Results: * 0 if s is a prefix of str. non-zero otherwise * * Side Effects: * None *----------------------------------------------------------------------- */ static int SuffSuffIsPrefix(void *s, void *str) { return (SuffStrIsPrefix(((Suff *)s)->name, str) == NULL ? 1 : 0); } /*- *----------------------------------------------------------------------- * SuffGNHasNameP -- * See if the graph node has the desired name * * Results: * 0 if it does. non-zero if it doesn't * * Side Effects: * None *----------------------------------------------------------------------- */ static int SuffGNHasNameP(void *gn, void *name) { return (strcmp(name, ((GNode *)gn)->name)); } /*********** Maintenance Functions ************/ /*- *----------------------------------------------------------------------- * SuffFree -- * Free up all memory associated with the given suffix structure. * * Results: * none * * Side Effects: * the suffix entry is detroyed *----------------------------------------------------------------------- */ static void SuffFree(void *sp) { Suff *s = sp; if (s == suffNull) suffNull = NULL; if (s == emptySuff) emptySuff = NULL; Lst_Destroy(s->ref, NOFREE); Lst_Destroy(s->children, NOFREE); Lst_Destroy(s->parents, NOFREE); Lst_Destroy(s->searchPath, Dir_Destroy); free(s->name); free(s); } /*- *----------------------------------------------------------------------- * SuffRemove -- * Remove the suffix into the list * * Results: * None * * Side Effects: * The reference count for the suffix is decremented *----------------------------------------------------------------------- */ static void -SuffRemove(Lst l, Suff *s) +SuffRemove(Lst *l, Suff *s) { - LstNode ln = Lst_Member(l, s); + LstNode *ln = Lst_Member(l, s); if (ln != NULL) { Lst_Remove(l, ln); s->refCount--; } } /*- *----------------------------------------------------------------------- * SuffInsert -- * Insert the suffix into the list keeping the list ordered by suffix * numbers. * * Results: * None * * Side Effects: * The reference count of the suffix is incremented *----------------------------------------------------------------------- */ static void -SuffInsert(Lst l, Suff *s) +SuffInsert(Lst *l, Suff *s) { - LstNode ln; /* current element in l we're examining */ + LstNode *ln; /* current element in l we're examining */ Suff *s2 = NULL; /* the suffix descriptor in this element */ if (Lst_Open(l) == FAILURE) { return; } while ((ln = Lst_Next(l)) != NULL) { s2 = Lst_Datum(ln); if (s2->sNum >= s->sNum) { break; } } if (s2 == NULL) { DEBUGF(SUFF, ("inserting an empty list?...")); } Lst_Close(l); DEBUGF(SUFF, ("inserting %s(%d)...", s->name, s->sNum)); if (ln == NULL) { DEBUGF(SUFF, ("at end of list\n")); Lst_AtEnd (l, s); s->refCount++; Lst_AtEnd(s->ref, l); } else if (s2->sNum != s->sNum) { DEBUGF(SUFF, ("before %s(%d)\n", s2->name, s2->sNum)); Lst_Insert(l, ln, s); s->refCount++; Lst_AtEnd(s->ref, l); } else { DEBUGF(SUFF, ("already there\n")); } } /*- *----------------------------------------------------------------------- * Suff_ClearSuffixes -- * This is gross. Nuke the list of suffixes but keep all transformation * rules around. The transformation graph is destroyed in this process, * but we leave the list of rules so when a new graph is formed the rules * will remain. * This function is called from the parse module when a * .SUFFIXES:\n line is encountered. * * Results: * none * * Side Effects: * the sufflist and its graph nodes are destroyed *----------------------------------------------------------------------- */ void Suff_ClearSuffixes(void) { Lst_Concat(suffClean, sufflist, LST_CONCLINK); sufflist = Lst_Init(); sNum = 1; suffNull = emptySuff; /* * Clear suffNull's children list (the other suffixes are built new, but * suffNull is used as is). * NOFREE is used because all suffixes are are on the suffClean list. * suffNull should not have parents. */ Lst_Destroy(suffNull->children, NOFREE); suffNull->children = Lst_Init(); } /*- *----------------------------------------------------------------------- * SuffParseTransform -- * Parse a transformation string to find its two component suffixes. * * Results: * TRUE if the string is a valid transformation and FALSE otherwise. * * Side Effects: * The passed pointers are overwritten. * *----------------------------------------------------------------------- */ static Boolean SuffParseTransform(char *str, Suff **srcPtr, Suff **targPtr) { - LstNode srcLn; /* element in suffix list of trans source*/ + LstNode *srcLn; /* element in suffix list of trans source*/ Suff *src; /* Source of transformation */ - LstNode targLn; /* element in suffix list of trans target*/ + LstNode *targLn; /* element in suffix list of trans target*/ char *str2; /* Extra pointer (maybe target suffix) */ - LstNode singleLn; /* element in suffix list of any suffix + LstNode *singleLn; /* element in suffix list of any suffix * that exactly matches str */ Suff *single = NULL;/* Source of possible transformation to * null suffix */ srcLn = NULL; singleLn = NULL; /* * Loop looking first for a suffix that matches the start of the * string and then for one that exactly matches the rest of it. If * we can find two that meet these criteria, we've successfully * parsed the string. */ for (;;) { if (srcLn == NULL) { srcLn = Lst_Find(sufflist, str, SuffSuffIsPrefix); } else { srcLn = Lst_FindFrom(sufflist, Lst_Succ(srcLn), str, SuffSuffIsPrefix); } if (srcLn == NULL) { /* * Ran out of source suffixes -- no such rule */ if (singleLn != NULL) { /* * Not so fast Mr. Smith! There was a suffix that encompassed * the entire string, so we assume it was a transformation * to the null suffix (thank you POSIX). We still prefer to * find a double rule over a singleton, hence we leave this * check until the end. * * XXX: Use emptySuff over suffNull? */ *srcPtr = single; *targPtr = suffNull; return (TRUE); } return (FALSE); } src = Lst_Datum (srcLn); str2 = str + src->nameLen; if (*str2 == '\0') { single = src; singleLn = srcLn; } else { targLn = Lst_Find(sufflist, str2, SuffSuffHasNameP); if (targLn != NULL) { *srcPtr = src; *targPtr = Lst_Datum(targLn); return (TRUE); } } } } /*- *----------------------------------------------------------------------- * Suff_IsTransform -- * Return TRUE if the given string is a transformation rule * * * Results: * TRUE if the string is a concatenation of two known suffixes. * FALSE otherwise * * Side Effects: * None *----------------------------------------------------------------------- */ Boolean Suff_IsTransform(char *str) { Suff *src, *targ; return (SuffParseTransform(str, &src, &targ)); } /*- *----------------------------------------------------------------------- * Suff_AddTransform -- * Add the transformation rule described by the line to the * list of rules and place the transformation itself in the graph * * Results: * The node created for the transformation in the transforms list * * Side Effects: * The node is placed on the end of the transforms Lst and links are * made between the two suffixes mentioned in the target name *----------------------------------------------------------------------- */ GNode * Suff_AddTransform(char *line) { GNode *gn; /* GNode of transformation rule */ Suff *s, /* source suffix */ *t; /* target suffix */ - LstNode ln; /* Node for existing transformation */ + LstNode *ln; /* Node for existing transformation */ ln = Lst_Find(transforms, line, SuffGNHasNameP); if (ln == NULL) { /* * Make a new graph node for the transformation. It will be filled in * by the Parse module. */ gn = Targ_NewGN(line); Lst_AtEnd(transforms, gn); } else { /* * New specification for transformation rule. Just nuke the old list * of commands so they can be filled in again... We don't actually * free the commands themselves, because a given command can be * attached to several different transformations. */ gn = Lst_Datum(ln); Lst_Destroy(gn->commands, NOFREE); Lst_Destroy(gn->children, NOFREE); gn->commands = Lst_Init(); gn->children = Lst_Init(); } gn->type = OP_TRANSFORM; SuffParseTransform(line, &s, &t); /* * link the two together in the proper relationship and order */ DEBUGF(SUFF, ("defining transformation from `%s' to `%s'\n", s->name, t->name)); SuffInsert(t->children, s); SuffInsert(s->parents, t); return (gn); } /*- *----------------------------------------------------------------------- * Suff_EndTransform -- * Handle the finish of a transformation definition, removing the * transformation from the graph if it has neither commands nor * sources. This is a callback procedure for the Parse module via * Lst_ForEach * * Results: * === 0 * * Side Effects: * If the node has no commands or children, the children and parents * lists of the affected suffices are altered. * *----------------------------------------------------------------------- */ int Suff_EndTransform(void *gnp, void *dummy __unused) { GNode *gn = (GNode *)gnp; if ((gn->type & OP_TRANSFORM) && Lst_IsEmpty(gn->commands) && Lst_IsEmpty(gn->children)) { Suff *s, *t; /* * SuffParseTransform() may fail for special rules which are not * actual transformation rules (e.g., .DEFAULT). */ if (!SuffParseTransform(gn->name, &s, &t)) return (0); DEBUGF(SUFF, ("deleting transformation from `%s' to `%s'\n", s->name, t->name)); /* * Remove the source from the target's children list. We check for a * NULL return to handle a beanhead saying something like * .c.o .c.o: * * We'll be called twice when the next target is seen, but .c and .o * are only linked once... */ SuffRemove(t->children, s); /* * Remove the target from the source's parents list */ SuffRemove(s->parents, t); } else if (gn->type & OP_TRANSFORM) { DEBUGF(SUFF, ("transformation %s complete\n", gn->name)); } return (0); } /*- *----------------------------------------------------------------------- * SuffRebuildGraph -- * Called from Suff_AddSuffix via Lst_ForEach to search through the * list of existing transformation rules and rebuild the transformation * graph when it has been destroyed by Suff_ClearSuffixes. If the * given rule is a transformation involving this suffix and another, * existing suffix, the proper relationship is established between * the two. * * Results: * Always 0. * * Side Effects: * The appropriate links will be made between this suffix and * others if transformation rules exist for it. * *----------------------------------------------------------------------- */ static int SuffRebuildGraph(void *transformp, void *sp) { GNode *transform = transformp; Suff *s = sp; char *cp; - LstNode ln; + LstNode *ln; Suff *s2 = NULL; /* * First see if it is a transformation from this suffix. */ cp = SuffStrIsPrefix(s->name, transform->name); if (cp != (char *)NULL) { if (cp[0] == '\0') /* null rule */ s2 = suffNull; else { ln = Lst_Find(sufflist, cp, SuffSuffHasNameP); if (ln != NULL) s2 = Lst_Datum(ln); } if (s2 != NULL) { /* * Found target. Link in and return, since it can't be anything * else. */ SuffInsert(s2->children, s); SuffInsert(s->parents, s2); return (0); } } /* * Not from, maybe to? */ cp = SuffSuffIsSuffix(s, transform->name + strlen(transform->name)); if (cp != NULL) { /* * Null-terminate the source suffix in order to find it. */ cp[1] = '\0'; ln = Lst_Find(sufflist, transform->name, SuffSuffHasNameP); /* * Replace the start of the target suffix */ cp[1] = s->name[0]; if (ln != NULL) { /* * Found it -- establish the proper relationship */ s2 = Lst_Datum(ln); SuffInsert(s->children, s2); SuffInsert(s2->parents, s); } } return (0); } /*- *----------------------------------------------------------------------- * Suff_AddSuffix -- * Add the suffix in string to the end of the list of known suffixes. * Should we restructure the suffix graph? Make doesn't... * * Results: * None * * Side Effects: * A GNode is created for the suffix and a Suff structure is created and * added to the suffixes list unless the suffix was already known. *----------------------------------------------------------------------- */ void Suff_AddSuffix(char *str) { Suff *s; /* new suffix descriptor */ - LstNode ln; + LstNode *ln; ln = Lst_Find(sufflist, str, SuffSuffHasNameP); if (ln == NULL) { s = emalloc(sizeof(Suff)); s->name = estrdup(str); s->nameLen = strlen (s->name); s->searchPath = Lst_Init(); s->children = Lst_Init(); s->parents = Lst_Init(); s->ref = Lst_Init(); s->sNum = sNum++; s->flags = 0; s->refCount = 0; Lst_AtEnd(sufflist, s); /* * Look for any existing transformations from or to this suffix. * XXX: Only do this after a Suff_ClearSuffixes? */ Lst_ForEach(transforms, SuffRebuildGraph, s); } } /*- *----------------------------------------------------------------------- * Suff_GetPath -- * Return the search path for the given suffix, if it's defined. * * Results: * The searchPath for the desired suffix or NULL if the suffix isn't * defined. * * Side Effects: * None *----------------------------------------------------------------------- */ -Lst +Lst * Suff_GetPath(char *sname) { - LstNode ln; + LstNode *ln; Suff *s; ln = Lst_Find(sufflist, sname, SuffSuffHasNameP); if (ln == NULL) { return (NULL); } else { s = Lst_Datum(ln); return (s->searchPath); } } /*- *----------------------------------------------------------------------- * Suff_DoPaths -- * Extend the search paths for all suffixes to include the default * search path. * * Results: * None. * * Side Effects: * The searchPath field of all the suffixes is extended by the * directories in dirSearchPath. If paths were specified for the * ".h" suffix, the directories are stuffed into a global variable * called ".INCLUDES" with each directory preceded by a -I. The same * is done for the ".a" suffix, except the variable is called * ".LIBS" and the flag is -L. *----------------------------------------------------------------------- */ void Suff_DoPaths(void) { - Suff *s; - LstNode ln; - char *ptr; - Lst inIncludes; /* Cumulative .INCLUDES path */ - Lst inLibs; /* Cumulative .LIBS path */ + Suff *s; + LstNode *ln; + char *ptr; + Lst *inIncludes; /* Cumulative .INCLUDES path */ + Lst *inLibs; /* Cumulative .LIBS path */ if (Lst_Open(sufflist) == FAILURE) { return; } inIncludes = Lst_Init(); inLibs = Lst_Init(); while ((ln = Lst_Next(sufflist)) != NULL) { s = Lst_Datum(ln); if (!Lst_IsEmpty(s->searchPath)) { #ifdef INCLUDES if (s->flags & SUFF_INCLUDE) { Dir_Concat(inIncludes, s->searchPath); } #endif /* INCLUDES */ #ifdef LIBRARIES if (s->flags & SUFF_LIBRARY) { Dir_Concat(inLibs, s->searchPath); } #endif /* LIBRARIES */ Dir_Concat(s->searchPath, dirSearchPath); } else { Lst_Destroy(s->searchPath, Dir_Destroy); s->searchPath = Lst_Duplicate(dirSearchPath, Dir_CopyDir); } } Var_Set(".INCLUDES", ptr = Dir_MakeFlags("-I", inIncludes), VAR_GLOBAL); free(ptr); Var_Set(".LIBS", ptr = Dir_MakeFlags("-L", inLibs), VAR_GLOBAL); free(ptr); Lst_Destroy(inIncludes, Dir_Destroy); Lst_Destroy(inLibs, Dir_Destroy); Lst_Close(sufflist); } /*- *----------------------------------------------------------------------- * Suff_AddInclude -- * Add the given suffix as a type of file which gets included. * Called from the parse module when a .INCLUDES line is parsed. * The suffix must have already been defined. * * Results: * None. * * Side Effects: * The SUFF_INCLUDE bit is set in the suffix's flags field * *----------------------------------------------------------------------- */ void Suff_AddInclude(char *sname) { - LstNode ln; + LstNode *ln; Suff *s; ln = Lst_Find(sufflist, sname, SuffSuffHasNameP); if (ln != NULL) { s = Lst_Datum(ln); s->flags |= SUFF_INCLUDE; } } /*- *----------------------------------------------------------------------- * Suff_AddLib -- * Add the given suffix as a type of file which is a library. * Called from the parse module when parsing a .LIBS line. The * suffix must have been defined via .SUFFIXES before this is * called. * * Results: * None. * * Side Effects: * The SUFF_LIBRARY bit is set in the suffix's flags field * *----------------------------------------------------------------------- */ void Suff_AddLib(char *sname) { - LstNode ln; + LstNode *ln; Suff *s; ln = Lst_Find(sufflist, sname, SuffSuffHasNameP); if (ln != NULL) { s = Lst_Datum(ln); s->flags |= SUFF_LIBRARY; } } /********** Implicit Source Search Functions *********/ /*- *----------------------------------------------------------------------- * SuffAddSrc -- * Add a suffix as a Src structure to the given list with its parent * being the given Src structure. If the suffix is the null suffix, * the prefix is used unaltered as the file name in the Src structure. * * Results: * always returns 0 * * Side Effects: * A Src structure is created and tacked onto the end of the list *----------------------------------------------------------------------- */ static int SuffAddSrc(void *sp, void *lsp) { Suff *s = sp; LstSrc *ls = lsp; Src *s2; /* new Src structure */ Src *targ; /* Target structure */ targ = ls->s; if ((s->flags & SUFF_NULL) && (*s->name != '\0')) { /* * If the suffix has been marked as the NULL suffix, also create a Src * structure for a file with no suffix attached. Two birds, and all * that... */ s2 = emalloc(sizeof(Src)); s2->file = estrdup(targ->pref); s2->pref = targ->pref; s2->parent = targ; s2->node = NULL; s2->suff = s; s->refCount++; s2->children = 0; targ->children += 1; Lst_AtEnd(ls->l, s2); #ifdef DEBUG_SRC s2->cp = Lst_Init(); Lst_AtEnd(targ->cp, s2); printf("1 add %x %x to %x:", targ, s2, ls->l); Lst_ForEach(ls->l, PrintAddr, (void *)NULL); printf("\n"); #endif } s2 = emalloc(sizeof(Src)); s2->file = str_concat(targ->pref, s->name, 0); s2->pref = targ->pref; s2->parent = targ; s2->node = NULL; s2->suff = s; s->refCount++; s2->children = 0; targ->children += 1; Lst_AtEnd(ls->l, s2); #ifdef DEBUG_SRC s2->cp = Lst_Init(); Lst_AtEnd(targ->cp, s2); printf("2 add %x %x to %x:", targ, s2, ls->l); Lst_ForEach(ls->l, PrintAddr, (void *)NULL); printf("\n"); #endif return (0); } /*- *----------------------------------------------------------------------- * SuffAddLevel -- * Add all the children of targ as Src structures to the given list * * Results: * None * * Side Effects: * Lots of structures are created and added to the list *----------------------------------------------------------------------- */ static void -SuffAddLevel(Lst l, Src *targ) +SuffAddLevel(Lst *l, Src *targ) { LstSrc ls; ls.s = targ; ls.l = l; Lst_ForEach(targ->suff->children, SuffAddSrc, &ls); } /*- *---------------------------------------------------------------------- * SuffRemoveSrc -- * Free all src structures in list that don't have a reference count * * Results: * True if a src was removed * * Side Effects: * The memory is free'd. *---------------------------------------------------------------------- */ static int -SuffRemoveSrc(Lst l) +SuffRemoveSrc(Lst *l) { - LstNode ln; + LstNode *ln; Src *s; int t = 0; if (Lst_Open(l) == FAILURE) { return (0); } #ifdef DEBUG_SRC printf("cleaning %lx: ", (unsigned long) l); Lst_ForEach(l, PrintAddr, (void *)NULL); printf("\n"); #endif while ((ln = Lst_Next(l)) != NULL) { s = (Src *)Lst_Datum(ln); if (s->children == 0) { free(s->file); if (!s->parent) free(s->pref); else { #ifdef DEBUG_SRC - LstNode ln = Lst_Member(s->parent->cp, s); + LstNode *ln = Lst_Member(s->parent->cp, s); if (ln != NULL) Lst_Remove(s->parent->cp, ln); #endif --s->parent->children; } #ifdef DEBUG_SRC printf("free: [l=%x] p=%x %d\n", l, s, s->children); Lst_Destroy(s->cp, NOFREE); #endif Lst_Remove(l, ln); free(s); t |= 1; Lst_Close(l); return TRUE; } #ifdef DEBUG_SRC else { printf("keep: [l=%x] p=%x %d: ", l, s, s->children); Lst_ForEach(s->cp, PrintAddr, (void *)NULL); printf("\n"); } #endif } Lst_Close(l); return (t); } /*- *----------------------------------------------------------------------- * SuffFindThem -- * Find the first existing file/target in the list srcs * * Results: * The lowest structure in the chain of transformations * * Side Effects: * None *----------------------------------------------------------------------- */ static Src * -SuffFindThem (Lst srcs, Lst slst) +SuffFindThem(Lst *srcs, Lst *slst) { Src *s; /* current Src */ Src *rs; /* returned Src */ char *ptr; rs = NULL; while (!Lst_IsEmpty (srcs)) { s = Lst_DeQueue(srcs); DEBUGF(SUFF, ("\ttrying %s...", s->file)); /* * A file is considered to exist if either a node exists in the * graph for it or the file actually exists. */ if (Targ_FindNode(s->file, TARG_NOCREATE) != NULL) { #ifdef DEBUG_SRC printf("remove %x from %x\n", s, srcs); #endif rs = s; break; } if ((ptr = Dir_FindFile(s->file, s->suff->searchPath)) != NULL) { rs = s; #ifdef DEBUG_SRC printf("remove %x from %x\n", s, srcs); #endif free(ptr); break; } DEBUGF(SUFF, ("not there\n")); SuffAddLevel(srcs, s); Lst_AtEnd(slst, s); } if (rs) { DEBUGF(SUFF, ("got it\n")); } return (rs); } /*- *----------------------------------------------------------------------- * SuffFindCmds -- * See if any of the children of the target in the Src structure is * one from which the target can be transformed. If there is one, * a Src structure is put together for it and returned. * * Results: * The Src structure of the "winning" child, or NULL if no such beast. * * Side Effects: * A Src structure may be allocated. * *----------------------------------------------------------------------- */ static Src * -SuffFindCmds (Src *targ, Lst slst) +SuffFindCmds (Src *targ, Lst *slst) { - LstNode ln; /* General-purpose list node */ + LstNode *ln; /* General-purpose list node */ GNode *t, /* Target GNode */ *s; /* Source GNode */ int prefLen;/* The length of the defined prefix */ Suff *suff; /* Suffix on matching beastie */ Src *ret; /* Return value */ char *cp; t = targ->node; Lst_Open(t->children); prefLen = strlen(targ->pref); while ((ln = Lst_Next(t->children)) != NULL) { s = Lst_Datum(ln); cp = strrchr(s->name, '/'); if (cp == NULL) { cp = s->name; } else { cp++; } if (strncmp(cp, targ->pref, prefLen) == 0) { /* * The node matches the prefix ok, see if it has a known * suffix. */ ln = Lst_Find(sufflist, &cp[prefLen], SuffSuffHasNameP); if (ln != NULL) { /* * It even has a known suffix, see if there's a transformation * defined between the node's suffix and the target's suffix. * * XXX: Handle multi-stage transformations here, too. */ suff = Lst_Datum(ln); if (Lst_Member(suff->parents, targ->suff) != NULL) { /* * Hot Damn! Create a new Src structure to describe * this transformation (making sure to duplicate the * source node's name so Suff_FindDeps can free it * again (ick)), and return the new structure. */ ret = emalloc(sizeof(Src)); ret->file = estrdup(s->name); ret->pref = targ->pref; ret->suff = suff; suff->refCount++; ret->parent = targ; ret->node = s; ret->children = 0; targ->children += 1; #ifdef DEBUG_SRC ret->cp = Lst_Init(); printf("3 add %x %x\n", targ, ret); Lst_AtEnd(targ->cp, ret); #endif Lst_AtEnd(slst, ret); DEBUGF(SUFF, ("\tusing existing source %s\n", s->name)); return (ret); } } } } Lst_Close(t->children); return (NULL); } /*- *----------------------------------------------------------------------- * SuffExpandChildren -- * Expand the names of any children of a given node that contain * variable invocations or file wildcards into actual targets. * * Results: * == 0 (continue) * * Side Effects: * The expanded node is removed from the parent's list of children, * and the parent's unmade counter is decremented, but other nodes * may be added. * *----------------------------------------------------------------------- */ static int SuffExpandChildren(void *cgnp, void *pgnp) { GNode *cgn = cgnp; GNode *pgn = pgnp; GNode *gn; /* New source 8) */ - LstNode prevLN; /* Node after which new source should be put */ - LstNode ln; /* List element for old source */ + LstNode *prevLN; /* Node after which new source should be put */ + LstNode *ln; /* List element for old source */ char *cp; /* Expanded value */ /* * New nodes effectively take the place of the child, so place them * after the child */ prevLN = Lst_Member(pgn->children, cgn); /* * First do variable expansion -- this takes precedence over * wildcard expansion. If the result contains wildcards, they'll be gotten * to later since the resulting words are tacked on to the end of * the children list. */ if (strchr(cgn->name, '$') != NULL) { DEBUGF(SUFF, ("Expanding \"%s\"...", cgn->name)); cp = Var_Subst(NULL, cgn->name, pgn, TRUE); if (cp != NULL) { - Lst members = Lst_Init(); + Lst *members = Lst_Init(); if (cgn->type & OP_ARCHV) { /* * Node was an archive(member) target, so we want to call * on the Arch module to find the nodes for us, expanding * variables in the parent's context. */ char *sacrifice = cp; Arch_ParseArchive(&sacrifice, members, pgn); } else { /* * Break the result into a vector of strings whose nodes * we can find, then add those nodes to the members list. * Unfortunately, we can't use brk_string b/c it * doesn't understand about variable specifications with * spaces in them... */ char *start; char *initcp = cp; /* For freeing... */ for (start = cp; *start == ' ' || *start == '\t'; start++) continue; for (cp = start; *cp != '\0'; cp++) { if (*cp == ' ' || *cp == '\t') { /* * White-space -- terminate element, find the node, * add it, skip any further spaces. */ *cp++ = '\0'; gn = Targ_FindNode(start, TARG_CREATE); Lst_AtEnd(members, gn); while (*cp == ' ' || *cp == '\t') { cp++; } /* * Adjust cp for increment at start of loop, but * set start to first non-space. */ start = cp--; } else if (*cp == '$') { /* * Start of a variable spec -- contact variable module * to find the end so we can skip over it. */ char *junk; size_t len; Boolean doFree; junk = Var_Parse(cp, pgn, TRUE, &len, &doFree); if (junk != var_Error) { cp += len - 1; } if (doFree) { free(junk); } } else if (*cp == '\\' && *cp != '\0') { /* * Escaped something -- skip over it */ cp++; } } if (cp != start) { /* * Stuff left over -- add it to the list too */ gn = Targ_FindNode(start, TARG_CREATE); Lst_AtEnd(members, gn); } /* * Point cp back at the beginning again so the variable value * can be freed. */ cp = initcp; } /* * Add all elements of the members list to the parent node. */ while(!Lst_IsEmpty(members)) { gn = Lst_DeQueue(members); DEBUGF(SUFF, ("%s...", gn->name)); if (Lst_Member(pgn->children, gn) == NULL) { Lst_Append(pgn->children, prevLN, gn); prevLN = Lst_Succ(prevLN); Lst_AtEnd(gn->parents, pgn); pgn->unmade++; } } Lst_Destroy(members, NOFREE); /* * Free the result */ free(cp); } /* * Now the source is expanded, remove it from the list of children to * keep it from being processed. */ ln = Lst_Member(pgn->children, cgn); pgn->unmade--; Lst_Remove(pgn->children, ln); DEBUGF(SUFF, ("\n")); } else if (Dir_HasWildcards(cgn->name)) { - Lst exp; /* List of expansions */ - Lst path; /* Search path along which to expand */ + Lst *exp; /* List of expansions */ + Lst *path; /* Search path along which to expand */ /* * Find a path along which to expand the word. * * If the word has a known suffix, use that path. * If it has no known suffix and we're allowed to use the null * suffix, use its path. * Else use the default system search path. */ cp = cgn->name + strlen(cgn->name); ln = Lst_Find(sufflist, cp, SuffSuffIsSuffixP); DEBUGF(SUFF, ("Wildcard expanding \"%s\"...", cgn->name)); if (ln != NULL) { Suff *s = Lst_Datum(ln); DEBUGF(SUFF, ("suffix is \"%s\"...", s->name)); path = s->searchPath; } else { /* * Use default search path */ path = dirSearchPath; } /* * Expand the word along the chosen path */ exp = Lst_Init(); Dir_Expand(cgn->name, path, exp); while (!Lst_IsEmpty(exp)) { /* * Fetch next expansion off the list and find its GNode */ cp = Lst_DeQueue(exp); DEBUGF(SUFF, ("%s...", cp)); gn = Targ_FindNode(cp, TARG_CREATE); /* * If gn isn't already a child of the parent, make it so and * up the parent's count of unmade children. */ if (Lst_Member(pgn->children, gn) == NULL) { Lst_Append(pgn->children, prevLN, gn); prevLN = Lst_Succ(prevLN); Lst_AtEnd(gn->parents, pgn); pgn->unmade++; } } /* * Nuke what's left of the list */ Lst_Destroy(exp, NOFREE); /* * Now the source is expanded, remove it from the list of children to * keep it from being processed. */ ln = Lst_Member(pgn->children, cgn); pgn->unmade--; Lst_Remove(pgn->children, ln); DEBUGF(SUFF, ("\n")); } return (0); } /*- *----------------------------------------------------------------------- * SuffApplyTransform -- * Apply a transformation rule, given the source and target nodes * and suffixes. * * Results: * TRUE if successful, FALSE if not. * * Side Effects: * The source and target are linked and the commands from the * transformation are added to the target node's commands list. * All attributes but OP_DEPMASK and OP_TRANSFORM are applied * to the target. The target also inherits all the sources for * the transformation rule. * *----------------------------------------------------------------------- */ static Boolean SuffApplyTransform(GNode *tGn, GNode *sGn, Suff *t, Suff *s) { - LstNode ln; /* General node */ + LstNode *ln; /* General node */ char *tname; /* Name of transformation rule */ GNode *gn; /* Node for same */ if (Lst_Member(tGn->children, sGn) == NULL) { /* * Not already linked, so form the proper links between the * target and source. */ Lst_AtEnd(tGn->children, sGn); Lst_AtEnd(sGn->parents, tGn); tGn->unmade += 1; } if ((sGn->type & OP_OPMASK) == OP_DOUBLEDEP) { /* * When a :: node is used as the implied source of a node, we have * to link all its cohorts in as sources as well. Only the initial * sGn gets the target in its iParents list, however, as that * will be sufficient to get the .IMPSRC variable set for tGn */ for (ln = Lst_First(sGn->cohorts); ln != NULL; ln = Lst_Succ(ln)) { gn = Lst_Datum(ln); if (Lst_Member(tGn->children, gn) == NULL) { /* * Not already linked, so form the proper links between the * target and source. */ Lst_AtEnd(tGn->children, gn); Lst_AtEnd(gn->parents, tGn); tGn->unmade += 1; } } } /* * Locate the transformation rule itself */ tname = str_concat(s->name, t->name, 0); ln = Lst_Find(transforms, tname, SuffGNHasNameP); free(tname); if (ln == NULL) { /* * Not really such a transformation rule (can happen when we're * called to link an OP_MEMBER and OP_ARCHV node), so return * FALSE. */ return (FALSE); } gn = Lst_Datum(ln); DEBUGF(SUFF, ("\tapplying %s -> %s to \"%s\"\n", s->name, t->name, tGn->name)); /* * Record last child for expansion purposes */ ln = Lst_Last(tGn->children); /* * Pass the buck to Make_HandleUse to apply the rule */ Make_HandleUse(gn, tGn); /* * Deal with wildcards and variables in any acquired sources */ ln = Lst_Succ(ln); if (ln != NULL) { Lst_ForEachFrom(tGn->children, ln, SuffExpandChildren, tGn); } /* * Keep track of another parent to which this beast is transformed so * the .IMPSRC variable can be set correctly for the parent. */ Lst_AtEnd(sGn->iParents, tGn); return (TRUE); } /*- *----------------------------------------------------------------------- * SuffFindArchiveDeps -- * Locate dependencies for an OP_ARCHV node. * * Results: * None * * Side Effects: * Same as Suff_FindDeps * *----------------------------------------------------------------------- */ static void -SuffFindArchiveDeps(GNode *gn, Lst slst) +SuffFindArchiveDeps(GNode *gn, Lst *slst) { char *eoarch; /* End of archive portion */ char *eoname; /* End of member portion */ GNode *mem; /* Node for member */ static char *copy[] = { /* Variables to be copied from the member node */ TARGET, /* Must be first */ PREFIX, /* Must be second */ }; int i; /* Index into copy and vals */ Suff *ms; /* Suffix descriptor for member */ char *name; /* Start of member's name */ /* * The node is an archive(member) pair. so we must find a * suffix for both of them. */ eoarch = strchr(gn->name, '('); eoname = strchr(eoarch, ')'); *eoname = '\0'; /* Nuke parentheses during suffix search */ *eoarch = '\0'; /* So a suffix can be found */ name = eoarch + 1; /* * To simplify things, call Suff_FindDeps recursively on the member now, * so we can simply compare the member's .PREFIX and .TARGET variables * to locate its suffix. This allows us to figure out the suffix to * use for the archive without having to do a quadratic search over the * suffix list, backtracking for each one... */ mem = Targ_FindNode(name, TARG_CREATE); SuffFindDeps(mem, slst); /* * Create the link between the two nodes right off */ if (Lst_Member(gn->children, mem) == NULL) { Lst_AtEnd(gn->children, mem); Lst_AtEnd(mem->parents, gn); gn->unmade += 1; } /* * Copy in the variables from the member node to this one. */ for (i = (sizeof(copy) / sizeof(copy[0]))-1; i >= 0; i--) { char *p1; Var_Set(copy[i], Var_Value(copy[i], mem, &p1), gn); free(p1); } ms = mem->suffix; if (ms == NULL) { /* * Didn't know what it was -- use .NULL suffix if not in make mode */ DEBUGF(SUFF, ("using null suffix\n")); ms = suffNull; } /* * Set the other two local variables required for this target. */ Var_Set(MEMBER, name, gn); Var_Set(ARCHIVE, gn->name, gn); if (ms != NULL) { /* * Member has a known suffix, so look for a transformation rule from * it to a possible suffix of the archive. Rather than searching * through the entire list, we just look at suffixes to which the * member's suffix may be transformed... */ - LstNode ln; + LstNode *ln; /* * Use first matching suffix... */ ln = Lst_Find(ms->parents, eoarch, SuffSuffIsSuffixP); if (ln != NULL) { /* * Got one -- apply it */ if (!SuffApplyTransform(gn, mem, Lst_Datum(ln), ms)) { DEBUGF(SUFF, ("\tNo transformation from %s -> %s\n", ms->name, ((Suff *)Lst_Datum(ln))->name)); } } } /* * Replace the opening and closing parens now we've no need of the separate * pieces. */ *eoarch = '('; *eoname = ')'; /* * Pretend gn appeared to the left of a dependency operator so * the user needn't provide a transformation from the member to the * archive. */ if (OP_NOP(gn->type)) { gn->type |= OP_DEPENDS; } /* * Flag the member as such so we remember to look in the archive for * its modification time. */ mem->type |= OP_MEMBER; } /*- *----------------------------------------------------------------------- * SuffFindNormalDeps -- * Locate implicit dependencies for regular targets. * * Results: * None. * * Side Effects: * Same as Suff_FindDeps... * *----------------------------------------------------------------------- */ static void -SuffFindNormalDeps(GNode *gn, Lst slst) +SuffFindNormalDeps(GNode *gn, Lst *slst) { char *eoname; /* End of name */ char *sopref; /* Start of prefix */ - LstNode ln; /* Next suffix node to check */ - Lst srcs; /* List of sources at which to look */ - Lst targs; /* List of targets to which things can be + LstNode *ln; /* Next suffix node to check */ + Lst *srcs; /* List of sources at which to look */ + Lst *targs; /* List of targets to which things can be * transformed. They all have the same file, * but different suff and pref fields */ Src *bottom; /* Start of found transformation path */ Src *src; /* General Src pointer */ char *pref; /* Prefix to use */ Src *targ; /* General Src target pointer */ eoname = gn->name + strlen(gn->name); sopref = gn->name; /* * Begin at the beginning... */ ln = Lst_First(sufflist); srcs = Lst_Init(); targs = Lst_Init(); /* * We're caught in a catch-22 here. On the one hand, we want to use any * transformation implied by the target's sources, but we can't examine * the sources until we've expanded any variables/wildcards they may hold, * and we can't do that until we've set up the target's local variables * and we can't do that until we know what the proper suffix for the * target is (in case there are two suffixes one of which is a suffix of * the other) and we can't know that until we've found its implied * source, which we may not want to use if there's an existing source * that implies a different transformation. * * In an attempt to get around this, which may not work all the time, * but should work most of the time, we look for implied sources first, * checking transformations to all possible suffixes of the target, * use what we find to set the target's local variables, expand the * children, then look for any overriding transformations they imply. * Should we find one, we discard the one we found before. */ while (ln != NULL) { /* * Look for next possible suffix... */ ln = Lst_FindFrom(sufflist, ln, eoname, SuffSuffIsSuffixP); if (ln != NULL) { int prefLen; /* Length of the prefix */ Src *target; /* * Allocate a Src structure to which things can be transformed */ target = emalloc(sizeof(Src)); target->file = estrdup(gn->name); target->suff = Lst_Datum(ln); target->suff->refCount++; target->node = gn; target->parent = NULL; target->children = 0; #ifdef DEBUG_SRC target->cp = Lst_Init(); #endif /* * Allocate room for the prefix, whose end is found by subtracting * the length of the suffix from the end of the name. */ prefLen = (eoname - target->suff->nameLen) - sopref; target->pref = emalloc(prefLen + 1); memcpy(target->pref, sopref, prefLen); target->pref[prefLen] = '\0'; /* * Add nodes from which the target can be made */ SuffAddLevel(srcs, target); /* * Record the target so we can nuke it */ Lst_AtEnd(targs, target); /* * Search from this suffix's successor... */ ln = Lst_Succ(ln); } } /* * Handle target of unknown suffix... */ if (Lst_IsEmpty(targs) && suffNull != NULL) { DEBUGF(SUFF, ("\tNo known suffix on %s. Using .NULL suffix\n", gn->name)); targ = emalloc(sizeof(Src)); targ->file = estrdup(gn->name); targ->suff = suffNull; targ->suff->refCount++; targ->node = gn; targ->parent = NULL; targ->children = 0; targ->pref = estrdup(sopref); #ifdef DEBUG_SRC targ->cp = Lst_Init(); #endif /* * Only use the default suffix rules if we don't have commands * or dependencies defined for this gnode */ if (Lst_IsEmpty(gn->commands) && Lst_IsEmpty(gn->children)) SuffAddLevel(srcs, targ); else { DEBUGF(SUFF, ("not ")); } DEBUGF(SUFF, ("adding suffix rules\n")); Lst_AtEnd(targs, targ); } /* * Using the list of possible sources built up from the target suffix(es), * try and find an existing file/target that matches. */ bottom = SuffFindThem(srcs, slst); if (bottom == NULL) { /* * No known transformations -- use the first suffix found for setting * the local variables. */ if (!Lst_IsEmpty(targs)) { targ = Lst_Datum(Lst_First(targs)); } else { targ = NULL; } } else { /* * Work up the transformation path to find the suffix of the * target to which the transformation was made. */ for (targ = bottom; targ->parent != NULL; targ = targ->parent) continue; } /* * The .TARGET variable we always set to be the name at this point, * since it's only set to the path if the thing is only a source and * if it's only a source, it doesn't matter what we put here as far * as expanding sources is concerned, since it has none... */ Var_Set(TARGET, gn->name, gn); pref = (targ != NULL) ? targ->pref : gn->name; Var_Set(PREFIX, pref, gn); /* * Now we've got the important local variables set, expand any sources * that still contain variables or wildcards in their names. */ Lst_ForEach(gn->children, SuffExpandChildren, (void *)gn); if (targ == NULL) { DEBUGF(SUFF, ("\tNo valid suffix on %s\n", gn->name)); sfnd_abort: /* * Deal with finding the thing on the default search path if the * node is only a source (not on the lhs of a dependency operator * or [XXX] it has neither children or commands). */ if (OP_NOP(gn->type) || (Lst_IsEmpty(gn->children) && Lst_IsEmpty(gn->commands))) { gn->path = Dir_FindFile(gn->name, (targ == NULL ? dirSearchPath : targ->suff->searchPath)); if (gn->path != NULL) { char *ptr; Var_Set(TARGET, gn->path, gn); if (targ != NULL) { /* * Suffix known for the thing -- trim the suffix off * the path to form the proper .PREFIX variable. */ int savep = strlen(gn->path) - targ->suff->nameLen; char savec; if (gn->suffix) gn->suffix->refCount--; gn->suffix = targ->suff; gn->suffix->refCount++; savec = gn->path[savep]; gn->path[savep] = '\0'; if ((ptr = strrchr(gn->path, '/')) != NULL) ptr++; else ptr = gn->path; Var_Set(PREFIX, ptr, gn); gn->path[savep] = savec; } else { /* * The .PREFIX gets the full path if the target has * no known suffix. */ if (gn->suffix) gn->suffix->refCount--; gn->suffix = NULL; if ((ptr = strrchr(gn->path, '/')) != NULL) ptr++; else ptr = gn->path; Var_Set(PREFIX, ptr, gn); } } } else { /* * Not appropriate to search for the thing -- set the * path to be the name so Dir_MTime won't go grovelling for * it. */ if (gn->suffix) gn->suffix->refCount--; gn->suffix = (targ == NULL) ? NULL : targ->suff; if (gn->suffix) gn->suffix->refCount++; free(gn->path); gn->path = estrdup(gn->name); } goto sfnd_return; } /* * If the suffix indicates that the target is a library, mark that in * the node's type field. */ if (targ->suff->flags & SUFF_LIBRARY) { gn->type |= OP_LIB; } /* * Check for overriding transformation rule implied by sources */ if (!Lst_IsEmpty(gn->children)) { src = SuffFindCmds(targ, slst); if (src != NULL) { /* * Free up all the Src structures in the transformation path * up to, but not including, the parent node. */ while (bottom && bottom->parent != NULL) { if (Lst_Member(slst, bottom) == NULL) { Lst_AtEnd(slst, bottom); } bottom = bottom->parent; } bottom = src; } } if (bottom == NULL) { /* * No idea from where it can come -- return now. */ goto sfnd_abort; } /* * We now have a list of Src structures headed by 'bottom' and linked via * their 'parent' pointers. What we do next is create links between * source and target nodes (which may or may not have been created) * and set the necessary local variables in each target. The * commands for each target are set from the commands of the * transformation rule used to get from the src suffix to the targ * suffix. Note that this causes the commands list of the original * node, gn, to be replaced by the commands of the final * transformation rule. Also, the unmade field of gn is incremented. * Etc. */ if (bottom->node == NULL) { bottom->node = Targ_FindNode(bottom->file, TARG_CREATE); } for (src = bottom; src->parent != NULL; src = src->parent) { targ = src->parent; if (src->node->suffix) src->node->suffix->refCount--; src->node->suffix = src->suff; src->node->suffix->refCount++; if (targ->node == NULL) { targ->node = Targ_FindNode(targ->file, TARG_CREATE); } SuffApplyTransform(targ->node, src->node, targ->suff, src->suff); if (targ->node != gn) { /* * Finish off the dependency-search process for any nodes * between bottom and gn (no point in questing around the * filesystem for their implicit source when it's already * known). Note that the node can't have any sources that * need expanding, since SuffFindThem will stop on an existing * node, so all we need to do is set the standard and System V * variables. */ targ->node->type |= OP_DEPS_FOUND; Var_Set(PREFIX, targ->pref, targ->node); Var_Set(TARGET, targ->node->name, targ->node); } } if (gn->suffix) gn->suffix->refCount--; gn->suffix = src->suff; gn->suffix->refCount++; /* * So Dir_MTime doesn't go questing for it... */ free(gn->path); gn->path = estrdup(gn->name); /* * Nuke the transformation path and the Src structures left over in the * two lists. */ sfnd_return: if (bottom) if (Lst_Member(slst, bottom) == NULL) Lst_AtEnd(slst, bottom); while (SuffRemoveSrc(srcs) || SuffRemoveSrc(targs)) continue; Lst_Concat(slst, srcs, LST_CONCLINK); Lst_Concat(slst, targs, LST_CONCLINK); } /*- *----------------------------------------------------------------------- * Suff_FindDeps -- * Find implicit sources for the target described by the graph node * gn * * Results: * Nothing. * * Side Effects: * Nodes are added to the graph below the passed-in node. The nodes * are marked to have their IMPSRC variable filled in. The * PREFIX variable is set for the given node and all its * implied children. * * Notes: * The path found by this target is the shortest path in the * transformation graph, which may pass through non-existent targets, * to an existing target. The search continues on all paths from the * root suffix until a file is found. I.e. if there's a path * .o -> .c -> .l -> .l,v from the root and the .l,v file exists but * the .c and .l files don't, the search will branch out in * all directions from .o and again from all the nodes on the * next level until the .l,v node is encountered. * *----------------------------------------------------------------------- */ void Suff_FindDeps(GNode *gn) { SuffFindDeps(gn, srclist); while (SuffRemoveSrc(srclist)) continue; } static void -SuffFindDeps(GNode *gn, Lst slst) +SuffFindDeps(GNode *gn, Lst *slst) { if (gn->type & OP_DEPS_FOUND) { /* * If dependencies already found, no need to do it again... */ return; } else { gn->type |= OP_DEPS_FOUND; } DEBUGF(SUFF, ("SuffFindDeps (%s)\n", gn->name)); if (gn->type & OP_ARCHV) { SuffFindArchiveDeps(gn, slst); } else if (gn->type & OP_LIB) { /* * If the node is a library, it is the arch module's job to find it * and set the TARGET variable accordingly. We merely provide the * search path, assuming all libraries end in ".a" (if the suffix * hasn't been defined, there's nothing we can do for it, so we just * set the TARGET variable to the node's name in order to give it a * value). */ - LstNode ln; + LstNode *ln; Suff *s; ln = Lst_Find(sufflist, LIBSUFF, SuffSuffHasNameP); if (gn->suffix) gn->suffix->refCount--; if (ln != NULL) { gn->suffix = s = Lst_Datum (ln); gn->suffix->refCount++; Arch_FindLib(gn, s->searchPath); } else { gn->suffix = NULL; Var_Set(TARGET, gn->name, gn); } /* * Because a library (-lfoo) target doesn't follow the standard * filesystem conventions, we don't set the regular variables for * the thing. .PREFIX is simply made empty... */ Var_Set(PREFIX, "", gn); } else { SuffFindNormalDeps(gn, slst); } } /*- *----------------------------------------------------------------------- * Suff_SetNull -- * Define which suffix is the null suffix. * * Results: * None. * * Side Effects: * 'suffNull' is altered. * * Notes: * Need to handle the changing of the null suffix gracefully so the * old transformation rules don't just go away. * *----------------------------------------------------------------------- */ void Suff_SetNull(char *name) { Suff *s; - LstNode ln; + LstNode *ln; ln = Lst_Find(sufflist, name, SuffSuffHasNameP); if (ln != NULL) { s = Lst_Datum(ln); if (suffNull != NULL) { suffNull->flags &= ~SUFF_NULL; } s->flags |= SUFF_NULL; /* * XXX: Here's where the transformation mangling would take place */ suffNull = s; } else { Parse_Error(PARSE_WARNING, "Desired null suffix %s not defined.", name); } } /*- *----------------------------------------------------------------------- * Suff_Init -- * Initialize suffixes module * * Results: * None * * Side Effects: * Many *----------------------------------------------------------------------- */ void Suff_Init(void) { sufflist = Lst_Init(); suffClean = Lst_Init(); srclist = Lst_Init(); transforms = Lst_Init(); sNum = 0; /* * Create null suffix for single-suffix rules (POSIX). The thing doesn't * actually go on the suffix list or everyone will think that's its * suffix. */ emptySuff = suffNull = emalloc(sizeof(Suff)); suffNull->name = estrdup(""); suffNull->nameLen = 0; suffNull->searchPath = Lst_Init(); Dir_Concat(suffNull->searchPath, dirSearchPath); suffNull->children = Lst_Init(); suffNull->parents = Lst_Init(); suffNull->ref = Lst_Init(); suffNull->sNum = sNum++; suffNull->flags = SUFF_NULL; suffNull->refCount = 1; } /*- *---------------------------------------------------------------------- * Suff_End -- * Cleanup the this module * * Results: * None * * Side Effects: * The memory is free'd. *---------------------------------------------------------------------- */ void Suff_End(void) { Lst_Destroy(sufflist, SuffFree); Lst_Destroy(suffClean, SuffFree); if (suffNull) SuffFree(suffNull); Lst_Destroy(srclist, NOFREE); Lst_Destroy(transforms, NOFREE); } /********************* DEBUGGING FUNCTIONS **********************/ static int SuffPrintName(void *s, void *dummy __unused) { printf("`%s' ", ((Suff *)s)->name); return (0); } static int SuffPrintSuff(void *sp, void *dummy __unused) { Suff *s = sp; int flags; int flag; printf("# `%s' [%d] ", s->name, s->refCount); flags = s->flags; if (flags) { fputs(" (", stdout); while (flags) { flag = 1 << (ffs(flags) - 1); flags &= ~flag; switch (flag) { case SUFF_NULL: printf("NULL"); break; case SUFF_INCLUDE: printf("INCLUDE"); break; case SUFF_LIBRARY: printf("LIBRARY"); break; default: break; } fputc(flags ? '|' : ')', stdout); } } fputc('\n', stdout); printf("#\tTo: "); Lst_ForEach(s->parents, SuffPrintName, (void *)NULL); fputc('\n', stdout); printf("#\tFrom: "); Lst_ForEach(s->children, SuffPrintName, (void *)NULL); fputc('\n', stdout); printf("#\tSearch Path: "); Dir_PrintPath(s->searchPath); fputc('\n', stdout); return (0); } static int SuffPrintTrans(void *tp, void *dummy __unused) { GNode *t = tp; printf("%-16s: ", t->name); Targ_PrintType(t->type); fputc('\n', stdout); Lst_ForEach(t->commands, Targ_PrintCmd, (void *)NULL); fputc('\n', stdout); return (0); } void Suff_PrintAll(void) { printf("#*** Suffixes:\n"); Lst_ForEach(sufflist, SuffPrintSuff, (void *)NULL); printf("#*** Transformations:\n"); Lst_ForEach(transforms, SuffPrintTrans, (void *)NULL); } Index: head/usr.bin/make/targ.c =================================================================== --- head/usr.bin/make/targ.c (revision 138511) +++ head/usr.bin/make/targ.c (revision 138512) @@ -1,635 +1,635 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)targ.c 8.2 (Berkeley) 3/19/94 */ #include __FBSDID("$FreeBSD$"); /*- * targ.c -- * Functions for maintaining the Lst allTargets. Target nodes are * kept in two structures: a Lst, maintained by the list library, and a * hash table, maintained by the hash library. * * Interface: * Targ_Init Initialization procedure. * * Targ_End Cleanup the module * * Targ_NewGN Create a new GNode for the passed target * (string). The node is *not* placed in the * hash table, though all its fields are * initialized. * * Targ_FindNode Find the node for a given target, creating * and storing it if it doesn't exist and the * flags are right (TARG_CREATE) * * Targ_FindList Given a list of names, find nodes for all * of them. If a name doesn't exist and the * TARG_NOCREATE flag was given, an error message * is printed. Else, if a name doesn't exist, * its node is created. * * Targ_Ignore Return TRUE if errors should be ignored when * creating the given target. * * Targ_Silent Return TRUE if we should be silent when * creating the given target. * * Targ_Precious Return TRUE if the target is precious and * should not be removed if we are interrupted. * * Debugging: * Targ_PrintGraph Print out the entire graphm all variables * and statistics for the directory cache. Should * print something for suffixes, too, but... */ #include #include #include "make.h" #include "hash.h" #include "dir.h" -static Lst allTargets; /* the list of all targets found so far */ -static Lst allGNs; /* List of all the GNodes */ +static Lst *allTargets; /* the list of all targets found so far */ +static Lst *allGNs; /* List of all the GNodes */ static Hash_Table targets; /* a hash table of same */ #define HTSIZE 191 /* initial size of hash table */ static int TargPrintOnlySrc(void *, void *); static int TargPrintName(void *, void *); static int TargPrintNode(void *, void *); static void TargFreeGN(void *); /*- *----------------------------------------------------------------------- * Targ_Init -- * Initialize this module * * Results: * None * * Side Effects: * The allTargets list and the targets hash table are initialized *----------------------------------------------------------------------- */ void Targ_Init(void) { allTargets = Lst_Init(); Hash_InitTable(&targets, HTSIZE); } /*- *----------------------------------------------------------------------- * Targ_End -- * Finalize this module * * Results: * None * * Side Effects: * All lists and gnodes are cleared *----------------------------------------------------------------------- */ void Targ_End(void) { Lst_Destroy(allTargets, NOFREE); if (allGNs) Lst_Destroy(allGNs, TargFreeGN); Hash_DeleteTable(&targets); } /*- *----------------------------------------------------------------------- * Targ_NewGN -- * Create and initialize a new graph node * * Results: * An initialized graph node with the name field filled with a copy * of the passed name * * Side Effects: * The gnode is added to the list of all gnodes. *----------------------------------------------------------------------- */ GNode * Targ_NewGN(char *name) { GNode *gn; gn = emalloc(sizeof(GNode)); gn->name = estrdup(name); gn->path = NULL; if (name[0] == '-' && name[1] == 'l') { gn->type = OP_LIB; } else { gn->type = 0; } gn->unmade = 0; gn->make = FALSE; gn->made = UNMADE; gn->childMade = FALSE; gn->order = 0; gn->mtime = gn->cmtime = 0; gn->iParents = Lst_Init(); gn->cohorts = Lst_Init(); gn->parents = Lst_Init(); gn->children = Lst_Init(); gn->successors = Lst_Init(); gn->preds = Lst_Init(); gn->context = Lst_Init(); gn->commands = Lst_Init(); gn->suffix = NULL; if (allGNs == NULL) allGNs = Lst_Init(); Lst_AtEnd(allGNs, (void *)gn); return (gn); } /*- *----------------------------------------------------------------------- * TargFreeGN -- * Destroy a GNode * * Results: * None. * * Side Effects: * None. *----------------------------------------------------------------------- */ static void TargFreeGN(void *gnp) { GNode *gn = gnp; free(gn->name); free(gn->path); Lst_Destroy(gn->iParents, NOFREE); Lst_Destroy(gn->cohorts, NOFREE); Lst_Destroy(gn->parents, NOFREE); Lst_Destroy(gn->children, NOFREE); Lst_Destroy(gn->successors, NOFREE); Lst_Destroy(gn->preds, NOFREE); Lst_Destroy(gn->context, NOFREE); Lst_Destroy(gn->commands, NOFREE); free(gn); } /*- *----------------------------------------------------------------------- * Targ_FindNode -- * Find a node in the list using the given name for matching * * Results: * The node in the list if it was. If it wasn't, return NULL of * flags was TARG_NOCREATE or the newly created and initialized node * if it was TARG_CREATE * * Side Effects: * Sometimes a node is created and added to the list *----------------------------------------------------------------------- */ GNode * Targ_FindNode(char *name, int flags) { GNode *gn; /* node in that element */ Hash_Entry *he; /* New or used hash entry for node */ Boolean isNew; /* Set TRUE if Hash_CreateEntry had to create */ /* an entry for the node */ if (flags & TARG_CREATE) { he = Hash_CreateEntry(&targets, name, &isNew); if (isNew) { gn = Targ_NewGN(name); Hash_SetValue (he, gn); Lst_AtEnd(allTargets, gn); } } else { he = Hash_FindEntry(&targets, name); } if (he == NULL) { return (NULL); } else { return (Hash_GetValue(he)); } } /*- *----------------------------------------------------------------------- * Targ_FindList -- * Make a complete list of GNodes from the given list of names * * Results: * A complete list of graph nodes corresponding to all instances of all * the names in names. * * Side Effects: * If flags is TARG_CREATE, nodes will be created for all names in * names which do not yet have graph nodes. If flags is TARG_NOCREATE, * an error message will be printed for each name which can't be found. * ----------------------------------------------------------------------- */ -Lst -Targ_FindList(Lst names, int flags) +Lst * +Targ_FindList(Lst *names, int flags) { - Lst nodes; /* result list */ - LstNode ln; /* name list element */ + Lst *nodes; /* result list */ + LstNode *ln; /* name list element */ GNode *gn; /* node in tLn */ char *name; nodes = Lst_Init(); if (Lst_Open(names) == FAILURE) { return (nodes); } while ((ln = Lst_Next(names)) != NULL) { name = Lst_Datum(ln); gn = Targ_FindNode(name, flags); if (gn != NULL) { /* * Note: Lst_AtEnd must come before the Lst_Concat so the nodes * are added to the list in the order in which they were * encountered in the makefile. */ Lst_AtEnd(nodes, gn); if (gn->type & OP_DOUBLEDEP) { Lst_Concat(nodes, gn->cohorts, LST_CONCNEW); } } else if (flags == TARG_NOCREATE) { Error("\"%s\" -- target unknown.", name); } } Lst_Close(names); return (nodes); } /*- *----------------------------------------------------------------------- * Targ_Ignore -- * Return true if should ignore errors when creating gn * * Results: * TRUE if should ignore errors * * Side Effects: * None *----------------------------------------------------------------------- */ Boolean Targ_Ignore(GNode *gn) { if (ignoreErrors || (gn->type & OP_IGNORE)) { return (TRUE); } else { return (FALSE); } } /*- *----------------------------------------------------------------------- * Targ_Silent -- * Return true if be silent when creating gn * * Results: * TRUE if should be silent * * Side Effects: * None *----------------------------------------------------------------------- */ Boolean Targ_Silent(GNode *gn) { if (beSilent || (gn->type & OP_SILENT)) { return (TRUE); } else { return (FALSE); } } /*- *----------------------------------------------------------------------- * Targ_Precious -- * See if the given target is precious * * Results: * TRUE if it is precious. FALSE otherwise * * Side Effects: * None *----------------------------------------------------------------------- */ Boolean Targ_Precious(GNode *gn) { if (allPrecious || (gn->type & (OP_PRECIOUS | OP_DOUBLEDEP))) { return (TRUE); } else { return (FALSE); } } /******************* DEBUG INFO PRINTING ****************/ static GNode *mainTarg; /* the main target, as set by Targ_SetMain */ /*- *----------------------------------------------------------------------- * Targ_SetMain -- * Set our idea of the main target we'll be creating. Used for * debugging output. * * Results: * None. * * Side Effects: * "mainTarg" is set to the main target's node. *----------------------------------------------------------------------- */ void Targ_SetMain(GNode *gn) { mainTarg = gn; } static int TargPrintName(void *gnp, void *ppath) { GNode *gn = (GNode *) gnp; printf("%s ", gn->name); #ifdef notdef if (ppath) { if (gn->path) { printf("[%s] ", gn->path); } if (gn == mainTarg) { printf("(MAIN NAME) "); } } #endif /* notdef */ return (ppath ? 0 : 0); } int Targ_PrintCmd(void *cmd, void *dummy __unused) { printf("\t%s\n", (char *)cmd); return (0); } /*- *----------------------------------------------------------------------- * Targ_FmtTime -- * Format a modification time in some reasonable way and return it. * * Results: * The time reformatted. * * Side Effects: * The time is placed in a static area, so it is overwritten * with each call. * *----------------------------------------------------------------------- */ char * Targ_FmtTime(time_t modtime) { struct tm *parts; static char buf[128]; parts = localtime(&modtime); strftime(buf, sizeof buf, "%H:%M:%S %b %d, %Y", parts); buf[sizeof(buf) - 1] = '\0'; return (buf); } /*- *----------------------------------------------------------------------- * Targ_PrintType -- * Print out a type field giving only those attributes the user can * set. * * Results: * * Side Effects: * *----------------------------------------------------------------------- */ void Targ_PrintType(int type) { int tbit; #define PRINTBIT(attr) case CONCAT(OP_,attr): printf("." #attr " "); break #define PRINTDBIT(attr) case CONCAT(OP_,attr): DEBUGF(TARG, ("." #attr " ")); break type &= ~OP_OPMASK; while (type) { tbit = 1 << (ffs(type) - 1); type &= ~tbit; switch(tbit) { PRINTBIT(OPTIONAL); PRINTBIT(USE); PRINTBIT(EXEC); PRINTBIT(IGNORE); PRINTBIT(PRECIOUS); PRINTBIT(SILENT); PRINTBIT(MAKE); PRINTBIT(JOIN); PRINTBIT(INVISIBLE); PRINTBIT(NOTMAIN); PRINTDBIT(LIB); /*XXX: MEMBER is defined, so CONCAT(OP_,MEMBER) gives OP_"%" */ case OP_MEMBER: DEBUGF(TARG, (".MEMBER ")); break; PRINTDBIT(ARCHV); } } } /*- *----------------------------------------------------------------------- * TargPrintNode -- * print the contents of a node *----------------------------------------------------------------------- */ static int TargPrintNode(void *gnp, void *passp) { GNode *gn = gnp; int pass = *(int *)passp; if (!OP_NOP(gn->type)) { printf("#\n"); if (gn == mainTarg) { printf("# *** MAIN TARGET ***\n"); } if (pass == 2) { if (gn->unmade) { printf("# %d unmade children\n", gn->unmade); } else { printf("# No unmade children\n"); } if (!(gn->type & (OP_JOIN | OP_USE | OP_EXEC))) { if (gn->mtime != 0) { printf("# last modified %s: %s\n", Targ_FmtTime(gn->mtime), (gn->made == UNMADE ? "unmade" : (gn->made == MADE ? "made" : (gn->made == UPTODATE ? "up-to-date" : "error when made")))); } else if (gn->made != UNMADE) { printf("# non-existent (maybe): %s\n", (gn->made == MADE ? "made" : (gn->made == UPTODATE ? "up-to-date" : (gn->made == ERROR ? "error when made" : "aborted")))); } else { printf("# unmade\n"); } } if (!Lst_IsEmpty (gn->iParents)) { printf("# implicit parents: "); Lst_ForEach(gn->iParents, TargPrintName, (void *)NULL); fputc('\n', stdout); } } if (!Lst_IsEmpty (gn->parents)) { printf("# parents: "); Lst_ForEach(gn->parents, TargPrintName, (void *)NULL); fputc('\n', stdout); } printf("%-16s", gn->name); switch (gn->type & OP_OPMASK) { case OP_DEPENDS: printf(": "); break; case OP_FORCE: printf("! "); break; case OP_DOUBLEDEP: printf(":: "); break; default: break; } Targ_PrintType(gn->type); Lst_ForEach(gn->children, TargPrintName, (void *)NULL); fputc('\n', stdout); Lst_ForEach(gn->commands, Targ_PrintCmd, (void *)NULL); printf("\n\n"); if (gn->type & OP_DOUBLEDEP) { Lst_ForEach(gn->cohorts, TargPrintNode, &pass); } } return (0); } /*- *----------------------------------------------------------------------- * TargPrintOnlySrc -- * Print only those targets that are just a source. * * Results: * 0. * * Side Effects: * The name of each file is printed preceded by #\t * *----------------------------------------------------------------------- */ static int TargPrintOnlySrc(void *gnp, void *dummy __unused) { GNode *gn = gnp; if (OP_NOP(gn->type)) printf("#\t%s [%s]\n", gn->name, gn->path ? gn->path : gn->name); return (0); } /*- *----------------------------------------------------------------------- * Targ_PrintGraph -- * Print the entire graph. * * Results: * none * * Side Effects: * lots o' output *----------------------------------------------------------------------- */ void Targ_PrintGraph(int pass) { printf("#*** Input graph:\n"); Lst_ForEach(allTargets, TargPrintNode, &pass); printf("\n\n"); printf("#\n# Files that are only sources:\n"); Lst_ForEach(allTargets, TargPrintOnlySrc, (void *)NULL); printf("#*** Global Variables:\n"); Var_Dump(VAR_GLOBAL); printf("#*** Command-line Variables:\n"); Var_Dump(VAR_CMD); printf("\n"); Dir_PrintDirectories(); printf("\n"); Suff_PrintAll(); } Index: head/usr.bin/make/var.c =================================================================== --- head/usr.bin/make/var.c (revision 138511) +++ head/usr.bin/make/var.c (revision 138512) @@ -1,1965 +1,1965 @@ /* * Copyright (c) 1988, 1989, 1990, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 1989 by Berkeley Softworks * All rights reserved. * * This code is derived from software contributed to Berkeley by * Adam de Boor. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)var.c 8.3 (Berkeley) 3/19/94 */ #include __FBSDID("$FreeBSD$"); /*- * var.c -- * Variable-handling functions * * Interface: * Var_Set Set the value of a variable in the given * context. The variable is created if it doesn't * yet exist. The value and variable name need not * be preserved. * * Var_Append Append more characters to an existing variable * in the given context. The variable needn't * exist already -- it will be created if it doesn't. * A space is placed between the old value and the * new one. * * Var_Exists See if a variable exists. * * Var_Value Return the value of a variable in a context or * NULL if the variable is undefined. * * Var_Subst Substitute named variable, or all variables if * NULL in a string using * the given context as the top-most one. If the * third argument is non-zero, Parse_Error is * called if any variables are undefined. * * Var_Parse Parse a variable expansion from a string and * return the result and the number of characters * consumed. * * Var_Delete Delete a variable in a context. * * Var_Init Initialize this module. * * Debugging: * Var_Dump Print out all variables defined in the given * context. * * XXX: There's a lot of duplication in these functions. */ #include #include #include #include #include "make.h" #include "buf.h" #include "var.h" /* * This is a harmless return value for Var_Parse that can be used by Var_Subst * to determine if there was an error in parsing -- easier than returning * a flag, as things outside this module don't give a hoot. */ char var_Error[] = ""; /* * Similar to var_Error, but returned when the 'err' flag for Var_Parse is * set false. Why not just use a constant? Well, gcc likes to condense * identical string instances... */ static char varNoError[] = ""; /* * Internally, variables are contained in four different contexts. * 1) the environment. They may not be changed. If an environment * variable is appended-to, the result is placed in the global * context. * 2) the global context. Variables set in the Makefile are located in * the global context. It is the penultimate context searched when * substituting. * 3) the command-line context. All variables set on the command line * are placed in this context. They are UNALTERABLE once placed here. * 4) the local context. Each target has associated with it a context * list. On this list are located the structures describing such * local variables as $(@) and $(*) * The four contexts are searched in the reverse order from which they are * listed. */ GNode *VAR_GLOBAL; /* variables from the makefile */ GNode *VAR_CMD; /* variables defined on the command-line */ -static Lst allVars; /* List of all variables */ +static Lst *allVars; /* List of all variables */ #define FIND_CMD 0x1 /* look in VAR_CMD when searching */ #define FIND_GLOBAL 0x2 /* look in VAR_GLOBAL as well */ #define FIND_ENV 0x4 /* look in the environment also */ static int VarCmp(void *, void *); static void VarPossiblyExpand(char **, GNode *); static Var *VarFind(char *, GNode *, int); static void VarAdd(char *, char *, GNode *); static void VarDelete(void *); static char *VarGetPattern(GNode *, int, char **, int, int *, size_t *, VarPattern *); static char *VarModify(char *, Boolean (*)(const char *, Boolean, Buffer, void *), void *); static int VarPrintVar(void *, void *); /*- *----------------------------------------------------------------------- * VarCmp -- * See if the given variable matches the named one. Called from * Lst_Find when searching for a variable of a given name. * * Results: * 0 if they match. non-zero otherwise. * * Side Effects: * none *----------------------------------------------------------------------- */ static int VarCmp(void *v, void *name) { return (strcmp(name, ((Var *)v)->name)); } /*- *----------------------------------------------------------------------- * VarPossiblyExpand -- * Expand a variable name's embedded variables in the given context. * * Results: * The contents of name, possibly expanded. * * Side Effects: * The caller must free the new contents or old contents of name. *----------------------------------------------------------------------- */ static void VarPossiblyExpand(char **name, GNode *ctxt) { if (strchr(*name, '$') != NULL) *name = Var_Subst(NULL, *name, ctxt, 0); else *name = estrdup(*name); } /*- *----------------------------------------------------------------------- * VarFind -- * Find the given variable in the given context and any other contexts * indicated. * * Flags: * FIND_GLOBAL set means look in the VAR_GLOBAL context too * FIND_CMD set means to look in the VAR_CMD context too * FIND_ENV set means to look in the environment * * Results: * A pointer to the structure describing the desired variable or * NULL if the variable does not exist. * * Side Effects: * None *----------------------------------------------------------------------- */ static Var * VarFind(char *name, GNode *ctxt, int flags) { Boolean localCheckEnvFirst; - LstNode var; + LstNode *var; Var *v; /* * If the variable name begins with a '.', it could very well be one of * the local ones. We check the name against all the local variables * and substitute the short version in for 'name' if it matches one of * them. */ if (*name == '.' && isupper((unsigned char)name[1])) switch (name[1]) { case 'A': if (!strcmp(name, ".ALLSRC")) name = ALLSRC; if (!strcmp(name, ".ARCHIVE")) name = ARCHIVE; break; case 'I': if (!strcmp(name, ".IMPSRC")) name = IMPSRC; break; case 'M': if (!strcmp(name, ".MEMBER")) name = MEMBER; break; case 'O': if (!strcmp(name, ".OODATE")) name = OODATE; break; case 'P': if (!strcmp(name, ".PREFIX")) name = PREFIX; break; case 'T': if (!strcmp(name, ".TARGET")) name = TARGET; break; } /* * Note whether this is one of the specific variables we were told through * the -E flag to use environment-variable-override for. */ if (Lst_Find(envFirstVars, name, (CompareProc *)strcmp) != NULL) { localCheckEnvFirst = TRUE; } else { localCheckEnvFirst = FALSE; } /* * First look for the variable in the given context. If it's not there, * look for it in VAR_CMD, VAR_GLOBAL and the environment, in that order, * depending on the FIND_* flags in 'flags' */ var = Lst_Find(ctxt->context, name, VarCmp); if ((var == NULL) && (flags & FIND_CMD) && (ctxt != VAR_CMD)) { var = Lst_Find(VAR_CMD->context, name, VarCmp); } if ((var == NULL) && (flags & FIND_GLOBAL) && (ctxt != VAR_GLOBAL) && !checkEnvFirst && !localCheckEnvFirst) { var = Lst_Find(VAR_GLOBAL->context, name, VarCmp); } if ((var == NULL) && (flags & FIND_ENV)) { char *env; if ((env = getenv(name)) != NULL) { int len; v = emalloc(sizeof(Var)); v->name = estrdup(name); len = strlen(env); v->val = Buf_Init(len); Buf_AddBytes(v->val, len, (Byte *)env); v->flags = VAR_FROM_ENV; return (v); } else if ((checkEnvFirst || localCheckEnvFirst) && (flags & FIND_GLOBAL) && (ctxt != VAR_GLOBAL)) { var = Lst_Find(VAR_GLOBAL->context, name, VarCmp); if (var == NULL) { return (NULL); } else { return (Lst_Datum(var)); } } else { return (NULL); } } else if (var == NULL) { return (NULL); } else { return (Lst_Datum(var)); } } /*- *----------------------------------------------------------------------- * VarAdd -- * Add a new variable of name name and value val to the given context. * * Results: * None * * Side Effects: * The new variable is placed at the front of the given context * The name and val arguments are duplicated so they may * safely be freed. *----------------------------------------------------------------------- */ static void VarAdd(char *name, char *val, GNode *ctxt) { Var *v; int len; v = emalloc(sizeof(Var)); v->name = estrdup(name); len = val ? strlen(val) : 0; v->val = Buf_Init(len+1); Buf_AddBytes(v->val, len, (Byte *)val); v->flags = 0; Lst_AtFront(ctxt->context, v); Lst_AtEnd(allVars, v); DEBUGF(VAR, ("%s:%s = %s\n", ctxt->name, name, val)); } /*- *----------------------------------------------------------------------- * VarDelete -- * Delete a variable and all the space associated with it. * * Results: * None * * Side Effects: * None *----------------------------------------------------------------------- */ static void VarDelete(void *vp) { Var *v = vp; free(v->name); Buf_Destroy(v->val, TRUE); free(v); } /*- *----------------------------------------------------------------------- * Var_Delete -- * Remove a variable from a context. * * Results: * None. * * Side Effects: * The Var structure is removed and freed. * *----------------------------------------------------------------------- */ void Var_Delete(char *name, GNode *ctxt) { - LstNode ln; + LstNode *ln; DEBUGF(VAR, ("%s:delete %s\n", ctxt->name, name)); ln = Lst_Find(ctxt->context, name, VarCmp); if (ln != NULL) { Var *v; v = Lst_Datum(ln); Lst_Remove(ctxt->context, ln); ln = Lst_Member(allVars, v); Lst_Remove(allVars, ln); VarDelete(v); } } /*- *----------------------------------------------------------------------- * Var_Set -- * Set the variable name to the value val in the given context. * * Results: * None. * * Side Effects: * If the variable doesn't yet exist, a new record is created for it. * Else the old value is freed and the new one stuck in its place * * Notes: * The variable is searched for only in its context before being * created in that context. I.e. if the context is VAR_GLOBAL, * only VAR_GLOBAL->context is searched. Likewise if it is VAR_CMD, only * VAR_CMD->context is searched. This is done to avoid the literally * thousands of unnecessary strcmp's that used to be done to * set, say, $(@) or $(<). *----------------------------------------------------------------------- */ void Var_Set(char *name, char *val, GNode *ctxt) { Var *v; /* * We only look for a variable in the given context since anything set * here will override anything in a lower context, so there's not much * point in searching them all just to save a bit of memory... */ VarPossiblyExpand(&name, ctxt); v = VarFind(name, ctxt, 0); if (v == NULL) { VarAdd(name, val, ctxt); } else { Buf_Discard(v->val, Buf_Size(v->val)); Buf_AddBytes(v->val, strlen(val), (Byte *)val); DEBUGF(VAR, ("%s:%s = %s\n", ctxt->name, name, val)); } /* * Any variables given on the command line are automatically exported * to the environment (as per POSIX standard) */ if (ctxt == VAR_CMD) { setenv(name, val, 1); } free(name); } /*- *----------------------------------------------------------------------- * Var_Append -- * The variable of the given name has the given value appended to it in * the given context. * * Results: * None * * Side Effects: * If the variable doesn't exist, it is created. Else the strings * are concatenated (with a space in between). * * Notes: * Only if the variable is being sought in the global context is the * environment searched. * XXX: Knows its calling circumstances in that if called with ctxt * an actual target, it will only search that context since only * a local variable could be being appended to. This is actually * a big win and must be tolerated. *----------------------------------------------------------------------- */ void Var_Append(char *name, char *val, GNode *ctxt) { Var *v; VarPossiblyExpand(&name, ctxt); v = VarFind(name, ctxt, (ctxt == VAR_GLOBAL) ? FIND_ENV : 0); if (v == NULL) { VarAdd(name, val, ctxt); } else { Buf_AddByte(v->val, (Byte)' '); Buf_AddBytes(v->val, strlen(val), (Byte *)val); DEBUGF(VAR, ("%s:%s = %s\n", ctxt->name, name, (char *)Buf_GetAll(v->val, (size_t *)NULL))); if (v->flags & VAR_FROM_ENV) { /* * If the original variable came from the environment, we * have to install it in the global context (we could place * it in the environment, but then we should provide a way to * export other variables...) */ v->flags &= ~VAR_FROM_ENV; Lst_AtFront(ctxt->context, v); } } free(name); } /*- *----------------------------------------------------------------------- * Var_Exists -- * See if the given variable exists. * * Results: * TRUE if it does, FALSE if it doesn't * * Side Effects: * None. * *----------------------------------------------------------------------- */ Boolean Var_Exists(char *name, GNode *ctxt) { Var *v; VarPossiblyExpand(&name, ctxt); v = VarFind(name, ctxt, FIND_CMD|FIND_GLOBAL|FIND_ENV); free(name); if (v == NULL) { return (FALSE); } else if (v->flags & VAR_FROM_ENV) { free(v->name); Buf_Destroy(v->val, TRUE); free(v); } return (TRUE); } /*- *----------------------------------------------------------------------- * Var_Value -- * Return the value of the named variable in the given context * * Results: * The value if the variable exists, NULL if it doesn't * * Side Effects: * None *----------------------------------------------------------------------- */ char * Var_Value(char *name, GNode *ctxt, char **frp) { Var *v; VarPossiblyExpand(&name, ctxt); v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); free(name); *frp = NULL; if (v != NULL) { char *p = (char *)Buf_GetAll(v->val, (size_t *)NULL); if (v->flags & VAR_FROM_ENV) { Buf_Destroy(v->val, FALSE); free(v->name); free(v); *frp = p; } return (p); } else { return (NULL); } } /*- *----------------------------------------------------------------------- * VarModify -- * Modify each of the words of the passed string using the given * function. Used to implement all modifiers. * * Results: * A string of all the words modified appropriately. * * Side Effects: * None. * *----------------------------------------------------------------------- */ static char * VarModify(char *str, Boolean (*modProc)(const char *, Boolean, Buffer, void *), void *datum) { Buffer buf; /* Buffer for the new string */ Boolean addSpace; /* TRUE if need to add a space to the * buffer before adding the trimmed * word */ char **av; /* word list [first word does not count] */ int ac, i; buf = Buf_Init(0); addSpace = FALSE; av = brk_string(str, &ac, FALSE); for (i = 1; i < ac; i++) addSpace = (*modProc)(av[i], addSpace, buf, datum); Buf_AddByte (buf, '\0'); str = (char *)Buf_GetAll(buf, (size_t *)NULL); Buf_Destroy(buf, FALSE); return (str); } /*- *----------------------------------------------------------------------- * VarSortWords -- * Sort the words in the string. * * Input: * str String whose words should be sorted * cmp A comparison function to control the ordering * * Results: * A string containing the words sorted * * Side Effects: * None. * *----------------------------------------------------------------------- */ static char * VarSortWords(char *str, int (*cmp)(const void *, const void *)) { Buffer buf; char **av; int ac, i; buf = Buf_Init(0); av = brk_string(str, &ac, FALSE); qsort(av + 1, ac - 1, sizeof(char *), cmp); for (i = 1; i < ac; i++) { Buf_AddBytes(buf, strlen(av[i]), (Byte *)av[i]); Buf_AddByte(buf, (Byte)((i < ac - 1) ? ' ' : '\0')); } str = (char *)Buf_GetAll(buf, (size_t *)NULL); Buf_Destroy(buf, FALSE); return (str); } static int SortIncreasing(const void *l, const void *r) { return (strcmp(*(const char* const*)l, *(const char* const*)r)); } /*- *----------------------------------------------------------------------- * VarGetPattern -- * Pass through the tstr looking for 1) escaped delimiters, * '$'s and backslashes (place the escaped character in * uninterpreted) and 2) unescaped $'s that aren't before * the delimiter (expand the variable substitution unless flags * has VAR_NOSUBST set). * Return the expanded string or NULL if the delimiter was missing * If pattern is specified, handle escaped ampersands, and replace * unescaped ampersands with the lhs of the pattern. * * Results: * A string of all the words modified appropriately. * If length is specified, return the string length of the buffer * If flags is specified and the last character of the pattern is a * $ set the VAR_MATCH_END bit of flags. * * Side Effects: * None. *----------------------------------------------------------------------- */ static char * VarGetPattern(GNode *ctxt, int err, char **tstr, int delim, int *flags, size_t *length, VarPattern *pattern) { char *cp; Buffer buf = Buf_Init(0); size_t junk; if (length == NULL) length = &junk; #define IS_A_MATCH(cp, delim) \ ((cp[0] == '\\') && ((cp[1] == delim) || \ (cp[1] == '\\') || (cp[1] == '$') || (pattern && (cp[1] == '&')))) /* * Skim through until the matching delimiter is found; * pick up variable substitutions on the way. Also allow * backslashes to quote the delimiter, $, and \, but don't * touch other backslashes. */ for (cp = *tstr; *cp && (*cp != delim); cp++) { if (IS_A_MATCH(cp, delim)) { Buf_AddByte(buf, (Byte)cp[1]); cp++; } else if (*cp == '$') { if (cp[1] == delim) { if (flags == NULL) Buf_AddByte(buf, (Byte)*cp); else /* * Unescaped $ at end of pattern => anchor * pattern at end. */ *flags |= VAR_MATCH_END; } else { if (flags == NULL || (*flags & VAR_NOSUBST) == 0) { char *cp2; size_t len; Boolean freeIt; /* * If unescaped dollar sign not before the * delimiter, assume it's a variable * substitution and recurse. */ cp2 = Var_Parse(cp, ctxt, err, &len, &freeIt); Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2); if (freeIt) free(cp2); cp += len - 1; } else { char *cp2 = &cp[1]; if (*cp2 == '(' || *cp2 == '{') { /* * Find the end of this variable reference * and suck it in without further ado. * It will be interperated later. */ int have = *cp2; int want = (*cp2 == '(') ? ')' : '}'; int depth = 1; for (++cp2; *cp2 != '\0' && depth > 0; ++cp2) { if (cp2[-1] != '\\') { if (*cp2 == have) ++depth; if (*cp2 == want) --depth; } } Buf_AddBytes(buf, cp2 - cp, (Byte *)cp); cp = --cp2; } else Buf_AddByte(buf, (Byte)*cp); } } } else if (pattern && *cp == '&') Buf_AddBytes(buf, pattern->leftLen, (Byte *)pattern->lhs); else Buf_AddByte(buf, (Byte)*cp); } Buf_AddByte(buf, (Byte)'\0'); if (*cp != delim) { *tstr = cp; *length = 0; return (NULL); } else { *tstr = ++cp; cp = (char *)Buf_GetAll(buf, length); *length -= 1; /* Don't count the NULL */ Buf_Destroy(buf, FALSE); return (cp); } } /*- *----------------------------------------------------------------------- * Var_Quote -- * Quote shell meta-characters in the string * * Results: * The quoted string * * Side Effects: * None. * *----------------------------------------------------------------------- */ char * Var_Quote(const char *str) { Buffer buf; /* This should cover most shells :-( */ static char meta[] = "\n \t'`\";&<>()|*?{}[]\\$!#^~"; char *ret; buf = Buf_Init(MAKE_BSIZE); for (; *str; str++) { if (strchr(meta, *str) != NULL) Buf_AddByte(buf, (Byte)'\\'); Buf_AddByte(buf, (Byte)*str); } Buf_AddByte(buf, (Byte)'\0'); ret = Buf_GetAll(buf, NULL); Buf_Destroy(buf, FALSE); return (ret); } /*- *----------------------------------------------------------------------- * VarREError -- * Print the error caused by a regcomp or regexec call. * * Results: * None. * * Side Effects: * An error gets printed. * *----------------------------------------------------------------------- */ void VarREError(int err, regex_t *pat, const char *str) { char *errbuf; int errlen; errlen = regerror(err, pat, 0, 0); errbuf = emalloc(errlen); regerror(err, pat, errbuf, errlen); Error("%s: %s", str, errbuf); free(errbuf); } /*- *----------------------------------------------------------------------- * Var_Parse -- * Given the start of a variable invocation, extract the variable * name and find its value, then modify it according to the * specification. * * Results: * The (possibly-modified) value of the variable or var_Error if the * specification is invalid. The length of the specification is * placed in *lengthPtr (for invalid specifications, this is just * 2 to skip the '$' and the following letter, or 1 if '$' was the * last character in the string). * A Boolean in *freePtr telling whether the returned string should * be freed by the caller. * * Side Effects: * None. * *----------------------------------------------------------------------- */ char * Var_Parse(char *str, GNode *ctxt, Boolean err, size_t *lengthPtr, Boolean *freePtr) { char *tstr; /* Pointer into str */ Var *v; /* Variable in invocation */ char *cp; /* Secondary pointer into str (place marker * for tstr) */ Boolean haveModifier;/* TRUE if have modifiers for the variable */ char endc; /* Ending character when variable in parens * or braces */ char startc=0; /* Starting character when variable in parens * or braces */ int cnt; /* Used to count brace pairs when variable in * in parens or braces */ char *start; char delim; Boolean dynamic; /* TRUE if the variable is local and we're * expanding it in a non-local context. This * is done to support dynamic sources. The * result is just the invocation, unaltered */ int vlen; /* length of variable name, after embedded variable * expansion */ *freePtr = FALSE; dynamic = FALSE; start = str; if (str[1] != '(' && str[1] != '{') { /* * If it's not bounded by braces of some sort, life is much simpler. * We just need to check for the first character and return the * value if it exists. */ char name[2]; name[0] = str[1]; name[1] = '\0'; v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); if (v == (Var *)NULL) { if (str[1] != '\0') *lengthPtr = 2; else *lengthPtr = 1; if ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)) { /* * If substituting a local variable in a non-local context, * assume it's for dynamic source stuff. We have to handle * this specially and return the longhand for the variable * with the dollar sign escaped so it makes it back to the * caller. Only four of the local variables are treated * specially as they are the only four that will be set * when dynamic sources are expanded. */ /* XXX: It looks like $% and $! are reversed here */ switch (str[1]) { case '@': return ("$(.TARGET)"); case '%': return ("$(.ARCHIVE)"); case '*': return ("$(.PREFIX)"); case '!': return ("$(.MEMBER)"); default: break; } } /* * Error */ return (err ? var_Error : varNoError); } else { haveModifier = FALSE; tstr = &str[1]; endc = str[1]; } } else { /* build up expanded variable name in this buffer */ Buffer buf = Buf_Init(MAKE_BSIZE); startc = str[1]; endc = startc == '(' ? ')' : '}'; /* * Skip to the end character or a colon, whichever comes first, * replacing embedded variables as we go. */ for (tstr = str + 2; *tstr != '\0' && *tstr != endc && *tstr != ':'; tstr++) if (*tstr == '$') { size_t rlen; Boolean rfree; char* rval = Var_Parse(tstr, ctxt, err, &rlen, &rfree); if (rval == var_Error) { Fatal("Error expanding embedded variable."); } else if (rval != NULL) { Buf_AddBytes(buf, strlen(rval), (Byte *)rval); if (rfree) free(rval); } tstr += rlen - 1; } else Buf_AddByte(buf, (Byte)*tstr); if (*tstr == '\0') { /* * If we never did find the end character, return NULL * right now, setting the length to be the distance to * the end of the string, since that's what make does. */ *lengthPtr = tstr - str; return (var_Error); } haveModifier = (*tstr == ':'); *tstr = '\0'; Buf_AddByte(buf, (Byte)'\0'); str = Buf_GetAll(buf, (size_t *)NULL); vlen = strlen(str); v = VarFind(str, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); if ((v == (Var *)NULL) && (ctxt != VAR_CMD) && (ctxt != VAR_GLOBAL) && (vlen == 2) && (str[1] == 'F' || str[1] == 'D')) { /* * Check for bogus D and F forms of local variables since we're * in a local context and the name is the right length. */ switch (str[0]) { case '@': case '%': case '*': case '!': case '>': case '<': { char vname[2]; char *val; /* * Well, it's local -- go look for it. */ vname[0] = str[0]; vname[1] = '\0'; v = VarFind(vname, ctxt, 0); if (v != NULL && !haveModifier) { /* * No need for nested expansion or anything, as we're * the only one who sets these things and we sure don't * put nested invocations in them... */ val = (char *)Buf_GetAll(v->val, (size_t *)NULL); if (str[1] == 'D') { val = VarModify(val, VarHead, (void *)NULL); } else { val = VarModify(val, VarTail, (void *)NULL); } /* * Resulting string is dynamically allocated, so * tell caller to free it. */ *freePtr = TRUE; *lengthPtr = tstr-start+1; *tstr = endc; Buf_Destroy(buf, TRUE); return (val); } break; default: break; } } } if (v == (Var *)NULL) { if (((vlen == 1) || (((vlen == 2) && (str[1] == 'F' || str[1] == 'D')))) && ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL))) { /* * If substituting a local variable in a non-local context, * assume it's for dynamic source stuff. We have to handle * this specially and return the longhand for the variable * with the dollar sign escaped so it makes it back to the * caller. Only four of the local variables are treated * specially as they are the only four that will be set * when dynamic sources are expanded. */ switch (str[0]) { case '@': case '%': case '*': case '!': dynamic = TRUE; break; default: break; } } else if ((vlen > 2) && (str[0] == '.') && isupper((unsigned char)str[1]) && ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL))) { int len; len = vlen - 1; if ((strncmp(str, ".TARGET", len) == 0) || (strncmp(str, ".ARCHIVE", len) == 0) || (strncmp(str, ".PREFIX", len) == 0) || (strncmp(str, ".MEMBER", len) == 0)) { dynamic = TRUE; } } if (!haveModifier) { /* * No modifiers -- have specification length so we can return * now. */ *lengthPtr = tstr - start + 1; *tstr = endc; if (dynamic) { str = emalloc(*lengthPtr + 1); strncpy(str, start, *lengthPtr); str[*lengthPtr] = '\0'; *freePtr = TRUE; Buf_Destroy(buf, TRUE); return (str); } else { Buf_Destroy(buf, TRUE); return (err ? var_Error : varNoError); } } else { /* * Still need to get to the end of the variable specification, * so kludge up a Var structure for the modifications */ v = emalloc(sizeof(Var)); v->name = estrdup(str); v->val = Buf_Init(1); v->flags = VAR_JUNK; } } Buf_Destroy(buf, TRUE); } if (v->flags & VAR_IN_USE) { Fatal("Variable %s is recursive.", v->name); /*NOTREACHED*/ } else { v->flags |= VAR_IN_USE; } /* * Before doing any modification, we have to make sure the value * has been fully expanded. If it looks like recursion might be * necessary (there's a dollar sign somewhere in the variable's value) * we just call Var_Subst to do any other substitutions that are * necessary. Note that the value returned by Var_Subst will have * been dynamically-allocated, so it will need freeing when we * return. */ str = (char *)Buf_GetAll(v->val, (size_t *)NULL); if (strchr(str, '$') != (char *)NULL) { str = Var_Subst(NULL, str, ctxt, err); *freePtr = TRUE; } v->flags &= ~VAR_IN_USE; /* * Now we need to apply any modifiers the user wants applied. * These are: * :M words which match the given . * is of the standard file * wildcarding form. * :S[g] * Substitute for in the value * :C[g] * Substitute for regex in the value * :H Substitute the head of each word * :T Substitute the tail of each word * :E Substitute the extension (minus '.') of * each word * :R Substitute the root of each word * (pathname minus the suffix). * :lhs=rhs Like :S, but the rhs goes to the end of * the invocation. * :U Converts variable to upper-case. * :L Converts variable to lower-case. */ if ((str != NULL) && haveModifier) { /* * Skip initial colon while putting it back. */ *tstr++ = ':'; while (*tstr != endc) { char *newStr; /* New value to return */ char termc; /* Character which terminated scan */ DEBUGF(VAR, ("Applying :%c to \"%s\"\n", *tstr, str)); switch (*tstr) { case 'N': case 'M': { char *pattern; char *cp2; Boolean copy; copy = FALSE; for (cp = tstr + 1; *cp != '\0' && *cp != ':' && *cp != endc; cp++) { if (*cp == '\\' && (cp[1] == ':' || cp[1] == endc)) { copy = TRUE; cp++; } } termc = *cp; *cp = '\0'; if (copy) { /* * Need to compress the \:'s out of the pattern, so * allocate enough room to hold the uncompressed * pattern (note that cp started at tstr+1, so * cp - tstr takes the null byte into account) and * compress the pattern into the space. */ pattern = emalloc(cp - tstr); for (cp2 = pattern, cp = tstr + 1; *cp != '\0'; cp++, cp2++) { if ((*cp == '\\') && (cp[1] == ':' || cp[1] == endc)) { cp++; } *cp2 = *cp; } *cp2 = '\0'; } else { pattern = &tstr[1]; } if (*tstr == 'M' || *tstr == 'm') { newStr = VarModify(str, VarMatch, pattern); } else { newStr = VarModify(str, VarNoMatch, pattern); } if (copy) { free(pattern); } break; } case 'S': { VarPattern pattern; char del; Buffer buf; /* Buffer for patterns */ pattern.flags = 0; del = tstr[1]; tstr += 2; /* * If pattern begins with '^', it is anchored to the * start of the word -- skip over it and flag pattern. */ if (*tstr == '^') { pattern.flags |= VAR_MATCH_START; tstr += 1; } buf = Buf_Init(0); /* * Pass through the lhs looking for 1) escaped delimiters, * '$'s and backslashes (place the escaped character in * uninterpreted) and 2) unescaped $'s that aren't before * the delimiter (expand the variable substitution). * The result is left in the Buffer buf. */ for (cp = tstr; *cp != '\0' && *cp != del; cp++) { if ((*cp == '\\') && ((cp[1] == del) || (cp[1] == '$') || (cp[1] == '\\'))) { Buf_AddByte(buf, (Byte)cp[1]); cp++; } else if (*cp == '$') { if (cp[1] != del) { /* * If unescaped dollar sign not before the * delimiter, assume it's a variable * substitution and recurse. */ char *cp2; size_t len; Boolean freeIt; cp2 = Var_Parse(cp, ctxt, err, &len, &freeIt); Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2); if (freeIt) { free(cp2); } cp += len - 1; } else { /* * Unescaped $ at end of pattern => anchor * pattern at end. */ pattern.flags |= VAR_MATCH_END; } } else { Buf_AddByte(buf, (Byte)*cp); } } Buf_AddByte(buf, (Byte)'\0'); /* * If lhs didn't end with the delimiter, complain and * exit. */ if (*cp != del) { Fatal("Unclosed substitution for %s (%c missing)", v->name, del); } /* * Fetch pattern and destroy buffer, but preserve the data * in it, since that's our lhs. Note that Buf_GetAll * will return the actual number of bytes, which includes * the null byte, so we have to decrement the length by * one. */ pattern.lhs = (char *)Buf_GetAll(buf, &pattern.leftLen); pattern.leftLen--; Buf_Destroy(buf, FALSE); /* * Now comes the replacement string. Three things need to * be done here: 1) need to compress escaped delimiters and * ampersands and 2) need to replace unescaped ampersands * with the l.h.s. (since this isn't regexp, we can do * it right here) and 3) expand any variable substitutions. */ buf = Buf_Init(0); tstr = cp + 1; for (cp = tstr; *cp != '\0' && *cp != del; cp++) { if ((*cp == '\\') && ((cp[1] == del) || (cp[1] == '&') || (cp[1] == '\\') || (cp[1] == '$'))) { Buf_AddByte(buf, (Byte)cp[1]); cp++; } else if ((*cp == '$') && (cp[1] != del)) { char *cp2; size_t len; Boolean freeIt; cp2 = Var_Parse(cp, ctxt, err, &len, &freeIt); Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2); cp += len - 1; if (freeIt) { free(cp2); } } else if (*cp == '&') { Buf_AddBytes(buf, pattern.leftLen, (Byte *)pattern.lhs); } else { Buf_AddByte(buf, (Byte)*cp); } } Buf_AddByte(buf, (Byte)'\0'); /* * If didn't end in delimiter character, complain */ if (*cp != del) { Fatal("Unclosed substitution for %s (%c missing)", v->name, del); } pattern.rhs = (char *)Buf_GetAll(buf, &pattern.rightLen); pattern.rightLen--; Buf_Destroy(buf, FALSE); /* * Check for global substitution. If 'g' after the final * delimiter, substitution is global and is marked that * way. */ cp++; if (*cp == 'g') { pattern.flags |= VAR_SUB_GLOBAL; cp++; } /* * Global substitution of the empty string causes an * infinite number of matches, unless anchored by '^' * (start of string) or '$' (end of string). Catch the * infinite substitution here. * Note that flags can only contain the 3 bits we're * interested in so we don't have to mask unrelated * bits. We can test for equality. */ if (!pattern.leftLen && pattern.flags == VAR_SUB_GLOBAL) Fatal("Global substitution of the empty string"); termc = *cp; newStr = VarModify(str, VarSubstitute, &pattern); /* * Free the two strings. */ free(pattern.lhs); free(pattern.rhs); break; } case 'C': { VarREPattern pattern; char *re; int error; pattern.flags = 0; delim = tstr[1]; tstr += 2; cp = tstr; if ((re = VarGetPattern(ctxt, err, &cp, delim, NULL, NULL, NULL)) == NULL) { /* was: goto cleanup */ *lengthPtr = cp - start + 1; if (*freePtr) free(str); if (delim != '\0') Fatal("Unclosed substitution for %s (%c missing)", v->name, delim); return (var_Error); } if ((pattern.replace = VarGetPattern(ctxt, err, &cp, delim, NULL, NULL, NULL)) == NULL){ free(re); /* was: goto cleanup */ *lengthPtr = cp - start + 1; if (*freePtr) free(str); if (delim != '\0') Fatal("Unclosed substitution for %s (%c missing)", v->name, delim); return (var_Error); } for (;; cp++) { switch (*cp) { case 'g': pattern.flags |= VAR_SUB_GLOBAL; continue; case '1': pattern.flags |= VAR_SUB_ONE; continue; default: break; } break; } termc = *cp; error = regcomp(&pattern.re, re, REG_EXTENDED); free(re); if (error) { *lengthPtr = cp - start + 1; VarREError(error, &pattern.re, "RE substitution error"); free(pattern.replace); return (var_Error); } pattern.nsub = pattern.re.re_nsub + 1; if (pattern.nsub < 1) pattern.nsub = 1; if (pattern.nsub > 10) pattern.nsub = 10; pattern.matches = emalloc(pattern.nsub * sizeof(regmatch_t)); newStr = VarModify(str, VarRESubstitute, &pattern); regfree(&pattern.re); free(pattern.replace); free(pattern.matches); break; } case 'L': if (tstr[1] == endc || tstr[1] == ':') { Buffer buf; buf = Buf_Init(MAKE_BSIZE); for (cp = str; *cp ; cp++) Buf_AddByte(buf, (Byte)tolower(*cp)); Buf_AddByte(buf, (Byte)'\0'); newStr = (char *)Buf_GetAll(buf, (size_t *)NULL); Buf_Destroy(buf, FALSE); cp = tstr + 1; termc = *cp; break; } /* FALLTHROUGH */ case 'O': if (tstr[1] == endc || tstr[1] == ':') { newStr = VarSortWords(str, SortIncreasing); cp = tstr + 1; termc = *cp; break; } /* FALLTHROUGH */ case 'Q': if (tstr[1] == endc || tstr[1] == ':') { newStr = Var_Quote(str); cp = tstr + 1; termc = *cp; break; } /*FALLTHRU*/ case 'T': if (tstr[1] == endc || tstr[1] == ':') { newStr = VarModify(str, VarTail, (void *)NULL); cp = tstr + 1; termc = *cp; break; } /*FALLTHRU*/ case 'U': if (tstr[1] == endc || tstr[1] == ':') { Buffer buf; buf = Buf_Init(MAKE_BSIZE); for (cp = str; *cp ; cp++) Buf_AddByte(buf, (Byte)toupper(*cp)); Buf_AddByte(buf, (Byte)'\0'); newStr = (char *)Buf_GetAll(buf, (size_t *)NULL); Buf_Destroy(buf, FALSE); cp = tstr + 1; termc = *cp; break; } /* FALLTHROUGH */ case 'H': if (tstr[1] == endc || tstr[1] == ':') { newStr = VarModify(str, VarHead, (void *)NULL); cp = tstr + 1; termc = *cp; break; } /*FALLTHRU*/ case 'E': if (tstr[1] == endc || tstr[1] == ':') { newStr = VarModify(str, VarSuffix, (void *)NULL); cp = tstr + 1; termc = *cp; break; } /*FALLTHRU*/ case 'R': if (tstr[1] == endc || tstr[1] == ':') { newStr = VarModify(str, VarRoot, (void *)NULL); cp = tstr + 1; termc = *cp; break; } /*FALLTHRU*/ #ifdef SUNSHCMD case 's': if (tstr[1] == 'h' && (tstr[2] == endc || tstr[2] == ':')) { char *error; newStr = Cmd_Exec(str, &error); if (error) Error(error, str); cp = tstr + 2; termc = *cp; break; } /*FALLTHRU*/ #endif default: { #ifdef SYSVVARSUB /* * This can either be a bogus modifier or a System-V * substitution command. */ VarPattern pattern; Boolean eqFound; pattern.flags = 0; eqFound = FALSE; /* * First we make a pass through the string trying * to verify it is a SYSV-make-style translation: * it must be: =) */ cp = tstr; cnt = 1; while (*cp != '\0' && cnt) { if (*cp == '=') { eqFound = TRUE; /* continue looking for endc */ } else if (*cp == endc) cnt--; else if (*cp == startc) cnt++; if (cnt) cp++; } if (*cp == endc && eqFound) { /* * Now we break this sucker into the lhs and * rhs. We must null terminate them of course. */ cp = tstr; delim = '='; if ((pattern.lhs = VarGetPattern(ctxt, err, &cp, delim, &pattern.flags, &pattern.leftLen, NULL)) == NULL) { /* was: goto cleanup */ *lengthPtr = cp - start + 1; if (*freePtr) free(str); if (delim != '\0') Fatal("Unclosed substitution for %s (%c missing)", v->name, delim); return (var_Error); } delim = endc; if ((pattern.rhs = VarGetPattern(ctxt, err, &cp, delim, NULL, &pattern.rightLen, &pattern)) == NULL) { /* was: goto cleanup */ *lengthPtr = cp - start + 1; if (*freePtr) free(str); if (delim != '\0') Fatal("Unclosed substitution for %s (%c missing)", v->name, delim); return (var_Error); } /* * SYSV modifications happen through the whole * string. Note the pattern is anchored at the end. */ termc = *--cp; delim = '\0'; newStr = VarModify(str, VarSYSVMatch, &pattern); free(pattern.lhs); free(pattern.rhs); termc = endc; } else #endif { Error("Unknown modifier '%c'\n", *tstr); for (cp = tstr+1; *cp != ':' && *cp != endc && *cp != '\0'; cp++) continue; termc = *cp; newStr = var_Error; } } } DEBUGF(VAR, ("Result is \"%s\"\n", newStr)); if (*freePtr) { free(str); } str = newStr; if (str != var_Error) { *freePtr = TRUE; } else { *freePtr = FALSE; } if (termc == '\0') { Error("Unclosed variable specification for %s", v->name); } else if (termc == ':') { *cp++ = termc; } else { *cp = termc; } tstr = cp; } *lengthPtr = tstr - start + 1; } else { *lengthPtr = tstr - start + 1; *tstr = endc; } if (v->flags & VAR_FROM_ENV) { Boolean destroy = FALSE; if (str != (char *)Buf_GetAll(v->val, (size_t *)NULL)) { destroy = TRUE; } else { /* * Returning the value unmodified, so tell the caller to free * the thing. */ *freePtr = TRUE; } free(v->name); Buf_Destroy(v->val, destroy); free(v); } else if (v->flags & VAR_JUNK) { /* * Perform any free'ing needed and set *freePtr to FALSE so the caller * doesn't try to free a static pointer. */ if (*freePtr) { free(str); } *freePtr = FALSE; free(v->name); Buf_Destroy(v->val, TRUE); free(v); if (dynamic) { str = emalloc(*lengthPtr + 1); strncpy(str, start, *lengthPtr); str[*lengthPtr] = '\0'; *freePtr = TRUE; } else { str = err ? var_Error : varNoError; } } return (str); } /*- *----------------------------------------------------------------------- * Var_Subst -- * Substitute for all variables in the given string in the given context * If undefErr is TRUE, Parse_Error will be called when an undefined * variable is encountered. * * Results: * The resulting string. * * Side Effects: * None. The old string must be freed by the caller *----------------------------------------------------------------------- */ char * Var_Subst(char *var, char *str, GNode *ctxt, Boolean undefErr) { Buffer buf; /* Buffer for forming things */ char *val; /* Value to substitute for a variable */ size_t length; /* Length of the variable invocation */ Boolean doFree; /* Set true if val should be freed */ static Boolean errorReported; /* Set true if an error has already * been reported to prevent a plethora * of messages when recursing */ buf = Buf_Init(MAKE_BSIZE); errorReported = FALSE; while (*str) { if (var == NULL && (*str == '$') && (str[1] == '$')) { /* * A dollar sign may be escaped either with another dollar sign. * In such a case, we skip over the escape character and store the * dollar sign into the buffer directly. */ str++; Buf_AddByte(buf, (Byte)*str); str++; } else if (*str != '$') { /* * Skip as many characters as possible -- either to the end of * the string or to the next dollar sign (variable invocation). */ char *cp; for (cp = str++; *str != '$' && *str != '\0'; str++) continue; Buf_AddBytes(buf, str - cp, (Byte *)cp); } else { if (var != NULL) { int expand; for (;;) { if (str[1] != '(' && str[1] != '{') { if (str[1] != *var || var[1] != '\0') { Buf_AddBytes(buf, 2, (Byte *)str); str += 2; expand = FALSE; } else expand = TRUE; break; } else { char *p; /* * Scan up to the end of the variable name. */ for (p = &str[2]; *p && *p != ':' && *p != ')' && *p != '}'; p++) if (*p == '$') break; /* * A variable inside the variable. We cannot expand * the external variable yet, so we try again with * the nested one */ if (*p == '$') { Buf_AddBytes(buf, p - str, (Byte *)str); str = p; continue; } if (strncmp(var, str + 2, p - str - 2) != 0 || var[p - str - 2] != '\0') { /* * Not the variable we want to expand, scan * until the next variable */ for (;*p != '$' && *p != '\0'; p++) continue; Buf_AddBytes(buf, p - str, (Byte *)str); str = p; expand = FALSE; } else expand = TRUE; break; } } if (!expand) continue; } val = Var_Parse(str, ctxt, undefErr, &length, &doFree); /* * When we come down here, val should either point to the * value of this variable, suitably modified, or be NULL. * Length should be the total length of the potential * variable invocation (from $ to end character...) */ if (val == var_Error || val == varNoError) { /* * If performing old-time variable substitution, skip over * the variable and continue with the substitution. Otherwise, * store the dollar sign and advance str so we continue with * the string... */ if (oldVars) { str += length; } else if (undefErr) { /* * If variable is undefined, complain and skip the * variable. The complaint will stop us from doing anything * when the file is parsed. */ if (!errorReported) { Parse_Error(PARSE_FATAL, "Undefined variable \"%.*s\"",length,str); } str += length; errorReported = TRUE; } else { Buf_AddByte (buf, (Byte)*str); str += 1; } } else { /* * We've now got a variable structure to store in. But first, * advance the string pointer. */ str += length; /* * Copy all the characters from the variable value straight * into the new string. */ Buf_AddBytes(buf, strlen(val), (Byte *)val); if (doFree) { free(val); } } } } Buf_AddByte(buf, '\0'); str = (char *)Buf_GetAll(buf, (size_t *)NULL); Buf_Destroy(buf, FALSE); return (str); } /*- *----------------------------------------------------------------------- * Var_GetTail -- * Return the tail from each of a list of words. Used to set the * System V local variables. * * Results: * The resulting string. * * Side Effects: * None. * *----------------------------------------------------------------------- */ char * Var_GetTail(char *file) { return (VarModify(file, VarTail, (void *)NULL)); } /*- *----------------------------------------------------------------------- * Var_GetHead -- * Find the leading components of a (list of) filename(s). * XXX: VarHead does not replace foo by ., as (sun) System V make * does. * * Results: * The leading components. * * Side Effects: * None. * *----------------------------------------------------------------------- */ char * Var_GetHead(char *file) { return (VarModify(file, VarHead, (void *)NULL)); } /*- *----------------------------------------------------------------------- * Var_Init -- * Initialize the module * * Results: * None * * Side Effects: * The VAR_CMD and VAR_GLOBAL contexts are created *----------------------------------------------------------------------- */ void Var_Init(void) { VAR_GLOBAL = Targ_NewGN("Global"); VAR_CMD = Targ_NewGN("Command"); allVars = Lst_Init(); } void Var_End(void) { Lst_Destroy(allVars, VarDelete); } /****************** PRINT DEBUGGING INFO *****************/ static int VarPrintVar(void *vp, void *dummy __unused) { Var *v = (Var *) vp; printf("%-16s = %s\n", v->name, (char *)Buf_GetAll(v->val, (size_t *)NULL)); return (0); } /*- *----------------------------------------------------------------------- * Var_Dump -- * print all variables in a context *----------------------------------------------------------------------- */ void Var_Dump(GNode *ctxt) { Lst_ForEach (ctxt->context, VarPrintVar, (void *)NULL); }