summaryrefslogblamecommitdiffstats
path: root/02_exercise/shell.c
blob: 2452347b213e7d46050cced9328ec28f91c91576 (plain) (tree)
1
2
3
4
5
6
7
8
9
                  

                   
                   
                    
                 
                  

                  
                    
                         
                     
 
                   
 
                                 

                                                          
                                         
                           





                                  
                
                                     
                                     
 
                                                      
                                             


                                          
 


                                                                                                  
                                                                                        
               

                                                                                            

      
                                                           
                                                                 

                      


                               
 
                                
                                                         
                                                         
                           
                     
         
 
                                                    
                                                                 

                     

                                                     
                         
                                     
 
                                                      
                                           

                                                              
                    
                                                  









                                                          
             
 

                                     
 
                                  

                                                            
                                      
                                                               
                        
                                                               



                                                                                                  
                

                                                                                          
             
                                                              
                                                     

                                         
             
                                    
         
 
                            
                    
                 
                                   
     
 

                               
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <wait.h>
#include <errno.h>

#include "array.h"
#include "process.h"
#include "prompt_utils.h"
#include "builtins.h"

process *processes;

void signal_handler(int signal) {
    if (signal == SIGINT) {
        for (size_t i = 0; i < arrayLen(processes); ++i) {
            pid_t pid = processes[i].pid;
            if (pid != 0) {
                kill(pid, SIGINT);
            }
        }
    }
}

int main(void) {
    setvbuf(stderr, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    // I don't think the shell should exit on SIG_TERM
    if (signal(SIGINT, SIG_IGN) == SIG_ERR) {
        perror("Couldn't ignore sigterm");
        exit(errno);
    }

    printf("Welcome! Available built-ins are:\n"
           "cd: `cd <path>` - if no path is given, return to the current dir\n"
           "wait: `wait pid1 ... pidN` - wait on the processes and report their exit conditions\n"
           "fg: `fg pid` - pulls a process from the background back in the foreground\n"
           "\n"
           "You can put processes in the background using `&`\n"
           "And with `|` you can pipe the output from one process to the input of another\n"
    );

    char const *const original_wd = get_current_dir_name();
    char const *prompt = relative_path(original_wd, original_wd);
    bool done = false;
    while (!done) {
        char      *line = NULL;
        size_t    cap   = 0;
        __ssize_t length;

        printf("%s > ", prompt);
        if ((length = getline(&line, &cap, stdin)) < 0) {
            fprintf(stderr, "Failed to read from STDIN");
            fflush(stderr);
            exit(-1);
        }

        if (strspn(line, " \n\t") == strlen(line)) {
            // skip empty lines - empty being just spaces or tabs
            continue;
        }
        line[length - 1] = '\0'; // cut the line feed

        processes = NULL;
        parse_line(line, &processes);

        if (strcmp(processes[0].argv[0], "cd") == 0) {
            if (arrayLen(processes) != 1) {
                perror("Can't chain cd with other processes");
            }
            int ret;
            switch (arrayLen(processes[0].argv)) {
                case 3:
                    ret = chdir(processes[0].argv[1]);
                    break;
                case 2:
                    ret = chdir(original_wd);
                    break;
                default:
                    fprintf(stderr, "usage: cd <path>\n");
                    fflush(stderr);
                    ret = -1;
            }

            if (ret)
                printf("[%i] ", ret);

            free((void *) prompt);
            char const *current_wd = get_current_dir_name();
            prompt = relative_path(original_wd, current_wd);
            free((void *) current_wd);
        } else if (strcmp(processes[0].argv[0], "exit") == 0) {
            done = true;
        } else if (strcmp(processes[0].argv[0], "wait") == 0) {
            builtin_wait(processes[0], false);
        } else if (strcmp(processes[0].argv[0], "fg") == 0) {
            // same behaviour as wait, just bind to shell again (i.e. terminate process on ctrl-c)
            builtin_wait(processes[0], true);
        } else {
            if (arrayLen(processes) != 0 && processes[arrayLen(processes) - 1].blocking) {
                signal(SIGINT, signal_handler);
            }
            for (size_t i = 0; i < arrayLen(processes); ++i) {
                int ret = exec_command(processes[i]);
                if (ret)
                    printf("[%i] ", ret);
            }
            signal(SIGINT, SIG_IGN);
        }

        free((void *) line);
        line = NULL;
        cap  = 0;
        free_processes(&processes);
    }

    free((void *) original_wd);
    free((void *) prompt);
}