/*++

Copyright (c) 2013 Minoca Corp. All Rights Reserved

Module Name:

    exec.c

Abstract:

    This module handles execution of commands for the shell.

Author:

    Evan Green 6-Jun-2013

Environment:

    POSIX

--*/

//
// ------------------------------------------------------------------- Includes
//

#include "sh.h"
#include "shparse.h"
#include "../swiss.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "../swlib.h"

//
// --------------------------------------------------------------------- Macros
//

//
// Define the file creation mask for new files created by I/O redirection.
//

#define SHELL_FILE_CREATION_MASK 0664

//
// ---------------------------------------------------------------- Definitions
//

//
// ------------------------------------------------------ Data Type Definitions
//

//
// ----------------------------------------------- Internal Function Prototypes
//

BOOL
ShExecuteNode (
    PSHELL Shell,
    PSHELL_NODE Node
    );

BOOL
ShExecuteAsynchronousNode (
    PSHELL Shell,
    PSHELL_NODE Node
    );

BOOL
ShExecuteList (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    );

BOOL
ShExecuteAndOr (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    );

BOOL
ShExecutePipeline (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    );

BOOL
ShExecuteSimpleCommand (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    );

BOOL
ShExecuteFunctionDefinition (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    );

BOOL
ShExecuteFunctionInvocation (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutingNode,
    PSHELL_NODE Function,
    PSTR *Arguments,
    ULONG ArgumentCount
    );

BOOL
ShExecuteIf (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    );

BOOL
ShExecuteFor (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    );

BOOL
ShExecuteCase (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    );

BOOL
ShExecuteWhileOrUntil (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    );

BOOL
ShExecuteSubshellGroup (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    );

VOID
ShExitOnError (
    PSHELL Shell
    );

BOOL
ShApplyRedirections (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    );

//
// -------------------------------------------------------------------- Globals
//

//
// Set this variable if swiss command should be recognized even before
// searching the path.
//

BOOL ShUseSwissBuiltins = TRUE;

//
// Define the quoted at arguments string.
//

CHAR ShQuotedAtArgumentsString[] =
    {SHELL_CONTROL_QUOTE, '$', '@', SHELL_CONTROL_QUOTE, '\0'};

//
// ------------------------------------------------------------------ Functions
//

BOOL
ShExecute (
    PSHELL Shell,
    PINT ReturnValue
    )

/*++

Routine Description:

    This routine executes commands from the input of the shell.

Arguments:

    Shell - Supplies a pointer to the shell whose input should be read and
        executed.

    ReturnValue - Supplies a pointer where the return value of the shell will
        be returned.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    PSHELL_NODE Command;
    BOOL Result;

    Result = FALSE;
    ShPrintPrompt(Shell, 1);
    while (Shell->Exited == FALSE) {
        ShCheckForSignals(Shell);
        Result = ShParse(Shell, &Command);
        if (Result == FALSE) {
            if ((Shell->Options & SHELL_OPTION_INTERACTIVE) != 0) {
                ShPrintPrompt(Shell, 1);
                Shell->Lexer.LexerPrimed = FALSE;
                continue;
            }

            break;
        }

        if (Command == NULL) {
            break;
        }

        if ((Shell->Options & SHELL_OPTION_NO_EXECUTE) == 0) {
            Result = ShExecuteNode(Shell, Command);
        }

        ShReleaseNode(Command);
        if (Result == FALSE) {
            if ((Shell->Options & SHELL_OPTION_INTERACTIVE) == 0) {
                break;
            }
        }
    }

    *ReturnValue = Shell->LastReturnValue;
    return Result;
}

VOID
ShRestoreRedirections (
    PSHELL Shell,
    PLIST_ENTRY ActiveRedirectList
    )

/*++

Routine Description:

    This routine restores all active redirections back to their previous state.

Arguments:

    Shell - Supplies a pointer to the shell.

    ActiveRedirectList - Supplies a pointer to the active redirect list.

Return Value:

    None.

--*/

{

    PSHELL_ACTIVE_REDIRECT ActiveRedirect;
    INT ReplacedDescriptor;

    while (LIST_EMPTY(ActiveRedirectList) == FALSE) {

        //
        // Loop backwards so that if the same descriptor is redirected multiple
        // times then its value gets popped back to the original.
        //

        ActiveRedirect = LIST_VALUE(ActiveRedirectList->Previous,
                                    SHELL_ACTIVE_REDIRECT,
                                    ListEntry);

        LIST_REMOVE(&(ActiveRedirect->ListEntry));
        if (ActiveRedirect->OriginalDescriptor != -1) {
            ReplacedDescriptor = ShDup2(Shell,
                                        ActiveRedirect->OriginalDescriptor,
                                        ActiveRedirect->FileNumber);

            if (ReplacedDescriptor < 0) {
                PRINT_ERROR("Failed to restore file number %d.\n",
                            ActiveRedirect->FileNumber);
            }

            ShClose(Shell, ActiveRedirect->OriginalDescriptor);
        }

        if (ActiveRedirect->ChildProcessId > 0) {
            SwWaitPid(ActiveRedirect->ChildProcessId, 0, NULL);
        }

        free(ActiveRedirect);
    }

    return;
}

VOID
ShSetTerminalMode (
    PSHELL Shell,
    BOOL Raw
    )

/*++

Routine Description:

    This routine potentially sets the terminal input mode one way or another.
    If the interactive flag is not set, this does nothing.

Arguments:

    Shell - Supplies a pointer to the shell. If the interactive flag is not set
        in this shell, then nothing happens.

    Raw - Supplies a boolean indicating whether to set it in raw mode (TRUE)
        or it's previous original mode (FALSE).

Return Value:

    None.

--*/

{

    if ((Shell->Options & SHELL_OPTION_INTERACTIVE) == 0) {
        return;
    }

    if (Raw != FALSE) {
        SwSetRawInputMode(&ShBackspaceCharacter, &ShKillLineCharacter);

    } else {
        SwRestoreInputMode();
    }

    return;
}

INT
ShRunCommand (
    PSHELL Shell,
    PSTR Command,
    PSTR *Arguments,
    INT ArgumentCount,
    INT Asynchronous,
    PINT ReturnValue
    )

/*++

Routine Description:

    This routine is called to run a basic command for the shell.

Arguments:

    Shell - Supplies a pointer to the shell.

    Command - Supplies a pointer to the command name to run.

    Arguments - Supplies a pointer to an array of command argument strings.
        This includes the first argument, the command name.

    ArgumentCount - Supplies the number of arguments on the command line.

    Asynchronous - Supplies 0 if the shell should wait until the command is
        finished, or 1 if the function should return immediately with a
        return value of 0.

    ReturnValue - Supplies a pointer where the return value from the executed
        program will be returned.

Return Value:

    0 if the executable was successfully launched.

    Non-zero if there was trouble launching the executable.

--*/

{

    pid_t Child;
    PSTR FullCommandPath;
    ULONG FullCommandPathSize;
    BOOL Result;
    INT Status;
    PSWISS_COMMAND_ENTRY SwissCommand;
    id_t UserId;

    FullCommandPath = NULL;
    *ReturnValue = -1;
    Child = -1;

    //
    // If enabled, try the builtin commands.
    //

    if (ShUseSwissBuiltins != FALSE) {
        SwissCommand = SwissFindCommand(Arguments[0]);

        //
        // If the command is setuid and the environment is currently not setuid,
        // pretend like the command wasn't found.
        //

        if ((SwissCommand != NULL) &&
            ((SwissCommand->Flags & SWISS_APP_SETUID_OK) != 0)) {

            UserId = SwGetEffectiveUserId();
            if ((UserId != 0) && (UserId == SwGetRealUserId())) {
                SwissCommand = NULL;
            }
        }

        if (SwissCommand != NULL) {
            if (SwForkSupported != 0) {
                Child = SwFork();
                if (Child < 0) {
                    PRINT_ERROR("sh: Failed to fork: %s\n", strerror(errno));
                    Status = -1;
                    goto RunCommandEnd;

                } else if (Child == 0) {
                    ShRestoreOriginalSignalDispositions();
                    SwissRunCommand(SwissCommand,
                                    Arguments,
                                    ArgumentCount,
                                    FALSE,
                                    TRUE,
                                    ReturnValue);

                    exit(*ReturnValue);

                //
                // In the parent, jump down to wait for the child.
                //

                } else {
                    Status = 0;
                    goto RunCommandEnd;
                }

            //
            // If fork is not supported (Windows), just execute the command in
            // a separate process.
            //

            } else {
                fflush(NULL);
                Result = SwissRunCommand(SwissCommand,
                                         Arguments,
                                         ArgumentCount,
                                         TRUE,
                                         !Asynchronous,
                                         ReturnValue);

                if (Result != FALSE) {
                    Status = 0;
                    ShOsConvertExitStatus(ReturnValue);
                    goto RunCommandEnd;
                }
            }
        }
    }

    *ReturnValue = 0;
    Result = ShLocateCommand(Shell,
                             Arguments[0],
                             strlen(Arguments[0]) + 1,
                             TRUE,
                             &FullCommandPath,
                             &FullCommandPathSize,
                             ReturnValue);

    if (Result == FALSE) {
        Status = -1;
        goto RunCommandEnd;
    }

    if (*ReturnValue != 0) {
        if (*ReturnValue == SHELL_ERROR_OPEN) {
            PRINT_ERROR("sh: %s: Command not found.\n", Arguments[0]);

        } else if (*ReturnValue == SHELL_ERROR_EXECUTE) {
            PRINT_ERROR("sh: %s: Permission denied.\n", Arguments[0]);
        }

        Status = 0;
        goto RunCommandEnd;
    }

    if (SwForkSupported != 0) {
        Child = SwFork();
        if (Child < 0) {
            PRINT_ERROR("sh: Failed to fork: %s\n", strerror(errno));
            Status = -1;
            goto RunCommandEnd;

        } else if (Child == 0) {
            ShRestoreOriginalSignalDispositions();
            SwExec(FullCommandPath, Arguments, ArgumentCount);
            exit(errno);

        //
        // In the parent, jump down to wait for the child.
        //

        } else {
            Status = 0;
            goto RunCommandEnd;
        }

    } else {
        fflush(NULL);
        Status = SwRunCommand(FullCommandPath,
                              Arguments,
                              ArgumentCount,
                              Asynchronous,
                              ReturnValue);

        ShOsConvertExitStatus(ReturnValue);
    }

RunCommandEnd:

    //
    // Wait for the child if there is one.
    //

    if (Child > 0) {
        if (Asynchronous != 0) {
            *ReturnValue = 0;

        } else {
            Status = SwWaitPid(Child, 0, ReturnValue);
            if (Status != Child) {
                PRINT_ERROR("sh: Failed to wait for child %d\n", Child);
                Status = -1;

            } else {
                ShOsConvertExitStatus(ReturnValue);
            }
        }
    }

    if (*ReturnValue > SHELL_EXIT_SIGNALED) {
        if ((Shell->Options & SHELL_OPTION_INTERACTIVE) != 0) {
            printf("%s terminated by signal %d: %s\n",
                   Arguments[0],
                   *ReturnValue - SHELL_EXIT_SIGNALED,
                   strsignal(*ReturnValue - SHELL_EXIT_SIGNALED));
        }

    //
    // If the command exited normally, save any terminal changes it may have
    // made.
    //

    } else {
        SwSaveTerminalMode();
    }

    if ((FullCommandPath != NULL) && (FullCommandPath != Arguments[0])) {
        free(FullCommandPath);
    }

    return Status;
}

//
// --------------------------------------------------------- Internal Functions
//

BOOL
ShExecuteNode (
    PSHELL Shell,
    PSHELL_NODE Node
    )

/*++

Routine Description:

    This routine executes a generic shell node.

Arguments:

    Shell - Supplies a pointer to the shell containing the node.

    Node - Supplies a pointer to the node to execute.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    PSHELL_EXECUTION_NODE ExecutionNode;
    ULONG OriginalLineNumber;
    BOOL Result;

    if ((Node->RunInBackground != FALSE) && (SwForkSupported != 0)) {
        return ShExecuteAsynchronousNode(Shell, Node);
    }

    //
    // Create an execution node and push it on the stack.
    //

    ExecutionNode = malloc(sizeof(SHELL_EXECUTION_NODE));
    if (ExecutionNode == NULL) {
        return FALSE;
    }

    INITIALIZE_LIST_HEAD(&(ExecutionNode->VariableList));
    INITIALIZE_LIST_HEAD(&(ExecutionNode->ArgumentList));
    INITIALIZE_LIST_HEAD(&(ExecutionNode->ActiveRedirectList));
    ExecutionNode->Node = Node;
    ExecutionNode->Flags = 0;
    ExecutionNode->ReturnValue = 0;
    INSERT_AFTER(&(ExecutionNode->ListEntry), &(Shell->ExecutionStack));
    OriginalLineNumber = Shell->ExecutingLineNumber;
    Shell->ExecutingLineNumber = Node->LineNumber;
    Result = ShApplyRedirections(Shell, ExecutionNode);
    if (Result == FALSE) {
        goto ExecuteNodeEnd;
    }

    Result = FALSE;
    switch (Node->Type) {
    case ShellNodeList:
    case ShellNodeTerm:
    case ShellNodeBraceGroup:
        Result = ShExecuteList(Shell, ExecutionNode);
        break;

    case ShellNodeAndOr:
        Result = ShExecuteAndOr(Shell, ExecutionNode);
        break;

    case ShellNodePipeline:
        Result = ShExecutePipeline(Shell, ExecutionNode);
        break;

    case ShellNodeSimpleCommand:
        Result = ShExecuteSimpleCommand(Shell, ExecutionNode);
        break;

    case ShellNodeFunction:
        Result = ShExecuteFunctionDefinition(Shell, ExecutionNode);
        break;

    case ShellNodeIf:
        Result = ShExecuteIf(Shell, ExecutionNode);
        break;

    case ShellNodeFor:
        Result = ShExecuteFor(Shell, ExecutionNode);
        break;

    case ShellNodeCase:
        Result = ShExecuteCase(Shell, ExecutionNode);
        break;

    case ShellNodeWhile:
    case ShellNodeUntil:
        Result = ShExecuteWhileOrUntil(Shell, ExecutionNode);
        break;

    case ShellNodeSubshell:
        Result = ShExecuteSubshellGroup(Shell, ExecutionNode);
        break;

    default:

        assert(FALSE);

        Result = FALSE;
    }

ExecuteNodeEnd:

    //
    // Remove the node from the stack if not already done.
    //

    if (ExecutionNode->ListEntry.Next != NULL) {
        LIST_REMOVE(&(ExecutionNode->ListEntry));
    }

    ShDestroyArgumentList(&(ExecutionNode->ArgumentList));
    ShDestroyVariableList(&(ExecutionNode->VariableList));
    ShRestoreRedirections(Shell, &(ExecutionNode->ActiveRedirectList));
    free(ExecutionNode);
    Shell->ExecutingLineNumber = OriginalLineNumber;
    Shell->LastReturnValue = Shell->ReturnValue;
    return Result;
}

BOOL
ShExecuteAsynchronousNode (
    PSHELL Shell,
    PSHELL_NODE Node
    )

/*++

Routine Description:

    This routine executes a shell node asynchronously.

Arguments:

    Shell - Supplies a pointer to the shell containing the node.

    Node - Supplies a pointer to the node to execute.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    pid_t Process;
    BOOL Result;

    assert((Node->RunInBackground != FALSE) && (SwForkSupported != 0));

    //
    // Attempt to fork the current process, which is easiest and most in line
    // with what the shell would like to do.
    //

    Process = SwFork();

    //
    // If this is the child, set the node to be synchronous, run the node, and
    // exit straight away. Altering the node here doesn't change the memory of
    // the parent process remember. See how easy that was?
    //

    if (Process == 0) {
        if (Shell->PostForkCloseDescriptor != -1) {
            close(Shell->PostForkCloseDescriptor);
            Shell->PostForkCloseDescriptor = -1;
        }

        Node->RunInBackground = FALSE;
        ShExecuteNode(Shell, Node);
        exit(Shell->LastReturnValue);

    //
    // If this is the parent process, then work is finished here. Even easier!
    //

    } else if (Process != -1) {
        Result = TRUE;
        goto ExecuteAsynchronousNodeEnd;

    //
    // Fork failed.
    //

    } else {
        Result = FALSE;
        goto ExecuteAsynchronousNodeEnd;
    }

    Result = TRUE;

ExecuteAsynchronousNodeEnd:
    if (Result == FALSE) {
        Shell->ReturnValue = 1;

    } else {
        Shell->ReturnValue = 0;
    }

    return Result;
}

BOOL
ShExecuteList (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    )

/*++

Routine Description:

    This routine executes a list node.

Arguments:

    Shell - Supplies a pointer to the shell containing the node.

    ExecutionNode - Supplies a pointer to the node to execute.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    PSHELL_NODE Child;
    PLIST_ENTRY CurrentEntry;
    PSHELL_NODE Node;
    BOOL Result;

    Node = ExecutionNode->Node;

    assert((Node->Type == ShellNodeList) || (Node->Type == ShellNodeTerm) ||
           (Node->Type == ShellNodeBraceGroup));

    CurrentEntry = Node->Children.Next;
    while (CurrentEntry != &(Node->Children)) {
        Child = LIST_VALUE(CurrentEntry, SHELL_NODE, SiblingListEntry);
        CurrentEntry = CurrentEntry->Next;
        Result = ShExecuteNode(Shell, Child);
        if (Result == FALSE) {
            return FALSE;
        }

        if (Shell->Exited != FALSE) {
            break;
        }

        //
        // Break out if the execution node was removed from the stack.
        //

        if (ExecutionNode->ListEntry.Next == NULL) {
            break;
        }
    }

    return TRUE;
}

BOOL
ShExecuteAndOr (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    )

/*++

Routine Description:

    This routine executes a logical And-Or (&&/||) node.

Arguments:

    Shell - Supplies a pointer to the shell containing the node.

    ExecutionNode - Supplies a pointer to the node to execute.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    PSHELL_NODE Child;
    PLIST_ENTRY CurrentEntry;
    BOOL Execute;
    PSHELL_NODE Node;
    PSHELL_NODE Previous;
    BOOL Result;

    Node = ExecutionNode->Node;

    assert(Node->Type == ShellNodeAndOr);

    Previous = NULL;
    CurrentEntry = Node->Children.Next;
    while (CurrentEntry != &(Node->Children)) {
        Child = LIST_VALUE(CurrentEntry, SHELL_NODE, SiblingListEntry);
        CurrentEntry = CurrentEntry->Next;

        //
        // The first node always executes. If the previous node was an AND,
        // then don't execute this node if the previous node failed. If the
        // previous node was an OR, then don't execute this node if the
        // previous node succeeded.
        //

        Execute = TRUE;
        if (Previous != NULL) {
            if (Previous->AndOr == TOKEN_DOUBLE_AND) {
                if (Shell->LastReturnValue != 0) {
                    Execute = FALSE;
                }

            } else if (Previous->AndOr == TOKEN_DOUBLE_OR) {
                if (Shell->LastReturnValue == 0) {
                    Execute = FALSE;
                }
            }
        }

        if (Execute != FALSE) {
            Result = ShExecuteNode(Shell, Child);
            if (Result == FALSE) {
                return FALSE;
            }
        }

        if (Shell->Exited != FALSE) {
            break;
        }

        //
        // Break out if the execution node was removed from the stack.
        //

        if (ExecutionNode->ListEntry.Next == NULL) {
            break;
        }

        Previous = Child;
    }

    return TRUE;
}

BOOL
ShExecutePipeline (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    )

/*++

Routine Description:

    This routine executes a pipeline.

Arguments:

    Shell - Supplies a pointer to the shell containing the node.

    ExecutionNode - Supplies a pointer to the node to execute.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    PSHELL_NODE Child;
    PLIST_ENTRY CurrentEntry;
    INT NextPipe[2];
    PSHELL_NODE Node;
    INT OriginalStandardIn;
    INT OriginalStandardOut;
    INT PreviousPipeRead;
    BOOL Result;

    NextPipe[0] = -1;
    NextPipe[1] = -1;
    Node = ExecutionNode->Node;
    OriginalStandardIn = -1;
    OriginalStandardOut = -1;
    PreviousPipeRead = -1;
    Result = TRUE;

    assert(Node->Type == ShellNodePipeline);

    CurrentEntry = Node->Children.Next;
    while (CurrentEntry != &(Node->Children)) {
        Child = LIST_VALUE(CurrentEntry, SHELL_NODE, SiblingListEntry);
        CurrentEntry = CurrentEntry->Next;

        //
        // If this is not the last node, create a new pipe and wire standard out
        // up to that pipe. If it is the last node, leave standard out wired up
        // the way it is.
        //

        if (Child->SiblingListEntry.Next != &(Node->Children)) {
            Result = ShCreatePipe(NextPipe);
            if (Result == FALSE) {
                return FALSE;
            }

            OriginalStandardOut = ShDup(Shell, STDOUT_FILENO, FALSE);
            ShDup2(Shell, NextPipe[1], STDOUT_FILENO);
            ShClose(Shell, NextPipe[1]);
            NextPipe[1] = -1;
        }

        //
        // If this is not the first node, wire up standard input to the
        // previous pipe's read end.
        //

        if (Child->SiblingListEntry.Previous != &(Node->Children)) {
            OriginalStandardIn = ShDup(Shell, STDIN_FILENO, FALSE);

            assert(PreviousPipeRead != -1);

            ShDup2(Shell, PreviousPipeRead, STDIN_FILENO);
            ShClose(Shell, PreviousPipeRead);
            PreviousPipeRead = -1;
        }

        //
        // Save the previous pipe's read entry. Make it a non-inheritable
        // handle so that when the next process closes standard in, that's the
        // last open handle.
        //

        if (NextPipe[0] != -1) {
            PreviousPipeRead = ShDup(Shell, NextPipe[0], FALSE);
            ShClose(Shell, NextPipe[0]);
            NextPipe[0] = -1;
            Shell->PostForkCloseDescriptor = PreviousPipeRead;
        }

        Result = ShExecuteNode(Shell, Child);

        //
        // Restore standard in and standard out if they were changed.
        //

        if (OriginalStandardIn != -1) {
            ShDup2(Shell, OriginalStandardIn, STDIN_FILENO);
            ShClose(Shell, OriginalStandardIn);
            OriginalStandardIn = -1;
        }

        if (OriginalStandardOut != -1) {
            ShDup2(Shell, OriginalStandardOut, STDOUT_FILENO);
            ShClose(Shell, OriginalStandardOut);
            OriginalStandardOut = -1;
        }

        if (PreviousPipeRead != -1) {

            assert((Shell->PostForkCloseDescriptor == PreviousPipeRead) ||
                   (Shell->PostForkCloseDescriptor == -1));

            Shell->PostForkCloseDescriptor = -1;
        }

        //
        // If executing the command failed, stop now.
        //

        if (Result == FALSE) {
            goto ExecutePipelineEnd;
        }

        if (Shell->Exited != FALSE) {
            break;
        }

        //
        // Break out if the execution node was removed from the stack.
        //

        if (ExecutionNode->ListEntry.Next == NULL) {
            break;
        }
    }

    assert(Shell->ReturnValue == Shell->LastReturnValue);

    if ((Shell->Exited == FALSE) && (Node->U.Pipeline.Bang != FALSE)) {
        Shell->ReturnValue = !Shell->LastReturnValue;
    }

ExecutePipelineEnd:
    if (OriginalStandardIn != -1) {
        ShDup2(Shell, OriginalStandardIn, STDIN_FILENO);
        ShClose(Shell, OriginalStandardIn);
        OriginalStandardIn = -1;
    }

    if (OriginalStandardOut != -1) {
        ShDup2(Shell, OriginalStandardOut, STDOUT_FILENO);
        ShClose(Shell, OriginalStandardOut);
        OriginalStandardOut = -1;
    }

    if (NextPipe[0] != -1) {
        ShClose(Shell, NextPipe[0]);
    }

    if (NextPipe[1] != -1) {
        ShClose(Shell, NextPipe[1]);
    }

    //
    // Check for signals to reap any child processes that were created.
    //

    ShCheckForSignals(Shell);
    return Result;
}

BOOL
ShExecuteSimpleCommand (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    )

/*++

Routine Description:

    This routine executes a simple command. The function makes it sound easier
    than it is.

Arguments:

    Shell - Supplies a pointer to the shell containing the node.

    ExecutionNode - Supplies a pointer to the node to execute.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    ULONG ArgumentCount;
    ULONG ArgumentIndex;
    PSTR *Arguments;
    BOOL Asynchronous;
    PSHELL_BUILTIN_COMMAND BuiltinCommand;
    PSTR ExpandedArguments;
    UINTN ExpandedArgumentsSize;
    PSHELL_FUNCTION Function;
    PSHELL_EXECUTION_NODE LatestExecutionNode;
    PSHELL_NODE Node;
    BOOL Result;
    INT ReturnValue;
    PSHELL_NODE_SIMPLE_COMMAND SimpleCommand;

    ArgumentCount = 0;
    Arguments = NULL;
    ExpandedArguments = NULL;
    Node = ExecutionNode->Node;

    assert(Node->Type == ShellNodeSimpleCommand);

    SimpleCommand = &(Node->U.SimpleCommand);
    if ((Shell->Options & SHELL_OPTION_TRACE_COMMAND) != 0) {
        ShPrintPrompt(Shell, 4);
    }

    Shell->ReturnValue = 0;
    Result = ShExecuteVariableAssignments(Shell, ExecutionNode);
    if (Result == FALSE) {
        goto ExecuteSimpleCommandEnd;
    }

    if (SimpleCommand->Arguments != NULL) {

        //
        // Perform expansions, field splitting, and quote removal.
        //

        Result = ShPerformExpansions(Shell,
                                     SimpleCommand->Arguments,
                                     SimpleCommand->ArgumentsSize,
                                     0,
                                     &ExpandedArguments,
                                     &ExpandedArgumentsSize,
                                     &Arguments,
                                     &ArgumentCount);

        if (Result == FALSE) {
            goto ExecuteSimpleCommandEnd;
        }
    }

    //
    // If tracing is enabled, print the tracing prompt and then the command
    //

    if ((Shell->Options & SHELL_OPTION_TRACE_COMMAND) != 0) {
        for (ArgumentIndex = 0;
             ArgumentIndex < ArgumentCount;
             ArgumentIndex += 1) {

            ShPrintTrace(Shell, "%s ", Arguments[ArgumentIndex]);
        }

        ShPrintTrace(Shell, "\n");
    }

    if (SimpleCommand->Arguments == NULL) {
        Result = TRUE;
        goto ExecuteSimpleCommandEnd;
    }

    //
    // If the command is empty, don't do much.
    //

    if ((ArgumentCount == 0) || (strlen(Arguments[0]) == 0)) {
        Result = TRUE;
        goto ExecuteSimpleCommandEnd;
    }

    Asynchronous = FALSE;
    if (Node->RunInBackground != FALSE) {
        Asynchronous = TRUE;
    }

    //
    // Check to see if this is a builtin command, and run it if it is.
    //

    BuiltinCommand = ShIsBuiltinCommand(Arguments[0]);
    if (BuiltinCommand != NULL) {
        ReturnValue = ShRunBuiltinCommand(Shell,
                                          BuiltinCommand,
                                          ArgumentCount,
                                          Arguments);

        //
        // Put the return value on the most recent execution node.
        //

        if (!LIST_EMPTY(&(Shell->ExecutionStack))) {
            LatestExecutionNode = LIST_VALUE(Shell->ExecutionStack.Next,
                                             SHELL_EXECUTION_NODE,
                                             ListEntry);

            LatestExecutionNode->ReturnValue = ReturnValue;
        }

    } else {

        //
        // Look to see if this is a function, and run that function if so.
        //

        Function = ShGetFunction(Shell, Arguments[0], strlen(Arguments[0]) + 1);
        if (Function != NULL) {
            Result = ShExecuteFunctionInvocation(Shell,
                                                 ExecutionNode,
                                                 Function->Node,
                                                 Arguments + 1,
                                                 ArgumentCount - 1);

            goto ExecuteSimpleCommandEnd;
        }

        Result = TRUE;
        ShRunCommand(Shell,
                     Arguments[0],
                     Arguments,
                     ArgumentCount,
                     Asynchronous,
                     &ReturnValue);
    }

    Shell->ReturnValue = ReturnValue;
    Result = TRUE;

ExecuteSimpleCommandEnd:

    //
    // If the simple command failed and exit on errors is set, potentially
    // exit.
    //

    if ((Shell->Options & SHELL_OPTION_EXIT_ON_FAILURE) != 0) {
        ShExitOnError(Shell);
    }

    if (Arguments != NULL) {
        free(Arguments);
    }

    if (ExpandedArguments != NULL) {
        free(ExpandedArguments);
    }

    return Result;
}

BOOL
ShExecuteFunctionDefinition (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    )

/*++

Routine Description:

    This routine executes a function definition node. The definitions don't
    actually run the function, so this is a no-op.

Arguments:

    Shell - Supplies a pointer to the shell containing the node.

    ExecutionNode - Supplies a pointer to the node to execute.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    BOOL Result;

    //
    // Make the function declaration active in the shell.
    //

    Result = ShDeclareFunction(Shell, ExecutionNode->Node);
    if (Result == FALSE) {
        return FALSE;
    }

    //
    // Function definitions are successful if they were parsed correctly, which
    // it was.
    //

    Shell->ReturnValue = 0;
    return TRUE;
}

BOOL
ShExecuteFunctionInvocation (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutingNode,
    PSHELL_NODE Function,
    PSTR *Arguments,
    ULONG ArgumentCount
    )

/*++

Routine Description:

    This routine executes a function.

Arguments:

    Shell - Supplies a pointer to the shell containing the node.

    ExecutingNode - Supplies a pointer to the execution node this function is
        run as. This will temporarily get pointed to the function node.

    Function - Supplies a pointer to the function to execute.

    Arguments - Supplies a pointer to the array of strings containing the
        arguments.

    ArgumentCount - Supplies the number of arguments in the array.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    PSHELL_NODE Body;
    PSHELL_NODE OriginalNode;
    BOOL Result;

    assert(ExecutingNode->Node->Type == ShellNodeSimpleCommand);
    assert((ExecutingNode->Flags & SHELL_EXECUTION_BODY) == 0);

    OriginalNode = ExecutingNode->Node;
    ExecutingNode->Node = Function;
    ExecutingNode->Flags |= SHELL_EXECUTION_BODY;

    assert(LIST_EMPTY(&(ExecutingNode->ArgumentList)) != FALSE);

    //
    // Create an argument list out of the incoming arguments.
    //

    Result = ShCreateArgumentList(Arguments,
                                  ArgumentCount,
                                  &(ExecutingNode->ArgumentList));

    if (Result == FALSE) {
        goto ExecuteFunctionInvocationEnd;
    }

    //
    // There should only be one thing in the children list, the compound
    // body statement.
    //

    assert((LIST_EMPTY(&(Function->Children)) == FALSE) &&
           (Function->Children.Next->Next == &(Function->Children)));

    Body = LIST_VALUE(Function->Children.Next, SHELL_NODE, SiblingListEntry);
    Result = ShExecuteNode(Shell, Body);
    if (Result == FALSE) {
        goto ExecuteFunctionInvocationEnd;
    }

ExecuteFunctionInvocationEnd:
    ExecutingNode->Flags &= ~SHELL_EXECUTION_BODY;

    //
    // If the options were made local, restore them now.
    //

    if ((ExecutingNode->Flags & SHELL_EXECUTION_RESTORE_OPTIONS) != 0) {
        Shell->Options = ExecutingNode->SavedOptions;
    }

    ExecutingNode->Node = OriginalNode;

    //
    // Destroy the current argument list, as it's the ones set up for the
    // function.
    //

    ShDestroyArgumentList(&(ExecutingNode->ArgumentList));
    return Result;
}

BOOL
ShExecuteIf (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    )

/*++

Routine Description:

    This routine executes an if statement.

Arguments:

    Shell - Supplies a pointer to the shell containing the node.

    ExecutionNode - Supplies a pointer to the node to execute.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    PSHELL_NODE Condition;
    INT ConditionReturn;
    PSHELL_NODE FalseStatement;
    PSHELL_NODE Node;
    BOOL Result;
    PSHELL_NODE TrueStatement;

    Node = ExecutionNode->Node;

    assert(Node->Type == ShellNodeIf);

    //
    // Get the condition, true, and maybe the false conditions.
    //

    assert(Node->Children.Next != &(Node->Children));

    Condition = LIST_VALUE(Node->Children.Next, SHELL_NODE, SiblingListEntry);

    assert(Condition->SiblingListEntry.Next != &(Node->Children));

    TrueStatement = LIST_VALUE(Condition->SiblingListEntry.Next,
                               SHELL_NODE,
                               SiblingListEntry);

    FalseStatement = NULL;
    if (TrueStatement->SiblingListEntry.Next != &(Node->Children)) {
        FalseStatement = LIST_VALUE(TrueStatement->SiblingListEntry.Next,
                                    SHELL_NODE,
                                    SiblingListEntry);
    }

    Result = ShExecuteNode(Shell, Condition);
    if (Result == FALSE) {
        return FALSE;
    }

    if (Shell->Exited != FALSE) {
        return TRUE;
    }

    ConditionReturn = Shell->LastReturnValue;
    Shell->ReturnValue = 0;

    //
    // Break out if no longer on the execution stack.
    //

    if (ExecutionNode->ListEntry.Next == NULL) {
        return TRUE;
    }

    //
    // Go to the true statement if the return value was zero.
    //

    ExecutionNode->Flags |= SHELL_EXECUTION_BODY;
    if (ConditionReturn == 0) {
        Result = ShExecuteNode(Shell, TrueStatement);

    } else if (FalseStatement != NULL) {
        Result = ShExecuteNode(Shell, FalseStatement);
    }

    ExecutionNode->Flags &= ~SHELL_EXECUTION_BODY;
    return Result;
}

BOOL
ShExecuteFor (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    )

/*++

Routine Description:

    This routine executes a for loop.

Arguments:

    Shell - Supplies a pointer to the shell containing the node.

    ExecutionNode - Supplies a pointer to the node to execute.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    PSHELL_NODE DoGroup;
    PSHELL_NODE_FOR ForStatement;
    PSHELL_NODE Node;
    BOOL Result;
    PSTR SplitBuffer;
    ULONG WordCount;
    ULONG WordIndex;
    PSTR WordListString;
    UINTN WordListStringSize;
    PSTR *Words;

    Node = ExecutionNode->Node;
    SplitBuffer = NULL;
    WordListString = NULL;
    Words = NULL;

    assert(Node->Type == ShellNodeFor);
    assert(LIST_EMPTY(&(Node->Children)) == FALSE);

    ForStatement = &(Node->U.For);
    DoGroup = LIST_VALUE(Node->Children.Next, SHELL_NODE, SiblingListEntry);

    assert(DoGroup->SiblingListEntry.Next == &(Node->Children));

    //
    // Expand the word list. If there is no word list, use the parameters.
    //

    if (ForStatement->WordListBuffer == NULL) {
        Result = ShPerformExpansions(Shell,
                                     ShQuotedAtArgumentsString,
                                     sizeof(ShQuotedAtArgumentsString),
                                     0,
                                     &WordListString,
                                     &WordListStringSize,
                                     &Words,
                                     &WordCount);

    } else {
        Result = ShPerformExpansions(Shell,
                                     ForStatement->WordListBuffer,
                                     ForStatement->WordListBufferSize,
                                     0,
                                     &WordListString,
                                     &WordListStringSize,
                                     &Words,
                                     &WordCount);
    }

    if (Result == FALSE) {
        goto ExecuteForEnd;
    }

    //
    // If there are no words anymore, simply end.
    //

    if (WordCount == 0) {
        Shell->ReturnValue = 0;
        Result = TRUE;
        goto ExecuteForEnd;
    }

    //
    // Loop through every word, assign the variable, and execute the do group.
    //

    for (WordIndex = 0; WordIndex < WordCount; WordIndex += 1) {
        Result = ShSetVariable(Shell,
                               ForStatement->Name,
                               ForStatement->NameSize,
                               Words[WordIndex],
                               strlen(Words[WordIndex]) + 1);

        if (Result == FALSE) {
            goto ExecuteForEnd;
        }

        Result = ShExecuteNode(Shell, DoGroup);
        if (Result == FALSE) {
            goto ExecuteForEnd;
        }

        if (Shell->Exited != FALSE) {
            break;
        }

        //
        // Stop if this execution node is no longer on the stack.
        //

        if (ExecutionNode->ListEntry.Next == NULL) {
            break;
        }
    }

ExecuteForEnd:
    if (WordListString != NULL) {
        free(WordListString);
    }

    if (SplitBuffer != NULL) {
        free(SplitBuffer);
    }

    if (Words != NULL) {
        free(Words);
    }

    return Result;
}

BOOL
ShExecuteCase (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    )

/*++

Routine Description:

    This routine executes a list node.

Arguments:

    Shell - Supplies a pointer to the shell containing the node.

    ExecutionNode - Supplies a pointer to the node to execute.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    PLIST_ENTRY CurrentPatternEntry;
    PLIST_ENTRY CurrentSetEntry;
    PSTR ExpandedPattern;
    UINTN ExpandedPatternSize;
    PSTR Input;
    UINTN InputSize;
    BOOL Match;
    PSHELL_NODE Node;
    ULONG Options;
    PSHELL_CASE_PATTERN_ENTRY PatternEntry;
    BOOL Result;
    PSHELL_CASE_PATTERN_SET Set;

    Input = NULL;
    Match = FALSE;
    Node = ExecutionNode->Node;

    assert(Node->Type == ShellNodeCase);

    //
    // Get and expand the input.
    //

    Result = ShPerformExpansions(Shell,
                                 Node->U.Case.Name,
                                 Node->U.Case.NameSize,
                                 SHELL_EXPANSION_OPTION_NO_FIELD_SPLIT,
                                 &Input,
                                 &InputSize,
                                 NULL,
                                 NULL);

    if (Result == FALSE) {
        goto ExecuteCaseEnd;
    }

    if (LIST_EMPTY(&(Node->U.Case.PatternList)) != FALSE) {
        Result = TRUE;
        goto ExecuteCaseEnd;
    }

    Options = SHELL_EXPANSION_OPTION_NO_FIELD_SPLIT |
              SHELL_EXPANSION_OPTION_NO_QUOTE_REMOVAL;

    //
    // Loop through every case and see if any of the sets of patterns match.
    //

    CurrentSetEntry = Node->U.Case.PatternList.Next;
    while (CurrentSetEntry != &(Node->U.Case.PatternList)) {
        Set = LIST_VALUE(CurrentSetEntry, SHELL_CASE_PATTERN_SET, ListEntry);
        CurrentSetEntry = CurrentSetEntry->Next;

        //
        // Loop through every pattern in the set.
        //

        CurrentPatternEntry = Set->PatternEntryList.Next;
        while (CurrentPatternEntry != &(Set->PatternEntryList)) {
            PatternEntry = LIST_VALUE(CurrentPatternEntry,
                                      SHELL_CASE_PATTERN_ENTRY,
                                      ListEntry);

            CurrentPatternEntry = CurrentPatternEntry->Next;
            Result = ShPerformExpansions(Shell,
                                         PatternEntry->Pattern,
                                         PatternEntry->PatternSize,
                                         Options,
                                         &ExpandedPattern,
                                         &ExpandedPatternSize,
                                         NULL,
                                         NULL);

            if (Result == FALSE) {
                goto ExecuteCaseEnd;
            }

            ShStringDequote(ExpandedPattern,
                            ExpandedPatternSize,
                            SHELL_DEQUOTE_FOR_PATTERN_MATCHING,
                            &ExpandedPatternSize);

            Match = SwDoesPatternMatch(Input,
                                       InputSize,
                                       ExpandedPattern,
                                       ExpandedPatternSize);

            free(ExpandedPattern);

            //
            // If the input matches the case, run the action associated with
            // it and end the case.
            //

            if (Match != FALSE) {
                Result = TRUE;
                if (Set->Action != NULL) {
                    Result = ShExecuteNode(Shell, Set->Action);
                }

                goto ExecuteCaseEnd;
            }
        }
    }

ExecuteCaseEnd:
    if (Input != NULL) {
        free(Input);
    }

    //
    // If no case was executed, the return value is zero.
    //

    if (Match == FALSE) {
        Shell->ReturnValue = 0;
    }

    return Result;
}

BOOL
ShExecuteWhileOrUntil (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    )

/*++

Routine Description:

    This routine executes a while statement or an until statement.

Arguments:

    Shell - Supplies a pointer to the shell containing the node.

    ExecutionNode - Supplies a pointer to the node to execute.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    BOOL BeenAround;
    PSHELL_NODE Condition;
    INT ConditionResult;
    PSHELL_NODE DoGroup;
    BOOL ExecuteDoGroup;
    PSHELL_NODE Node;
    BOOL Result;

    Node = ExecutionNode->Node;

    assert((Node->Type == ShellNodeWhile) || (Node->Type == ShellNodeUntil));

    //
    // Get the condition, true, and maybe the false conditions.
    //

    assert(Node->Children.Next != &(Node->Children));

    Condition = LIST_VALUE(Node->Children.Next, SHELL_NODE, SiblingListEntry);

    assert(Condition->SiblingListEntry.Next != &(Node->Children));

    DoGroup = LIST_VALUE(Condition->SiblingListEntry.Next,
                         SHELL_NODE,
                         SiblingListEntry);

    //
    // Execute the do-group as long as the condition is zero for while loops
    // or non-zero for until loops.
    //

    BeenAround = FALSE;
    while (TRUE) {
        Result = ShExecuteNode(Shell, Condition);
        if (Result == FALSE) {
            return FALSE;
        }

        if (Shell->Exited != FALSE) {
            break;
        }

        ConditionResult = Shell->LastReturnValue;
        Shell->ReturnValue = 0;

        //
        // Break out if no longer on the execution stack.
        //

        if (ExecutionNode->ListEntry.Next == NULL) {
            return TRUE;
        }

        //
        // Figure out whether or not to execute the do group.
        //

        ExecuteDoGroup = FALSE;
        if (Node->Type == ShellNodeWhile) {
            if (ConditionResult == 0) {
                ExecuteDoGroup = TRUE;
            }

        } else {
            if (ConditionResult != 0) {
                ExecuteDoGroup = TRUE;
            }
        }

        //
        // If the do-group isn't going to be executed and never has before,
        // the return value is zero. Otherwise the return value is left as the
        // last command in the do-group.
        //

        if (ExecuteDoGroup == FALSE) {
            if (BeenAround == FALSE) {
                Shell->ReturnValue = 0;
            }

            break;
        }

        //
        // Run the do-group.
        //

        ExecutionNode->Flags |= SHELL_EXECUTION_BODY;
        Result = ShExecuteNode(Shell, DoGroup);
        ExecutionNode->Flags &= ~SHELL_EXECUTION_BODY;
        if (Result == FALSE) {
            return FALSE;
        }

        //
        // Break out if no longer on the execution stack, otherwise loop
        // around and run the condition again.
        //

        if (ExecutionNode->ListEntry.Next == NULL) {
            return TRUE;
        }
    }

    return Result;
}

BOOL
ShExecuteSubshellGroup (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    )

/*++

Routine Description:

    This routine executes a subshell compound statement, which is a compound
    list inside of parentheses.

Arguments:

    Shell - Supplies a pointer to the shell containing the node.

    ExecutionNode - Supplies a pointer to the node to execute.

Return Value:

    TRUE on success.

    FALSE on failure.

--*/

{

    PSHELL_NODE Child;
    pid_t ChildProcess;
    PLIST_ENTRY CurrentEntry;
    PSHELL_NODE Node;
    PSTR OriginalDirectory;
    BOOL Result;
    INT Status;
    PSHELL Subshell;
    pid_t WaitResult;

    Node = ExecutionNode->Node;
    OriginalDirectory = NULL;

    assert(Node->Type == ShellNodeSubshell);

    Subshell = ShCreateSubshell(Shell, NULL, 0, FALSE);
    if (Subshell == NULL) {
        return FALSE;
    }

    ChildProcess = -1;
    if (SwForkSupported != FALSE) {
        ChildProcess = SwFork();

    } else {

        //
        // Save the current directory.
        //

        OriginalDirectory = getcwd(NULL, 0);
    }

    //
    // Execute all the children on the subshell (either if this is the child
    // process or fork never happened.
    //

    if (ChildProcess <= 0) {
        CurrentEntry = Node->Children.Next;
        while (CurrentEntry != &(Node->Children)) {
            Child = LIST_VALUE(CurrentEntry, SHELL_NODE, SiblingListEntry);
            CurrentEntry = CurrentEntry->Next;
            Result = ShExecuteNode(Subshell, Child);
            if (Result == FALSE) {
                goto ExecuteSubshellGroupEnd;
            }

            if (Shell->Exited != FALSE) {
                break;
            }

            //
            // Break out if the execution node was removed from the stack.
            //

            if (ExecutionNode->ListEntry.Next == NULL) {
                break;
            }
        }
    }

    //
    // If this is the child process, exit now.
    //

    if (ChildProcess == 0) {
        exit(Subshell->LastReturnValue);

    //
    // If this is the parent process, wait for the child.
    //

    } else if (ChildProcess > 0) {
        WaitResult = SwWaitPid(ChildProcess, 0, &(Subshell->LastReturnValue));
        if (WaitResult == -1) {
            Subshell->LastReturnValue = SHELL_ERROR_OPEN;
            SwPrintError(errno,
                         NULL,
                         "Failed to wait for pid %d",
                         ChildProcess);

            Result = FALSE;
            goto ExecuteSubshellGroupEnd;
        }

        ShOsConvertExitStatus(&(Subshell->LastReturnValue));
    }

    Shell->ReturnValue = Subshell->LastReturnValue;
    Result = TRUE;

ExecuteSubshellGroupEnd:
    if (Subshell != NULL) {
        ShDestroyShell(Subshell);
    }

    //
    // Restore the current directory.
    //

    if (OriginalDirectory != NULL) {
        Status = chdir(OriginalDirectory);
        if (Status != 0) {
            Result = FALSE;
        }

        free(OriginalDirectory);
    }

    return Result;
}

VOID
ShExitOnError (
    PSHELL Shell
    )

/*++

Routine Description:

    This routine exits the shell if the most recent simple command failed,
    unless the simple command is part of a compound list inside a while, until,
    or if, is part of an And-Or list, or is a pipeline with a bang.

Arguments:

    Shell - Supplies a pointer to the shell containing the node.

Return Value:

    None.

--*/

{

    PLIST_ENTRY CurrentEntry;
    PSHELL_EXECUTION_NODE ExecutionNode;
    SHELL_NODE_TYPE Type;

    if ((Shell->Exited != FALSE) || (Shell->ReturnValue == 0)) {
        return;
    }

    CurrentEntry = Shell->ExecutionStack.Next;
    while (CurrentEntry != &(Shell->ExecutionStack)) {
        ExecutionNode = LIST_VALUE(CurrentEntry,
                                   SHELL_EXECUTION_NODE,
                                   ListEntry);

        CurrentEntry = CurrentEntry->Next;

        //
        // If an if/while/until is found with the body executing, an And-Or
        // is found, or a pipeline is found with a bang then don't exit, just
        // return.
        //

        Type = ExecutionNode->Node->Type;
        if (((Type == ShellNodeIf) || (Type == ShellNodeWhile) ||
             (Type == ShellNodeUntil) || (Type == ShellNodeAndOr)) &&
            ((ExecutionNode->Flags & SHELL_EXECUTION_BODY) == 0)) {

            return;
        }

        if ((Type == ShellNodePipeline) &&
            (ExecutionNode->Node->U.Pipeline.Bang != FALSE)) {

            return;
        }
    }

    //
    // None of the conditions were met, so exit this shell.
    //

    Shell->Exited = TRUE;
    return;
}

BOOL
ShApplyRedirections (
    PSHELL Shell,
    PSHELL_EXECUTION_NODE ExecutionNode
    )

/*++

Routine Description:

    This routine applies any redirections to the current command.

Arguments:

    Shell - Supplies a pointer to the shell.

    ExecutionNode - Supplies a pointer to the execution node to apply
        redirections of.

Return Value:

    TRUE on success.

--*/

{

    PSHELL_ACTIVE_REDIRECT ActiveRedirect;
    PSTR AfterScan;
    PLIST_ENTRY CurrentEntry;
    PSTR DocumentText;
    UINTN DocumentTextSize;
    PSTR ExpandedFileName;
    UINTN ExpandedFileNameSize;
    PSTR ExpandedString;
    UINTN ExpandedStringSize;
    PSHELL_HERE_DOCUMENT HereDocument;
    INT NewDescriptor;
    INT NewDescriptorAnywhere;
    INT OpenFlags;
    ULONG Options;
    INT OriginalDescriptor;
    unsigned long PathSize;
    INT Pipe[2];
    PSHELL_IO_REDIRECT Redirect;
    BOOL Result;
    INT SourceFileNumber;
    SHELL_IO_REDIRECTION_TYPE Type;
    INT WriteCopy;

    ActiveRedirect = NULL;
    ExpandedFileName = NULL;
    ExpandedFileNameSize = 0;
    ExpandedString = NULL;
    Pipe[0] = -1;
    Pipe[1] = -1;

    //
    // Loop through all the redirections.
    //

    CurrentEntry = ExecutionNode->Node->RedirectList.Next;
    while (CurrentEntry != &(ExecutionNode->Node->RedirectList)) {
        Redirect = LIST_VALUE(CurrentEntry, SHELL_IO_REDIRECT, ListEntry);
        CurrentEntry = CurrentEntry->Next;
        Type = Redirect->Type;

        //
        // Allocate an active redirect entry.
        //

        ActiveRedirect = malloc(sizeof(SHELL_ACTIVE_REDIRECT));
        if (ActiveRedirect == NULL) {
            Result = FALSE;
            goto ApplyRedirectionsEnd;
        }

        ActiveRedirect->ChildProcessId = -1;

        //
        // Expand the file name.
        //

        if (Redirect->FileName != NULL) {
            Options = SHELL_EXPANSION_OPTION_NO_FIELD_SPLIT;
            Result = ShPerformExpansions(Shell,
                                         Redirect->FileName,
                                         Redirect->FileNameSize,
                                         Options,
                                         &ExpandedFileName,
                                         &ExpandedFileNameSize,
                                         NULL,
                                         NULL);

            if (Result == FALSE) {
                goto ApplyRedirectionsEnd;
            }

            //
            // Let the OS layer play with the path if it wants.
            //

            PathSize = ExpandedFileNameSize;
            Result = ShFixUpPath(&ExpandedFileName, &PathSize);
            if (Result == FALSE) {
                goto ApplyRedirectionsEnd;
            }
        }

        //
        // Perform normal file redirections.
        //

        if ((Type == ShellRedirectRead) ||
            (Type == ShellRedirectWrite) ||
            (Type == ShellRedirectAppend) ||
            (Type == ShellRedirectReadWrite) ||
            (Type == ShellRedirectClobber)) {

            OpenFlags = O_CREAT | O_BINARY;
            if ((Shell->Options & SHELL_OPTION_NO_CLOBBER) != 0) {
                OpenFlags |= O_EXCL;
            }

            if (Type == ShellRedirectRead) {
                OpenFlags |= O_RDONLY;
                OpenFlags &= ~(O_CREAT | O_EXCL);

            } else if (Type == ShellRedirectWrite) {
                OpenFlags |= O_WRONLY | O_TRUNC;

            } else if (Type == ShellRedirectAppend) {
                OpenFlags |= O_WRONLY | O_APPEND;

            } else if (Type == ShellRedirectReadWrite) {
                OpenFlags |= O_RDWR;

            } else if (Type == ShellRedirectClobber) {
                OpenFlags |= O_WRONLY | O_TRUNC;
                OpenFlags &= ~O_EXCL;
            }

            //
            // Open up the file.
            //

            NewDescriptorAnywhere = open(ExpandedFileName,
                                         OpenFlags,
                                         SHELL_FILE_CREATION_MASK);

            if (NewDescriptorAnywhere < 0) {
                PRINT_ERROR("sh: Unable to open redirection file %s: %s.\n",
                            ExpandedFileName,
                            strerror(errno));

                Result = FALSE;
                goto ApplyRedirectionsEnd;
            }

            //
            // Copy the original descriptor somewhere, then close the
            // descriptor and copy the newly opened file into it.
            //

            OriginalDescriptor = ShDup(Shell, Redirect->FileNumber, FALSE);
            if (NewDescriptorAnywhere != Redirect->FileNumber) {
                NewDescriptor = ShDup2(Shell,
                                       NewDescriptorAnywhere,
                                       Redirect->FileNumber);

                if (NewDescriptor < 0) {
                    Result = FALSE;
                    goto ApplyRedirectionsEnd;
                }

                ShClose(Shell, NewDescriptorAnywhere);
            }

        //
        // Perform redirections from other file descriptors.
        //

        } else if ((Type == ShellRedirectReadFromDescriptor) ||
                   (Type == ShellRedirectWriteToDescriptor)) {

            //
            // If the source file number evaluates to -, then the file number is
            // closed.
            //

            if (strcmp(ExpandedFileName, "-") == 0) {
                OriginalDescriptor = ShDup(Shell, Redirect->FileNumber, FALSE);
                ShClose(Shell, Redirect->FileNumber);

            } else {
                SourceFileNumber = strtol(ExpandedFileName, &AfterScan, 10);
                if ((SourceFileNumber < 0) || (AfterScan == ExpandedFileName)) {
                    PRINT_ERROR("sh: Bad file descriptor number '%s'.",
                                ExpandedFileName);

                    Result = FALSE;
                    goto ApplyRedirectionsEnd;
                }

                //
                // Copy the original descriptor, then close the destination and
                // copy the source in there.
                //

                OriginalDescriptor = ShDup(Shell, Redirect->FileNumber, FALSE);
                if (Redirect->FileNumber != SourceFileNumber) {
                    NewDescriptor = ShDup2(Shell,
                                           SourceFileNumber,
                                           Redirect->FileNumber);

                    if (NewDescriptor < 0) {
                        PRINT_ERROR("sh: Unable to duplicate file %d.\n",
                                    SourceFileNumber);

                        Result = FALSE;
                        goto ApplyRedirectionsEnd;
                    }
                }
            }

        //
        // Perform a redirection from a here document.
        //

        } else if ((Type == ShellRedirectHereDocument) ||
                   (Type == ShellRedirectStrippedHereDocument)) {

            HereDocument = Redirect->HereDocument;
            DocumentText = HereDocument->Document;
            DocumentTextSize = HereDocument->DocumentSize;

            //
            // Perform expansions on the here document.
            //

            if (Redirect->HereDocument->EndWordWasQuoted == FALSE) {
                Options = SHELL_EXPANSION_OPTION_NO_TILDE_EXPANSION |
                          SHELL_EXPANSION_OPTION_NO_FIELD_SPLIT;

                Result = ShPerformExpansions(Shell,
                                             DocumentText,
                                             DocumentTextSize,
                                             Options,
                                             &ExpandedString,
                                             &ExpandedStringSize,
                                             NULL,
                                             NULL);

                if (Result == FALSE) {
                    goto ApplyRedirectionsEnd;
                }

                DocumentText = ExpandedString;
                DocumentTextSize = ExpandedStringSize;
            }

            //
            // Create a pipe for the here document and wire up the file
            // descriptor to the read end of the pipe.
            //

            Result = ShCreatePipe(Pipe);
            if (Result == FALSE) {
                goto ApplyRedirectionsEnd;
            }

            OriginalDescriptor = ShDup(Shell, Redirect->FileNumber, FALSE);
            if (OriginalDescriptor == -1) {
                Result = FALSE;
                goto ApplyRedirectionsEnd;
            }

            //
            // Copy the write descriptor out of range of the shell standard
            // descriptors, since on Windows the write side is just a thread,
            // so it stays open.
            //

            if (SwForkSupported == 0) {
                WriteCopy = ShDup(Shell, Pipe[1], 0);
                ShClose(Shell, Pipe[1]);
                Pipe[1] = WriteCopy;
            }

            //
            // Launch the subprocess or thread to feed the document text into
            // the descriptor.
            //

            assert(DocumentTextSize != 0);

            DocumentTextSize -= 1;
            Result = ShPushInputText(DocumentText, DocumentTextSize, Pipe);
            if (Result < 0) {
                goto ApplyRedirectionsEnd;
            }

            Pipe[1] = -1;
            ShDup2(Shell, Pipe[0], Redirect->FileNumber);
            ShClose(Shell, Pipe[0]);
            Pipe[0] = -1;
            if (Result > 0) {
                ActiveRedirect->ChildProcessId = Result;
            }

        } else {

            assert(FALSE);

            Result = FALSE;
            goto ApplyRedirectionsEnd;
        }

        if (Redirect->FileNumber == STDOUT_FILENO) {
            fflush(stdout);

        } else if (Redirect->FileNumber == STDERR_FILENO) {
            fflush(stderr);
        }

        //
        // Initialize the active redirect so that the original descriptor can
        // be restored.
        //

        ActiveRedirect->FileNumber = Redirect->FileNumber;
        ActiveRedirect->OriginalDescriptor = OriginalDescriptor;
        INSERT_BEFORE(&(ActiveRedirect->ListEntry),
                      &(ExecutionNode->ActiveRedirectList));

        ActiveRedirect = NULL;

        //
        // Free the expanded file name.
        //

        if (ExpandedFileName != NULL) {
            free(ExpandedFileName);
            ExpandedFileName = NULL;
        }
    }

    Result = TRUE;

ApplyRedirectionsEnd:
    if (Pipe[0] != -1) {
        ShClose(Shell, Pipe[0]);
    }

    if (Pipe[1] != -1) {
        ShClose(Shell, Pipe[1]);
    }

    if (ExpandedString != NULL) {
        free(ExpandedString);
    }

    if (ExpandedFileName != NULL) {
        free(ExpandedFileName);
    }

    if (Result == FALSE) {
        if (ActiveRedirect != NULL) {
            free(ActiveRedirect);
        }
    }

    return Result;
}

