mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-24 11:29:10 +00:00
473 lines
14 KiB
C
473 lines
14 KiB
C
|
/*-
|
||
|
* 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);
|
||
|
}
|