├── .gitignore ├── Makefile ├── README.md ├── builtin.c ├── builtin.h ├── command.c ├── command.h ├── command_handler.c ├── command_handler.h ├── global.h ├── iron_shell.c ├── iron_shell.h ├── job.c ├── job.h ├── log.h ├── main.c ├── signals.c └── signals.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.*.un~ 2 | *.un~ 3 | *.txt 4 | iron_shell 5 | tags 6 | *.o 7 | 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | EXE=iron_shell 2 | 3 | CC = gcc 4 | CC_FLAGS = -fsanitize=address -Wall 5 | 6 | SOURCE = main.c \ 7 | iron_shell.c \ 8 | command.c \ 9 | job.c \ 10 | command_handler.c \ 11 | builtin.c \ 12 | signals.c \ 13 | 14 | OBJECT = $(SOURCE:.c=.o) 15 | 16 | $(EXE): $(OBJECT) 17 | $(CC) $(CC_FLAGS) $(OBJECT) -o $(EXE) 18 | 19 | %.o : %.c 20 | $(CC) $(FLAGS) -c $< -o $@ 21 | 22 | valgrind: iron_shell 23 | valgrind --leak-check=full --show-leak-kinds=all ./$(EXE) 24 | 25 | clean: 26 | rm *.o 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :crossed\_swords: IRON shell 2 | 3 | * 💻 Shell's are programs which provide **command line interface** to prefrom tasks, 4 | or in true sense they invoke execution of other programs. **IRON** here means 5 | **hard/strong**. This Shell inherits alot similar functionality as provided 6 | by many shells in market like bash, zsh. 7 | 8 | * 😄 Fun Fact: Windows command prompt is world's most horrible shell program. 9 | 10 | ## :sunglasses: UNIX Ideaology 11 | 12 | * UNIX is made on basis of writing various programs such that each one of them 13 | can executes, handle and manage **one particular task completely**. In-order 14 | to achieve execution of complicated task, it is broken in sub-task and each 15 | sub-task is handled independently by the programs written and execution 16 | happens concurrently with each subtask I/O connected using **PIPES**. 17 | 18 | * UNIX assumes everthing to be file (there are no drives, even folder/directory 19 | are files !!). The program execution can make use of file for I/O purpose. 20 | This is made possible using I/O **REDIRECTION** to files. 21 | 22 | ## :wrench: Installation Building IRON-shell executable 23 | 24 | * Install for github UI, or by any command line interface with following commands 25 | 26 | ```sh 27 | $ git clone https://github.com/kishanpatel22/IRON-shell 28 | $ cd IRON-shell 29 | ``` 30 | 31 | * Run the following commands to create the executable file and start the shell 32 | 33 | ```sh 34 | $ make 35 | $ ./iron-shell 36 | ``` 37 | 38 | * Tip : use make clean to clean all the objects files created by make 39 | 40 | 41 | ## :dart: Example regarding the features and usage 42 | 43 | * The video contains a small demo of how to user the command line interface 44 | 45 | [![asciicast](https://asciinema.org/a/401781.svg)](https://asciinema.org/a/401781) 46 | 47 | ## :package: FEATURES OF IRON-SHELL 48 | 49 | ### :eight\_pointed\_black\_star: Build in IRON-Shell commands 50 | 51 | * Support to build in iron-shell commands include the following things 52 | 53 | | Command | Significance | 54 | |-------------|-------------------------------------------------| 55 | | **cd** | command to change the current working directory | 56 | | **exit** | to exit the shell | 57 | | **fg** | to run any fore-ground process | 58 | | **bg** | run any background process | 59 | 60 | * Support to signals for the iron-shell 61 | 62 | | Signal | Significance | 63 | |---------------|-------------------------------------------------| 64 | | **control-c** | terminates the currently executing program / process | 65 | | **control-z** | suspends the execution of the currently running program / processes | 66 | | **control-d** | to exit from the iron-shell prompt | 67 | 68 | ### :eight\_pointed\_black\_star: PIPES, RIDRECTION and STOPPING/RESUMING of processes 69 | 70 | * Support to unix pipelining, redirection I/O with files and suspension for 71 | processes in iron-shell background. 72 | 73 | * Examples for OUTPUT and INPUT Redirection 74 | 75 | ```sh 76 | 77 | /home/kishan/code/shell 78 | ISHELL >> wc < satellite_data.txt > file.txt 79 | 80 | /home/kishan/code/shell 81 | ISHELL >> cat file.txt 82 | 864000 6912000 77817099 satellite_data.txt 83 | 84 | ``` 85 | 86 | * Examples for Pipelining 87 | 88 | ```sh 89 | /home/kishan/code/C++_codes 90 | ISHELL >> ls -l | grep ^d | sort 91 | drwxr-xr-x 21 kishan kishan 4096 Feb 9 22:14 DSA 92 | drwxr-xr-x 2 kishan kishan 4096 Jan 10 21:04 Makefile 93 | drwxr-xr-x 2 kishan kishan 4096 Jan 11 15:04 cmake_demo 94 | drwxr-xr-x 2 kishan kishan 4096 Mar 7 19:52 pavan 95 | drwxr-xr-x 2 kishan kishan 4096 May 15 2020 cplusplus 96 | drwxr-xr-x 5 kishan kishan 4096 Aug 24 2020 TOC 97 | drwxr-xr-x 8 kishan kishan 4096 Jan 2 22:24 OOPS 98 | 99 | /home/kishan/code/C++_codes 100 | ISHELL >> ls -l | grep ^d | sort | head -n 3 101 | drwxr-xr-x 21 kishan kishan 4096 Feb 9 22:14 DSA 102 | drwxr-xr-x 2 kishan kishan 4096 Jan 10 21:04 Makefile 103 | drwxr-xr-x 2 kishan kishan 4096 Jan 11 15:04 cmake_demo 104 | 105 | /home/kishan/code/C++_codes 106 | ISHELL >> ls -l | grep ^d | sort | head -n 3 | wc > new_data.txt 107 | 108 | /home/kishan/code/C++_codes 109 | ISHELL >> cat new_data.txt 110 | 3 27 165 111 | ``` 112 | 113 | 114 | * Examples for Background process 115 | 116 | ```sh 117 | 118 | /home/kishan/code/shell 119 | ISHELL >> firefox & 120 | 121 | /home/kishan/code/shell 122 | ISHELL >> echo I can run next command 123 | I can run next command 124 | 125 | /home/kishan/code/shell 126 | ISHELL >> jobs 127 | 128 | [1] + 29020 running firefox 129 | 130 | ``` 131 | 132 | * Example of signal handling and jobs 133 | 134 | ```sh 135 | 136 | /home/kishan/code/shell 137 | ISHELL >> ping www.google.com | grep ttl 138 | 64 bytes from bom12s04-in-f4.1e100.net (216.58.203.4): icmp_seq=1 ttl=120 time=5.88 ms 139 | 64 bytes from bom12s04-in-f4.1e100.net (216.58.203.4): icmp_seq=2 ttl=120 time=8.36 ms 140 | 64 bytes from bom12s04-in-f4.1e100.net (216.58.203.4): icmp_seq=3 ttl=120 time=8.82 ms 141 | 64 bytes from bom12s04-in-f4.1e100.net (216.58.203.4): icmp_seq=4 ttl=120 time=8.42 ms 142 | 64 bytes from bom12s04-in-f4.1e100.net (216.58.203.4): icmp_seq=5 ttl=120 time=8.50 ms 143 | ^Z 144 | [1] + 28924 stopped grep 145 | + 28923 stopped ping 146 | 147 | /home/kishan/code/shell 148 | ISHELL >> jobs 149 | 150 | [1] + 28924 stopped grep 151 | + 28923 stopped ping 152 | 153 | /home/kishan/code/shell 154 | ISHELL >> fg 155 | 156 | [1] + 28924 running grep 157 | + 28923 running ping 158 | 64 bytes from bom12s04-in-f4.1e100.net (216.58.203.4): icmp_seq=6 ttl=120 time=8.48 ms 159 | 64 bytes from bom12s04-in-f4.1e100.net (216.58.203.4): icmp_seq=7 ttl=120 time=8.62 ms 160 | 64 bytes from bom12s04-in-f4.1e100.net (216.58.203.4): icmp_seq=8 ttl=120 time=10.4 ms 161 | ^C 162 | 163 | /home/kishan/code/shell 164 | ISHELL >> 165 | 166 | ``` 167 | 168 | -------------------------------------------------------------------------------- /builtin.c: -------------------------------------------------------------------------------- 1 | #include "builtin.h" 2 | #include "global.h" 3 | 4 | /* check if the command is shell buildin command example 5 | * 1) cd 6 | * 2) history 7 | * 3) exit 8 | * 4) fg 9 | * 5) bg 10 | * 6) jobs 11 | * On success returns the true, else false 12 | */ 13 | 14 | bool check_builtin_command(char *command_buffer) { 15 | char command[128]; 16 | int index = 0; 17 | while(command_buffer[index] != ' ' && command_buffer[index] != '\0') { 18 | command[index] = command_buffer[index]; 19 | index++; 20 | } 21 | command[index] = '\0'; 22 | 23 | /* compare with the build in shell commands */ 24 | if(strcmp(command, CD_COMMAND) == 0) { 25 | return true; 26 | } else if(strcmp(command, HISTORY_COMMAND) == 0) { 27 | return true; 28 | } else if(strcmp(command, EXIT_COMMAND) == 0) { 29 | return true; 30 | } else if(strcmp(command, FG_COMMAND) == 0) { 31 | return true; 32 | } else if(strcmp(command, BG_COMAMND) == 0) { 33 | return true; 34 | } else if(strcmp(command, JOBS_COMMAND) == 0) { 35 | return true; 36 | } 37 | /* its no a shell command */ 38 | return false; 39 | } 40 | 41 | /* returns the unique number of the shell command */ 42 | int builtin_shell_command_number(char *command) { 43 | if(strcmp(command, CD_COMMAND) == 0) { 44 | return CHANGE_DIR; 45 | } else if(strcmp(command, HISTORY_COMMAND) == 0) { 46 | return HISTORY; 47 | } else if(strcmp(command, EXIT_COMMAND) == 0) { 48 | return EXIT; 49 | } else if(strcmp(command, FG_COMMAND) == 0) { 50 | return FG; 51 | } else if(strcmp(command, BG_COMAMND) == 0) { 52 | return BG; 53 | } else if(strcmp(command, JOBS_COMMAND) == 0) { 54 | return JOBS; 55 | } 56 | /* its no a shell command */ 57 | return -1; 58 | } 59 | 60 | /* executes the shell buildin comamnd */ 61 | void execute_shell_builtin_command(char *command) { 62 | 63 | char cwd[512]; 64 | IronShellCommand shell_command; 65 | strcpy(shell_command.command, command); 66 | command_argument_parser(&shell_command); 67 | 68 | int command_type = builtin_shell_command_number(shell_command.arguments[0]); 69 | 70 | /* set the appropriate signal handlers */ 71 | set_signals(); 72 | 73 | /* initialize the foreground job */ 74 | init_job(&(iscb.fg_job), ""); 75 | 76 | switch(command_type) { 77 | 78 | /* change the directory */ 79 | case CHANGE_DIR: 80 | /* store the current working directory */ 81 | getcwd(cwd, 512); 82 | /* more than two arguments to cd is not permissible */ 83 | if(shell_command.n > 2) { 84 | PRINT_EXECPTION("cd : too many arguments !"); 85 | return; 86 | } else if(shell_command.arguments[1] == NULL) { 87 | chdir(HOME_DIR); 88 | strcpy(iscb.pwd, cwd); 89 | } else if(strcmp(shell_command.arguments[1], "-") == 0) { 90 | chdir(iscb.pwd); 91 | strcpy(iscb.pwd, cwd); 92 | } else if(chdir(shell_command.arguments[1]) == -1) { 93 | PRINT_EXECPTION("cd : No such file or directory !\n"); 94 | return; 95 | } 96 | break; 97 | 98 | /* HISTORY operation in the shell */ 99 | case HISTORY: 100 | break; 101 | 102 | /* BG command to run process in background */ 103 | case BG: 104 | if(shell_command.n > 2) { 105 | PRINT_EXECPTION("bg works with only index numbers of jobs"); 106 | break; 107 | } else if(shell_command.n == 2) { 108 | resume_job_bg(&(iscb.jobs), atoi(shell_command.arguments[1])); 109 | return; 110 | } else { 111 | resume_job_bg(&(iscb.jobs), 1); 112 | return; 113 | } 114 | break; 115 | 116 | /* FG command to run process in foreground */ 117 | case FG: 118 | if(shell_command.n > 2) { 119 | PRINT_EXECPTION("fg works with only index numbers of jobs"); 120 | break; 121 | } else if(shell_command.n == 2) { 122 | resume_job_fg(&(iscb.jobs), atoi(shell_command.arguments[1]), &(iscb.fg_job)); 123 | } else { 124 | resume_job_fg(&(iscb.jobs), 1, &(iscb.fg_job)); 125 | } 126 | 127 | /* wait for all the concurrently running process to get over */ 128 | for(int i = 0; i < iscb.fg_job.process_count; i++) { 129 | /* wait pid waits for any process to changes its state */ 130 | waitpid(-1, NULL, WUNTRACED); 131 | } 132 | 133 | /* destroy the job */ 134 | destroy_job(&(iscb.fg_job)); 135 | 136 | break; 137 | 138 | /* JOBS command to display list of suspended and running processes */ 139 | case JOBS: 140 | print_jobs(iscb.jobs); 141 | break; 142 | 143 | /* exit command */ 144 | case EXIT: 145 | destroy_jobs(&(iscb.jobs)); 146 | /* terminate the program */ 147 | exit(errno); 148 | 149 | /* invalid command as input */ 150 | default: 151 | break; 152 | } 153 | 154 | /* reset the original signals handlers */ 155 | reset_signals(); 156 | } 157 | 158 | 159 | -------------------------------------------------------------------------------- /builtin.h: -------------------------------------------------------------------------------- 1 | #ifndef BUILDIN_H 2 | #define BUILDIN_H 1 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "command.h" 13 | 14 | /* Module provides basic functionalities that are provided by iron-shell 15 | * which include changing directories, history executed and exiting iron-shell 16 | */ 17 | 18 | #define CD_COMMAND "cd" 19 | #define HISTORY_COMMAND "history" 20 | #define EXIT_COMMAND "exit" 21 | #define FG_COMMAND "fg" 22 | #define BG_COMAMND "bg" 23 | #define JOBS_COMMAND "jobs" 24 | 25 | /* identifier values for identification for the builtin shell commands */ 26 | #define CHANGE_DIR (1) 27 | #define HISTORY (2) 28 | #define EXIT (3) 29 | #define FG (4) 30 | #define BG (5) 31 | #define JOBS (6) 32 | 33 | 34 | #define HOME_DIR getenv("HOME") 35 | 36 | /* maximum length of the directory */ 37 | #define MAX_DIR_LENGHT (512) 38 | 39 | /* checks the command is iron-shell built in command */ 40 | bool check_builtin_command(char *command_buffer); 41 | 42 | /* returns the unique identifier of iron-shell command */ 43 | int builtin_shell_command_number(char *command); 44 | 45 | /* executes the iron-shell builtin command */ 46 | void execute_shell_builtin_command(char *command); 47 | 48 | #endif /* BUILDIN_H */ 49 | 50 | -------------------------------------------------------------------------------- /command.c: -------------------------------------------------------------------------------- 1 | #include "command.h" 2 | 3 | /* parses the arguments of given iron shell command */ 4 | int command_argument_parser(IronShellCommand *sh_command) { 5 | 6 | /* initialize the number of arguments of the command */ 7 | sh_command->n = 0; 8 | 9 | /* loop through the shell comamnd for parsing arguments */ 10 | char *args = strtok(sh_command->command, COMMAND_ARGUMENT_DELIMITERS); 11 | while(args != NULL) { 12 | sh_command->arguments[sh_command->n] = args; 13 | sh_command->n++; 14 | args = strtok(NULL, COMMAND_ARGUMENT_DELIMITERS); 15 | } 16 | 17 | /* last argument is NULL (helps in exec) */ 18 | sh_command->arguments[sh_command->n] = NULL; 19 | 20 | return 0; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /command.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMAND_H 2 | #define COMMAND_H 1 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "log.h" 16 | 17 | /* command argument delimiters */ 18 | #define COMMAND_ARGUMENT_DELIMITERS " " 19 | 20 | #define MAX_COMMAND_SIZE (1024) 21 | #define MAX_ARGS (16) 22 | 23 | /* IRON-shell command structure */ 24 | typedef struct IronShellCommand { 25 | char command[MAX_COMMAND_SIZE]; /* command string */ 26 | char *arguments[MAX_ARGS]; /* array of pointers to the command */ 27 | int n; /* number of iron-shell command args*/ 28 | } IronShellCommand; 29 | 30 | int command_argument_parser(IronShellCommand *sh_command); 31 | 32 | 33 | 34 | #endif /* COMMAND_H */ 35 | -------------------------------------------------------------------------------- /command_handler.c: -------------------------------------------------------------------------------- 1 | #include "command_handler.h" 2 | #include "job.h" 3 | #include "global.h" 4 | #include "signals.h" 5 | 6 | /* global variable keeps the track of where next token to be extracted */ 7 | static int parser_index; 8 | 9 | static IOContext io_context; 10 | 11 | /* shell delimiters are pipes, redirects and ampersand */ 12 | const char shell_delimiters[] = {'|', '>', '<', '&'}; 13 | 14 | /* reads the command from standard input */ 15 | int read_command(char *command_token, int max_command_length) { 16 | int i = 0, ch; 17 | while((ch = getchar()) != '\n' && ch != EOF && i < max_command_length) { 18 | command_token[i++] = ch; 19 | } 20 | command_token[i] = '\0'; 21 | /* comments are by default ignored */ 22 | if(i > 0 && command_token[0] == '#') { 23 | command_token[0] = '\0'; 24 | return 0; 25 | } 26 | return i == 0 && ch == EOF ? -1 : i; 27 | } 28 | 29 | /* checks if the given character is shell delimiter */ 30 | bool check_shell_delimiter(char ch) { 31 | int n = sizeof(shell_delimiters) / sizeof(shell_delimiters[0]); 32 | for(int i = 0; i < n; i++) { 33 | if(shell_delimiters[i] == ch) { 34 | return true; 35 | } 36 | } 37 | return false; 38 | } 39 | 40 | /* returns the state of given shell delimiter */ 41 | int get_shell_delimiter_state(char shell_delimiter) { 42 | if(shell_delimiter == '|') { 43 | return PIPE; 44 | } 45 | if(shell_delimiter == '<') { 46 | return INPUT_REDIRECT; 47 | } 48 | if(shell_delimiter == '>') { 49 | return OUTPUT_REDIRECT; 50 | } 51 | if(shell_delimiter == '&') { 52 | return NON_BLOCKING_COMMAND; 53 | } 54 | return INVALID; 55 | } 56 | 57 | /* tokenizer : returns the next token */ 58 | int parser(char *token, char *command) { 59 | int n = strlen(command); 60 | /* skip the spaces */ 61 | while(command[parser_index] == ' ' && parser_index < n) { 62 | parser_index++; 63 | } 64 | /* parse given commannd */ 65 | /* CASE 1 : end of the command */ 66 | if(parser_index == n) { 67 | return END; 68 | } 69 | 70 | /* CASE 2 : check for shell command delimiters */ 71 | if(check_shell_delimiter(command[parser_index])) { 72 | int state = get_shell_delimiter_state(command[parser_index]); 73 | parser_index++; 74 | return state; 75 | } 76 | 77 | int token_index = 0; 78 | /* CASE 3 : extract the shell command */ 79 | while(parser_index < n && !check_shell_delimiter(command[parser_index])) { 80 | token[token_index++] = command[parser_index++]; 81 | } 82 | token[token_index] = '\0'; 83 | return COMMAND; 84 | } 85 | 86 | /* FSM for handling the command */ 87 | void command_handler(char *command_token) { 88 | char token[128]; /* stores string token */ 89 | char file_name[128]; /* stores the file name */ 90 | int pfd[2], fd; /* stores file descripters */ 91 | IronShellCommand shell_command; /* stores shell command */ 92 | 93 | parser_index = 0; 94 | int current_state = START, next_state; /* stores state of FSM */ 95 | int num_process = 0; 96 | 97 | /* initialize the current foreground job */ 98 | init_job(&(iscb.fg_job), command_token); 99 | 100 | /* set the appropriate signal handlers */ 101 | set_signals(); 102 | 103 | /* save the context of the file descripters */ 104 | save_io_context(); 105 | 106 | /* parse until the end of shell command */ 107 | while(current_state != END) { 108 | /* get next token */ 109 | next_state = parser(token, command_token); 110 | /* switch state accordingly and take actions */ 111 | switch(next_state) { 112 | case COMMAND: 113 | switch(current_state) { 114 | /* seeing the first command after starting */ 115 | case START: 116 | /* extract the command and store the command */ 117 | strcpy(shell_command.command, token); 118 | command_argument_parser(&shell_command); 119 | break; 120 | /* seeing command after pipe before */ 121 | case PIPE: 122 | /* extract the command and store the command */ 123 | strcpy(shell_command.command, token); 124 | command_argument_parser(&shell_command); 125 | 126 | /* close the write end of the pipe */ 127 | close(pfd[1]); 128 | /* duplicate read fd with pipe read end */ 129 | dup2(pfd[0], STDIN_FILENO); 130 | break; 131 | 132 | /* seeing command after input redirect */ 133 | case INPUT_REDIRECT: 134 | /* interrpret current token as file not command */ 135 | strcpy(file_name, strtok(token, " ")); 136 | fd = open(file_name, O_RDONLY); 137 | if(fd == -1) { 138 | PRINT_ERR("cannot open file !"); 139 | } 140 | dup2(fd, STDIN_FILENO); 141 | break; 142 | 143 | /* seeing command after output redirect */ 144 | case OUTPUT_REDIRECT: 145 | /* interrpret current token as file not command */ 146 | strcpy(file_name, strtok(token, " ")); 147 | fd = open(file_name, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); 148 | if(fd == -1) { 149 | PRINT_ERR("cannot open file !"); 150 | } 151 | dup2(fd, STDOUT_FILENO); 152 | break; 153 | 154 | /* seeing command after & symbol */ 155 | case NON_BLOCKING_COMMAND: 156 | /* execute the previous command and extract then new command */ 157 | execute_shell_command(shell_command); 158 | num_process++; 159 | /* extract the command and store the command */ 160 | strcpy(shell_command.command, token); 161 | command_argument_parser(&shell_command); 162 | break; 163 | } 164 | break; 165 | case PIPE: 166 | switch(current_state) { 167 | /* seeing a pipe after previous command */ 168 | case COMMAND: 169 | /* create new pipe and close the read end of pipe 170 | * and execute the shell command 171 | */ 172 | execute_shell_command_with_pipe(shell_command, pfd); 173 | num_process++; 174 | break; 175 | 176 | /* malicious user : need learn how write shell commands */ 177 | case INPUT_REDIRECT: 178 | PRINT_ERR("syntax error near unexpected token `newline'"); 179 | break; 180 | 181 | /* malicious user : need learn how write shell commands */ 182 | case OUTPUT_REDIRECT: 183 | PRINT_ERR("syntax error near unexpected token `newline'"); 184 | break; 185 | 186 | /* malicious user : need learn how write shell commands */ 187 | case START: 188 | PRINT_ERR("syntax error near unexpected token `|'"); 189 | break; 190 | } 191 | break; 192 | case INPUT_REDIRECT: 193 | switch(current_state) { 194 | case PIPE: 195 | PRINT_ERR("syntax error near unexpected token `|'"); 196 | break; 197 | case START: 198 | PRINT_ERR("syntax error near unexpected token `|'"); 199 | break; 200 | case OUTPUT_REDIRECT: 201 | PRINT_ERR("syntax error near unexpected token `|'"); 202 | } 203 | break; 204 | case OUTPUT_REDIRECT: 205 | switch(current_state) { 206 | case PIPE: 207 | PRINT_ERR("syntax error near unexpected token `newline'"); 208 | break; 209 | case START: 210 | PRINT_ERR("syntax error near unexpected token `newline'"); 211 | break; 212 | case INPUT_REDIRECT: 213 | PRINT_ERR("syntax error near unexpected token `newline'"); 214 | break; 215 | } 216 | break; 217 | case NON_BLOCKING_COMMAND: 218 | switch(current_state) { 219 | case START: 220 | PRINT_ERR("syntax error near unexpected token `|'"); 221 | break; 222 | case PIPE: 223 | PRINT_ERR("syntax error near unexpected token `newline'"); 224 | break; 225 | case INPUT_REDIRECT: 226 | PRINT_ERR("syntax error near unexpected token `newline'"); 227 | break; 228 | } 229 | break; 230 | case END: 231 | switch(current_state) { 232 | /* empty string command */ 233 | case START: 234 | break; 235 | /* malicious user : need learn how write shell commands */ 236 | case PIPE: 237 | PRINT_ERR("syntax error near unexpected token `|'"); 238 | break; 239 | /* malicious user : need learn how write shell commands */ 240 | case INPUT_REDIRECT: 241 | PRINT_ERR("syntax error near unexpected token `newline'"); 242 | break; 243 | /* malicious user : need learn how write shell commands */ 244 | case OUTPUT_REDIRECT: 245 | PRINT_ERR("syntax error near unexpected token `newline'"); 246 | break; 247 | /* execute command such that it's non blocking for shell terminal */ 248 | case NON_BLOCKING_COMMAND: 249 | execute_shell_command(shell_command); 250 | /* add this command to the currently running jobs in the background */ 251 | add_job(&(iscb.jobs), iscb.fg_job); 252 | /* initialize the foreground job as empty */ 253 | init_job(&(iscb.fg_job), ""); 254 | return; 255 | /* execute command such that it's blocking for shell terminal */ 256 | case COMMAND: 257 | execute_shell_command(shell_command); 258 | num_process++; 259 | break; 260 | } 261 | break; 262 | default: 263 | break; 264 | } 265 | current_state = next_state; 266 | } 267 | 268 | /* wait for all the concurrently running process to get over */ 269 | for(int i = 0; i < num_process; i++) { 270 | /* wait pid waits for any process to changes its state */ 271 | waitpid(-1, NULL, WUNTRACED); 272 | } 273 | 274 | /* restore the context of input output file descripters */ 275 | restore_io_context(); 276 | 277 | /* reset the original signals handlers */ 278 | reset_signals(); 279 | 280 | /* destroy the resource for foreground process which was running */ 281 | destroy_job(&(iscb.fg_job)); 282 | 283 | return; 284 | } 285 | 286 | 287 | /* function executes the simple shell command concurrently */ 288 | void execute_shell_command(IronShellCommand shell_command) { 289 | 290 | pid_t pid = fork(); 291 | 292 | /* child process will execute command */ 293 | if(pid == 0) { 294 | 295 | /* reset the signal handlers before executing the child process 296 | * thus it executes with the default signal handlers given by kernel 297 | */ 298 | reset_signals(); 299 | execvp(shell_command.arguments[0], shell_command.arguments); 300 | PRINT_ERR("execvp failed"); 301 | } 302 | /* parent process will be remain unblocked and update the process control list */ 303 | else { 304 | add_sub_job(&(iscb.fg_job), shell_command.arguments[0], pid); 305 | return; 306 | } 307 | } 308 | 309 | 310 | /* function executes the shell command connecting output to pipe */ 311 | void execute_shell_command_with_pipe(IronShellCommand shell_command, int pfd[]) { 312 | pipe(pfd); 313 | pid_t pid = fork(); 314 | if(pid == 0) { 315 | /* close the read end and duplicate the STDOUT with write end */ 316 | close(pfd[0]); 317 | dup2(pfd[1], STDOUT_FILENO); 318 | 319 | /* reset the signal handlers before executing the child process 320 | * thus it executes with the default signal handlers given by kernel 321 | */ 322 | reset_signals(); 323 | execvp(shell_command.arguments[0], shell_command.arguments); 324 | PRINT_ERR("execvp failed"); 325 | } else { 326 | /* this makes commands in pipe to execute concurrently and 327 | * update the process control list 328 | */ 329 | add_sub_job(&(iscb.fg_job), shell_command.arguments[0], pid); 330 | return; 331 | } 332 | } 333 | 334 | /* saves the current context of the standard file descripters */ 335 | void save_io_context() { 336 | io_context.stdin_fileno = dup(STDIN_FILENO); 337 | io_context.stdout_fileno = dup(STDOUT_FILENO); 338 | } 339 | 340 | /* restores the context of the standard IO file descripter */ 341 | void restore_io_context() { 342 | dup2(io_context.stdin_fileno, STDIN_FILENO); 343 | dup2(io_context.stdout_fileno, STDOUT_FILENO); 344 | } 345 | 346 | 347 | -------------------------------------------------------------------------------- /command_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMAND_HANDLER_H 2 | #define COMMAND_HANDLER_H 1 3 | 4 | #include "command.h" 5 | 6 | /* FINITE STATE MACHINE STATES for parsing the shell command */ 7 | #define START (100) 8 | #define COMMAND (200) 9 | #define PIPE (300) 10 | #define INPUT_REDIRECT (400) 11 | #define OUTPUT_REDIRECT (500) 12 | #define NON_BLOCKING_COMMAND (600) 13 | #define END (700) 14 | 15 | #define INVALID (-1) 16 | 17 | typedef struct IOContext { 18 | int stdout_fileno, stdin_fileno; 19 | } IOContext; 20 | 21 | 22 | /* parser for shell command arguments */ 23 | int command_argument_parser(IronShellCommand *sh_command); 24 | 25 | /* reads the mulitple shell command from user */ 26 | int read_command(char *command_buffer, int max_command_length); 27 | 28 | /* returns the shell delimiter state */ 29 | int get_shell_delimiter_state(char shell_delimiter); 30 | 31 | /* checks if the given character is shell delimiter */ 32 | bool check_shell_delimiter(char ch); 33 | 34 | /* parser for the multiple shell commads */ 35 | int parser(char *token, char *command); 36 | 37 | /* FSM based approach for command handler */ 38 | void command_handler(char *command_buffer); 39 | 40 | /* function executes the iron shell commands concurrently without blocking 41 | * the shell prompt with the sense of background process 42 | */ 43 | void execute_shell_command(IronShellCommand shell_command); 44 | 45 | /* function executes the iron shell command connecting to a pipe */ 46 | void execute_shell_command_with_pipe(IronShellCommand shell_command, int pfd[]); 47 | 48 | /* saves the current context of the standard file descripters */ 49 | void save_io_context(); 50 | 51 | /* restores the context of the standard IO file descripter */ 52 | void restore_io_context(); 53 | 54 | 55 | #endif /* COMMAND_HANDLER_H */ 56 | -------------------------------------------------------------------------------- /global.h: -------------------------------------------------------------------------------- 1 | #ifndef GLOBAL_H 2 | #define GLOBAL_H 1 3 | 4 | #include "iron_shell.h" 5 | 6 | /* global variable used for tracking the shell control block */ 7 | extern IronShellControlBlock iscb; 8 | 9 | IronShellControlBlock iscb; 10 | 11 | #endif /* GLOBAL_H */ 12 | -------------------------------------------------------------------------------- /iron_shell.c: -------------------------------------------------------------------------------- 1 | #include "iron_shell.h" 2 | #include "global.h" 3 | 4 | /* initializes the iron shell control block */ 5 | void init_shell_control_block(IronShellControlBlock *iscb) { 6 | 7 | char cwd[512]; 8 | 9 | /* initialize the foreground command running in shell */ 10 | init_job(&(iscb->fg_job), ""); 11 | 12 | /* execution time of the last command */ 13 | iscb->execution_time = 0.0; 14 | 15 | /* history log file */ 16 | strcpy(iscb->history_commands_file, HISTORY_LOG); 17 | 18 | /* suspended or running list of jobs for the shell */ 19 | init_job_list(&(iscb->jobs)); 20 | 21 | /* default previous directory is the home directory */ 22 | getcwd(cwd, 512); 23 | strcpy(iscb->pwd, cwd); 24 | 25 | } 26 | 27 | /* initializes the job list data structure for suspended or runnning tasks */ 28 | void init_job_list(IronShellJobList *jobs) { 29 | jobs->head = jobs->tail = NULL; 30 | jobs->count_jobs = 0; 31 | return; 32 | } 33 | 34 | /* add job to the job list */ 35 | void add_job(IronShellJobList *jobs, IronShellJob job) { 36 | IronShellJobNode *new_job_node = (IronShellJobNode *)malloc(sizeof(IronShellJobNode)); 37 | if(new_job_node == NULL) { 38 | PRINT_ERR("malloc failed"); 39 | } 40 | new_job_node->job = job; 41 | new_job_node->next = NULL; 42 | 43 | /* first job to be added in the job list */ 44 | if(jobs->head == NULL && jobs->tail == NULL) { 45 | jobs->head = jobs->tail = new_job_node; 46 | jobs->count_jobs++; 47 | return; 48 | } 49 | new_job_node->next = jobs->head; 50 | jobs->head = new_job_node; 51 | jobs->count_jobs++; 52 | return; 53 | } 54 | 55 | 56 | /* resumes job in foreground */ 57 | void resume_job_fg(IronShellJobList *jobs, int index, IronShellJob *job) { 58 | if(no_jobs(*jobs)) { 59 | PRINT_EXECPTION("no foreground jobs present"); 60 | return; 61 | } 62 | IronShellJobNode* ptr = jobs->head, *temp = NULL; 63 | while(ptr != NULL && index > 1) { 64 | temp = ptr; 65 | ptr = ptr->next; 66 | index--; 67 | } 68 | if(ptr == NULL) { 69 | PRINT_EXECPTION("no foreground jobs for given argument"); 70 | return; 71 | } 72 | 73 | /* job which is made to start in foreground if it was stopped 74 | */ 75 | resume_job(&(ptr->job), index); 76 | *job = ptr->job; 77 | 78 | /* delete the node from the job list */ 79 | if(temp == NULL) { 80 | /* deleting the first node in the list */ 81 | jobs->head = jobs->head->next; 82 | free(ptr); 83 | } else { 84 | temp->next = ptr->next; 85 | free(ptr); 86 | } 87 | jobs->count_jobs--; 88 | return; 89 | } 90 | 91 | 92 | /* resumes job in background */ 93 | void resume_job_bg(IronShellJobList *jobs, int index) { 94 | if(no_jobs(*jobs)) { 95 | PRINT_EXECPTION("no background jobs present"); 96 | return; 97 | } 98 | IronShellJobNode* ptr = jobs->head; 99 | while(ptr != NULL && index > 1) { 100 | ptr = ptr->next; 101 | index--; 102 | } 103 | if(ptr == NULL) { 104 | PRINT_EXECPTION("no background jobs for given argument"); 105 | return; 106 | } 107 | /* there is not change in the data structure */ 108 | if(ptr->job.command_status == RUNNING) { 109 | PRINT_EXECPTION("process is already running in background"); 110 | return; 111 | } else { 112 | resume_job(&(ptr->job), index); 113 | } 114 | return; 115 | } 116 | 117 | 118 | /* traverse the job lists : prints the list of running and suspensed jobs */ 119 | void print_jobs(IronShellJobList jobs) { 120 | IronShellJobNode *ptr = jobs.head; 121 | if(ptr == NULL) { 122 | PRINT_EXECPTION("no suspeneded or stopped jobs"); 123 | return; 124 | } 125 | int i = 1; 126 | while(ptr != NULL) { 127 | print_job(ptr->job, i++); 128 | ptr = ptr->next; 129 | } 130 | } 131 | 132 | 133 | /* destory all the jobs which are currently active */ 134 | void destroy_jobs(IronShellJobList *jobs) { 135 | IronShellJobNode *ptr = jobs->head, *temp; 136 | while(ptr != NULL) { 137 | kill_job(&(ptr->job)); 138 | jobs->count_jobs--; 139 | temp = ptr->next; 140 | free(ptr); 141 | ptr = temp; 142 | } 143 | jobs->head = jobs->tail = NULL; 144 | return; 145 | } 146 | 147 | 148 | /* returns true if there are no jobs suspended or running */ 149 | bool no_jobs(IronShellJobList jobs) { 150 | if(jobs.head == NULL && jobs.tail == NULL) { 151 | return true; 152 | } 153 | return false; 154 | } 155 | 156 | /* delelte the job having the given pid */ 157 | bool delete_job(IronShellJobList *jobs, pid_t pid) { 158 | IronShellJobNode *ptr = jobs->head, *temp = NULL; 159 | int index = 1; 160 | while(ptr != NULL) { 161 | if(delete_sub_job(&(ptr->job), pid, index)) { 162 | /* now check if the given process is completely done 163 | * thus free the allocated memory to data structure 164 | */ 165 | if(ptr->job.head == NULL) { 166 | if(temp == NULL) { 167 | jobs->head = ptr->next; 168 | if(jobs->head == NULL) { 169 | jobs->tail = NULL; 170 | } 171 | } else { 172 | temp->next = ptr->next; 173 | } 174 | free(ptr); 175 | jobs->count_jobs--; 176 | } 177 | } 178 | temp = ptr; 179 | ptr = ptr->next; 180 | index++; 181 | } 182 | return false; 183 | } 184 | 185 | -------------------------------------------------------------------------------- /iron_shell.h: -------------------------------------------------------------------------------- 1 | #ifndef IRON_SHELL_H 2 | #define IRON_SHELL_H 1 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "command_handler.h" 9 | #include "signals.h" 10 | #include "builtin.h" 11 | #include "job.h" 12 | 13 | #define HISTORY_LOG "/tmp/iron-shell-logs.txt" 14 | 15 | #define MAX_JOBS (1024) 16 | 17 | 18 | typedef struct IronShellJobNode { 19 | 20 | IronShellJob job; /* the job which is suspended */ 21 | 22 | struct IronShellJobNode *next; /* the next job reference in list */ 23 | 24 | } IronShellJobNode; 25 | 26 | 27 | typedef struct IronShellJobList { 28 | 29 | IronShellJobNode *head, *tail; /* list of jobs suspended or running */ 30 | 31 | int count_jobs; /* number of such jobs */ 32 | 33 | } IronShellJobList; 34 | 35 | 36 | /* IRON-shell Control Block */ 37 | typedef struct IronShellControlBlock { 38 | 39 | IronShellJob fg_job; /* currently executing foreground job */ 40 | 41 | double execution_time; /* execution time of last command */ 42 | 43 | char history_commands_file[128]; /* command history log file */ 44 | 45 | IronShellJobList jobs; /* list of currently suspened and runing jobs */ 46 | 47 | char pwd[128]; /* previous working directory */ 48 | 49 | } IronShellControlBlock; 50 | 51 | 52 | /* initializes the shell control block */ 53 | void init_shell_control_block(IronShellControlBlock *iscb); 54 | 55 | /* initializes the job list data structure for suspended or runnning tasks */ 56 | void init_job_list(IronShellJobList *jobs); 57 | 58 | /* add job to the job list */ 59 | void add_job(IronShellJobList *jobs, IronShellJob job); 60 | 61 | /* resumes job in foreground */ 62 | void resume_job_fg(IronShellJobList *jobs, int index, IronShellJob *job); 63 | 64 | /* resumes job in background */ 65 | void resume_job_bg(IronShellJobList *jobs, int index); 66 | 67 | /* prints the current running and suspended jobs */ 68 | void print_jobs(IronShellJobList jobs); 69 | 70 | /* destory all the jobs which are currently active */ 71 | void destroy_jobs(IronShellJobList *jobs); 72 | 73 | /* returns true if there no jobs present in background */ 74 | bool no_jobs(IronShellJobList jobs); 75 | 76 | /* delelte the job having the given pid */ 77 | bool delete_job(IronShellJobList *jobs, pid_t pid); 78 | 79 | 80 | #endif /* IRON_SHELL_H */ 81 | 82 | 83 | -------------------------------------------------------------------------------- /job.c: -------------------------------------------------------------------------------- 1 | #include "job.h" 2 | #include "global.h" 3 | 4 | /* initializes the job list data structure */ 5 | void init_job(IronShellJob *job, char *command) { 6 | job->head = job->tail = NULL; 7 | strcpy(job->command, command); 8 | job->process_count = 0; 9 | job->command_status = RUNNING; 10 | return; 11 | } 12 | 13 | /* adds sub job to the exisition job list data structure */ 14 | void add_sub_job(IronShellJob *job, char *command_name, pid_t pid) { 15 | IronShellJobProcess *new_sub_job = 16 | (IronShellJobProcess *)malloc(sizeof(IronShellJobProcess)); 17 | if(new_sub_job == NULL) { 18 | PRINT_ERR("malloc failed"); 19 | } 20 | strcpy(new_sub_job->process_name, command_name); 21 | new_sub_job->pid = pid; 22 | new_sub_job->next = NULL; 23 | 24 | /* first job to the added in the list */ 25 | if(job->head == NULL && job->tail == NULL) { 26 | job->head = job->tail = new_sub_job; 27 | job->process_count++; 28 | return; 29 | } 30 | new_sub_job->next = job->head; 31 | job->head = new_sub_job; 32 | job->process_count++; 33 | return; 34 | } 35 | 36 | /* void traverse the job list */ 37 | void traverse_job(IronShellJob job) { 38 | IronShellJobProcess *ptr = job.head; 39 | while(ptr != NULL) { 40 | printf("(%s, %d)\t", ptr->process_name, ptr->pid); 41 | ptr = ptr->next; 42 | } 43 | printf("\n"); 44 | return; 45 | } 46 | 47 | /* destories the job and allocated resources */ 48 | void destroy_job(IronShellJob* job) { 49 | IronShellJobProcess *ptr = job->head, *temp; 50 | while(ptr != NULL) { 51 | temp = ptr->next; 52 | free(ptr); 53 | ptr = temp; 54 | } 55 | job->head = job->tail = NULL; 56 | job->process_count = 0; 57 | return; 58 | } 59 | 60 | /* kills the job's running process 61 | * Note : this frees the dynamically allocated resources 62 | */ 63 | void kill_job(IronShellJob *job) { 64 | IronShellJobProcess *ptr = job->head; 65 | while(ptr != NULL) { 66 | kill(ptr->pid, SIGINT); 67 | ptr = ptr->next; 68 | } 69 | destroy_job(job); 70 | return; 71 | } 72 | 73 | /* suspends the job's running processes 74 | * Note this doesn't free the dynamically allocated resources 75 | */ 76 | void suspend_job(IronShellJob *job) { 77 | IronShellJobProcess *ptr = job->head; 78 | while(ptr != NULL) { 79 | kill(ptr->pid, SIGSTOP); 80 | ptr = ptr->next; 81 | } 82 | job->command_status = STOPPED; 83 | print_job(*job, iscb.jobs.count_jobs + 1); 84 | return; 85 | } 86 | 87 | /* resumes the execution of the job and since any paritcular 88 | * job contains many process thus execution all process is resumed. 89 | */ 90 | void resume_job(IronShellJob *job, int index) { 91 | IronShellJobProcess *ptr = job->head; 92 | if(ptr == NULL) { 93 | PRINT_EXECPTION("job has no associated processes"); 94 | return; 95 | } 96 | while(ptr != NULL) { 97 | kill(ptr->pid, SIGCONT); 98 | ptr = ptr->next; 99 | } 100 | job->command_status = RUNNING; 101 | print_job(*job, index); 102 | return; 103 | } 104 | 105 | /* logs the message on the prompt regarding the job which is stopped or running 106 | */ 107 | void print_job(IronShellJob job, int index) { 108 | IronShellJobProcess *ptr = job.head; 109 | if(ptr == NULL) { 110 | PRINT_EXECPTION("job has no associated processes"); 111 | return; 112 | } 113 | if(job.command_status == STOPPED) { 114 | PRINT_JOB_PROCESS_HEAD(index, ptr->pid, ptr->process_name, "stopped"); 115 | } else { 116 | PRINT_JOB_PROCESS_HEAD(index, ptr->pid, ptr->process_name, "running"); 117 | } 118 | ptr = ptr->next; 119 | while(ptr != NULL) { 120 | if(job.command_status == STOPPED) { 121 | PRINT_JOB_PROCESS_NODE(ptr->pid, ptr->process_name, "stopped"); 122 | } else { 123 | PRINT_JOB_PROCESS_NODE(ptr->pid, ptr->process_name, "running"); 124 | } 125 | ptr = ptr->next; 126 | } 127 | return; 128 | } 129 | 130 | /* deletes a sub job process with given pid */ 131 | bool delete_sub_job(IronShellJob *jobs, pid_t pid, int index) { 132 | IronShellJobProcess *ptr = jobs->head, *temp = NULL; 133 | while(ptr != NULL) { 134 | if(ptr->pid == pid) { 135 | /* you need to delete this particular node from the list */ 136 | if(temp == NULL) { 137 | jobs->head = ptr->next; 138 | if(jobs->head == NULL) { 139 | jobs->tail = NULL; 140 | } 141 | } else { 142 | temp->next = ptr->next; 143 | } 144 | PRINT_JOB_DONE(index, pid, ptr->process_name); 145 | free(ptr); 146 | jobs->process_count--; 147 | return true; 148 | } 149 | temp = ptr; 150 | ptr = ptr->next; 151 | } 152 | return false; 153 | } 154 | 155 | -------------------------------------------------------------------------------- /job.h: -------------------------------------------------------------------------------- 1 | #ifndef JOB_H 2 | #define JOB_H 1 3 | 4 | #include 5 | #include 6 | #include "command.h" 7 | 8 | #define MAX_PROCESS_NAME_SIZE (128) 9 | 10 | /* status of the last command executed on the interface */ 11 | enum shell_command_status {NO_COMMAND, RUNNING, STOPPED}; 12 | 13 | 14 | /* The module provides implementation details related to the IRON-shell JOB 15 | * JOB is defined as single unit of work done by shell, i.e. it is single 16 | * process of group of process which are created by shell using single user command 17 | */ 18 | 19 | /* IRON-shell JOB process structure */ 20 | typedef struct IronShellJobProcess { 21 | 22 | char process_name[MAX_PROCESS_NAME_SIZE]; /* name of process */ 23 | 24 | pid_t pid; /* pid of process associated with command */ 25 | 26 | struct IronShellJobProcess *next; /* next element in the list */ 27 | 28 | } IronShellJobProcess; 29 | 30 | 31 | /* IRON-shell JOB structure which is implemented using List data structure */ 32 | typedef struct IronShellJob { 33 | 34 | IronShellJobProcess *head, *tail; /* head and tail of queue */ 35 | 36 | char command[MAX_COMMAND_SIZE]; /* the actual command name */ 37 | 38 | int command_status; /* status of the command */ 39 | 40 | int process_count; /* number of process created by single command */ 41 | 42 | } IronShellJob; 43 | 44 | 45 | /* initializes the job list data structure */ 46 | void init_job(IronShellJob *job, char *command); 47 | 48 | /* adds sub job to the exisition job list data structure */ 49 | void add_sub_job(IronShellJob *job, char *command_name, pid_t pid); 50 | 51 | /* void traverse the job list */ 52 | void traverse_job(IronShellJob job); 53 | 54 | /* destories the job and allocated resources */ 55 | void destroy_job(IronShellJob *job); 56 | 57 | /* kills the job's running processes and free the allocated resources */ 58 | void kill_job(IronShellJob *job); 59 | 60 | /* suspends the job's running processes */ 61 | void suspend_job(IronShellJob *job); 62 | 63 | /* resumes the execution of the job */ 64 | void resume_job(IronShellJob *job, int index); 65 | 66 | /* logs the message of the job on the console */ 67 | void print_job(IronShellJob job, int index); 68 | 69 | /* deletes a sub job process with given pid */ 70 | bool delete_sub_job(IronShellJob *jobs, pid_t pid, int index); 71 | 72 | #endif /* JOB_H */ 73 | 74 | -------------------------------------------------------------------------------- /log.h: -------------------------------------------------------------------------------- 1 | #ifndef LOG_H 2 | #define LOG_H 1 3 | 4 | /* prints the prompt on console */ 5 | #define PRINT_PROMPT(curr_dir) \ 6 | printf("\n%s\n ISHELL >> ", curr_dir); \ 7 | 8 | /* print the error message on the terminal 9 | * advisible to use in case of fork and exec 10 | */ 11 | #define PRINT_ERR(msg, ...) \ 12 | fprintf(stderr,"IRON-shell : " msg \ 13 | "\nfile = %s, line number = %d, in function = %s()\n" \ 14 | ##__VA_ARGS__, __FILE__, __LINE__, __func__); \ 15 | exit(errno); \ 16 | 17 | 18 | /* prints the exeption message on the shell */ 19 | #define PRINT_EXECPTION(msg) \ 20 | fprintf(stderr, "IRON-shell : " msg "\n") 21 | 22 | 23 | /* prints the head of the jobs */ 24 | #define PRINT_JOB_PROCESS_HEAD(id, pid, pname, msg_stat) \ 25 | fprintf(stdout, "\n [%d]\t+ %d " msg_stat "\t%s\n", \ 26 | id, pid, pname); \ 27 | 28 | 29 | /* prints any process node from job (not the head) */ 30 | #define PRINT_JOB_PROCESS_NODE(pid, pname, msg_stat) \ 31 | fprintf(stdout, " \t+ %d " msg_stat "\t%s\n", \ 32 | pid, pname); 33 | 34 | 35 | /* prints the job ones it is done and exitied */ 36 | #define PRINT_JOB_DONE(id, pid, pname) \ 37 | fprintf(stdout, "\n [%d] + Done %d\t%s\n", id, pid, pname); \ 38 | 39 | 40 | #endif /* LOG_H */ 41 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include "global.h" 2 | 3 | int main(int argc, char *argv[]) { 4 | 5 | /* holds the commands entered as single string */ 6 | char command_buffer[MAX_COMMAND_SIZE]; 7 | 8 | /* the name of the current working directory */ 9 | char current_working_dir[MAX_DIR_LENGHT]; 10 | 11 | /* initialize the iron shell control block */ 12 | init_shell_control_block(&iscb); 13 | 14 | /* main loop for the iron-shell prompt */ 15 | while(1) { 16 | 17 | /* shell prompt being logged on screen with every command */ 18 | getcwd(current_working_dir, 512); 19 | PRINT_PROMPT(current_working_dir); 20 | 21 | if(read_command(command_buffer, MAX_COMMAND_SIZE) != EOF) { 22 | /* check if its build in shell command */ 23 | if(check_builtin_command(command_buffer)) { 24 | execute_shell_builtin_command(command_buffer); 25 | } 26 | /* if not build in shell command */ 27 | else { 28 | /* command handler handles the execution of the user commmand */ 29 | command_handler(command_buffer); 30 | } 31 | } else { 32 | /* exit as user pressed control-D */ 33 | break; 34 | } 35 | } 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /signals.c: -------------------------------------------------------------------------------- 1 | #include "signals.h" 2 | #include "global.h" 3 | #include "job.h" 4 | 5 | /* default signal hanlder which manages all the signals for the main shell */ 6 | void signal_handler(int signo) { 7 | 8 | /* control-C will kill the currently running foreground job */ 9 | if(signo == SIGINT) { 10 | kill_job(&(iscb.fg_job)); 11 | } 12 | 13 | /* control-Z will suspend the currently running job and will add the 14 | * the parituclar job context in list of suspened and running jobs. 15 | */ 16 | else if(signo == SIGTSTP) { 17 | 18 | /* suspened the current jobs i.e. stop its execution */ 19 | suspend_job(&(iscb.fg_job)); 20 | 21 | /* add the job the list of suspened / running jobs maintained */ 22 | add_job(&(iscb.jobs), iscb.fg_job); 23 | 24 | /* initialize the job again for new foreground job in shell */ 25 | init_job(&(iscb.fg_job), ""); 26 | 27 | } 28 | 29 | /* SIGCHILD will inform the shell about the completion of the child process 30 | * the shell will free the dynamically allocated resource regarding 31 | * particular process which was part of the job 32 | */ 33 | else if(signo == SIGCHLD) { 34 | pid_t pid = waitpid(-1, 0, WNOHANG); 35 | 36 | if(pid == -1 || pid == 0) { 37 | return; 38 | } 39 | /* delete the job with the given pid */ 40 | delete_job(&(iscb.jobs), pid); 41 | } 42 | 43 | return; 44 | } 45 | 46 | /* adds the user defined shell signals */ 47 | void set_signals() { 48 | signal(SIGINT, signal_handler); 49 | signal(SIGTSTP, signal_handler); 50 | signal(SIGCHLD, signal_handler); 51 | } 52 | 53 | 54 | /* reset to default signals */ 55 | void reset_signals() { 56 | signal(SIGINT, SIG_DFL); 57 | signal(SIGSTOP, SIG_DFL); 58 | signal(SIGCHLD, signal_handler); 59 | } 60 | 61 | 62 | -------------------------------------------------------------------------------- /signals.h: -------------------------------------------------------------------------------- 1 | #ifndef SIGNALS_H 2 | #define SIGNALS_H 1 3 | 4 | #include 5 | #include 6 | 7 | /* adds the user defined signals */ 8 | void set_signals(); 9 | 10 | /* reset to default signals */ 11 | void reset_signals(); 12 | 13 | /* default signal hanlder which manages all the signals for the main shell */ 14 | void signal_handler(int signo); 15 | 16 | #endif /* SIGNALS_H */ 17 | --------------------------------------------------------------------------------