123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817 |
- /*++
- Copyright (c) 2013 Minoca Corp. All Rights Reserved
- Module Name:
- sh.c
- Abstract:
- This module implements the shell application.
- Author:
- Evan Green 5-Jun-2013
- Environment:
- User Mode
- --*/
- //
- // ------------------------------------------------------------------- Includes
- //
- #include "sh.h"
- #include "shparse.h"
- #include <assert.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include "../swlib.h"
- //
- // ---------------------------------------------------------------- Definitions
- //
- #define SHELL_PS1_INTERACTIVE_DEFAULT "\\w\\$ "
- #define SH_USAGE \
- "usage: sh [-abCefhimnuvx] [-o option] command_file [argument...]\n" \
- " sh -c [-abCefhimnuvx] command_string [command_name [argument...]]" \
- "\n" \
- " sh -s [-abCefhimnuvx] [argument]\n" \
- "The sh utility provides a basic POSIX shell. Basic forms are:\n" \
- " sh ... command_file - Read shell commands from the given file.\n" \
- " sh ... -c command_string - Interpret the given command string in the " \
- "shell.\n" \
- " sh ... -s - Read commands from standard in. This is the default.\n\n" \
- "Options can be turned on by specifying -abCefhimnuvx or -o <option>.\n" \
- "Options can be turned off by using +abCefhimnuvx or +o <option>.\n" \
- "Options are:\n" \
- " -a (allexport) -- Set the export attribute to any variable \n" \
- " assignment of shell-wide scope.\n" \
- " -b (notify) -- Enables asynchronous background notifications.\n" \
- " -C -- Do not clobber existing files with the > redirection operator.\n" \
- " -d -- Debug mode. Prints the lexing and parsing of shell commands.\n" \
- " -e (errexit) -- Exit the shell if any command returns a non-zero \n" \
- " status.\n" \
- " -f (noglob) -- Disables pathname expansions.\n" \
- " -h -- Cache utility paths invoked by functions.\n" \
- " -i -- Treat the shell as interactive.\n" \
- " -m -- Run all jobs in their own process groups.\n" \
- " -n (noexec) -- Read but do not execute commands (ignored if \n" \
- " interactive).\n" \
- " -o -- Sets a long-form option (clear on +o).\n" \
- " -u (nounset) -- Print a message to standard error whenever an \n" \
- " attempt is made to expand an unset variable and immediately \n" \
- " exit (except if interactive).\n" \
- " -v (verbose) -- Write all input to standard out as it is read.\n" \
- " -x (xtrace) -- Write a trace of each command after it expands but \n" \
- " before it executes.\n" \
- " --help -- Show this help text and exit.\n" \
- " --version -- Show the application version information and exit.\n\n" \
- //
- // ------------------------------------------------------ Data Type Definitions
- //
- typedef struct _SHELL_OPTION_STRING {
- PSTR String;
- CHAR Character;
- ULONG Option;
- } SHELL_OPTION_STRING, *PSHELL_OPTION_STRING;
- //
- // ----------------------------------------------- Internal Function Prototypes
- //
- VOID
- ShRunEnvVariable (
- PSHELL Shell
- );
- //
- // -------------------------------------------------------------------- Globals
- //
- SHELL_OPTION_STRING ShOptionStrings[] = {
- {"allexport", 'a', SHELL_OPTION_EXPORT_ALL},
- {"errexit", 'e', SHELL_OPTION_EXIT_ON_FAILURE},
- {"ignoreeof", 0, SHELL_OPTION_IGNORE_EOF},
- {"monitor", 'm', SHELL_OPTION_RUN_JOBS_IN_SEPARATE_PROCESS_GROUP},
- {"noclobber", 'C', SHELL_OPTION_NO_CLOBBER},
- {"noglob", 'f', SHELL_OPTION_NO_PATHNAME_EXPANSION},
- {"noexec", 'n', SHELL_OPTION_NO_EXECUTE},
- {"nolog", 0, SHELL_OPTION_NO_COMMAND_HISTORY},
- {"notify", 'b', SHELL_OPTION_ASYNCHRONOUS_JOB_NOTIFICATION},
- {"nounset", 'u', SHELL_OPTION_EXIT_ON_UNSET_VARIABLE},
- {"verbose", 'v', SHELL_OPTION_DISPLAY_INPUT},
- {"interactive", 'i', SHELL_INTERACTIVE_OPTIONS},
- {"xtrace", 'x', SHELL_OPTION_TRACE_COMMAND},
- {"debug", 'd', SHELL_OPTION_DEBUG},
- {"stdin", 's', SHELL_OPTION_READ_FROM_STDIN},
- {NULL, 'h', SHELL_OPTION_LOCATE_UTILITIES_IN_DECLARATION},
- };
- //
- // ------------------------------------------------------------------ Functions
- //
- INT
- ShMain (
- INT ArgumentCount,
- CHAR **Arguments
- )
- /*++
- Routine Description:
- This routine implements the main entry point for the shell app.
- Arguments:
- ArgumentCount - Supplies the number of arguments on the command line.
- Arguments - Supplies an array of pointers to strings representing the
- arguments.
- Return Value:
- 0 on success.
- Non-zero on failure.
- --*/
- {
- PSTR Argument;
- ULONG ArgumentIndex;
- BOOL ArgumentIsInput;
- UINTN ArgumentSize;
- INT InputDescriptor;
- INT InputDescriptorHigh;
- BOOL Result;
- INT ReturnValue;
- BOOL Set;
- PSHELL Shell;
- INT StandardErrorCopy;
- InputDescriptor = -1;
- InputDescriptorHigh = -1;
- srand(time(NULL));
- ReturnValue = ENOMEM;
- Shell = ShCreateShell(NULL, 0);
- if (Shell == NULL) {
- PRINT_ERROR("Error: Unable to allocate shell.\n");
- goto MainEnd;
- }
- StandardErrorCopy = ShDup(Shell, STDERR_FILENO, FALSE);
- if (StandardErrorCopy >= 0) {
- Shell->NonStandardError = fdopen(StandardErrorCopy, "w");
- if (Shell->NonStandardError == NULL) {
- ShClose(Shell, StandardErrorCopy);
- }
- }
- //
- // Loop through all the options.
- //
- ArgumentIsInput = FALSE;
- for (ArgumentIndex = 1; ArgumentIndex < ArgumentCount; ArgumentIndex += 1) {
- Argument = Arguments[ArgumentIndex];
- //
- // First look out for the longform options.
- //
- if ((strcmp(Argument, "-o") == 0) || (strcmp(Argument, "+o") == 0)) {
- Set = TRUE;
- if (Argument[0] == '+') {
- Set = FALSE;
- }
- if (ArgumentIndex == ArgumentCount - 1) {
- Argument = "";
- ArgumentSize = 1;
- } else {
- ArgumentIndex += 1;
- Argument = Arguments[ArgumentIndex];
- ArgumentSize = strlen(Arguments[ArgumentIndex + 1]) + 1;
- }
- Result = ShSetOptions(Shell,
- Argument,
- ArgumentSize,
- TRUE,
- Set,
- NULL);
- if (Result == FALSE) {
- PRINT_ERROR("Error: Unknown option %s.\n",
- Arguments[ArgumentIndex + 1]);
- ReturnValue = EINVAL;
- goto MainEnd;
- }
- continue;
- }
- //
- // Stop processing for --.
- //
- if (strcmp(Argument, "--") == 0) {
- break;
- }
- //
- // Look for help or version if they're the only arguments.
- //
- if (ArgumentCount == 2) {
- if (strcmp(Argument, "--help") == 0) {
- printf(SH_USAGE);
- Result = 1;
- goto MainEnd;
- } else if (strcmp(Argument, "--version") == 0) {
- SwPrintVersion(SH_VERSION_MAJOR, SH_VERSION_MINOR);
- Result = 1;
- goto MainEnd;
- }
- }
- if ((Argument[0] == '-') || (Argument[0] == '+')) {
- Result = ShSetOptions(Shell,
- Argument,
- strlen(Argument) + 1,
- FALSE,
- FALSE,
- &ArgumentIsInput);
- if (Result == FALSE) {
- ReturnValue = EINVAL;
- goto MainEnd;
- }
- //
- // This is either a command file, command string, or argument, so stop
- // processing options.
- //
- } else {
- break;
- }
- }
- //
- // If the next command is the input, set that up directly.
- //
- if (ArgumentIsInput != FALSE) {
- if (ArgumentIndex == ArgumentCount) {
- PRINT_ERROR("Error: -c requires an argument.");
- ReturnValue = EINVAL;
- goto MainEnd;
- }
- Argument = Arguments[ArgumentIndex];
- Shell->Lexer.InputBufferSize = strlen(Argument) + 1;
- Shell->Lexer.InputBuffer = SwStringDuplicate(
- Argument,
- Shell->Lexer.InputBufferSize);
- if (Shell->Lexer.InputBuffer == NULL) {
- goto MainEnd;
- }
- Shell->Lexer.InputBufferCapacity = Shell->Lexer.InputBufferSize;
- ArgumentIndex += 1;
- //
- // The following argument if provided is the command name.
- //
- if (ArgumentIndex < ArgumentCount) {
- Argument = Arguments[ArgumentIndex];
- ArgumentIndex += 1;
- //
- // If no argument zero was provided, then use this argument zero.
- //
- } else {
- Argument = Arguments[0];
- }
- Shell->CommandNameSize = strlen(Argument) + 1;
- Shell->CommandName = SwStringDuplicate(Argument,
- Shell->CommandNameSize);
- if (Shell->CommandName == NULL) {
- goto MainEnd;
- }
- //
- // If not reading from standard in, then the next argument is a script. If
- // there is no argument, then it's standard in.
- //
- } else if ((Shell->Options & SHELL_OPTION_READ_FROM_STDIN) == 0) {
- if (ArgumentIndex == ArgumentCount) {
- Shell->Lexer.InputFile = NULL;
- Shell->Options |= SHELL_OPTION_READ_FROM_STDIN;
- } else {
- Argument = Arguments[ArgumentIndex];
- InputDescriptor = open(Argument, O_RDONLY | O_BINARY);
- if (InputDescriptor < 0) {
- SwPrintError(errno, Argument, "Unable to open script");
- ReturnValue = SHELL_ERROR_OPEN;
- goto MainEnd;
- }
- if (InputDescriptor >= SHELL_MINIMUM_FILE_DESCRIPTOR) {
- InputDescriptorHigh = InputDescriptor;
- } else {
- InputDescriptorHigh = ShDup(Shell, InputDescriptor, FALSE);
- if (InputDescriptorHigh < 0) {
- SwPrintError(errno, Argument, "Unable to dup");
- ReturnValue = SHELL_ERROR_OPEN;
- goto MainEnd;
- }
- assert(InputDescriptorHigh >= SHELL_MINIMUM_FILE_DESCRIPTOR);
- close(InputDescriptor);
- }
- InputDescriptor = -1;
- Shell->Lexer.InputFile = fdopen(InputDescriptorHigh, "rb");
- if (Shell->Lexer.InputFile == NULL) {
- PRINT_ERROR("Error: Unable to open script %s.\n", Argument);
- ReturnValue = SHELL_ERROR_OPEN;
- goto MainEnd;
- }
- InputDescriptorHigh = -1;
- //
- // Also set the first argument to the name of this script.
- //
- Shell->CommandNameSize = strlen(Argument) + 1;
- Shell->CommandName = SwStringDuplicate(Argument,
- Shell->CommandNameSize);
- if (Shell->CommandName == NULL) {
- goto MainEnd;
- }
- ArgumentIndex += 1;
- }
- }
- //
- // If the command name has not yet been set, assign it to the first
- // argument.
- //
- if (Shell->CommandName == NULL) {
- Shell->CommandNameSize = strlen(Arguments[0]) + 1;
- Shell->CommandName = SwStringDuplicate(Arguments[0],
- Shell->CommandNameSize);
- if (Shell->CommandName == NULL) {
- goto MainEnd;
- }
- }
- //
- // Set up any remaining arguments as the positional arguments.
- //
- if (ArgumentIndex != ArgumentCount) {
- Result = ShCreateArgumentList(Arguments + ArgumentIndex,
- ArgumentCount - ArgumentIndex,
- &(Shell->ArgumentList));
- if (Result == FALSE) {
- goto MainEnd;
- }
- }
- //
- // If the input is a terminal, then mark this shell as interactive.
- //
- assert(InputDescriptor == -1);
- if ((Shell->Options & SHELL_OPTION_READ_FROM_STDIN) != 0) {
- InputDescriptor = STDIN_FILENO;
- } else if (Shell->Lexer.InputFile != NULL) {
- InputDescriptor = fileno(Shell->Lexer.InputFile);
- }
- if ((InputDescriptor != -1) && (isatty(InputDescriptor) != 0)) {
- Shell->Options |= SHELL_INTERACTIVE_OPTIONS;
- }
- InputDescriptor = -1;
- //
- // Give them something a little snazzier than a plain jane dollar sign
- // if it's interactive. Failure here isn't critical.
- //
- if ((Shell->Options & SHELL_OPTION_INTERACTIVE) != 0) {
- ShSetVariable(Shell,
- SHELL_PS1,
- sizeof(SHELL_PS1),
- SHELL_PS1_INTERACTIVE_DEFAULT,
- sizeof(SHELL_PS1_INTERACTIVE_DEFAULT));
- }
- ShInitializeSignals(Shell);
- //
- // Run the contents of the ENV environment variable if appropriate.
- //
- ShRunEnvVariable(Shell);
- //
- // Here we go, run that shell!
- //
- Result = ShExecute(Shell, &ReturnValue);
- if (Result == FALSE) {
- ReturnValue = errno;
- if (ReturnValue == 0) {
- ReturnValue = EINVAL;
- }
- }
- Shell->Exited = TRUE;
- ShRunAtExitSignal(Shell);
- ShRestoreOriginalSignalDispositions();
- MainEnd:
- if (InputDescriptor >= 0) {
- close(InputDescriptor);
- }
- if (InputDescriptorHigh >= 0) {
- close(InputDescriptor);
- }
- if (Shell != NULL) {
- ShDestroyShell(Shell);
- }
- return ReturnValue;
- }
- BOOL
- ShSetOptions (
- PSHELL Shell,
- PSTR String,
- UINTN StringSize,
- BOOL LongForm,
- BOOL Set,
- PBOOL HasC
- )
- /*++
- Routine Description:
- This routine sets shell behavior options.
- Arguments:
- Shell - Supplies a pointer to the shell.
- String - Supplies a pointer to the string containing the options.
- StringSize - Supplies the size of the string in bytes including the null
- terminator.
- LongForm - Supplies a boolean indicating if this is a long form option,
- where the name of the option is spelled out.
- Set - Supplies whether or not the longform argument is a set (-o) or clear
- (+o) operation. For non longform arguments, this parameter is ignored.
- HasC - Supplies an optional boolean indicating if the option string has -c
- int it somewhere. If NULL, then -c is not allowed.
- Return Value:
- TRUE on success.
- FALSE if an invalid option was supplied.
- --*/
- {
- CHAR Character;
- UINTN Index;
- UINTN OptionCount;
- ULONG Options;
- UINTN StringIndex;
- BOOL WriteOptions;
- WriteOptions = FALSE;
- Options = 0;
- OptionCount = sizeof(ShOptionStrings) / sizeof(ShOptionStrings[0]);
- if (LongForm != FALSE) {
- for (Index = 0; Index < OptionCount; Index += 1) {
- if ((ShOptionStrings[Index].String != NULL) &&
- (strcasecmp(String, ShOptionStrings[Index].String) == 0)) {
- Options |= ShOptionStrings[Index].Option;
- break;
- }
- }
- if (Options == 0) {
- if (StringSize == 1) {
- WriteOptions = TRUE;
- } else {
- SwPrintError(0, String, "Unrecognized option");
- return FALSE;
- }
- }
- } else {
- Set = TRUE;
- if (String[0] == '+') {
- Set = FALSE;
- }
- for (StringIndex = 1; StringIndex < StringSize; StringIndex += 1) {
- Character = String[StringIndex];
- if (Character == '\0') {
- break;
- }
- for (Index = 0; Index < OptionCount; Index += 1) {
- if ((ShOptionStrings[Index].Character != 0) &&
- (ShOptionStrings[Index].Character == Character)) {
- Options |= ShOptionStrings[Index].Option;
- break;
- }
- }
- if (Index == OptionCount) {
- //
- // Ignore a -c and notify the caller if the caller might be
- // expecting it.
- //
- if ((Character == 'c') && (HasC != NULL)) {
- *HasC = TRUE;
- continue;
- }
- PRINT_ERROR("Error: Invalid option '%c'.\n", Character);
- return FALSE;
- }
- }
- }
- if (Set != FALSE) {
- Shell->Options |= Options;
- } else {
- Shell->Options &= ~Options;
- }
- //
- // Set or clear the various debug globals.
- //
- if ((Shell->Options & SHELL_OPTION_DEBUG) != 0) {
- ShDebugAlias = TRUE;
- ShDebugArithmeticLexer = TRUE;
- ShDebugArithmeticParser = TRUE;
- ShDebugLexer = TRUE;
- ShDebugPrintParseTree = TRUE;
- } else {
- ShDebugAlias = FALSE;
- ShDebugArithmeticLexer = FALSE;
- ShDebugArithmeticParser = FALSE;
- ShDebugLexer = FALSE;
- ShDebugPrintParseTree = FALSE;
- }
- //
- // If asked, write out all the options.
- //
- if (WriteOptions != FALSE) {
- Options = Shell->Options;
- for (Index = 0; Index < OptionCount; Index += 1) {
- if (ShOptionStrings[Index].String != NULL) {
- if ((ShOptionStrings[Index].Option & Options) != 0) {
- printf("set -o %s\n", ShOptionStrings[Index].String);
- } else {
- printf("set +o %s\n", ShOptionStrings[Index].String);
- }
- }
- }
- }
- return TRUE;
- }
- //
- // --------------------------------------------------------- Internal Functions
- //
- VOID
- ShRunEnvVariable (
- PSHELL Shell
- )
- /*++
- Routine Description:
- This routine expands and runs the contents of the ENV variable in the
- current context if appropriate.
- Arguments:
- Shell - Supplies a pointer to the shell.
- Return Value:
- None.
- --*/
- {
- int DescriptorAnywhere;
- int EnvironmentDescriptor;
- FILE *EnvironmentFile;
- PSTR ExpandedScript;
- UINTN ExpandedScriptSize;
- SHELL_LEXER_STATE OriginalLexer;
- ULONG OriginalOptions;
- BOOL Result;
- INT ReturnValue;
- PSTR Value;
- UINTN ValueSize;
- EnvironmentFile = NULL;
- ExpandedScript = NULL;
- //
- // Ignore the ENV variable if this is not an interactive script or the
- // real and effective user IDs differ.
- //
- if (((Shell->Options & SHELL_OPTION_INTERACTIVE) == 0) ||
- (SwGetRealUserId() != SwGetEffectiveUserId())) {
- return;
- }
- //
- // If the ENV variable is set, it is expanded and then represents the
- // path of a file to execute in the context of this shell.
- //
- Result = ShGetVariable(Shell,
- SHELL_ENV,
- sizeof(SHELL_ENV),
- &Value,
- &ValueSize);
- if (Result == FALSE) {
- goto RunEnvVariableEnd;
- }
- Result = ShPerformExpansions(Shell,
- Value,
- ValueSize,
- SHELL_EXPANSION_OPTION_NO_FIELD_SPLIT,
- &ExpandedScript,
- &ExpandedScriptSize,
- NULL,
- NULL);
- if (Result == FALSE) {
- PRINT_ERROR("Warning: Unable to expand ENV.\n");
- goto RunEnvVariableEnd;
- }
- assert(Shell->Lexer.LexerPrimed == FALSE);
- assert(Shell->Lexer.InputBufferNextIndex == 0);
- assert(Shell->Lexer.UnputCharacterValid == FALSE);
- assert(Shell->Lexer.LineNumber == 1);
- //
- // Open up the env file.
- //
- DescriptorAnywhere = open(ExpandedScript, O_RDONLY | O_BINARY);
- if (DescriptorAnywhere == -1) {
- PRINT_ERROR("Warning: Unable to open ENV file %s: %s.\n",
- ExpandedScript,
- strerror(errno));
- goto RunEnvVariableEnd;
- }
- EnvironmentDescriptor = ShDup(Shell, DescriptorAnywhere, TRUE);
- close(DescriptorAnywhere);
- if (EnvironmentDescriptor < 0) {
- goto RunEnvVariableEnd;
- }
- EnvironmentFile = fdopen(EnvironmentDescriptor, "rb");
- if (EnvironmentFile == NULL) {
- close(EnvironmentDescriptor);
- goto RunEnvVariableEnd;
- }
- assert((Shell->Options & SHELL_OPTION_INPUT_BUFFER_ONLY) == 0);
- //
- // Copy the original lexer state over and reinitialize the lexer
- // for this string.
- //
- memcpy(&OriginalLexer, &(Shell->Lexer), sizeof(SHELL_LEXER_STATE));
- Result = ShInitializeLexer(&(Shell->Lexer),
- EnvironmentFile,
- NULL,
- 0);
- if (Result == FALSE) {
- goto RunEnvVariableEnd;
- }
- EnvironmentFile = NULL;
- //
- // Run the env script.
- //
- OriginalOptions = Shell->Options;
- Shell->Options &= ~(SHELL_OPTION_RAW_INPUT |
- SHELL_OPTION_PRINT_PROMPTS |
- SHELL_OPTION_NO_COMMAND_HISTORY |
- SHELL_OPTION_READ_FROM_STDIN);
- Result = ShExecute(Shell, &ReturnValue);
- Shell->Options = OriginalOptions;
- if (Result == FALSE) {
- PRINT_ERROR("Warning: Failed to execute ENV script %s.\n",
- ExpandedScript);
- }
- //
- // Clean up this lexer (which includes closing the file) and
- // restore the original lexer.
- //
- ShDestroyLexer(&(Shell->Lexer));
- memcpy(&(Shell->Lexer), &OriginalLexer, sizeof(SHELL_LEXER_STATE));
- Result = TRUE;
- RunEnvVariableEnd:
- if (EnvironmentFile != NULL) {
- fclose(EnvironmentFile);
- }
- if (ExpandedScript != NULL) {
- free(ExpandedScript);
- }
- return;
- }
|