├── .gitignore ├── LICENSE ├── README.md └── src ├── Makefile ├── builtin.c ├── builtin.h ├── common.c ├── common.h ├── helpers.c ├── helpers.h ├── input.c ├── input.h ├── job_control.c ├── job_control.h ├── main.c ├── shell.c ├── shell.h ├── signal_handling.c └── signal_handling.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | *~ 55 | \#*\# 56 | /.emacs.desktop 57 | /.emacs.desktop.lock 58 | *.elc 59 | auto-save-list 60 | tramp 61 | .\#* 62 | 63 | # Org-mode 64 | .org-id-locations 65 | *_archive 66 | 67 | # flymake-mode 68 | *_flymake.* 69 | 70 | # eshell files 71 | /eshell/history 72 | /eshell/lastdir 73 | 74 | # elpa packages 75 | /elpa/ 76 | 77 | # reftex files 78 | *.rel 79 | 80 | # AUCTeX auto folder 81 | /auto/ 82 | 83 | # cask packages 84 | .cask/ 85 | dist/ 86 | 87 | # Flycheck 88 | flycheck_*.el 89 | 90 | # server auth directory 91 | /server/ 92 | 93 | # projectiles files 94 | .projectile 95 | 96 | # directory configuration 97 | .dir-locals.el 98 | 99 | # Project specific 100 | rcshell 101 | notes.md 102 | hello.ll -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Indradhanush Gupta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RC Shell 2 | === 3 | 4 | A minimal UNIX shell. 5 | 6 | 7 | Note: This is a work in progress 8 | 9 | I am explaining this project as I progress on my blog: 10 | * [Part I](https://indradhanush.github.io/blog/writing-a-unix-shell-part-1/) 11 | deals with understanding `fork`. 12 | * [Part II](https://indradhanush.github.io/blog/writing-a-unix-shell-part-2/) 13 | explains how to execute commands like `ls` and `pwd`. 14 | * [Part III](https://indradhanush.github.io/blog/writing-a-unix-shell-part-3/) 15 | explains the concepts of signal handling. 16 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | EXEC = rcshell 2 | 3 | LIBS = -lreadline 4 | 5 | SRC = \ 6 | common.c \ 7 | signal_handling.c \ 8 | builtin.c \ 9 | input.c \ 10 | job_control.c \ 11 | helpers.c \ 12 | shell.c \ 13 | main.c \ 14 | 15 | build: 16 | gcc -g -Wall -Wpedantic $(LIBS) $(SRC) -o $(EXEC) && chmod +x $(EXEC) 17 | 18 | run: build 19 | ./$(EXEC) 20 | 21 | debug: build 22 | gdb $(EXEC) 23 | 24 | valgrind: build 25 | valgrind -v --leak-check=full ./$(EXEC) 26 | 27 | docker: 28 | docker run --workdir $(PWD) --rm --volume $(PWD):$(PWD) -it gcc bash 29 | -------------------------------------------------------------------------------- /src/builtin.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "common.h" 6 | #include "builtin.h" 7 | 8 | 9 | struct builtin *make_builtin() { 10 | static struct builtin *s; 11 | s = emalloc(2 * sizeof(struct builtin)); 12 | s[0].command = "cd"; 13 | s[0].function = cd; 14 | 15 | s[1].command = NULL; 16 | s[1].function = NULL; 17 | 18 | return s; 19 | } 20 | 21 | struct builtin *is_builtin(struct builtin *builtins_ptr, char **command) { 22 | while(builtins_ptr->command != NULL) { 23 | if (strcmp(builtins_ptr->command, *command) == 0) { 24 | return builtins_ptr; 25 | } 26 | 27 | builtins_ptr++; 28 | } 29 | 30 | return NULL; 31 | } 32 | 33 | void run_builtin(struct builtin *builtin_ptr, char **command) { 34 | if (builtin_ptr->function(command) < 0) { 35 | perror(builtin_ptr->command); 36 | } 37 | } 38 | 39 | int cd(char **command) { 40 | return chdir(command[1]); 41 | } 42 | -------------------------------------------------------------------------------- /src/builtin.h: -------------------------------------------------------------------------------- 1 | #ifndef builtin_h 2 | #define builtin_h 3 | 4 | struct builtin { 5 | char *command; 6 | int (*function) (char **); 7 | }; 8 | 9 | struct builtin *make_builtin(); 10 | struct builtin *is_builtin(struct builtin *, char **); 11 | void run_builtin(struct builtin *, char **); 12 | int cd(char **); 13 | 14 | #endif // builtin_h 15 | -------------------------------------------------------------------------------- /src/common.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | 5 | void *emalloc(int size) { 6 | void *ptr = malloc(size); 7 | if (!ptr) { 8 | perror("Out of memory"); 9 | exit(1); 10 | } 11 | 12 | return ptr; 13 | } 14 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | #ifndef common_h 2 | #define common_h 3 | 4 | /* Helper function that quits the program if malloc fails */ 5 | void *emalloc(int); 6 | 7 | #endif // common_h 8 | -------------------------------------------------------------------------------- /src/helpers.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | 5 | void exit_on_error(int result, char *msg) { 6 | if (result < 0) { 7 | if (msg != NULL) { 8 | perror(msg); 9 | } 10 | 11 | exit(1); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef helpers_h 2 | #define helpers_h 3 | 4 | void exit_on_error(int, char *); 5 | 6 | #endif /* helpers */ 7 | -------------------------------------------------------------------------------- /src/input.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "common.h" 5 | #include "input.h" 6 | 7 | 8 | struct input *make_input(struct input *inp_ptr, char *line) { 9 | inp_ptr = tokenize(inp_ptr, line); 10 | if (inp_ptr == NULL) { 11 | return NULL; 12 | } 13 | 14 | inp_ptr->is_background_command = is_background_command(inp_ptr); 15 | 16 | if (create_command(inp_ptr) < 0) { 17 | free(inp_ptr->argv); 18 | free(inp_ptr); 19 | return NULL; 20 | } 21 | 22 | return inp_ptr; 23 | } 24 | 25 | /* 26 | * tokenize parses the command input and initializes it into a NULL 27 | * padded array of strings. 28 | */ 29 | struct input *tokenize(struct input *inp_ptr, char *line) { 30 | int word_count = 0; 31 | int block = 4; 32 | static char *separator = " "; 33 | 34 | char *parsed = strtok(line, separator); 35 | 36 | if (parsed == NULL) { 37 | return NULL; 38 | } 39 | 40 | while (parsed != NULL) { 41 | if (word_count % block == 0) { 42 | inp_ptr = expand_argv(inp_ptr); 43 | if (inp_ptr == NULL) { 44 | return NULL; 45 | } 46 | } 47 | 48 | inp_ptr->argv[word_count] = parsed; 49 | parsed = strtok(NULL, separator); 50 | word_count++; 51 | } 52 | 53 | if (inp_ptr->size_argv == word_count) { 54 | inp_ptr->argv = realloc(inp_ptr->argv, 55 | inp_ptr->size_argv + sizeof(char *)); 56 | if (inp_ptr->argv == NULL) { 57 | return NULL; 58 | } 59 | } 60 | inp_ptr->argv[word_count++] = (char *)0; 61 | inp_ptr->size_argv += sizeof(char *); 62 | inp_ptr->len_argv = word_count; 63 | 64 | return inp_ptr; 65 | } 66 | 67 | struct input *expand_argv(struct input *inp_ptr) { 68 | int new_size_argv; 69 | 70 | if (inp_ptr == NULL) { 71 | inp_ptr = emalloc(sizeof(struct input )); 72 | inp_ptr->argv = emalloc(MEMORY_CHUNK); 73 | inp_ptr->size_argv = MEMORY_CHUNK; 74 | return inp_ptr; 75 | } 76 | 77 | new_size_argv = 2 * inp_ptr->size_argv; 78 | 79 | inp_ptr->argv = realloc(inp_ptr->argv, new_size_argv); 80 | if (inp_ptr->argv == NULL) { 81 | return NULL; 82 | } 83 | 84 | inp_ptr->size_argv = new_size_argv; 85 | return inp_ptr; 86 | } 87 | 88 | /* 89 | * A command is a background command if the last character is an 90 | * ampersand (&). 91 | * The last element is NULL, so we want to check for 92 | * "&" at (size - 2) 93 | * 0 1 2 3 94 | * foo arg1 & NULL 95 | */ 96 | int is_background_command(struct input *inp_ptr) { 97 | int index = inp_ptr->len_argv - 2; 98 | if (strcmp(inp_ptr->argv[index], "&") == 0) { 99 | return 1; 100 | } 101 | 102 | return 0; 103 | } 104 | 105 | int create_command(struct input *inp_ptr) { 106 | int bytes; 107 | int size = inp_ptr->len_argv; 108 | if (inp_ptr->is_background_command) { 109 | size -= 1; 110 | } /* Remove the "&" from a background command */ 111 | 112 | bytes = (sizeof(char *) * size); 113 | inp_ptr->command = emalloc(bytes); 114 | 115 | inp_ptr->command = memcpy(inp_ptr->command, inp_ptr->argv, bytes); 116 | if (!inp_ptr->command) { 117 | return -1; 118 | } 119 | 120 | inp_ptr->command[size-1] = NULL; 121 | 122 | return 0; 123 | } 124 | -------------------------------------------------------------------------------- /src/input.h: -------------------------------------------------------------------------------- 1 | #ifndef input_h 2 | #define input_h 3 | 4 | #define MEMORY_CHUNK 64 5 | 6 | struct input{ 7 | char **argv; 8 | int size_argv; 9 | int len_argv; 10 | int is_background_command; 11 | char **command; 12 | }; 13 | 14 | struct input *make_input(struct input *, char *); 15 | struct input *tokenize(struct input *, char *); 16 | struct input *expand_argv(struct input *); 17 | int is_background_command(struct input *); 18 | int create_command(struct input *); 19 | 20 | #endif // input_h 21 | -------------------------------------------------------------------------------- /src/job_control.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "signal_handling.h" 5 | #include "job_control.h" 6 | 7 | 8 | struct process *make_process() { 9 | static struct process s; 10 | s.pid = getpid(); 11 | 12 | s.pgid = getpgid(0); 13 | s.fgid = tcgetpgrp(STDIN_FILENO); 14 | 15 | return &s; 16 | } 17 | 18 | 19 | int setup_job_control(struct process *ptr) { 20 | int result; 21 | 22 | /* Set process group id to the process id */ 23 | if ((result = setpgid(ptr->pid, ptr->pid)) < 0) { 24 | perror("Error in setpgid"); 25 | return result; 26 | } 27 | 28 | if ((ptr->pgid = getpgid(0)) < 0) { 29 | perror("Error in getpgid"); 30 | return ptr->pgid; 31 | } 32 | 33 | /* Set the foreground group for STDIN */ 34 | if ((result = tcsetpgrp(STDIN_FILENO, ptr->pgid)) < 0) { 35 | perror("Error in tcsetpgrp"); 36 | return result; 37 | } 38 | 39 | if ((ptr->fgid = tcgetpgrp(STDIN_FILENO)) < 0) { 40 | perror("Error in tcgetpgrp"); 41 | return ptr->fgid; 42 | } 43 | 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /src/job_control.h: -------------------------------------------------------------------------------- 1 | #ifndef job_control_h 2 | #define job_control_h 3 | 4 | #include 5 | 6 | struct process { 7 | pid_t pid; 8 | pid_t pgid; 9 | pid_t fgid; 10 | }; 11 | 12 | struct process *make_process(); 13 | int setup_job_control(struct process *); 14 | 15 | #endif /* job_control */ 16 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "signal_handling.h" 12 | #include "builtin.h" 13 | #include "input.h" 14 | #include "job_control.h" 15 | #include "helpers.h" 16 | #include "shell.h" 17 | 18 | 19 | int main() { 20 | char *command; 21 | pid_t child_pid; 22 | int stat_loc; 23 | 24 | struct process *parent_ptr; 25 | struct process *child_ptr; 26 | 27 | struct builtin *builtins_ptr; 28 | struct builtin *builtin_found; 29 | 30 | struct input *inp_ptr = NULL; 31 | 32 | struct shell *shell_ptr = make_shell(); 33 | 34 | /* Setup builtins */ 35 | builtins_ptr = make_builtin(); 36 | 37 | while (1) { 38 | /* Check if the terminal is in interactive mode */ 39 | if (shell_ptr->is_interactive) { 40 | parent_ptr = make_process(); 41 | exit_on_error(setup_parent_signals(), NULL); 42 | exit_on_error(setup_job_control(parent_ptr), NULL); 43 | } else { 44 | perror("Shell is not interactive"); 45 | exit(1); 46 | } 47 | 48 | if (sigsetjmp(jmpbuf, 1) == CODE_SIGINT) { 49 | printf("\n"); 50 | continue; 51 | } 52 | 53 | command = readline("rcsh> "); 54 | if (command == NULL) { /* Exit on Ctrl-D */ 55 | printf("\n"); 56 | exit(0); 57 | } 58 | 59 | add_history(command); 60 | inp_ptr = make_input(inp_ptr, command); 61 | if (inp_ptr == NULL) { 62 | // TODO: This also masks errors from functions lower down 63 | // in the invocation chain. Need to fix this. 64 | continue; 65 | } 66 | 67 | builtin_found = is_builtin(builtins_ptr, inp_ptr->command); 68 | if (builtin_found != NULL) { /* If we found an index */ 69 | run_builtin(builtin_found, inp_ptr->command); 70 | continue; 71 | } 72 | 73 | child_pid = fork(); 74 | exit_on_error(child_pid, "Error in fork"); 75 | 76 | if (child_pid == 0) { /* child */ 77 | child_ptr = make_process(); 78 | setpgid(0, child_ptr->pid); 79 | child_ptr->pgid = getpgid(0); 80 | 81 | exit_on_error(setup_child_signals(), NULL); 82 | 83 | if (!inp_ptr->is_background_command) { 84 | exit_on_error( 85 | tcsetpgrp(STDIN_FILENO, child_ptr->pgid), 86 | "tcsetpgrp failed" 87 | ); 88 | } 89 | 90 | exit_on_error( 91 | execvp(inp_ptr->command[0], inp_ptr->command), 92 | inp_ptr->command[0] 93 | ); 94 | } else { /* parent */ 95 | child_ptr = make_process(); 96 | setpgid(child_ptr->pid, child_ptr->pid); 97 | child_ptr->pgid = getpgid(child_ptr->pid); 98 | 99 | if (!inp_ptr->is_background_command) { 100 | exit_on_error( 101 | tcsetpgrp(STDIN_FILENO, child_ptr->pgid), 102 | "tcsetpgrp failed" 103 | ); 104 | waitpid(child_pid, &stat_loc, WUNTRACED); 105 | tcsetpgrp(STDIN_FILENO, parent_ptr->pgid); 106 | } 107 | } 108 | 109 | if (inp_ptr != NULL) { 110 | free(inp_ptr->argv); 111 | free(inp_ptr->command); 112 | free(inp_ptr); 113 | inp_ptr = NULL; 114 | free(command); 115 | } 116 | } 117 | 118 | return 0; 119 | } 120 | -------------------------------------------------------------------------------- /src/shell.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "shell.h" 3 | 4 | struct shell *make_shell() { 5 | static struct shell s; 6 | s.is_interactive = isatty(STDIN_FILENO); 7 | 8 | return &s; 9 | } 10 | -------------------------------------------------------------------------------- /src/shell.h: -------------------------------------------------------------------------------- 1 | #ifndef shell_h 2 | #define shell_h 3 | 4 | struct shell { 5 | int is_interactive; 6 | }; 7 | 8 | struct shell *make_shell(); 9 | 10 | #endif /* shell */ 11 | -------------------------------------------------------------------------------- /src/signal_handling.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "signal_handling.h" 6 | #include "helpers.h" 7 | 8 | 9 | sigjmp_buf jmpbuf; 10 | 11 | 12 | void sigint_handler(int signo __attribute__((unused))) { 13 | siglongjmp(jmpbuf, CODE_SIGINT); 14 | } 15 | 16 | 17 | static int setup_signal(int signal, void (*handler)(int)) { 18 | struct sigaction s; 19 | s.sa_handler = handler; 20 | sigemptyset(&s.sa_mask); 21 | s.sa_flags = SA_RESTART; 22 | return sigaction(signal, &s, NULL); 23 | } 24 | 25 | int setup_parent_signals() { 26 | exit_on_error(setup_signal(SIGINT, sigint_handler), NULL); 27 | exit_on_error(setup_signal(SIGTSTP, SIG_IGN), NULL); 28 | exit_on_error(setup_signal(SIGTTIN, SIG_IGN), NULL); 29 | exit_on_error(setup_signal(SIGTTOU, SIG_IGN), NULL); 30 | exit_on_error(setup_signal(SIGCHLD, SIG_IGN), NULL); 31 | exit_on_error(setup_signal(SIGQUIT, SIG_IGN), NULL); 32 | 33 | return 0; 34 | } 35 | 36 | int setup_child_signals() { 37 | struct sigaction s; 38 | s.sa_handler = SIG_DFL; 39 | sigemptyset(&s.sa_mask); 40 | s.sa_flags = SA_RESTART; 41 | 42 | exit_on_error(sigaction(SIGINT, &s, NULL), "Error in setting action for SIGINT"); 43 | exit_on_error(sigaction(SIGTSTP, &s, NULL), "Error in setting action for SIGTSTP"); 44 | exit_on_error(sigaction(SIGTTIN, &s, NULL), "Error in setting action for SIGTTIN"); 45 | /* exit_on_error(sigaction(SIGTTOU, &s, NULL), "Error in setting action for SIGTTOU"); */ 46 | exit_on_error(sigaction(SIGCHLD, &s, NULL), "Error in setting action for SIGCHLD"); 47 | exit_on_error(sigaction(SIGQUIT, &s, NULL), "Error in setting action for SIGQUIT"); 48 | 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /src/signal_handling.h: -------------------------------------------------------------------------------- 1 | #ifndef signal_handling_h 2 | #define signal_handling_h 3 | 4 | #include 5 | 6 | 7 | #define CODE_SIGINT 1002 8 | 9 | extern sigjmp_buf jmpbuf; 10 | 11 | void sigint_handler(int); 12 | 13 | int setup_parent_signals(); 14 | int setup_child_signals(); 15 | 16 | #endif // signal_handling_h 17 | --------------------------------------------------------------------------------