Custom Shell with Job Control and Piping

Project Overview

This project focuses on building a custom shell implementation in C, addressing complex challenges like signal handling, job control, and pipe-based inter-process communication. The implementation demonstrates accomplishments in processing user input to handle commands, redirection, and piping. Unique highlights include a dynamic system for managing background jobs with suspended state handling and an elegant mechanism for orderly job reactivation. Through modularized functions like execute_command, this project tackles intricate challenges such as robust memory management, error handling, and multi-process coordination, showcasing significant achievements in system-level programming.

Key Features

Technologies Used

//This program was created as part of the Operating Systems coursework at New York University with project specifications provided by Professor Yang Tang.
//Developed by Travis Perry


#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <pwd.h>
#include <fcntl.h>
#include <sys/types.h>

typedef struct {
    pid_t pid;
    char commandName[256];
    int status;
    int index;
} jobs;

jobs* jobList[100];


int any_suspended_jobs(jobs *jobList[]){
    int i=0;
    while (jobList[i]!=NULL){
        if(jobList[i]->status==1){
            return 1;
        }
        i++;
    }
    return 0;
}



void sigtstp_handler(int signo) {
    (void) signo;
}



void execute_command(char *cmd, char *inputFile, char *outputFile, int appendTF, int input_fd, int output_fd) {
    char *argList[100];
    char *args = strtok(cmd, " ");
    int j = 0;

    while (args != NULL) {
        argList[j++] = args;
        args = strtok(NULL, " ");
    }
    argList[j] = NULL;

    if (inputFile) {
        int fd = open(inputFile, O_RDONLY);
        if (fd < 0) {
            fprintf(stderr,"Error: could not open file");
            exit(1);
        }
        dup2(fd, STDIN_FILENO);
        close(fd);
    } 
    else if (input_fd != -1) {
        dup2(input_fd, STDIN_FILENO);
    }

    if (outputFile) {
        int fd;
        if (appendTF) {
            fd = open(outputFile, O_WRONLY | O_CREAT | O_APPEND, 0644);
        } 
        else {
            fd = open(outputFile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
        }
        if (fd < 0) {
            fprintf(stderr,"Error: could not open file");
            exit(1);
        }
        dup2(fd, STDOUT_FILENO);
        close(fd);
    }
    else if (output_fd != -1) {
        dup2(output_fd, STDOUT_FILENO);
    }

    execvp(argList[0], argList);
    fprintf(stderr, "Error: exec failed");
    exit(1);
}

int main(void) {
    signal(SIGTSTP, sigtstp_handler);
    int numJobs=0;
    
    while (1) {
        // Milestone 1
        
        char cwd[256];
        char prompt[256];
        getcwd(cwd, sizeof(cwd));
        size_t i = strlen(cwd) - 1;
        if (i == 0) {
            prompt[0] = '/';
        } else {
            while (i > 0) {
                if (cwd[i] == '/') {
                    break;
                }
                i--;
            }
        }
        i++;
        int arrIndex = 0;
        for (; i < strlen(cwd); i++) {
            prompt[arrIndex] = cwd[i];
            arrIndex++;
        }
        prompt[arrIndex] = '\0';
        
        if (strcmp(cwd, "/") == 0) {
            printf("[nyush /]$ ");
        } else {
            printf("[nyush %s]$ ", prompt);
        }
        fflush(stdout);
        
        // Milestone 2:Get user input
        signal(SIGINT, SIG_IGN);
        signal(SIGQUIT, SIG_IGN);
        signal(SIGTSTP, SIG_IGN);
        
        char *buffer;
        size_t maxSize = 1000;
        buffer = malloc(maxSize * sizeof(char));
        ssize_t command = getline(&buffer, &maxSize, stdin);
        if (command == -1) {
            free(buffer);
            exit(0);
        }
        
        char storedBuffer[1000];
        strcpy(storedBuffer,buffer);
        
        buffer[strlen(buffer) - 1] = '\0';
        
        
        int numPipes=0;
        int numArgs=1;
        
        for (size_t i=0; i") == 0) {
                    args = strtok(NULL, " ");
                    if (args != NULL) {
                        outputFile = args;
                    }
                } else if (strcmp(args, "<") == 0) {
                    args = strtok(NULL, " ");
                    if (args != NULL) {
                        inputFile = args;
                    }
                } else {
                    argList[j++] = args;
                }
                args = strtok(NULL, " ");
            }
            argList[j] = NULL;

            
            jobList[numJobs]=malloc(sizeof(jobs)+1);
            jobList[numJobs]->pid=getpid();
            strcpy(jobList[numJobs]->commandName,storedBuffer);
            jobList[numJobs]->status=0;
            jobList[numJobs]->index=0;
            numJobs++;
            

            
            if (strlen(buffer)==0){
                continue;
            }
            else if (strcmp(argList[0],"cd")==0){
                if (numArgs != 2) {
                    fprintf(stderr,"Error: invalid command\n");
                }
                else {
                    if (strcmp(argList[1],prompt)==0){
                        fprintf(stderr,"Error: invalid directory\n");
                    }
                    else {
                        chdir(argList[1]);
                    }
                }
            }
            else if (strcmp(buffer,"exit")==0){
                if (numArgs>1){
                    fprintf(stderr,"Error: invalid command\n");
                }
                else{
                    //Milestone 10
                    
                    if(any_suspended_jobs(jobList)){
                        fprintf(stderr,"Error: there are suspended jobs\n");
                        fflush(stderr);
                    }
                    else {
                        exit(0);
                    }
                }
            }
            else if (strcmp(argList[0],"jobs")==0){                       
                if (numArgs>1){
                    fprintf(stderr,"Error: invalid command\n");
                    continue;
                }
                
                if (!any_suspended_jobs(jobList)){
                    printf("\n");
                    continue;
                }
                
                jobs *suspendedJobs[100];  // Assuming max 100 suspended jobs
                int numSuspended = 0;
                    
                
                int i=0;
                while(jobList[i]!=NULL){
                    if (jobList[i]->status==1){
                        suspendedJobs[numSuspended] = jobList[i];
                        numSuspended++;
                    }
                    i++;
                    
                }
                
                for (int j = 0; j < numSuspended - 1; j++) {
                    for (int k = j + 1; k < numSuspended; k++) {
                        if (suspendedJobs[j]->index > suspendedJobs[k]->index) {
                            jobs *temp = suspendedJobs[j];
                            suspendedJobs[j] = suspendedJobs[k];
                            suspendedJobs[k] = temp;
                        }
                    }
                }
                    
                for (int j = 0; j < numSuspended; j++) {
                    printf("[%d] %s", suspendedJobs[j]->index, suspendedJobs[j]->commandName);
                }
                
            }
            else if (strcmp(argList[0], "fg") == 0) {
                if (numArgs != 2) {
                    fprintf(stderr, "Error: invalid command\n");
                    continue;
                }
                else if (!any_suspended_jobs(jobList)){
                    fprintf(stderr,"Error: invalid job\n");
                    continue;
                }
            
                
                int i=0;
                int jobIndex=0;
                while(jobList[i]!=NULL){
                    if (jobList[i]->index==atoi(argList[1])){
                        jobIndex=i;
                        break;
                    }
                    i++;
                }
                pid_t pid = jobList[jobIndex]->pid;
                
                
                kill(pid, SIGCONT);
                jobList[jobIndex]->status = 0;
                
                i=0;
                
                
                int k=0;
                while(jobList[k]!=NULL){
                    if (jobList[k]->index > jobList[jobIndex]->index) {
                        jobList[k]->index--;
                    }
                    k++;
                }
                jobList[jobIndex]->index = 0;
                
                int status;
                waitpid(pid, &status, WUNTRACED);
                
                if (WIFSTOPPED(status)) {
                    jobList[jobIndex]->status=1;
    
                    fflush(stdout);
                    int maxIndex=0;
                    int i=0;
                    while (jobList[i]!=NULL){
                        if(jobList[i]->index>maxIndex){
                            maxIndex=jobList[i]->index;
                        }
                        i++;
                    }
                    
                    jobList[jobIndex]->index=maxIndex+1;
                }
                

                
            }
            
            else {
                int pid = fork();
                if (pid < 0) {
                    fprintf(stderr, "Error: Child process creation failure\n");
                }
                
                //Milestones 6&7
                else if (pid == 0) {
                    signal(SIGINT, SIG_DFL);
                    signal(SIGQUIT, SIG_DFL);
                    signal(SIGTSTP, SIG_DFL);
                    if (outputFile) {
                        int fd;
                        if (appendTF) {
                            fd = open(outputFile, O_WRONLY | O_CREAT | O_APPEND, 0644);
                        } else {
                            fd = open(outputFile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
                        }
                        
                        if (fd < 0) {
                            fprintf(stderr,"Error: invalid file\n");
                            exit(1);
                        }
                        
                        if (dup2(fd, STDOUT_FILENO) < 0) {
                            fprintf(stderr,"Error: invalid file\n");
                            close(fd);
                            exit(1);
                        }
                        
                        close(fd);
                    }
                    if (inputFile){
                        int fd = open(inputFile, O_RDONLY);
                        if (fd < 0) {
                            fprintf(stderr,"Error: invalid file\n");
                            exit(1);
                        }
                        dup2(fd, STDIN_FILENO);
                        close(fd);
                    }
                    
                    
                    //Milestone 8
                    if (strcmp(argList[0],"fsck")==0){
                        fprintf(stderr,"Error: invalid program\n");
                        exit(1);
                    }
                    execvp(argList[0], argList);
                    fprintf(stderr,"Error: invalid program\n");
                    fflush(stderr);
                    exit(1);
                }
                else {
                    
                    jobList[numJobs]=malloc(sizeof(jobs));
                    jobList[numJobs]->pid=pid;
                    strcpy(jobList[numJobs]->commandName,storedBuffer);
                    numJobs++;
                    
                    int status;
                    waitpid(pid, &status, WUNTRACED);
                    
                    if (WIFSTOPPED(status)) {
                        
                        int maxIndex=0;
                        int i=0;
                        int suspendedJob=0;
                        
                        while (jobList[i]!=NULL){
                            if (pid==jobList[i]->pid){
                                jobList[i]->status=1;
                                suspendedJob=i;
                            }
                            if(jobList[i]->index>maxIndex){
                                maxIndex=jobList[i]->index;
                            }
                            i++;
                        }
                        
                        jobList[suspendedJob]->index=maxIndex+1;
                        
                        
                    }
                }
            }

        }
        else {
            
            char *inputFile = NULL, *outputFile = NULL;
            int appendTF = 0;
            char *commands[100];
            int numPipes = 0;
            
            char *command = strtok(buffer, "|");
            
            while (command != NULL) {
                commands[numPipes++] = command;
                command = strtok(NULL, "|");
            }
        
            int pipes[numPipes - 1][2];
            
            for (int i = 0; i < numPipes - 1; i++) {
                if(pipe(pipes[i])==-1){
                    fprintf(stderr,"Error: invalid command\n");
                    exit(1);
                };
            }
    
            for (int i = 0; i < numPipes; i++) {
                pid_t pid = fork();
                if (pid < 0) {
                    fprintf(stderr, "Error: fork failed");
                    exit(1);
                }
                
                if (pid == 0) {
                    char *redir_cmd = strtok(commands[i], ">");
                    if (redir_cmd != NULL && i == numPipes - 1) {
                        char *output_part = strtok(NULL, " ");
                        if (output_part != NULL) {
                            outputFile = output_part;
                        }
                    }
                    
                    redir_cmd = strtok(commands[i], "<");
                    if (redir_cmd != NULL && i == 0) {
                        char *input_part = strtok(NULL, " ");
                        if (input_part != NULL) {
                            inputFile = input_part;
                        }
                    }
                    
                    if (i > 0) {
                        dup2(pipes[i - 1][0], STDIN_FILENO);  // Read from previous pipe
                    }
                    
                    if (i < numPipes - 1) {
                        dup2(pipes[i][1], STDOUT_FILENO);  // Write to next pipe
                    }
                    
                    for (int j = 0; j < numPipes - 1; j++) {
                        close(pipes[j][0]);
                        close(pipes[j][1]);
                    }
                    execute_command(commands[i], inputFile, outputFile, appendTF, -1, -1);
                }
            }
            for (int i = 0; i < numPipes - 1; i++) {
                close(pipes[i][0]);
                close(pipes[i][1]);
            }
            
            for (int i = 0; i < numPipes; i++) {
                wait(NULL);
            }
        }
        
    }
    return 1;
}