diff options
-rw-r--r-- | usr.bin/make/Makefile | 3 | ||||
-rw-r--r-- | usr.bin/make/job.c | 473 | ||||
-rw-r--r-- | usr.bin/make/job.h | 3 | ||||
-rw-r--r-- | usr.bin/make/main.c | 1 | ||||
-rw-r--r-- | usr.bin/make/parse.c | 3 | ||||
-rw-r--r-- | usr.bin/make/shell.c | 472 | ||||
-rw-r--r-- | usr.bin/make/shell.h | 110 |
7 files changed, 588 insertions, 477 deletions
diff --git a/usr.bin/make/Makefile b/usr.bin/make/Makefile index 3cb53693..7252a25 100644 --- a/usr.bin/make/Makefile +++ b/usr.bin/make/Makefile @@ -5,7 +5,8 @@ PROG= make CFLAGS+=-I${.CURDIR} SRCS= arch.c buf.c cond.c dir.c for.c hash.c hash_tables.c job.c \ - lst.c main.c make.c parse.c str.c suff.c targ.c util.c var.c + lst.c main.c make.c parse.c shell.c str.c suff.c targ.c util.c \ + var.c NO_WERROR= WARNS?= 3 diff --git a/usr.bin/make/job.c b/usr.bin/make/job.c index 6567afd..e4e04e5 100644 --- a/usr.bin/make/job.c +++ b/usr.bin/make/job.c @@ -76,10 +76,6 @@ __FBSDID("$FreeBSD$"); * * 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 FALSE 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 @@ -138,6 +134,7 @@ __FBSDID("$FreeBSD$"); #include "make.h" #include "parse.h" #include "pathnames.h" +#include "shell.h" #include "str.h" #include "suff.h" #include "targ.h" @@ -268,60 +265,6 @@ typedef struct Job { TAILQ_HEAD(JobList, Job); /* - * Shell Specifications: - * - * Some special stuff goes on if a shell doesn't have error control. In such - * a case, errCheck becomes a printf template for echoing the command, - * should echoing be on and ignErr becomes another printf template for - * executing the command while ignoring the return status. If either of these - * strings is empty when hasErrCtl is FALSE, the command will be executed - * anyway as is and if it causes an error, so be it. - */ -struct Shell { - TAILQ_ENTRY(Shell) link; /* link all shell descriptions */ - - /* - * the name of the shell. For Bourne and C shells, this is used - * only to find the shell description when used as the single - * source of a .SHELL target. For user-defined shells, this is - * the full path of the shell. - */ - char *name; - char *path; - - /* True if both echoOff and echoOn defined */ - Boolean hasEchoCtl; - - char *echoOff; /* command to turn off echo */ - char *echoOn; /* command to turn it back on */ - - /* - * What the shell prints, when given the echo-off command. - * This line will not be printed when received from the shell. - * This is usually the command which was executed to turn off echoing. - */ - char *noPrint; - - /* set if can control error checking for individual commands */ - Boolean hasErrCtl; - - /* string to turn error checking on */ - char *errCheck; - - /* string to turn off error checking */ - char *ignErr; - - char *echo; /* command line flag: echo commands */ - char *exit; /* command line flag: exit on error */ - - ArgArray builtins; - char *meta; - - Boolean unsetenv; /* unsetenv("ENV") before exec */ -}; -TAILQ_HEAD(Shells, Shell); - -/* * error handling variables */ static int errors = 0; /* number of errors reported */ @@ -358,76 +301,6 @@ static int numCommands; #define JOB_STOPPED 3 /* The job is stopped */ /* - * Descriptions for various shells. What the list of builtins should contain - * is debatable: either all builtins or only those which may specified on - * a single line without use of meta-characters. For correct makefiles that - * contain only correct command lines there is no difference. But if a command - * line, for example, is: 'if -foo bar' and there is an executable named 'if' - * in the path, the first possibility would execute that 'if' while in the - * second case the shell would give an error. Histerically only a small - * subset of the builtins and no reserved words where given in the list which - * corresponds roughly to the first variant. So go with this but add missing - * words. - */ -#define CSH_BUILTINS \ - "alias cd eval exec exit read set ulimit unalias " \ - "umask unset wait" - -#define SH_BUILTINS \ - "alias cd eval exec exit read set ulimit unalias " \ - "umask unset wait" - -#define CSH_META "#=|^(){};&<>*?[]:$`\\@\n" -#define SH_META "#=|^(){};&<>*?[]:$`\\\n" - -static const char *const shells_init[] = { - /* - * 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. - */ - "name=csh path='" PATH_DEFSHELLDIR "/csh' " - "quiet='unset verbose' echo='set verbose' filter='unset verbose' " - "hasErrCtl=N check='echo \"%s\"\n' ignore='csh -c \"%s || exit 0\"' " - "echoFlag=v errFlag=e " - "meta='" CSH_META "' builtins='" CSH_BUILTINS "'", - - /* - * SH description. Echo control is also possible and, under - * sun UNIX anyway, one can even control error checking. - */ - "name=sh path='" PATH_DEFSHELLDIR "/sh' " - "quiet='set -' echo='set -v' filter='set -' " - "hasErrCtl=Y check='set -e' ignore='set +e' " - "echoFlag=v errFlag=e " - "meta='" SH_META "' builtins='" SH_BUILTINS "'", - - /* - * KSH description. The Korn shell has a superset of - * the Bourne shell's functionality. There are probably builtins - * missing here. - */ - "name=ksh path='" PATH_DEFSHELLDIR "/ksh' " - "quiet='set -' echo='set -v' filter='set -' " - "hasErrCtl=Y check='set -e' ignore='set +e' " - "echoFlag=v errFlag=e " - "meta='" SH_META "' builtins='" SH_BUILTINS "' unsetenv=T", - - NULL -}; - -/* - * This is the shell to which we pass all commands in the Makefile. - * It is set by the Job_ParseShell function. - */ -static struct Shell *commandShell = NULL; - -/* - * This is the list of all known shells. - */ -static struct Shells shells = TAILQ_HEAD_INITIALIZER(shells); - -/* * The maximum number of jobs that may run. This is initialize from the * -j argument for the leading make and from the FIFO for sub-makes. */ @@ -527,12 +400,10 @@ typedef struct ProcStuff { static void JobRestart(Job *); static int JobStart(GNode *, int, Job *); static void JobDoOutput(Job *, Boolean); -static struct Shell *JobMatchShell(const char *); static void JobInterrupt(int, int); static void JobRestartJobs(void); static void ProcExec(const ProcStuff *) __dead2; static int Compat_RunCommand(char *, struct GNode *); -static void JobShellDump(const struct Shell *) __unused; static GNode *curTarg = NULL; static GNode *ENDNode; @@ -2531,199 +2402,6 @@ Job_Make(GNode *gn) JobStart(gn, 0, NULL); } -static int -sort_builtins(const void *p1, const void *p2) -{ - - return (strcmp(*(const char* const*)p1, *(const char* const*)p2)); -} - -/** - * JobFreeShell - * Free a shell structure and all associated strings. - */ -static void -JobFreeShell(struct Shell *sh) -{ - - if (sh != NULL) { - free(sh->name); - free(sh->path); - free(sh->echoOff); - free(sh->echoOn); - free(sh->noPrint); - free(sh->errCheck); - free(sh->ignErr); - free(sh->echo); - free(sh->exit); - ArgArray_Done(&sh->builtins); - free(sh->meta); - free(sh); - } -} - -/** - * Dump a shell specification to stderr. - */ -static void -JobShellDump(const struct Shell *sh) -{ - int i; - - fprintf(stderr, "Shell %p:\n", sh); - fprintf(stderr, " name='%s' path='%s'\n", sh->name, sh->path); - fprintf(stderr, " hasEchoCtl=%d echoOff='%s' echoOn='%s'\n", - sh->hasEchoCtl, sh->echoOff, sh->echoOn); - fprintf(stderr, " noPrint='%s'\n", sh->noPrint); - fprintf(stderr, " hasErrCtl=%d errCheck='%s' ignErr='%s'\n", - sh->hasErrCtl, sh->errCheck, sh->ignErr); - fprintf(stderr, " echo='%s' exit='%s'\n", sh->echo, sh->exit); - fprintf(stderr, " builtins=%d\n", sh->builtins.argc - 1); - for (i = 1; i < sh->builtins.argc; i++) - fprintf(stderr, " '%s'", sh->builtins.argv[i]); - fprintf(stderr, "\n meta='%s'\n", sh->meta); - fprintf(stderr, " unsetenv=%d\n", sh->unsetenv); -} - -/** - * Parse a shell specification line and return the new Shell structure. - * In case of an error a message is printed and NULL is returned. - */ -static struct Shell * -JobParseShellSpec(const char *spec, Boolean *fullSpec) -{ - ArgArray aa; - struct Shell *sh; - char *eq; - char *keyw; - int arg; - - *fullSpec = FALSE; - - sh = emalloc(sizeof(*sh)); - memset(sh, 0, sizeof(*sh)); - ArgArray_Init(&sh->builtins); - - /* - * Parse the specification by keyword but skip the first word - */ - brk_string(&aa, spec, TRUE); - - for (arg = 1; arg < aa.argc; arg++) { - /* - * Split keyword and value - */ - keyw = aa.argv[arg]; - if ((eq = strchr(keyw, '=')) == NULL) { - Parse_Error(PARSE_FATAL, "missing '=' in shell " - "specification keyword '%s'", keyw); - ArgArray_Done(&aa); - JobFreeShell(sh); - return (NULL); - } - *eq++ = '\0'; - - if (strcmp(keyw, "path") == 0) { - free(sh->path); - sh->path = estrdup(eq); - } else if (strcmp(keyw, "name") == 0) { - free(sh->name); - sh->name = estrdup(eq); - } else if (strcmp(keyw, "quiet") == 0) { - free(sh->echoOff); - sh->echoOff = estrdup(eq); - *fullSpec = TRUE; - } else if (strcmp(keyw, "echo") == 0) { - free(sh->echoOn); - sh->echoOn = estrdup(eq); - *fullSpec = TRUE; - } else if (strcmp(keyw, "filter") == 0) { - free(sh->noPrint); - sh->noPrint = estrdup(eq); - *fullSpec = TRUE; - } else if (strcmp(keyw, "echoFlag") == 0) { - free(sh->echo); - sh->echo = estrdup(eq); - *fullSpec = TRUE; - } else if (strcmp(keyw, "errFlag") == 0) { - free(sh->exit); - sh->exit = estrdup(eq); - *fullSpec = TRUE; - } else if (strcmp(keyw, "hasErrCtl") == 0) { - sh->hasErrCtl = (*eq == 'Y' || *eq == 'y' || - *eq == 'T' || *eq == 't'); - *fullSpec = TRUE; - } else if (strcmp(keyw, "check") == 0) { - free(sh->errCheck); - sh->errCheck = estrdup(eq); - *fullSpec = TRUE; - } else if (strcmp(keyw, "ignore") == 0) { - free(sh->ignErr); - sh->ignErr = estrdup(eq); - *fullSpec = TRUE; - } else if (strcmp(keyw, "builtins") == 0) { - ArgArray_Done(&sh->builtins); - brk_string(&sh->builtins, eq, TRUE); - qsort(sh->builtins.argv + 1, sh->builtins.argc - 1, - sizeof(char *), sort_builtins); - *fullSpec = TRUE; - } else if (strcmp(keyw, "meta") == 0) { - free(sh->meta); - sh->meta = estrdup(eq); - *fullSpec = TRUE; - } else if (strcmp(keyw, "unsetenv") == 0) { - sh->unsetenv = (*eq == 'Y' || *eq == 'y' || - *eq == 'T' || *eq == 't'); - *fullSpec = TRUE; - } else { - Parse_Error(PARSE_FATAL, "unknown keyword in shell " - "specification '%s'", keyw); - ArgArray_Done(&aa); - JobFreeShell(sh); - return (NULL); - } - } - ArgArray_Done(&aa); - - /* - * Some checks (could be more) - */ - if (*fullSpec) { - if ((sh->echoOn != NULL) ^ (sh->echoOff != NULL)) { - Parse_Error(PARSE_FATAL, "Shell must have either both " - "echoOff and echoOn or none of them"); - JobFreeShell(sh); - return (NULL); - } - - if (sh->echoOn != NULL && sh->echoOff != NULL) - sh->hasEchoCtl = TRUE; - } - - return (sh); -} - -/** - * Parse the builtin shell specifications and put them into the shell - * list. Then select the default shell to be the current shell. This - * is called from main() before any parsing (including MAKEFLAGS and - * command line) is done. - */ -void -Shell_Init(void) -{ - int i; - struct Shell *sh; - Boolean fullSpec; - - for (i = 0; shells_init[i] != NULL; i++) { - sh = JobParseShellSpec(shells_init[i], &fullSpec); - TAILQ_INSERT_TAIL(&shells, sh, link); - if (strcmp(sh->name, DEFSHELLNAME) == 0) - commandShell = sh; - } -} - /** * Job_Init * Initialize the process module, given a maximum number of jobs. @@ -2920,155 +2598,6 @@ Job_Empty(void) } /** - * Find a matching shell in 'shells' given its final component. - * - * Results: - * A pointer to a freshly allocated Shell structure with the contents - * from static description or NULL if no shell with the given name - * is found. - */ -static struct Shell * -JobMatchShell(const char *name) -{ - struct Shell *sh; - - TAILQ_FOREACH(sh, &shells, link) - if (strcmp(sh->name, name) == 0) - return (sh); - - return (NULL); -} - -/** - * Job_ParseShell - * Parse a shell specification and set up commandShell appropriately. - * - * Results: - * TRUE if the specification was correct. FALSE otherwise. - * - * Side Effects: - * commandShell points to a Shell structure (either predefined or - * created from the shell spec). - * - * 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. - * builtins A space separated list of builtins. If one - * of these builtins is detected when make wants - * to execute a command line, the command line is - * handed to the shell. Otherwise make may try to - * execute the command directly. If this list is empty - * it is assumed, that the command must always be - * handed over to the shell. - * meta The shell meta characters. If this is not specified - * or empty, commands are alway passed to the shell. - * Otherwise they are not passed when they contain - * neither a meta character nor a builtin command. - */ -Boolean -Job_ParseShell(const char line[]) -{ - Boolean fullSpec; - struct Shell *sh; - struct Shell *match; - - /* parse the specification */ - if ((sh = JobParseShellSpec(line, &fullSpec)) == NULL) - return (FALSE); - - if (sh->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. - */ - if (sh->name == NULL) { - Parse_Error(PARSE_FATAL, - "Neither path nor name specified"); - JobFreeShell(sh); - return (FALSE); - } - if (fullSpec) { - Parse_Error(PARSE_FATAL, "No path specified"); - JobFreeShell(sh); - return (FALSE); - } - if ((match = JobMatchShell(sh->name)) == NULL) { - Parse_Error(PARSE_FATAL, "%s: no matching shell", - sh->name); - JobFreeShell(sh); - return (FALSE); - } - JobFreeShell(sh); - commandShell = match; - - return (TRUE); - } - - /* - * 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. - */ - if (sh->name == NULL) { - /* get the base name as the name */ - if ((sh->name = strrchr(sh->path, '/')) == NULL) { - sh->name = estrdup(sh->path); - } else { - sh->name = estrdup(sh->name + 1); - } - } - - if (!fullSpec) { - if ((match = JobMatchShell(sh->name)) == NULL) { - Parse_Error(PARSE_FATAL, - "%s: no matching shell", sh->name); - JobFreeShell(sh); - return (FALSE); - } - - /* set the patch on the matching shell */ - free(match->path); - match->path = sh->path; - sh->path = NULL; - - JobFreeShell(sh); - commandShell = match; - return (TRUE); - } - - TAILQ_INSERT_HEAD(&shells, sh, link); - - /* set the new shell */ - commandShell = sh; - return (TRUE); -} - -/** * JobInterrupt * Handle the receipt of an interrupt. * diff --git a/usr.bin/make/job.h b/usr.bin/make/job.h index d479211..1ef4b66 100644 --- a/usr.bin/make/job.h +++ b/usr.bin/make/job.h @@ -64,15 +64,12 @@ void Job_Make(struct GNode *); void Job_Init(int); Boolean Job_Full(void); Boolean Job_Empty(void); -Boolean Job_ParseShell(const char []); int Job_Finish(void); void Job_Wait(void); void Job_AbortAll(void); void Proc_Init(void); -void Shell_Init(void); - struct Buffer *Cmd_Exec(const char *, const char **); void Compat_Run(struct Lst *); diff --git a/usr.bin/make/main.c b/usr.bin/make/main.c index cbc2a30..f2eb77c 100644 --- a/usr.bin/make/main.c +++ b/usr.bin/make/main.c @@ -86,6 +86,7 @@ __FBSDID("$FreeBSD$"); #include "make.h" #include "parse.h" #include "pathnames.h" +#include "shell.h" #include "str.h" #include "suff.h" #include "targ.h" diff --git a/usr.bin/make/parse.c b/usr.bin/make/parse.c index 2f0a425..4d66103 100644 --- a/usr.bin/make/parse.c +++ b/usr.bin/make/parse.c @@ -90,6 +90,7 @@ __FBSDID("$FreeBSD$"); #include "make.h" #include "parse.h" #include "pathnames.h" +#include "shell.h" #include "str.h" #include "suff.h" #include "targ.h" @@ -1087,7 +1088,7 @@ ParseDoDependency(char *line) *line = '\0'; } else if (specType == ExShell) { - if (!Job_ParseShell(line)) { + if (!Shell_Parse(line)) { Parse_Error(PARSE_FATAL, "improper shell specification"); return; diff --git a/usr.bin/make/shell.c b/usr.bin/make/shell.c new file mode 100644 index 0000000..c1f82d1 --- /dev/null +++ b/usr.bin/make/shell.c @@ -0,0 +1,472 @@ +/*- + * 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/queue.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "parse.h" +#include "pathnames.h" +#include "shell.h" +#include "util.h" + +/* + * Descriptions for various shells. What the list of builtins should contain + * is debatable: either all builtins or only those which may specified on + * a single line without use of meta-characters. For correct makefiles that + * contain only correct command lines there is no difference. But if a command + * line, for example, is: 'if -foo bar' and there is an executable named 'if' + * in the path, the first possibility would execute that 'if' while in the + * second case the shell would give an error. Histerically only a small + * subset of the builtins and no reserved words where given in the list which + * corresponds roughly to the first variant. So go with this but add missing + * words. + */ +#define CSH_BUILTINS \ + "alias cd eval exec exit read set ulimit unalias " \ + "umask unset wait" + +#define SH_BUILTINS \ + "alias cd eval exec exit read set ulimit unalias " \ + "umask unset wait" + +#define CSH_META "#=|^(){};&<>*?[]:$`\\@\n" +#define SH_META "#=|^(){};&<>*?[]:$`\\\n" + +static const char *const shells_init[] = { + /* + * 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. + */ + "name=csh path='" PATH_DEFSHELLDIR "/csh' " + "quiet='unset verbose' echo='set verbose' filter='unset verbose' " + "hasErrCtl=N check='echo \"%s\"\n' ignore='csh -c \"%s || exit 0\"' " + "echoFlag=v errFlag=e " + "meta='" CSH_META "' builtins='" CSH_BUILTINS "'", + + /* + * SH description. Echo control is also possible and, under + * sun UNIX anyway, one can even control error checking. + */ + "name=sh path='" PATH_DEFSHELLDIR "/sh' " + "quiet='set -' echo='set -v' filter='set -' " + "hasErrCtl=Y check='set -e' ignore='set +e' " + "echoFlag=v errFlag=e " + "meta='" SH_META "' builtins='" SH_BUILTINS "'", + + /* + * KSH description. The Korn shell has a superset of + * the Bourne shell's functionality. There are probably builtins + * missing here. + */ + "name=ksh path='" PATH_DEFSHELLDIR "/ksh' " + "quiet='set -' echo='set -v' filter='set -' " + "hasErrCtl=Y check='set -e' ignore='set +e' " + "echoFlag=v errFlag=e " + "meta='" SH_META "' builtins='" SH_BUILTINS "' unsetenv=T", + + NULL +}; + +/* + * This is the shell to which we pass all commands in the Makefile. + * It is set by the Job_ParseShell function. + */ +struct Shell *commandShell; + +/* + * This is the list of all known shells. + */ +static struct Shells shells = TAILQ_HEAD_INITIALIZER(shells); + +void ShellDump(const struct Shell *) __unused; + +/** + * Helper function for sorting the builtin list alphabetically. + */ +static int +sort_builtins(const void *p1, const void *p2) +{ + + return (strcmp(*(const char* const*)p1, *(const char* const*)p2)); +} + +/** + * Free a shell structure and all associated strings. + */ +static void +ShellFree(struct Shell *sh) +{ + + if (sh != NULL) { + free(sh->name); + free(sh->path); + free(sh->echoOff); + free(sh->echoOn); + free(sh->noPrint); + free(sh->errCheck); + free(sh->ignErr); + free(sh->echo); + free(sh->exit); + ArgArray_Done(&sh->builtins); + free(sh->meta); + free(sh); + } +} + +/** + * Dump a shell specification to stderr. + */ +void +ShellDump(const struct Shell *sh) +{ + int i; + + fprintf(stderr, "Shell %p:\n", sh); + fprintf(stderr, " name='%s' path='%s'\n", sh->name, sh->path); + fprintf(stderr, " hasEchoCtl=%d echoOff='%s' echoOn='%s'\n", + sh->hasEchoCtl, sh->echoOff, sh->echoOn); + fprintf(stderr, " noPrint='%s'\n", sh->noPrint); + fprintf(stderr, " hasErrCtl=%d errCheck='%s' ignErr='%s'\n", + sh->hasErrCtl, sh->errCheck, sh->ignErr); + fprintf(stderr, " echo='%s' exit='%s'\n", sh->echo, sh->exit); + fprintf(stderr, " builtins=%d\n", sh->builtins.argc - 1); + for (i = 1; i < sh->builtins.argc; i++) + fprintf(stderr, " '%s'", sh->builtins.argv[i]); + fprintf(stderr, "\n meta='%s'\n", sh->meta); + fprintf(stderr, " unsetenv=%d\n", sh->unsetenv); +} + +/** + * Parse a shell specification line and return the new Shell structure. + * In case of an error a message is printed and NULL is returned. + */ +static struct Shell * +ShellParseSpec(const char *spec, Boolean *fullSpec) +{ + ArgArray aa; + struct Shell *sh; + char *eq; + char *keyw; + int arg; + + *fullSpec = FALSE; + + sh = emalloc(sizeof(*sh)); + memset(sh, 0, sizeof(*sh)); + ArgArray_Init(&sh->builtins); + + /* + * Parse the specification by keyword but skip the first word + */ + brk_string(&aa, spec, TRUE); + + for (arg = 1; arg < aa.argc; arg++) { + /* + * Split keyword and value + */ + keyw = aa.argv[arg]; + if ((eq = strchr(keyw, '=')) == NULL) { + Parse_Error(PARSE_FATAL, "missing '=' in shell " + "specification keyword '%s'", keyw); + ArgArray_Done(&aa); + ShellFree(sh); + return (NULL); + } + *eq++ = '\0'; + + if (strcmp(keyw, "path") == 0) { + free(sh->path); + sh->path = estrdup(eq); + } else if (strcmp(keyw, "name") == 0) { + free(sh->name); + sh->name = estrdup(eq); + } else if (strcmp(keyw, "quiet") == 0) { + free(sh->echoOff); + sh->echoOff = estrdup(eq); + *fullSpec = TRUE; + } else if (strcmp(keyw, "echo") == 0) { + free(sh->echoOn); + sh->echoOn = estrdup(eq); + *fullSpec = TRUE; + } else if (strcmp(keyw, "filter") == 0) { + free(sh->noPrint); + sh->noPrint = estrdup(eq); + *fullSpec = TRUE; + } else if (strcmp(keyw, "echoFlag") == 0) { + free(sh->echo); + sh->echo = estrdup(eq); + *fullSpec = TRUE; + } else if (strcmp(keyw, "errFlag") == 0) { + free(sh->exit); + sh->exit = estrdup(eq); + *fullSpec = TRUE; + } else if (strcmp(keyw, "hasErrCtl") == 0) { + sh->hasErrCtl = (*eq == 'Y' || *eq == 'y' || + *eq == 'T' || *eq == 't'); + *fullSpec = TRUE; + } else if (strcmp(keyw, "check") == 0) { + free(sh->errCheck); + sh->errCheck = estrdup(eq); + *fullSpec = TRUE; + } else if (strcmp(keyw, "ignore") == 0) { + free(sh->ignErr); + sh->ignErr = estrdup(eq); + *fullSpec = TRUE; + } else if (strcmp(keyw, "builtins") == 0) { + ArgArray_Done(&sh->builtins); + brk_string(&sh->builtins, eq, TRUE); + qsort(sh->builtins.argv + 1, sh->builtins.argc - 1, + sizeof(char *), sort_builtins); + *fullSpec = TRUE; + } else if (strcmp(keyw, "meta") == 0) { + free(sh->meta); + sh->meta = estrdup(eq); + *fullSpec = TRUE; + } else if (strcmp(keyw, "unsetenv") == 0) { + sh->unsetenv = (*eq == 'Y' || *eq == 'y' || + *eq == 'T' || *eq == 't'); + *fullSpec = TRUE; + } else { + Parse_Error(PARSE_FATAL, "unknown keyword in shell " + "specification '%s'", keyw); + ArgArray_Done(&aa); + ShellFree(sh); + return (NULL); + } + } + ArgArray_Done(&aa); + + /* + * Some checks (could be more) + */ + if (*fullSpec) { + if ((sh->echoOn != NULL) ^ (sh->echoOff != NULL)) { + Parse_Error(PARSE_FATAL, "Shell must have either both " + "echoOff and echoOn or none of them"); + ShellFree(sh); + return (NULL); + } + + if (sh->echoOn != NULL && sh->echoOff != NULL) + sh->hasEchoCtl = TRUE; + } + + return (sh); +} + +/** + * Parse the builtin shell specifications and put them into the shell + * list. Then select the default shell to be the current shell. This + * is called from main() before any parsing (including MAKEFLAGS and + * command line) is done. + */ +void +Shell_Init(void) +{ + int i; + struct Shell *sh; + Boolean fullSpec; + + for (i = 0; shells_init[i] != NULL; i++) { + sh = ShellParseSpec(shells_init[i], &fullSpec); + TAILQ_INSERT_TAIL(&shells, sh, link); + if (strcmp(sh->name, DEFSHELLNAME) == 0) + commandShell = sh; + } +} + +/** + * Find a matching shell in 'shells' given its final component. + * + * Results: + * A pointer to a freshly allocated Shell structure with the contents + * from static description or NULL if no shell with the given name + * is found. + */ +static struct Shell * +ShellMatch(const char *name) +{ + struct Shell *sh; + + TAILQ_FOREACH(sh, &shells, link) + if (strcmp(sh->name, name) == 0) + return (sh); + + return (NULL); +} + +/** + * Parse a shell specification and set up commandShell appropriately. + * + * Results: + * TRUE if the specification was correct. FALSE otherwise. + * + * Side Effects: + * commandShell points to a Shell structure. + * created from the shell spec). + * + * 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. + * builtins A space separated list of builtins. If one + * of these builtins is detected when make wants + * to execute a command line, the command line is + * handed to the shell. Otherwise make may try to + * execute the command directly. If this list is empty + * it is assumed, that the command must always be + * handed over to the shell. + * meta The shell meta characters. If this is not specified + * or empty, commands are alway passed to the shell. + * Otherwise they are not passed when they contain + * neither a meta character nor a builtin command. + * unsetenv Unsetenv("ENV") before executing anything. + */ +Boolean +Shell_Parse(const char line[]) +{ + Boolean fullSpec; + struct Shell *sh; + struct Shell *match; + + /* parse the specification */ + if ((sh = ShellParseSpec(line, &fullSpec)) == NULL) + return (FALSE); + + if (sh->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. + */ + if (sh->name == NULL) { + Parse_Error(PARSE_FATAL, + "Neither path nor name specified"); + ShellFree(sh); + return (FALSE); + } + if (fullSpec) { + /* + * XXX May want to merge sh into match. But this + * require ShellParseSpec to return information + * which attributes actuall have been specified. + */ + Parse_Error(PARSE_FATAL, "No path specified"); + ShellFree(sh); + return (FALSE); + } + if ((match = ShellMatch(sh->name)) == NULL) { + Parse_Error(PARSE_FATAL, "%s: no matching shell", + sh->name); + ShellFree(sh); + return (FALSE); + } + ShellFree(sh); + commandShell = match; + + return (TRUE); + } + + /* + * 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. + */ + if (sh->name == NULL) { + /* get the base name as the name */ + if ((sh->name = strrchr(sh->path, '/')) == NULL) { + sh->name = estrdup(sh->path); + } else { + sh->name = estrdup(sh->name + 1); + } + } + + if (!fullSpec) { + if ((match = ShellMatch(sh->name)) == NULL) { + Parse_Error(PARSE_FATAL, + "%s: no matching shell", sh->name); + ShellFree(sh); + return (FALSE); + } + + /* set the patch on the matching shell */ + free(match->path); + match->path = sh->path; + sh->path = NULL; + + ShellFree(sh); + commandShell = match; + return (TRUE); + } + + TAILQ_INSERT_HEAD(&shells, sh, link); + + /* set the new shell */ + commandShell = sh; + return (TRUE); +} diff --git a/usr.bin/make/shell.h b/usr.bin/make/shell.h new file mode 100644 index 0000000..a317058 --- /dev/null +++ b/usr.bin/make/shell.h @@ -0,0 +1,110 @@ +/*- + * 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. + * + * $FreeBSD$ + */ + +#ifndef shell_h_6002e3b8 +#define shell_h_6002e3b8 + +#include <sys/queue.h> + +#include "str.h" +#include "util.h" + +/** + * Shell Specifications: + * + * Some special stuff goes on if a shell doesn't have error control. In such + * a case, errCheck becomes a printf template for echoing the command, + * should echoing be on and ignErr becomes another printf template for + * executing the command while ignoring the return status. If either of these + * strings is empty when hasErrCtl is FALSE, the command will be executed + * anyway as is and if it causes an error, so be it. + */ +struct Shell { + TAILQ_ENTRY(Shell) link; /* link all shell descriptions */ + + /* + * the name of the shell. For Bourne and C shells, this is used + * only to find the shell description when used as the single + * source of a .SHELL target. + */ + char *name; + + char *path; /* full path to the shell */ + + /* True if both echoOff and echoOn defined */ + Boolean hasEchoCtl; + + char *echoOff; /* command to turn off echo */ + char *echoOn; /* command to turn it back on */ + + /* + * What the shell prints, and its length, when given the + * echo-off command. This line will not be printed when + * received from the shell. This is usually the command which + * was executed to turn off echoing + */ + char *noPrint; + + /* set if can control error checking for individual commands */ + Boolean hasErrCtl; + + /* string to turn error checking on */ + char *errCheck; + + /* string to turn off error checking */ + char *ignErr; + + char *echo; /* command line flag: echo commands */ + char *exit; /* command line flag: exit on error */ + + ArgArray builtins; /* ordered list of shell builtins */ + char *meta; /* shell meta characters */ + + Boolean unsetenv; /* unsetenv("ENV") before exec */ +}; +TAILQ_HEAD(Shells, Shell); + +extern struct Shell *commandShell; + +void Shell_Init(void); +Boolean Shell_Parse(const char []); + +#endif /* shell_h_6002e3b8 */ |