diff options
Diffstat (limited to 'usr.bin/make/shell.c')
-rw-r--r-- | usr.bin/make/shell.c | 472 |
1 files changed, 472 insertions, 0 deletions
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); +} |