custom-unix-shell / wish-v5.c
wish-v5.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>

#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.
    }
}

// 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 {
            char *path = (char*) malloc(255); // allocate initial buffer memory
            path[0] = '\0'; // initialize the buffer with a null terminator
            for (int i = 1; i < argc; i++) {
                if (strlen(path) + strlen(args[i]) > strlen(path)) {
                    path = realloc(path, (strlen(path) + strlen(args[i])) * 2 + 1);
                }
                path = strcat(path, args[i]);
                path = strcat(path, ":");
            }
            path[strlen(path) - 1] = '\0'; // replace the last colon with a null terminator
            setenv("PATH", path, 1); // set the PATH environment variable
            //printf("PATH=%s\n", getenv("PATH")); // print the PATH environment variable
            free(path);
            path = 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) {
            printf("Access OK1! Current path %s\n", p);
            // 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) {
                printf("Access OK2! current path %s\n", p);
                if (execv(exe, args) < 0) { 
                    fprintf(stderr, "%s\n", strerror(errno));
                }
                exit(0);
            } else {
                printf("Access OK3!\n");
                char error_message[30] = "An error has occurred\n";
                write(STDERR_FILENO, error_message, strlen(error_message)); 
                exit(0);
            }
            // free the allocated memory
            free(exe); 
            exe = NULL;
            // get the next token
            p = strtok(NULL, ":");
        }
        exit(0); // if path is NULL, kill child process
        
    } 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--;
        }
    }
    argsCounter = i;
    return argsCounter;
}

// this function executes when the user does not put
// any argument after ./wish, meaning they use stdin
void takeStdInput(char* str, int argsCounter, char **args, 
    int **parallelArgsCounter, char ***parallelArgs)
{
    while(1) {

        // print shell prompt to terminal
        printf("wish> ");

        // variables and buffers
        char str2[MAXCOM]; // ptr to store command line input string
        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(str2, line);
            }
            // Free the line buffer
            free(line);
            // Reset the line pointer and size for the next line
            line = NULL;
            len = 0;
            break;
        }

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

        // check for parallel cmds
        // change str to line XXX
        char *parallelStr[MAX_ARGS];
        int parallelCmdsNum = findParallelCmds(str2, parallelStr);
        
        if (parallelCmdsNum) {
            // parallel cmds found, return a number greater than 0
            parseSpaceParallel(parallelStr, parallelArgsCounter, parallelArgs);
            
        } else {
            // single cmd found
            argsCounter = parseSpace(str2, argsCounter, args);
            if (useBuiltInCmds(argsCounter, args) == 0) {
                //printf("Did not execute with built-in cmd\n");
                execvArgs(args);
            }
        }
    }
}

// function to execute given batch file
void takeBatchInput(char *inputFile, char* str, 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 *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
            argsCounter = parseSpace(line, argsCounter, args);
            if (useBuiltInCmds(argsCounter, args) == 0) {
                //printf("Did not execute with built-in cmd\n");
                execvArgs(args);
            }
        }
    }

    // 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 str[MAXCOM]; // ptr to store command line input string

    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(str, argsCounter, args, parallelArgsCounter, parallelArgs);
    }
    // If given ./wish and a batch file -> read and execute commands in file
    else {
        takeBatchInput(argv[1], str, argsCounter, args, parallelArgsCounter, parallelArgs);
    }
    return 0;
}