custom-unix-shell / wish-v7 (parallel missing).c
wish-v7 (parallel missing).c
Raw
//Add your code here
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<ctype.h>
#include<errno.h>
#include<fcntl.h>

#define MAXCOM 1000 // max length of command line
#define MAX_ARGS 100 // max number of commands to be supported

// function for finding ampersand (parallel commands)
int findParallelCmds(char* str, char** parallelStr)
{
    int i; // to be used as parallel cmds counter
    for (i = 0; i < MAX_ARGS; i++) {
        parallelStr[i] = strsep(&str, "&");
        //printf("i value here: %d, cmd: %s\n", i, parallelStr[i]);
        if (parallelStr[i] == NULL) {
            break;
        }
    }
    if (i > 1)
        return i; 
    else {
        return 0; // returns zero if no pipe is found.
    }
}

int checkRedirection(char *str, char *filename) 
{
    // Find the position of the > operator in the command
    char *p = strchr(str, '>');
    if (p == NULL) {
        return 0; // valid: no ">" found
    }
    else if (p == str) {
        return -1; // invalid: empty cmd with ">"
    }
    else {
        // The > operator was found, extract the filename
        //filename = strtok(p + 1, " ");
        char *f = strtok(p + 1, " ");
        if (f == NULL) {
            return -2; // invalid: no file specified
        }

        // get the next token
        p = strtok(NULL, " \t\r\n");
        if (p != NULL) {
            return -3; // invalid: more than one file injected
        }
        //printf("next: %s\n", p);

        strcpy(filename, f);
        // Remove any leading or trailing whitespace from the filename
        filename = strtok(filename, " \t\r\n");
        // Extract the remaining command args
        str = strtok(str, ">");
    }
    return 1; // valid redirection
}

// function to handle built-in commands
// if 0 is returned, it means that none of 
// the built-in commands was executed

int useBuiltInCmds(int argc, char **args) {

    int noOfBuiltInCmds = 3;
    int chosenCmd = 0;
    char* listOfBuiltInCmds[noOfBuiltInCmds];
    listOfBuiltInCmds[0] = "exit";
    listOfBuiltInCmds[1] = "cd";
    listOfBuiltInCmds[2] = "path";

    for (int i = 0; i < noOfBuiltInCmds; i++) {
        if (strcmp(args[0], listOfBuiltInCmds[i]) == 0) {
            chosenCmd = i + 1;
            break;
        }
    }
    switch (chosenCmd) {
    case 1: // exit
        if (argc > 1) {
            char error_message[30] = "An error has occurred\n";
            write(STDERR_FILENO, error_message, strlen(error_message));
            return 1;
        }
        exit(0);
    case 2: // cd
        if (argc == 1 || argc > 2) {
            char error_message[30] = "An error has occurred\n";
            write(STDERR_FILENO, error_message, strlen(error_message));
            return 1;
        }
        if (chdir(args[1]) != 0) {
            char error_message[30] = "An error has occurred\n";
            write(STDERR_FILENO, error_message, strlen(error_message));
        }
        return 1;
    case 3: // path
        if (argc == 1) {
            // set PATH to an empty string if no path passed in
            setenv("PATH", "", 1);
        } else {
            // allocate initial buffer memory for added path
            // initialize the buffer with a null terminator
            char *newPath = (char*) malloc(1);
            newPath[0] = '\0';
            // concatinate the old path to the new path separated by a colon
            char *oldPath = getenv("PATH"); // get the current search path
            newPath = realloc(newPath, strlen(newPath) + strlen(oldPath) + 2);
            newPath = strcat(newPath, oldPath);
            newPath = strcat(newPath, ":");
            // concatinate new input paths
            for (int i = 1; i < argc; i++) {
                if (strlen(newPath) + strlen(args[i]) > strlen(newPath)) {
                    newPath = realloc(newPath, (strlen(newPath) + strlen(args[i])) * 2 + 1);
                }
                newPath = strcat(newPath, args[i]);
                newPath = strcat(newPath, ":");
            }
            newPath[strlen(newPath) - 1] = '\0'; // replace the last colon with a null terminator
            setenv("PATH", newPath, 1); // set the PATH environment variable
            free(newPath);
            newPath = NULL;
        }
        return 1;
    default:
        break;
    }
    return 0;
}

// Function where the system command execv is executed
void execvArgs(char **args)
{
    // Forking a child
    pid_t pid = fork();

    if (pid == -1) {
        char error_message[30] = "An error has occurred\n";
        write(STDERR_FILENO, error_message, strlen(error_message));
        return;

    } else if (pid == 0) {
        char *path = getenv("PATH"); // get the current search path
        char *p = strtok(path, ":"); // tokenize the search path
        while (p != NULL) {
            // allocate memory for the executable path
            char *exe = malloc(strlen(p) + strlen(args[0]) + 2);
            // construct the full path to the executable
            sprintf(exe, "%s/%s", p, args[0]); 

            // check if the executable exists and is executable
            if (access(exe, X_OK) == 0) {
                if (execv(exe, args) < 0) { 
                    fprintf(stderr, "%s\n", strerror(errno));
                }
                exit(0);
            }
            // free the allocated memory
            free(exe);
            exe = NULL;
            // get the next token
            p = strtok(NULL, ":");
        }
        // if path is NULL, kill child process
        char error_message[30] = "An error has occurred\n";
        write(STDERR_FILENO, error_message, strlen(error_message));
        exit(0);
        
    } else {
        // waiting for child to terminate
        wait(NULL);
        return;
    }
}

// function for parsing parallel command words
int parseSpaceParallel(char **parallelStr, int **parallelArgsCounter, char ***parallelArgs) 
{

    return 1;
}

// function for parsing single command words
int parseSpace(char *str, int argsCounter, char **args)
{
    int i;
    for (i = 0; i < MAX_ARGS; i++) {
        args[i] = strsep(&str, " \t\n");
        if (args[i] == NULL) {
            break;
        }
        if (strlen(args[i]) == 0) {
            i--;
        }
    }
    return i;
}

void executeSingleCmd(char *str, char *filename, char** args)
{
    int argsCounter = 0; // number of arguments, including the cmd arg
    // single cmd found
    // base on redirection codes for cases
    int redirection_code = checkRedirection(str, filename);

    argsCounter = parseSpace(str, argsCounter, args);
    if (argsCounter == 0) { // only whitespace found
        return;
    }

    if (redirection_code == 1) { // redirection
        // save the original file descriptor for stdout/stderr
        int orig_stdout_fd = dup(STDOUT_FILENO);
        int orig_stderr_fd = dup(STDERR_FILENO);
        // open a file for writing and redirect to it
        int fd;
        fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
        if (fd == -1) {
            char error_message[30] = "An error has occurred\n";
            write(STDERR_FILENO, error_message, strlen(error_message));
        }
        // redirect stderr to stdout
        if (dup2(fd, STDOUT_FILENO) == -1 || dup2(fd, STDERR_FILENO) == -1) {
            char error_message[30] = "An error has occurred\n";
            write(STDERR_FILENO, error_message, strlen(error_message));
        }
        // check with built-in first
        if (useBuiltInCmds(argsCounter, args) == 0) {
            // did not execute with built-in cmd
            execvArgs(args);
        }
        // reverse redirection after finish
        dup2(orig_stderr_fd, STDERR_FILENO);
        dup2(orig_stdout_fd, STDOUT_FILENO);
        // close file
        close(fd);
    }
    else if (redirection_code == 0) { // no redirection
        // check with built-in first
        if (useBuiltInCmds(argsCounter, args) == 0) {
            // did not execute with built-in cmd
            execvArgs(args);
        }
    }
    else { // errors with cmd
        char error_message[30] = "An error has occurred\n";
        write(STDERR_FILENO, error_message, strlen(error_message));
    }
}

// this function executes when the user does not put
// any argument after ./wish, meaning they use stdin
void takeStdInput(int argsCounter, char **args, 
    int **parallelArgsCounter, char ***parallelArgs)
{
    while(1) {
        // print shell prompt to terminal
        printf("wish> ");

        // variables and buffers
        char str[MAXCOM]; // ptr to store command line input string
        char filename[MAXCOM];

        // for getline()
        char *line = NULL; // buffer to store each line read
        size_t len = 0; // length of the line read
        ssize_t read; // number of characters read by getline

        // take input line by line
        while ((read = getline(&line, &len, stdin)) != -1) {
            // remove newline character from command string
            if (line[strlen(line) - 1] == '\n') {
                line[strlen(line) - 1] = '\0';
            }
            if (strlen(line) > 0) { // check for empty string
                // copy to char array
                strcpy(str, line);
            }
            // Free the line buffer
            free(line);
            // Reset the line pointer and size for the next line
            line = NULL;
            len = 0;
            break;
        }

        if (strlen(str) == 0) { // check for empty string
            continue;
        }

        // check for parallel cmds
        char *parallelStr[MAX_ARGS];
        int parallelCmdsNum = findParallelCmds(str, parallelStr);

        
        if (parallelCmdsNum) {

            // for (int i=0; i<parallelCmdsNum; i++) {
            //     printf("%s\n", parallelStr[i]);
            //     executeSingleCmd(parallelStr[i], filename, parallelArgs[i]);

            // }
            // XXX need to work on parallel
            // parallel cmds found, return a number greater than 0
            //parseSpaceParallel(parallelStr, parallelArgsCounter, parallelArgs);
        } else {
            executeSingleCmd(str, filename, args);
        }
    }
}


// function to execute given batch file
void takeBatchInput(char *inputFile, int argsCounter, char **args, 
    int **parallelArgsCounter, char ***parallelArgs) 
{
    FILE * fp = fopen(inputFile, "r");
    if (fp == NULL) {
        char error_message[30] = "An error has occurred\n";
        write(STDERR_FILENO, error_message, strlen(error_message));
        exit(1); // bad batch file err
    }

    char filename[MAXCOM]; // store output filename if specified
    char *line = NULL; // buffer to store each line read
    size_t len = 0; // length of the line read
    ssize_t read; // number of characters read by getline

    // take input line by line
    while ((read = getline(&line, &len, fp)) != -1) {
        // remove newline character from command string
        if (line[strlen(line) - 1] == '\n') {
            line[strlen(line) - 1] = '\0';
        }
        // check for empty string
        if (strlen(line) == 0) {
            continue;
        }

        // check for parallel cmds
        char *parallelStr[MAX_ARGS];
        int parallelCmdsNum = findParallelCmds(line, parallelStr);
        
        if (parallelCmdsNum) {
            // parallel cmds found, return a number greater than 0
            parseSpaceParallel(parallelStr, parallelArgsCounter, parallelArgs);

        }
        else {
            // single cmd found
            // base on redirection codes for cases
            int redirection_code = checkRedirection(line, filename);

            if (redirection_code == 1) { // redirection
                // save the original file descriptor for stdout/stderr
                int orig_stdout_fd = dup(STDOUT_FILENO);
                int orig_stderr_fd = dup(STDERR_FILENO);
                // open a file for writing and redirect to it
                int fd;
                fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
                if (fd == -1) {
                    char error_message[30] = "An error has occurred\n";
                    write(STDERR_FILENO, error_message, strlen(error_message));
                }
                // redirect stderr to stdout
                if (dup2(fd, STDOUT_FILENO) == -1 || dup2(fd, STDERR_FILENO) == -1) {
                    char error_message[30] = "An error has occurred\n";
                    write(STDERR_FILENO, error_message, strlen(error_message));
                }
                // execute commands
                argsCounter = parseSpace(line, argsCounter, args);
                if (argsCounter == 0) { // only whitespace found
                    continue;
                }
                if (useBuiltInCmds(argsCounter, args) == 0) {
                    // did not execute with built-in cmd
                    execvArgs(args);
                }
                // reverse redirection after finish
                dup2(orig_stderr_fd, STDERR_FILENO);
                dup2(orig_stdout_fd, STDOUT_FILENO);
                // close file
                close(fd);
            }
            else if (redirection_code == 0) { // no redirection
                // execute commands
                argsCounter = parseSpace(line, argsCounter, args);
                if (argsCounter == 0) { // only whitespace found
                    continue;
                }
                if (useBuiltInCmds(argsCounter, args) == 0) {
                    // did not execute with built-in cmd
                    execvArgs(args);
                }
            }
            else { // errors with cmd
                char error_message[30] = "An error has occurred\n";
                write(STDERR_FILENO, error_message, strlen(error_message));
            }
        }
    }

    // Free the line buffer
    free(line);
    // Reset the line pointer and size for the next line
    line = NULL;
    len = 0;
    // close input file
    fclose(fp);
}

int main(int argc, char *argv[]) 
{   
    setenv("PATH", "/bin", 1); // initialize shell path to default

    char *args[MAX_ARGS]; // ptr to a list of arguments
    int argsCounter = 0; // number of arguments, including the cmd arg

    char **parallelArgs[MAX_ARGS];
    int *parallelArgsCounter[MAX_ARGS];

    // If given more than ./wish and a file
    if (argc > 2) {
        char error_message[30] = "An error has occurred\n";
        write(STDERR_FILENO, error_message, strlen(error_message));
        exit(1);
    }
    // If given just ./wish -> prompt input
    else if (argc == 1) {
        takeStdInput(argsCounter, args, parallelArgsCounter, parallelArgs);
    }
    // If given ./wish and a batch file -> read and execute commands in file
    else {
        takeBatchInput(argv[1], argsCounter, args, parallelArgsCounter, parallelArgs);
    }
    return 0;
}