Shell-nyush / src / fall22 / main / executor.c
executor.c
Raw
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "utils.h"


void executeCommandNoPiping(char **parsedTokens, int tokenCount, char *buf) {

    if (tokenCount > 0) {                                                           // The command line is not blank
        char *firstToken = copyString(parsedTokens[0]);                             // Temporary storage for the first token
        if (strcmp(firstToken, "cd\0") == 0) {                                      // The first token is "cd"
            nyushCd(parsedTokens, curDirectory);                                    // Runs the function "nyushCd()"
        }
        else if (strcmp(firstToken, "exit\0") == 0) {                               // The first token is "exit"
            nyushExit(parsedTokens, buf, firstToken);                               // Runs the function "nyushExit()"
        }
        else if (strcmp(firstToken, "fg\0") == 0) {                                 // The first token is "fg"
            nyushFg(parsedTokens);                                                  // Runs the function "nyushFg()"
        }
        else if (strcmp(firstToken, "jobs\0") == 0) {                               // The first token in "jobs"
            nyushJobs(parsedTokens);                                                // Runs the function "nyushJobs()"
        }
        else {                                                                      // The first token is not a built-in command
            nyushRun(parsedTokens, buf, firstToken);                                // Runs the function "nyushRun()"
        }
        free(firstToken);                                                           // Frees temporary storage for the first token
    }
    freeAll("112211", buf, commandLine, parsedTokens, pipedCommands, inputFileName, outputFileName);  // Frees all allocated memory

}

void executeCommandWithPiping(char **pipedCommands, int subCommandCount, char *buf) {

    pid_t pid;                                                                      // Variable for process id
    int status;                                                                     // Status of how the child is terminated
    if (subCommandCount != pipeCount + 1) {                                         // Some pipe does not have a source or target
        freeAll("1122", buf, commandLine, parsedTokens, pipedCommands);             // Frees all allocated memory
        fprintf(stderr, "Error: invalid command\n");                                // Raises error since some pipe does not have a source of target
        return;                                                                     // Skips execution
    }

    int *fileDescriptors = malloc(sizeof(int) * 2 * pipeCount);                     // Array of file descriptors, each a combination of input and output
    for (int i = 0; i < 2 * pipeCount - 1; i += 2) {                                // Iterates through each combination
        if (pipe(fileDescriptors + i) == -1) {                                      // Creates a pipe for interprocess communication, and the case that piping failed
            freeAll("d1122", fileDescriptors, buf, commandLine, parsedTokens, pipedCommands);  // Frees all allocated memory
            fprintf(stderr, "Error: execution failed\n");                           // Raises error since piping failed
            return;                                                                 // Skips execution
        }
    }

    for (int i = 0; i <= pipeCount; i ++) {                                         // Iterates through all piped commands
        char *tokenBuffer = malloc(sizeof(char) * MAXLENGTH);                       // Temporary buffer for parsing the subcommand line
        parseNoPiping(pipedCommands[i], tokenBuffer);                               // Parses the subcommand line
        isBackground = 0;                                                           // Sets current process as background
        pid = fork();                                                               // Forks a child process
        if (i != pipeCount) {                                                       // Not the last subcommand
            suspendNewJob(pid, commandLine);                                        // Suspends all subcommands that are not the last
        }
        if (pid < 0) {                                                              // Forking failed
            freeAll("d11122", fileDescriptors, buf, tokenBuffer, commandLine, parsedTokens, pipedCommands);  // Frees all allocated memory
            fprintf(stderr, "Error: execution failed\n");                           // Raises error since forking failed
            return;                                                                 // Skips execution
        }
        else if (pid == 0) {                                                        // In the forked child process
            if (hasInput > 0) {                                                     // Subcommand has input redirection
                if (i != 0) {                                                       // Input redirection occurs not in the first subcommand
                    freeAll("d111221", fileDescriptors, buf, tokenBuffer, commandLine, parsedTokens, pipedCommands, inputFileName);  // Frees all allocated memory
                    releaseSuspendedJob(pid);                                       // Releases the previously suspended job
                    fprintf(stderr, "Error: invalid command\n");                    // Raises error since input redirection occurs not in first subcommand
                    exit(-1);                                                       // Exits the child process
                }
                int fileIn = open(inputFileName, O_RDONLY, S_IRWXU);                // Opens the input file for reading only
                if (fileIn == -1) {                                                 // Input file opening failed
                    freeAll("d11122", fileDescriptors, buf, tokenBuffer, commandLine, parsedTokens, pipedCommands);  // Frees all allocated memory
                    fprintf(stderr, "Error: invalid file\n");                       // Raises error since input file opening failed
                    exit(-1);                                                       // Exits the child process
                }
                dup2(fileIn, STDIN_FILENO);                                         // Redirects the input file to STDIN
                close(fileIn);                                                      // Closes the input file
            }
            else if (i != 0) {                                                      // Not the first subcommand
                dup2(fileDescriptors[2 * i - 2], STDIN_FILENO);                     // Redirects the previous read end to STDIN
            }
            if (hasOutput > 0) {                                                    // Subcommand has output redirection
                if (i != pipeCount) {                                               // Output redirection occurs not in the last subcommand
                    freeAll("d111221", fileDescriptors, buf, tokenBuffer, commandLine, parsedTokens, pipedCommands, outputFileName);  // Frees all allocated memory
                    releaseSuspendedJob(pid);                                       // Releases the previously suspended job
                    fprintf(stderr, "Error: invalid command\n");                    // Raises error since output redirection occurs not in last subcommand
                    exit(-1);                                                       // Exits the child process
                }
                int fileOut;                                                        // Output file descriptor
                if (hasOutput == 1) {                                               // Output redirection is in write mode
                    fileOut = open(outputFileName, O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);  // Opens the output file for writing only, in trucating mode
                }
                else if (hasOutput == 2) {                                          // Output redirection is in append mode
                    fileOut = open(outputFileName, O_CREAT|O_WRONLY|O_APPEND, S_IRWXU);  // Opens the output file for writing only, in appending mode
                }
                if (fileOut == -1) {                                                // Output file opening failed
                    freeAll("d11122", fileDescriptors, buf, tokenBuffer, commandLine, parsedTokens, pipedCommands);  // Frees all allocated memory
                    fprintf(stderr, "Error: invalid file\n");                       // Raises error since output file opening failed
                    exit(-1);                                                       // Exits the child process
                }
                dup2(fileOut, STDOUT_FILENO);                                       // Redirects the output file to STDOUT
                close(fileOut);                                                     // Closes the output file
            }
            else if (i != pipeCount) {                                              // Not the last subcommand
                dup2(fileDescriptors[2 * i + 1], STDOUT_FILENO);                    // Redirects the current write end to STDOUT
            }
            signal(SIGINT, SIG_DFL);                                                // Restores SIGINT signal
            signal(SIGQUIT, SIG_DFL);                                               // Restores SIGQUIT signal
            signal(SIGTSTP, SIG_DFL);                                               // Restores SIGTSTP signal
            for (int i = 0; i < 2 * pipeCount; i ++) {                              // Iterates through all file descriptors
                close(fileDescriptors[i]);                                          // Closes each file descriptor
            }
            if (execvp(parsedTokens[0], parsedTokens) == -1) {                      // Executes the subcommand, case when execution failed
                freeAll("d11122", fileDescriptors, buf, tokenBuffer, commandLine, parsedTokens, pipedCommands);  // Frees all allocated memory
                fprintf(stderr, "Error: invalid program\n");                        // Raises error since execution failed
                exit(-1);                                                           // Exits the child process
            }
        }
        releaseSuspendedJob(pid);                                                   // Releases the currently suspended job
        freeAll("111", tokenBuffer, inputFileName, outputFileName);                 // Frees all allocated memory
    }

    for (int i = 0; i < 2 * pipeCount; i ++) {                                      // Iterates through all file descriptors
        close(fileDescriptors[i]);                                                  // Closes each file descriptor
    }
    free(fileDescriptors);                                                          // Frees the array of file descriptors

    if (isBackground == 0) {                                                        // Process is currently in background
        for (int i = 0; i <= pipeCount; i ++) {                                     // Iterates through all subcommands
            pid_t curPid = waitpid(pid, &status, WUNTRACED);                        // Waits until a change of status, return the child that changes status
            if (!WIFSTOPPED(status)) {                                              // The status change is not a stop
                releaseSuspendedJob(curPid);                                        // Releases the job of change of status
            }
        }
    }
    freeAll("1122", buf, commandLine, parsedTokens, pipedCommands);                 // Frees all allocated memory

}

int nyushCd(char **parsedTokens, char *curDirectory) {
	
    if (hasInput != 0 || hasOutput != 0) {                                          // Command line has redirection
        fprintf(stderr, "Error: invalid command\n");                                // Raises error since built-in commands cannot be redirected
        return -1;                                                                  // Skips execution and returns fail signal
    }

    if (parsedTokens[1] == NULL) {                                                  // Command line has only one argument
        fprintf(stderr, "Error: invalid command\n");                                // Raises error since "cd" cannot take 0 arguments
        return -1;                                                                  // Skips execution and returns fail signal
    }
    else if (parsedTokens[2] != NULL) {                                             // Command line has at least three arguments
        fprintf(stderr, "Error: invalid command\n");                                // Raises error since "cd" cannot take 2+ arguments
        return -1;                                                                  // Skips execution and returns fail signal
    }
    else {                                                                          // Command line has correct number of arguments
        char *secondToken = copyString(parsedTokens[1]);                             // Temporary storage for the second token
        if (chdir(secondToken) == 0) {                                              // Directory successfully changed
            getcwd(curDirectory, MAXDIRECTORY);                                     // Gets current working directory
            free(secondToken);                                                      // Frees temporary storage for the second token
            return 0;                                                               // Terminates execution and returns success signal
        }
        else {                                                                      // Directory changing failed
            free(secondToken);                                                      // Frees temporary storage for the second token
            fprintf(stderr, "Error: invalid directory\n");                          // Raises error since directory changing failed
            return -1;                                                              // Skips execution and returns fail signal
        }
    }

}

int nyushExit(char **parsedTokens, char *buf, char *firstToken) {

    if (hasInput != 0 || hasOutput != 0) {                                          // Command line has redirection
        fprintf(stderr, "Error: invalid command\n");                                // Raises error since built-in commands cannot be redirected
        return -1;                                                                  // Skips execution and returns fail signal
    }

    if (parsedTokens[1] != NULL) {                                                  // Command line has at least two arguments
        fprintf(stderr, "Error: invalid command\n");                                // Raises error since "exit" takes no arguments
        return -1;                                                                  // Skips execution and returns fail signal
    }
    else {                                                                          // Command line has correct number of arguments
        if (jobCount > 0) {                                                         // There are suspended jobs
            fprintf(stderr, "Error: there are suspended jobs\n");                   // Raises error since there are suspended jobs
            return -1;                                                              // Skips execution and returns fail signal
        }
        else {                                                                      // There are no suspended jobs
            freeAll("1112211", buf, commandLine, firstToken, parsedTokens, pipedCommands, inputFileName, outputFileName);  // Frees all allocated memory
            exit(0);                                                                // Exits the shell
        }
    }

}

int nyushFg(char **parsedTokens) {

    if (hasInput != 0 || hasOutput != 0) {                                          // Command line has redirection
        fprintf(stderr, "Error: invalid command\n");                                // Raises error since built-in commands cannot be redirected
        return -1;                                                                  // Skips execution and returns fail signal
    }

    if (parsedTokens[1] == NULL) {                                                  // Command line has only one argument
        fprintf(stderr, "Error: invalid command\n");                                // Raises error since "fg" cannot take 0 arguments
        return -1;                                                                  // Skips execution and returns fail signal
    }
    else if (parsedTokens[2] != NULL) {                                             // Command line has at least three arguments
        fprintf(stderr, "Error: invalid command\n");                                // Raises error since "fg" cannot take 2+ arguments
        return -1;                                                                  // Skips execution and returns fail signal
    }
    else {                                                                          // Command line has correct number of arguments
        int index = atoi(parsedTokens[1]);                                          // Gets the specified index (note that this starts from 1)
        if (index <= 0 || index > jobCount) {                                       // Index out of range
            fprintf(stderr, "Error: invalid job\n");                                // Raises error since index out of range
            return -1;                                                              // Skips execution and returns fail signal
        }
        else {                                                                      // Valid job index
            pid_t pid = suspendedJobs[index - 1].pid;                               // Gets the process id of the specified job
            kill(pid, SIGCONT);                                                     // Resumes the specified job
            int status;                                                             // Status of how the child is terminated
            waitpid(pid, &status, WUNTRACED);                                       // Waits until a change of status
            if (!WIFSTOPPED(status)) {                                              // The status is not a stop
                free(suspendedJobs[index - 1].pname);                               // Frees the allocated name space of the specified job
                for (int j = index - 1; j < jobCount; j ++) {                       // Iterates through its following jobs
                    suspendedJobs[j] = suspendedJobs[j + 1];                        // Moves each of its following jobs forward
                }
                jobCount --;                                                        // Decrements the number of jobs
            }
            return 0;                                                               // Returns success signal
        }
    }

}

int nyushJobs(char **parsedTokens) {

    if (hasInput != 0 || hasOutput != 0) {                                          // Command line has redirection
        fprintf(stderr, "Error: invalid command\n");                                // Raises error since built-in commands cannot be redirected
        return -1;                                                                  // Skips execution and returns fail signal
    }

    if (parsedTokens[1] != NULL) {                                                  // Command line has at least two arguments
        fprintf(stderr, "Error: invalid command\n");                                // Raises error since "jobs" can take no arguments
        return -1;                                                                  // Skips execution and returns fail signal
    }
    else {                                                                          // Command line has the correct number of arguments
        for (int i = 0; i < jobCount; i ++) {                                       // Iterates through all suspended jobs
            printf("[%d] %s\n", i + 1, suspendedJobs[i].pname);                     // Prints the index and command line of each suspended job
        }
        return 0;                                                                   // Returns success signal
    }

}

int nyushRun(char **parsedTokens, char *buf, char *firstToken) {

    pid_t pid = fork();                                                             // Forks a child process
	
    if (pid < 0) {                                                                  // Forking failed
        fprintf(stderr, "Error: execution failed\n");                               // Raises error since forking failed
        return -1;                                                                  // Skips execution and returns fail signal
    }

    else if (pid == 0) {                                                            // In the forked child process
        if (hasInput > 0) {                                                         // Input redirected in the command line
            int fileIn = open(inputFileName, O_RDONLY, S_IRWXU);                    // Opens the input file for reading only
            if (fileIn == -1) {                                                     // Input file opening failed
                freeAll("111221", buf, commandLine, firstToken, parsedTokens, pipedCommands, inputFileName);  // Frees all allocated memory
                fprintf(stderr, "Error: invalid file\n");                           // Raises error since input file opening failed
                exit(-1);                                                           // Exits the child process
            }
            dup2(fileIn, STDIN_FILENO);                                             // Redirects the input file to STDIN
            close(fileIn);                                                          // Closes the input file
        }
        if (hasOutput > 0) {                                                        // Output redirected in the command line
            int fileOut;                                                            // Output file descriptor
            if (hasOutput == 1) {                                                   // Output redirection in write mode
                fileOut = open(outputFileName, O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);  // Opens output file for writing only, in truncating mode
            }
            else if (hasOutput == 2) {                                              // Output redirection in append mode
                fileOut = open(outputFileName, O_CREAT|O_WRONLY|O_APPEND, S_IRWXU);  // Opens output file for writing only, in appending mode
            }
            if (fileOut == -1) {                                                    // Output file opening failed
                freeAll("111221", buf, commandLine, firstToken, parsedTokens, pipedCommands, outputFileName);  // Frees all allocated memory
                fprintf(stderr, "Error: invalid file\n");                           // Raises error since output file opening failed
                exit(-1);                                                           // Exits the child process
            }
            dup2(fileOut, STDOUT_FILENO);                                           // Redirects the output file to STDOUT
            close(fileOut);                                                         // Closes the output file
        }
        signal(SIGINT, SIG_DFL);                                                    // Restores SIGINT signal
        signal(SIGQUIT, SIG_DFL);                                                   // Restores SIGQUIT signal
        signal(SIGTSTP, SIG_DFL);                                                   // Restores SIGTSTP signal
        if (execvp(parsedTokens[0], parsedTokens) == -1) {                          // Executes the command, case when execution failed
            freeAll("11122", firstToken, commandLine, buf, parsedTokens, pipedCommands);  // Frees all allocated memory
            fprintf(stderr, "Error: invalid program\n");                            // Raises error since execution failed
            exit(-1);                                                               // Exits the child process
        }
        exit(0);                                                                    // Exits the child process if execution succeeded
    }

    if (isBackground == 0) {                                                        // Process is currently in background
        suspendNewJob(pid, commandLine);                                            // Suspend the current job
        int status;                                                                 // Status of how the child is terminated
        waitpid(pid, &status, WUNTRACED);                                           // Waits until a change of status
        if (!WIFSTOPPED(status)) {                                                  // The status is not a stop
            releaseSuspendedJob(pid);                                               // Releases the currently suspended job
        }
        return 0;                                                                   // Returns a success signal
    }
    else {                                                                          // Process is currently not in background
        suspendNewJob(pid, commandLine);                                            // Suspend the current job
        return 0;                                                                   // Returns a success signal
    }

}

void suspendNewJob(pid_t pid, char *pname) {

    suspendedJobs[jobCount].pid = pid;                                              // Updates the pid of the last suspended job
	suspendedJobs[jobCount].pname = copyString(pname);                              // Updates the name of the last suspended job
    jobCount ++;                                                                    // Increments the number of suspended jobs

}

void releaseSuspendedJob(pid_t pid) {

    for (int i = 0; i < jobCount; i ++) {                                           // Iterates through all suspended jobs
        if (suspendedJobs[i].pid == pid) {                                          // The pid of the job matches that of the job to be released
            free(suspendedJobs[i].pname);                                           // Frees name space of the job to be released
            for (int j = i; j < jobCount; j ++) {                                   // Iterates through its following jobs
                suspendedJobs[j] = suspendedJobs[j + 1];                            // Moves each following job forward
            }
            jobCount --;                                                            // Decrements the number of jobs
            break;                                                                  // Breaks out of loop since target already found
        }
    }

}

char *copyString(char *str) {

    char *copiedString = malloc(sizeof(char) * (strlen(str) + 1));                  // Allocates memory for the copied string
    strcpy(copiedString, str);                                                      // Copies original string
    return copiedString;                                                            // Returns the pointer to the copied string

}

void freeAll(const char *format, ...) {

    int count = 0;                                                                  // Iterating index
    va_list args;                                                                   // Varadic list
    va_start(args, format);                                                         // Start variadic function with format specifier
    while (format[count] != '\0') {                                                 // Not the end of the format
        if (format[count] == '1') {                                                 // Format specifier is 1, type is char *
            free(va_arg(args, char *));                                             // Moves pointer to next with type char *
        }
        else if (format[count] == '2') {                                            // Format specifier is 2, type is char **
            free(va_arg(args, char **));                                            // Moves pointer to next with type char **
        }
        else if (format[count] == 'd') {                                            // Format specifier is d, type is int *
            free(va_arg(args, int *));                                              // Moves pointer to next with type int *
        }
        count ++;                                                                   // Increments the iterating index
    }
    va_end(args);                                                                   // Marks end of variadic function

}