Index: head/usr.bin/make/arch.c =================================================================== --- head/usr.bin/make/arch.c (revision 138560) +++ head/usr.bin/make/arch.c (revision 138561) @@ -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 */ 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) { 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(); 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) +ArchFindArchive(const void *ar, const void *archName) { - return (strcmp((char *)archName, ((Arch *)ar)->name)); + + return (strcmp(archName, ((const 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 */ 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; 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) { 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/cond.c =================================================================== --- head/usr.bin/make/cond.c (revision 138560) +++ head/usr.bin/make/cond.c (revision 138561) @@ -1,1252 +1,1251 @@ /* * 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. * * @(#)cond.c 8.2 (Berkeley) 1/2/94 */ #include __FBSDID("$FreeBSD$"); /*- * cond.c -- * Functions to handle conditionals in a makefile. * * Interface: * Cond_Eval Evaluate the conditional in the passed line. * */ #include #include #include "make.h" #include "hash.h" #include "dir.h" #include "buf.h" /* * The parsing of conditional expressions is based on this grammar: * E -> F || E * E -> F * F -> T && F * F -> T * T -> defined(variable) * T -> make(target) * T -> exists(file) * T -> empty(varspec) * T -> target(name) * T -> symbol * T -> $(varspec) op value * T -> $(varspec) == "string" * T -> $(varspec) != "string" * T -> ( E ) * T -> ! T * op -> == | != | > | < | >= | <= * * 'symbol' is some other symbol to which the default function (condDefProc) * is applied. * * Tokens are scanned from the 'condExpr' string. The scanner (CondToken) * will return And for '&' and '&&', Or for '|' and '||', Not for '!', * LParen for '(', RParen for ')' and will evaluate the other terminal * symbols, using either the default function or the function given in the * terminal, and return the result as either True or False. * * All Non-Terminal functions (CondE, CondF and CondT) return Err on error. */ typedef enum { And, Or, Not, True, False, LParen, RParen, EndOfFile, None, Err } Token; /*- * Structures to handle elegantly the different forms of #if's. The * last two fields are stored in condInvert and condDefProc, respectively. */ static void CondPushBack(Token); static int CondGetArg(char **, char **, char *, Boolean); static Boolean CondDoDefined(int, char *); -static int CondStrMatch(void *, void *); static Boolean CondDoMake(int, char *); static Boolean CondDoExists(int, char *); static Boolean CondDoTarget(int, char *); static char * CondCvtArg(char *, double *); static Token CondToken(Boolean); static Token CondT(Boolean); static Token CondF(Boolean); static Token CondE(Boolean); static struct If { char *form; /* Form of if */ int formlen; /* Length of form */ Boolean doNot; /* TRUE if default function should be negated */ Boolean (*defProc)(int, char *); /* Default function to apply */ } ifs[] = { { "ifdef", 5, FALSE, CondDoDefined }, { "ifndef", 6, TRUE, CondDoDefined }, { "ifmake", 6, FALSE, CondDoMake }, { "ifnmake", 7, TRUE, CondDoMake }, { "if", 2, FALSE, CondDoDefined }, { NULL, 0, FALSE, NULL } }; static Boolean condInvert; /* Invert the default function */ static Boolean (*condDefProc) /* Default function to apply */ (int, char *); static char *condExpr; /* The expression to parse */ static Token condPushBack=None; /* Single push-back token used in * parsing */ #define MAXIF 30 /* greatest depth of #if'ing */ static Boolean condStack[MAXIF]; /* Stack of conditionals's values */ static int condLineno[MAXIF]; /* Line numbers of the opening .if */ static int condTop = MAXIF; /* Top-most conditional */ static int skipIfLevel=0; /* Depth of skipped conditionals */ static int skipIfLineno[MAXIF]; /* Line numbers of skipped .ifs */ static Boolean skipLine = FALSE; /* Whether the parse module is skipping * lines */ /*- *----------------------------------------------------------------------- * CondPushBack -- * Push back the most recent token read. We only need one level of * this, so the thing is just stored in 'condPushback'. * * Results: * None. * * Side Effects: * condPushback is overwritten. * *----------------------------------------------------------------------- */ static void CondPushBack(Token t) { condPushBack = t; } /*- *----------------------------------------------------------------------- * CondGetArg -- * Find the argument of a built-in function. parens is set to TRUE * if the arguments are bounded by parens. * * Results: * The length of the argument and the address of the argument. * * Side Effects: * The pointer is set to point to the closing parenthesis of the * function call. * *----------------------------------------------------------------------- */ static int CondGetArg(char **linePtr, char **argPtr, char *func, Boolean parens) { char *cp; size_t argLen; Buffer buf; cp = *linePtr; if (parens) { while (*cp != '(' && *cp != '\0') { cp++; } if (*cp == '(') { cp++; } } if (*cp == '\0') { /* * No arguments whatsoever. Because 'make' and 'defined' aren't really * "reserved words", we don't print a message. I think this is better * than hitting the user with a warning message every time s/he uses * the word 'make' or 'defined' at the beginning of a symbol... */ *argPtr = cp; return (0); } while (*cp == ' ' || *cp == '\t') { cp++; } /* * Create a buffer for the argument and start it out at 16 characters * long. Why 16? Why not? */ buf = Buf_Init(16); while ((strchr(" \t)&|", *cp) == NULL) && (*cp != '\0')) { if (*cp == '$') { /* * Parse the variable spec and install it as part of the argument * if it's valid. We tell Var_Parse to complain on an undefined * variable, so we don't do it too. Nor do we return an error, * though perhaps we should... */ char *cp2; size_t len; Boolean doFree; cp2 = Var_Parse(cp, VAR_CMD, TRUE, &len, &doFree); Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2); if (doFree) { free(cp2); } cp += len; } else { Buf_AddByte(buf, (Byte)*cp); cp++; } } Buf_AddByte(buf, (Byte)'\0'); *argPtr = (char *)Buf_GetAll(buf, &argLen); Buf_Destroy(buf, FALSE); while (*cp == ' ' || *cp == '\t') { cp++; } if (parens && *cp != ')') { Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()", func); return (0); } else if (parens) { /* * Advance pointer past close parenthesis. */ cp++; } *linePtr = cp; return (argLen); } /*- *----------------------------------------------------------------------- * CondDoDefined -- * Handle the 'defined' function for conditionals. * * Results: * TRUE if the given variable is defined. * * Side Effects: * None. * *----------------------------------------------------------------------- */ static Boolean CondDoDefined(int argLen, char *arg) { char savec = arg[argLen]; char *p1; Boolean result; arg[argLen] = '\0'; if (Var_Value(arg, VAR_CMD, &p1) != NULL) { result = TRUE; } else { result = FALSE; } free(p1); arg[argLen] = savec; return (result); } /*- *----------------------------------------------------------------------- * CondStrMatch -- * Front-end for Str_Match so it returns 0 on match and non-zero * on mismatch. Callback function for CondDoMake via Lst_Find * * Results: * 0 if string matches pattern * * Side Effects: * None * *----------------------------------------------------------------------- */ static int -CondStrMatch(void *string, void *pattern) +CondStrMatch(const void *string, const void *pattern) { - return (!Str_Match((char *)string, (char *)pattern)); + return (!Str_Match(string, pattern)); } /*- *----------------------------------------------------------------------- * CondDoMake -- * Handle the 'make' function for conditionals. * * Results: * TRUE if the given target is being made. * * Side Effects: * None. * *----------------------------------------------------------------------- */ static Boolean CondDoMake(int argLen, char *arg) { char savec = arg[argLen]; Boolean result; arg[argLen] = '\0'; if (Lst_Find(create, arg, CondStrMatch) == NULL) { result = FALSE; } else { result = TRUE; } arg[argLen] = savec; return (result); } /*- *----------------------------------------------------------------------- * CondDoExists -- * See if the given file exists. * * Results: * TRUE if the file exists and FALSE if it does not. * * Side Effects: * None. * *----------------------------------------------------------------------- */ static Boolean CondDoExists(int argLen, char *arg) { char savec = arg[argLen]; Boolean result; char *path; arg[argLen] = '\0'; path = Dir_FindFile(arg, dirSearchPath); if (path != NULL) { result = TRUE; free(path); } else { result = FALSE; } arg[argLen] = savec; return (result); } /*- *----------------------------------------------------------------------- * CondDoTarget -- * See if the given node exists and is an actual target. * * Results: * TRUE if the node exists as a target and FALSE if it does not. * * Side Effects: * None. * *----------------------------------------------------------------------- */ static Boolean CondDoTarget(int argLen, char *arg) { char savec = arg[argLen]; Boolean result; GNode *gn; arg[argLen] = '\0'; gn = Targ_FindNode(arg, TARG_NOCREATE); if ((gn != NULL) && !OP_NOP(gn->type)) { result = TRUE; } else { result = FALSE; } arg[argLen] = savec; return (result); } /*- *----------------------------------------------------------------------- * CondCvtArg -- * Convert the given number into a double. If the number begins * with 0x, it is interpreted as a hexadecimal integer * and converted to a double from there. All other strings just have * strtod called on them. * * Results: * Sets 'value' to double value of string. * Returns address of the first character after the last valid * character of the converted number. * * Side Effects: * Can change 'value' even if string is not a valid number. * * *----------------------------------------------------------------------- */ static char * CondCvtArg(char *str, double *value) { if ((*str == '0') && (str[1] == 'x')) { long i; for (str += 2, i = 0; ; str++) { int x; if (isdigit((unsigned char)*str)) x = *str - '0'; else if (isxdigit((unsigned char)*str)) x = 10 + *str - isupper((unsigned char)*str) ? 'A' : 'a'; else { *value = (double)i; return (str); } i = (i << 4) + x; } } else { char *eptr; *value = strtod(str, &eptr); return (eptr); } } /*- *----------------------------------------------------------------------- * CondToken -- * Return the next token from the input. * * Results: * A Token for the next lexical token in the stream. * * Side Effects: * condPushback will be set back to None if it is used. * *----------------------------------------------------------------------- */ static Token CondToken(Boolean doEval) { Token t; if (condPushBack == None) { while (*condExpr == ' ' || *condExpr == '\t') { condExpr++; } switch (*condExpr) { case '(': t = LParen; condExpr++; break; case ')': t = RParen; condExpr++; break; case '|': if (condExpr[1] == '|') { condExpr++; } condExpr++; t = Or; break; case '&': if (condExpr[1] == '&') { condExpr++; } condExpr++; t = And; break; case '!': t = Not; condExpr++; break; case '\n': case '\0': t = EndOfFile; break; case '$': { char *lhs; char *rhs; char *op; size_t varSpecLen; Boolean doFree; /* * Parse the variable spec and skip over it, saving its * value in lhs. */ t = Err; lhs = Var_Parse(condExpr, VAR_CMD, doEval, &varSpecLen, &doFree); if (lhs == var_Error) { /* * Even if !doEval, we still report syntax errors, which * is what getting var_Error back with !doEval means. */ return (Err); } condExpr += varSpecLen; if (!isspace((unsigned char)*condExpr) && strchr("!=><", *condExpr) == NULL) { Buffer buf; char *cp; buf = Buf_Init(0); for (cp = lhs; *cp; cp++) Buf_AddByte(buf, (Byte)*cp); if (doFree) free(lhs); for (;*condExpr && !isspace((unsigned char) *condExpr); condExpr++) Buf_AddByte(buf, (Byte)*condExpr); Buf_AddByte(buf, (Byte)'\0'); lhs = (char *)Buf_GetAll(buf, &varSpecLen); Buf_Destroy(buf, FALSE); doFree = TRUE; } /* * Skip whitespace to get to the operator */ while (isspace((unsigned char)*condExpr)) condExpr++; /* * Make sure the operator is a valid one. If it isn't a * known relational operator, pretend we got a * != 0 comparison. */ op = condExpr; switch (*condExpr) { case '!': case '=': case '<': case '>': if (condExpr[1] == '=') { condExpr += 2; } else { condExpr += 1; } break; default: op = "!="; rhs = "0"; goto do_compare; } while (isspace((unsigned char)*condExpr)) { condExpr++; } if (*condExpr == '\0') { Parse_Error(PARSE_WARNING, "Missing right-hand-side of operator"); goto error; } rhs = condExpr; do_compare: if (*rhs == '"') { /* * Doing a string comparison. Only allow == and != for * operators. */ char *string; char *cp, *cp2; int qt; Buffer buf; do_string_compare: if (((*op != '!') && (*op != '=')) || (op[1] != '=')) { Parse_Error(PARSE_WARNING, "String comparison operator should be either == or !="); goto error; } buf = Buf_Init(0); qt = *rhs == '"' ? 1 : 0; for (cp = &rhs[qt]; ((qt && (*cp != '"')) || (!qt && strchr(" \t)", *cp) == NULL)) && (*cp != '\0'); cp++) { if ((*cp == '\\') && (cp[1] != '\0')) { /* * Backslash escapes things -- skip over next * character, if it exists. */ cp++; Buf_AddByte(buf, (Byte)*cp); } else if (*cp == '$') { size_t len; Boolean freeIt; cp2 = Var_Parse(cp, VAR_CMD, doEval, &len, &freeIt); if (cp2 != var_Error) { Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2); if (freeIt) { free(cp2); } cp += len - 1; } else { Buf_AddByte(buf, (Byte)*cp); } } else { Buf_AddByte(buf, (Byte)*cp); } } Buf_AddByte(buf, (Byte)0); string = (char *)Buf_GetAll(buf, (size_t *)NULL); Buf_Destroy(buf, FALSE); DEBUGF(COND, ("lhs = \"%s\", rhs = \"%s\", op = %.2s\n", lhs, string, op)); /* * Null-terminate rhs and perform the comparison. * t is set to the result. */ if (*op == '=') { t = strcmp(lhs, string) ? False : True; } else { t = strcmp(lhs, string) ? True : False; } free(string); if (rhs == condExpr) { if (!qt && *cp == ')') condExpr = cp; else condExpr = cp + 1; } } else { /* * rhs is either a float or an integer. Convert both the * lhs and the rhs to a double and compare the two. */ double left, right; char *string; if (*CondCvtArg(lhs, &left) != '\0') goto do_string_compare; if (*rhs == '$') { size_t len; Boolean freeIt; string = Var_Parse(rhs, VAR_CMD, doEval, &len, &freeIt); if (string == var_Error) { right = 0.0; } else { if (*CondCvtArg(string, &right) != '\0') { if (freeIt) free(string); goto do_string_compare; } if (freeIt) free(string); if (rhs == condExpr) condExpr += len; } } else { char *c = CondCvtArg(rhs, &right); if (c == rhs) goto do_string_compare; if (rhs == condExpr) { /* * Skip over the right-hand side */ condExpr = c; } } DEBUGF(COND, ("left = %f, right = %f, op = %.2s\n", left, right, op)); switch (op[0]) { case '!': if (op[1] != '=') { Parse_Error(PARSE_WARNING, "Unknown operator"); goto error; } t = (left != right ? True : False); break; case '=': if (op[1] != '=') { Parse_Error(PARSE_WARNING, "Unknown operator"); goto error; } t = (left == right ? True : False); break; case '<': if (op[1] == '=') { t = (left <= right ? True : False); } else { t = (left < right ? True : False); } break; case '>': if (op[1] == '=') { t = (left >= right ? True : False); } else { t = (left > right ? True : False); } break; default: break; } } error: if (doFree) free(lhs); break; } default: { Boolean (*evalProc)(int, char *); Boolean invert = FALSE; char *arg; int arglen; if (strncmp(condExpr, "defined", 7) == 0) { /* * Use CondDoDefined to evaluate the argument and * CondGetArg to extract the argument from the 'function * call'. */ evalProc = CondDoDefined; condExpr += 7; arglen = CondGetArg(&condExpr, &arg, "defined", TRUE); if (arglen == 0) { condExpr -= 7; goto use_default; } } else if (strncmp(condExpr, "make", 4) == 0) { /* * Use CondDoMake to evaluate the argument and * CondGetArg to extract the argument from the 'function * call'. */ evalProc = CondDoMake; condExpr += 4; arglen = CondGetArg(&condExpr, &arg, "make", TRUE); if (arglen == 0) { condExpr -= 4; goto use_default; } } else if (strncmp(condExpr, "exists", 6) == 0) { /* * Use CondDoExists to evaluate the argument and * CondGetArg to extract the argument from the * 'function call'. */ evalProc = CondDoExists; condExpr += 6; arglen = CondGetArg(&condExpr, &arg, "exists", TRUE); if (arglen == 0) { condExpr -= 6; goto use_default; } } else if (strncmp(condExpr, "empty", 5) == 0) { /* * Use Var_Parse to parse the spec in parens and return * True if the resulting string is empty. */ size_t length; Boolean doFree; char *val; condExpr += 5; for (arglen = 0; condExpr[arglen] != '(' && condExpr[arglen] != '\0'; arglen += 1) continue; if (condExpr[arglen] != '\0') { val = Var_Parse(&condExpr[arglen - 1], VAR_CMD, FALSE, &length, &doFree); if (val == var_Error) { t = Err; } else { /* * A variable is empty when it just contains * spaces... 4/15/92, christos */ char *p; for (p = val; *p && isspace((unsigned char)*p); p++) continue; t = (*p == '\0') ? True : False; } if (doFree) { free(val); } /* * Advance condExpr to beyond the closing ). Note that * we subtract one from arglen + length b/c length * is calculated from condExpr[arglen - 1]. */ condExpr += arglen + length - 1; } else { condExpr -= 5; goto use_default; } break; } else if (strncmp(condExpr, "target", 6) == 0) { /* * Use CondDoTarget to evaluate the argument and * CondGetArg to extract the argument from the * 'function call'. */ evalProc = CondDoTarget; condExpr += 6; arglen = CondGetArg(&condExpr, &arg, "target", TRUE); if (arglen == 0) { condExpr -= 6; goto use_default; } } else { /* * The symbol is itself the argument to the default * function. We advance condExpr to the end of the symbol * by hand (the next whitespace, closing paren or * binary operator) and set to invert the evaluation * function if condInvert is TRUE. */ use_default: invert = condInvert; evalProc = condDefProc; arglen = CondGetArg(&condExpr, &arg, "", FALSE); } /* * Evaluate the argument using the set function. If invert * is TRUE, we invert the sense of the function. */ t = (!doEval || (* evalProc) (arglen, arg) ? (invert ? False : True) : (invert ? True : False)); free(arg); break; } } } else { t = condPushBack; condPushBack = None; } return (t); } /*- *----------------------------------------------------------------------- * CondT -- * Parse a single term in the expression. This consists of a terminal * symbol or Not and a terminal symbol (not including the binary * operators): * T -> defined(variable) | make(target) | exists(file) | symbol * T -> ! T | ( E ) * * Results: * True, False or Err. * * Side Effects: * Tokens are consumed. * *----------------------------------------------------------------------- */ static Token CondT(Boolean doEval) { Token t; t = CondToken(doEval); if (t == EndOfFile) { /* * If we reached the end of the expression, the expression * is malformed... */ t = Err; } else if (t == LParen) { /* * T -> ( E ) */ t = CondE(doEval); if (t != Err) { if (CondToken(doEval) != RParen) { t = Err; } } } else if (t == Not) { t = CondT(doEval); if (t == True) { t = False; } else if (t == False) { t = True; } } return (t); } /*- *----------------------------------------------------------------------- * CondF -- * Parse a conjunctive factor (nice name, wot?) * F -> T && F | T * * Results: * True, False or Err * * Side Effects: * Tokens are consumed. * *----------------------------------------------------------------------- */ static Token CondF(Boolean doEval) { Token l, o; l = CondT(doEval); if (l != Err) { o = CondToken(doEval); if (o == And) { /* * F -> T && F * * If T is False, the whole thing will be False, but we have to * parse the r.h.s. anyway (to throw it away). * If T is True, the result is the r.h.s., be it an Err or no. */ if (l == True) { l = CondF(doEval); } else { CondF(FALSE); } } else { /* * F -> T */ CondPushBack(o); } } return (l); } /*- *----------------------------------------------------------------------- * CondE -- * Main expression production. * E -> F || E | F * * Results: * True, False or Err. * * Side Effects: * Tokens are, of course, consumed. * *----------------------------------------------------------------------- */ static Token CondE(Boolean doEval) { Token l, o; l = CondF(doEval); if (l != Err) { o = CondToken(doEval); if (o == Or) { /* * E -> F || E * * A similar thing occurs for ||, except that here we make sure * the l.h.s. is False before we bother to evaluate the r.h.s. * Once again, if l is False, the result is the r.h.s. and once * again if l is True, we parse the r.h.s. to throw it away. */ if (l == False) { l = CondE(doEval); } else { CondE(FALSE); } } else { /* * E -> F */ CondPushBack(o); } } return (l); } /*- *----------------------------------------------------------------------- * Cond_Eval -- * Evaluate the conditional in the passed line. The line * looks like this: * # * where is any of if, ifmake, ifnmake, ifdef, * ifndef, elif, elifmake, elifnmake, elifdef, elifndef * and consists of &&, ||, !, make(target), defined(variable) * and parenthetical groupings thereof. * * Results: * COND_PARSE if should parse lines after the conditional * COND_SKIP if should skip lines after the conditional * COND_INVALID if not a valid conditional. * * Side Effects: * None. * *----------------------------------------------------------------------- */ int Cond_Eval(char *line) { struct If *ifp; Boolean isElse; Boolean value = FALSE; int level; /* Level at which to report errors. */ int lineno; level = PARSE_FATAL; lineno = curFile.lineno; for (line++; *line == ' ' || *line == '\t'; line++) { continue; } /* * Find what type of if we're dealing with. The result is left * in ifp and isElse is set TRUE if it's an elif line. */ if (line[0] == 'e' && line[1] == 'l') { line += 2; isElse = TRUE; } else if (strncmp(line, "endif", 5) == 0) { /* * End of a conditional section. If skipIfLevel is non-zero, that * conditional was skipped, so lines following it should also be * skipped. Hence, we return COND_SKIP. Otherwise, the conditional * was read so succeeding lines should be parsed (think about it...) * so we return COND_PARSE, unless this endif isn't paired with * a decent if. */ if (skipIfLevel != 0) { skipIfLevel -= 1; return (COND_SKIP); } else { if (condTop == MAXIF) { Parse_Error(level, "if-less endif"); return (COND_INVALID); } else { skipLine = FALSE; condTop += 1; return (COND_PARSE); } } } else { isElse = FALSE; } /* * Figure out what sort of conditional it is -- what its default * function is, etc. -- by looking in the table of valid "ifs" */ for (ifp = ifs; ifp->form != NULL; ifp++) { if (strncmp(ifp->form, line, ifp->formlen) == 0) { break; } } if (ifp->form == NULL) { /* * Nothing fit. If the first word on the line is actually * "else", it's a valid conditional whose value is the inverse * of the previous if we parsed. */ if (isElse && (line[0] == 's') && (line[1] == 'e')) { if (condTop == MAXIF) { Parse_Error(level, "if-less else"); return (COND_INVALID); } else if (skipIfLevel == 0) { value = !condStack[condTop]; lineno = condLineno[condTop]; } else { return (COND_SKIP); } } else { /* * Not a valid conditional type. No error... */ return (COND_INVALID); } } else { if (isElse) { if (condTop == MAXIF) { Parse_Error(level, "if-less elif"); return (COND_INVALID); } else if (skipIfLevel != 0) { /* * If skipping this conditional, just ignore the whole thing. * If we don't, the user might be employing a variable that's * undefined, for which there's an enclosing ifdef that * we're skipping... */ skipIfLineno[skipIfLevel - 1] = lineno; return (COND_SKIP); } } else if (skipLine) { /* * Don't even try to evaluate a conditional that's not an else if * we're skipping things... */ skipIfLineno[skipIfLevel] = lineno; skipIfLevel += 1; return (COND_SKIP); } /* * Initialize file-global variables for parsing */ condDefProc = ifp->defProc; condInvert = ifp->doNot; line += ifp->formlen; while (*line == ' ' || *line == '\t') { line++; } condExpr = line; condPushBack = None; switch (CondE(TRUE)) { case True: if (CondToken(TRUE) == EndOfFile) { value = TRUE; break; } goto err; /*FALLTHRU*/ case False: if (CondToken(TRUE) == EndOfFile) { value = FALSE; break; } /*FALLTHRU*/ case Err: err: Parse_Error(level, "Malformed conditional (%s)", line); return (COND_INVALID); default: break; } } if (!isElse) { condTop -= 1; } else if ((skipIfLevel != 0) || condStack[condTop]) { /* * If this is an else-type conditional, it should only take effect * if its corresponding if was evaluated and FALSE. If its if was * TRUE or skipped, we return COND_SKIP (and start skipping in case * we weren't already), leaving the stack unmolested so later elif's * don't screw up... */ skipLine = TRUE; return (COND_SKIP); } if (condTop < 0) { /* * This is the one case where we can definitely proclaim a fatal * error. If we don't, we're hosed. */ Parse_Error(PARSE_FATAL, "Too many nested if's. %d max.", MAXIF); return (COND_INVALID); } else { condStack[condTop] = value; condLineno[condTop] = lineno; skipLine = !value; return (value ? COND_PARSE : COND_SKIP); } } /*- *----------------------------------------------------------------------- * Cond_End -- * Make sure everything's clean at the end of a makefile. * * Results: * None. * * Side Effects: * Parse_Error will be called if open conditionals are around. * *----------------------------------------------------------------------- */ void Cond_End(void) { int level; if (condTop != MAXIF) { Parse_Error(PARSE_FATAL, "%d open conditional%s:", MAXIF - condTop + skipIfLevel, MAXIF - condTop + skipIfLevel== 1 ? "" : "s"); for (level = skipIfLevel; level > 0; level--) Parse_Error(PARSE_FATAL, "\t%*sat line %d (skipped)", MAXIF - condTop + level + 1, "", skipIfLineno[level - 1]); for (level = condTop; level < MAXIF; level++) Parse_Error(PARSE_FATAL, "\t%*sat line %d " "(evaluated to %s)", MAXIF - level + skipIfLevel, "", condLineno[level], condStack[level] ? "true" : "false"); } condTop = MAXIF; } Index: head/usr.bin/make/dir.c =================================================================== --- head/usr.bin/make/dir.c (revision 138560) +++ head/usr.bin/make/dir.c (revision 138561) @@ -1,1265 +1,1264 @@ /* * 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 */ 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; 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) +DirFindName(const void *p, const void *dname) { - return (strcmp(((Path *)p)->name, dname)); + return (strcmp(((const 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) { 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) { 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) { 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) { 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) { char *p1; /* pointer into p->name */ char *p2; /* pointer into name */ 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) { 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) { char *str; /* the string which will be returned */ char *tstr; /* the current directory preceded by 'flag' */ char *nstr; 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); nstr = str_concat(str, tstr, STR_ADDSPACE); free(str); free(tstr); str = nstr; } 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; 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) { 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) { 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; 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) { Lst_ForEach(path, DirPrintDir, (void *)NULL); } Index: head/usr.bin/make/job.c =================================================================== --- head/usr.bin/make/job.c (revision 138560) +++ head/usr.bin/make/job.c (revision 138561) @@ -1,2788 +1,2787 @@ /* * 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 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 * 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) +JobCmpPid(const void *job, const void *pid) { - return (*(int *)pid - ((Job *)job)->pid); + return (*(const int *)pid - ((const 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 */ 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); 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 */ 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; 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 */ 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 */ 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/lst.h =================================================================== --- head/usr.bin/make/lst.h (revision 138560) +++ head/usr.bin/make/lst.h (revision 138561) @@ -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; /* * 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 */ /* * 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 * *just* opened */ LstNode *prevPtr; /* Previous node, if open. Used by * Lst_Remove */ }; typedef struct Lst Lst; -typedef int CompareProc(void *, void *); +typedef int CompareProc(const void *, const 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); /* Duplicate an existing list */ Lst *Lst_Duplicate(Lst *, DuplicateProc *); /* Destroy an old one */ void Lst_Destroy(Lst *, FreeProc *); /* * Functions to modify a list */ /* Insert an element before another */ ReturnStatus Lst_Insert(Lst *, LstNode *, void *); /* Insert an element after another */ 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 *); /* 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); /* * 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 *, const void *, CompareProc *); /* * See if the given datum is on the list. Returns the LstNode containing * the datum */ LstNode *Lst_Member(Lst *, void *); /* Apply a function to all elements of a lst */ 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 *); /* * 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 *); /* Next element please */ LstNode *Lst_Next(Lst *); /* Done yet? */ Boolean Lst_IsAtEnd(Lst *); /* Finish table access */ 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 *); /* * 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/lstFindFrom.c =================================================================== --- head/usr.bin/make/lst.lib/lstFindFrom.c (revision 138560) +++ head/usr.bin/make/lst.lib/lstFindFrom.c (revision 138561) @@ -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) +Lst_FindFrom(Lst *l, LstNode *ln, const void *d, CompareProc *cProc) { 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/main.c =================================================================== --- head/usr.bin/make/main.c (revision 138560) +++ head/usr.bin/make/main.c (revision 138561) @@ -1,1133 +1,1134 @@ /* * 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 */ 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 Boolean expandVars; /* fully expand printed variables */ 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 */ 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 int ReadMakefile(const void *, const 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 */ 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 */ 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; 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; 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; 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; 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) +ReadMakefile(const void *p, const void *q __unused) { char *fname; /* makefile to read */ FILE *stream; char *name, path[MAXPATHLEN]; char *MAKEFILE; int setMAKEFILE; - fname = p; + /* XXX - remove this once constification is done */ + fname = estrdup(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/suff.c =================================================================== --- head/usr.bin/make/suff.c (revision 138560) +++ head/usr.bin/make/suff.c (revision 138561) @@ -1,2356 +1,2370 @@ /* * 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 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 * 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 */ } 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 */ #endif } Src; /* * A structure for passing more than one argument to the Lst-library-invoked * function... */ typedef struct { 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 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 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 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) +static char * +SuffStrIsPrefix(const 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) +SuffSuffIsSuffix(const Suff *s, char *str) { - char *p1; /* Pointer into suffix name */ + const 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. * + * XXX use the function above once constification is complete. *----------------------------------------------------------------------- */ static int -SuffSuffIsSuffixP(void *s, void *str) +SuffSuffIsSuffixP(const void *is, const void *str) { + const Suff *s = is; + const char *p1; /* Pointer into suffix name */ + const char *p2 = str; /* Pointer into string being examined */ - return (!SuffSuffIsSuffix(s, str)); + p1 = s->name + s->nameLen; + + while (p1 >= s->name && *p1 == *p2) { + p1--; + p2--; + } + + return (p1 != s->name - 1); } /*- *----------------------------------------------------------------------- * 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) +SuffSuffHasNameP(const void *s, const void *sname) { - return (strcmp(sname, ((Suff *)s)->name)); + return (strcmp(sname, ((const 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 + * + * XXX use the function above once constification is complete. *----------------------------------------------------------------------- */ static int -SuffSuffIsPrefix(void *s, void *str) +SuffSuffIsPrefix(const void *s, const void *istr) { + const char *pref = ((const Suff *)s)->name; + const char *str = istr; - return (SuffStrIsPrefix(((Suff *)s)->name, str) == NULL ? 1 : 0); + while (*str != '\0' && *pref == *str) { + pref++; + str++; + } + + return (*pref != '\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) +SuffGNHasNameP(const void *gn, const void *name) { - return (strcmp(name, ((GNode *)gn)->name)); + return (strcmp(name, ((const 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) { 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) { 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*/ Suff *src; /* Source of transformation */ 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 * 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 */ 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; 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; 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 * Suff_GetPath(char *sname) { 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 */ 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; 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; 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) { 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) { 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); 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) { 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) { 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 */ 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(); 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 */ /* * 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 */ 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) { 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; /* * 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) { 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 * 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) { 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; 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; 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/var.c =================================================================== --- head/usr.bin/make/var.c (revision 138560) +++ head/usr.bin/make/var.c (revision 138561) @@ -1,1965 +1,1964 @@ /* * 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 */ #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) +VarCmp(const void *v, const void *name) { - return (strcmp(name, ((Var *)v)->name)); + return (strcmp(name, ((const 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; 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; 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); }