/* msub - Read file(s) and perform substitutions using values of variables defined in makefile. Syntax: msub [ -e ] [ -f makefile ] [ +Rstr -Rstr] [ file ... ] -e Environment variables override makefile variables. -f makefile Specify alternate makefile. Multiple -f options may be given. +Rstr Specify variable reference initiator string. -Rstr Specify variable reference terminator string. +R and -R options must be given in pairs, although multiple pairs may be specified. 27 Mar 1990 Paul DuBois dubois@primate.wisc.edu 27 Mar 1990 V1.0. Created. 16 Apr 1993 V1.1. Rewritten. 25 Oct 1993 V1.11. Added support for -e and var=value on command line. 18 Sep 1998 Bert Gijsbers bert@xpilot.org Added unistd, stdio, stdlib, string header files. Commented out malloc declaration (now in stdlib.h). Removed unused variable pi. 04 Oct 1999 Bert Gijsbers bert@xpilot.org Also commented out other prototype declarations for strcpy, strncpy, strcat and getenv. 19 May 2001 Bert Gijsbers bert@xpilot.org Protoized functions. $Id: msub.c,v 5.1 2001/05/19 11:12:23 bertg Exp $ */ #include #include #include #include #include #include #include #define bufSiz 2048 #define FromMakefile(vp) (vp->source == precMakefile) /* extern char *malloc (); */ /* extern char *strcpy (); */ /* extern char *strncpy (); */ /* extern char *strcat (); */ /* extern char *getenv (); */ typedef struct Var Var; struct Var { char *var; char *value; int expanded; int source; Var *next; }; static void ReadMake (FILE *f); static int CheckAssignment (char *s, int source); static Var *AddVar (char *var, char *value, int source); static Var *FindVar (char *var); static Var *FindVarUsingEnv (char *var); static int FindVarRef (char *s, int nRefSeqs, char **initRef, char **termRef); static int FindEndVarRef (char *s, int idx, char *term); static void Expand (Var *vp); static void Substitute (FILE *f); static char *Malloc(int size); static char *NewString(char *s); static void Panic (char *s); static char *usage = "Usage: msub [ +Rstr -Rstr] [ -f makefile ] file"; static char *makefile = (char *) NULL; static Var *vvList = (Var *) NULL; static int nVars = 0; static char *mfRefInit[2] = { "${", "$(" }; static char *mfRefTerm[2] = { "}", ")" }; static char *usrRefInit[10]; static char *usrRefTerm[10]; static int usrRefSeqCnt = 0; static char **refInit; static char **refTerm; static int refSeqCnt; static int refInitIdx; static int varIdx; static int refTermIdx; static int restIdx; /* * Assignment source precedences. Command line assignments always override * assignments from Makefile or environment variable values. Normally * assignments with Makefiles override environment variables, but if -e is * specified on the command line, their precedences are reversed. */ static int precCmdLine = 3; static int precMakefile = 2; static int precEnvVar = 1; int main (int argc, char **argv) { FILE *f; Var *vp; char *p, *q; int pass; int needExpand; int toggle = 0; --argc; ++argv; while (argc > 0) { /* see if it's a var=value argument */ if (CheckAssignment (argv[0], precCmdLine)) { --argc; ++argv; continue; } /* if not, and it's not a flag, stop processing arguments */ if (argv[0][0] != '-' && argv[0][0] != '+') break; if (strcmp (argv[0], "-e") == 0) { int tmp; /* reverse precedence of makefile/env. assignments */ tmp = precMakefile; precMakefile = precEnvVar; precEnvVar = tmp; } else if (strcmp (argv[0], "-f") == 0) { if (argc < 2) Panic (usage); if ((f = fopen (makefile = argv[1], "r")) == (FILE *) NULL) Panic ("cannot open makefile argument"); ReadMake (f); (void) fclose (f); --argc; ++argv; } else if (strncmp (argv[0], "+R", 2) == 0) { if (toggle == 1) Panic ("missing -R argument after +R"); toggle = 1; if (strlen (argv[0]) == 2) Panic ("empty reference initiator specified"); usrRefInit[usrRefSeqCnt] = &argv[0][2]; } else if (strncmp (argv[0], "-R", 2) == 0) { if (toggle == 0) Panic ("missing +R argument before -R"); toggle = 0; if (strlen (argv[0]) == 2) Panic ("empty reference terminator specified"); usrRefTerm[usrRefSeqCnt] = &argv[0][2]; ++usrRefSeqCnt; } else Panic (usage); /* bad flag */ --argc; ++argv; } if (toggle == 1) Panic ("missing -R argument after +R"); if (usrRefSeqCnt == 0) /* no reference sequences specified */ { refInit = mfRefInit; refTerm = mfRefTerm; refSeqCnt = 2; } else { refInit = usrRefInit; refTerm = usrRefTerm; refSeqCnt = usrRefSeqCnt; } if (makefile == (char *) NULL) /* no -f options were given */ { if ((f = fopen ("makefile", "r")) == (FILE *) NULL) { if ((f = fopen ("Makefile", "r")) == (FILE *) NULL) Panic ("cannot open makefile or Makefile"); } ReadMake (f); (void) fclose (f); } /* Replace instances of '$$' with a single ^A */ /* (only for assignments from Makefile) */ for (vp = vvList; vp != (Var *) NULL; vp = vp->next) { if (!FromMakefile (vp)) continue; for (p = q = vp->value; *p != '\0'; p++, q++) { *q = *p; if (*p == '$' && *(p+1) == '$') { *q = '\01'; p++; } } *q = '\0'; } /* determine which variable values contain embedded references */ for (vp = vvList; vp != (Var *) NULL; vp = vp->next) { if (!FromMakefile (vp)) vp->expanded = 1; else vp->expanded = !FindVarRef (vp->value, 2, mfRefInit, mfRefTerm); } /* expand values to eliminate embedded references */ for (pass = 0; pass < nVars; pass++) { needExpand = 0; for (vp = vvList; vp != (Var *) NULL; vp = vp->next) { if (!vp->expanded) Expand (vp); if (!vp->expanded) needExpand = 1; } if (needExpand == 0) /* loop while values need expanding */ break; } /* sanity check: shouldn't have to make more than nVars passes */ if (pass >= nVars) Panic ("Too many expansion passes. Something's wrong!"); /* Replace instances of ^A with a '$' */ /* (only for assignments from Makefile) */ for (vp = vvList; vp != (Var *) NULL; vp = vp->next) { if (!FromMakefile (vp)) continue; for (p = vp->value; *p != '\0'; p++) { if (*p == '\01') *p = '$'; } } /* read source file(s) and perform substitutions */ if (argc == 0) Substitute (stdin); else while (argc > 0) { if ((f = fopen (*argv, "r")) == (FILE *) NULL) { (void) fprintf (stderr, "cannot open \"%s\".\n", *argv); Panic ("Quitting."); } Substitute (f); (void) fclose (f); --argc; ++argv; } return (0); } /* * Read a makefile, combine continuation lines (by joining lines with a space * and stripping leading whitespace on next line), null-terminate each line, * find variable assignments */ static void ReadMake (FILE *f) { struct stat st; size_t mfSize; int fd; char *p, *s; char *sNext; char *mfBuf; /* makefile buffer */ fd = fileno (f); if (fstat (fd, &st) != 0) Panic ("cannot stat Makefile"); mfSize = st.st_size; mfBuf = Malloc (mfSize + 1); if (read (fd, mfBuf, mfSize) != mfSize) Panic ("cannot read Makefile"); mfBuf[mfSize] = '\0'; /* make sure it's null-terminated */ p = s = mfBuf; while (s <= mfBuf + mfSize) { if (*s == '\\' && *(s+1) == '\n') { *p++ = ' '; s += 2; /* skip continuation characters */ while (isspace (*s)) ++s; } else { if (*s == '\n') /* convert linefeeds to nulls */ *s = '\0'; *p++ = *s++; } } *p = '\0'; s = mfBuf; while (s <= p) { /* need sNext because CheckAssignment() modifies current line */ sNext = s + strlen (s) + 1; /* find next line */ (void) CheckAssignment (s, precMakefile); s = sNext; } free (mfBuf); /* done with Makefile; jettison it */ } /* * Determine whether the line contains a variable assignment, i.e., * an identifier, an equals sign, and optionally a value following. * * If the value contains a comment afterward, the comment becomes * part of the value. This is a bug, but it's too much trouble to * do the parsing necessary to find such things. Just don't use them. * * This is called to process assignments from command line arguments and * from the makefile. * * Return non-zero if assignment was found, zero otherwise. */ static int CheckAssignment (char *s, int source) { char name[bufSiz], *np; int len; while (isspace (*s)) /* skip whitespace */ ++s; if (!isalpha (*s) && *s != '_') /* no variable */ return (0); np = name; while (isalnum (*s) || *s == '_') *np++ = *s++; *np = '\0'; while (isspace (*s)) /* skip whitespace */ ++s; if (*s != '=') /* no '=' */ return (0); ++s; /* skip '=' */ while (isspace (*s)) /* skip whitespace */ ++s; /* take rest of line as definition after trimming trailing whitespace */ len = strlen (s); while (len > 0 && isspace (s[len-1])) s[--len] = '\0'; (void) AddVar (name, s, source); return (1); } static Var * FindVar (char *var) { Var *vp; for (vp = vvList; vp != (Var *) NULL; vp = vp->next) { if (strcmp (vp->var, var) == 0) return (vp); } return ((Var *) NULL); } /* * Find a variable and return pointer to Var structure. Never returns NULL. * If the variable isn't found, create an instance (either with an empty * value or the value from the environment variable of the same name if one * exists). If the variable is found and is from the makefile, override it * from the environment if environment variabless have higher precedence (i.e., * if -e was specified on the command line). */ static Var * FindVarUsingEnv (char *var) { Var *vp; char *val; if ((vp = FindVar (var)) == (Var *) NULL) { if ((val = (char *) getenv (var)) == (char *) NULL) val = ""; vp = AddVar (var, val, precEnvVar); } else if (FromMakefile (vp) && precMakefile < precEnvVar) { if ((val = (char *) getenv (var)) != (char *) NULL) vp = AddVar (var, val, precEnvVar); } return (vp); } static Var * AddVar (char *var, char *value, int source) { Var *vp; if ((vp = FindVar (var)) == (Var *) NULL) { vp = (Var *) Malloc (sizeof (Var)); vp->var = NewString (var); vp->value = NewString (value); vp->source = source; vp->expanded = 0; vp->next = vvList; vvList = vp; ++nVars; } else if (vp->source <= source) /* exists; replace previous value if */ { /* new source has higher priority */ free (vp->value); vp->value = NewString (value); vp->source = source; } return (vp); } /* Find a variable reference in a string. Return zero if no reference found. Otherwise return non-zero and set four globals: refInitIdx index within s of beginning of reference initiator varIdx index within s of beginning of variable refTermIdx index within s of beginning of reference terminator restIdx index within s of rest of string following reference */ static int FindVarRef (char *s, int nRefSeqs, char **initRef, char **termRef) { char *p; int len, i; p = s; while (*p != '\0') { /* look for reference initiation sequence */ for (i = 0; i < nRefSeqs; i++) { len = strlen (initRef[i]); if (strncmp (p, initRef[i], len) == 0 && FindEndVarRef (s, p-s+len, termRef[i])) { refInitIdx = p - s; varIdx = p - s + len; return (1); } } ++p; } return (0); } static int FindEndVarRef (char *s, int idx, char *term) { char *p = s + idx; /* point to first char past reference initiator */ int len = strlen (term); if (!isalpha (*p) && *p != '_') /* no variable name present */ return (0); while (isalnum (*p) || *p == '_') { if (strncmp (p+1, term, len) == 0) { refTermIdx = p + 1 - s; restIdx = refTermIdx + len; return (1); } ++p; } return (0); } /* Look for variable references in a variable value and expand them when possible. If a variable is referenced that has not itself been fully expanded, defer expansion until another pass, at which time the referenced variable might then be expanded. This prevents infinite expansions on circular references. */ static void Expand (Var *vp) { Var *vp2; char buf[bufSiz * 4], *p; while (FindVarRef (vp->value, 2, mfRefInit, mfRefTerm)) { (void) strncpy (buf, vp->value+varIdx, refTermIdx-varIdx); buf[refTermIdx-varIdx] = '\0'; if ((vp2 = FindVarUsingEnv (buf)) != (Var *) NULL) { if (!vp2->expanded) return; /* can't substitute yet */ p = vp2->value; } else p = ""; /* substitute value for reference, replace value */ (void) strncpy (buf, vp->value, refInitIdx); (void) strcpy (buf+refInitIdx, p); (void) strcpy (buf+refInitIdx+strlen(p), vp->value+restIdx); free (vp->value); vp->value = NewString (buf); } vp->expanded = 1; /* no more embedded references */ } /* Read through file, substituting variable values for variable references. If a variable reference is found that is for an unknown variable, substitute the empty string. This really should check write() return values... */ static void Substitute (FILE *f) { Var *vp; char buf[bufSiz * 4], name[bufSiz], *p; while (fgets (buf, (int) sizeof (buf), f) != (char *) NULL) { p = buf; while (FindVarRef (p, refSeqCnt, refInit, refTerm)) { (void) write (1, p, refInitIdx); (void) strncpy (name, p+varIdx, refTermIdx-varIdx); name[refTermIdx-varIdx] = '\0'; if ((vp = FindVarUsingEnv (name)) != (Var *) NULL) (void) write (1, vp->value, strlen (vp->value)); p += restIdx; } (void) write (1, p, strlen (p)); } } /* Allocate space. */ static char *Malloc (int size) { char *p; if ((p = (char *) malloc (size)) == (char *) NULL) Panic ("Malloc: out of space"); return (p); } /* Allocate space for a string, copy the string into it, and return a pointer to the copy. */ static char *NewString (char *s) { return (strcpy (Malloc (strlen (s) + 1), s)); } static void Panic (char *s) { (void) fprintf (stderr, "msub: %s\n", s); exit (1); }