Last active
January 20, 2020 17:03
-
-
Save YahiaBakour/7fde9c2b903006326fea102dcb99a52a to your computer and use it in GitHub Desktop.
Fully functional & easy to understand shell implementation in C with Piping | / Redirection < , > / Background & / Arguments -
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Author: Yahia Bakour | |
License: MIT License | |
*/ | |
#include <unistd.h> | |
#include<stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <sys/types.h> | |
#include <sys/wait.h> | |
#include <sys/stat.h> | |
#include <fcntl.h> | |
#include <signal.h> | |
#define MAXSIZE 512 | |
//////////////////////////// SHELL Utility Functions //////////////////////////// | |
/* | |
-- Utility Function | |
- Use: To CleanUp any zombie processes without causing shell to hang | |
*/ | |
void CleanUp(){ | |
pid_t pid; | |
pid = waitpid(-1,NULL,WNOHANG); | |
while(pid != 0 && pid != -1){ | |
pid = waitpid(-1,NULL,WNOHANG); | |
} | |
} | |
/* | |
Utility Function | |
Use: Used to count number of times X appears in a string | |
*/ | |
int CountX(const char* STR, char X) | |
{ | |
int i,count,lngth; | |
lngth = strlen(STR); | |
count = 0; | |
for (i = 0; i < lngth ; i++){if (STR[i] == X) count++; } | |
return count; | |
} | |
/* | |
Utility Function | |
Use: Used to find out which special character the input has, Used to split up the string as well as evaluate it, can easily add more characters | |
*/ | |
char* FindTheSpecialChar(char* line) | |
{ | |
int i,lngth; | |
lngth = strlen(line); | |
for (i = 0; i < lngth ; i++){ | |
if (line[i] == '>') return ">\0"; | |
if (line[i] == '<') return "<\0"; | |
if (line[i] == '|') return "|\0"; | |
} | |
return NULL; | |
} | |
/* | |
Utility Function | |
Use: Used to find the index of a special char | |
*/ | |
int FindIndexOfSpecialChar(const char* line, char* special) | |
{ | |
int i = 0; | |
if(strlen(line) > 1){ | |
for(i = 0; i < strlen(line); i++){ | |
if(line[i] == special[0]){ | |
return(i); | |
} | |
} | |
} | |
return -1; | |
} | |
//////////////////////////// SHELL BUILTINS //////////////////////////// | |
/* | |
Shell BuiltIn | |
Use: Used to change directory to another directory | |
Note: Not required but added it for fun | |
*/ | |
int launch_cd(char **args) // Built in CD | |
{ | |
if (args[0] == NULL) { | |
perror("ERROR: CD wasnt used but it still didn't work"); | |
} | |
else if (args[1] == NULL) { | |
perror("ERROR: No directory supplied to CD"); | |
} | |
else { | |
if ( chdir( args[1] ) < 0) { | |
perror("ERROR: "); | |
} | |
} | |
return 1; | |
} | |
//////////////////////////// SHELL IMPLEMENTATION //////////////////////////// | |
/* | |
Read Line from shell | |
Use: First part of shell architecture, Read input from user | |
*/ | |
char *read_line_from_shell(){ | |
char* line = (char *)malloc(256); | |
size_t bfsz = 255; | |
if(getline(&line, &bfsz, stdin) == -1){exit(0);} | |
line[strlen(line)-1] = 0; // get rid of newline character at end | |
return line; | |
} | |
/* | |
Split Line from shell | |
Use: Only use is to split the line from the shell by whitespace and special characters | |
Note: This is useful because cat>x becomes ['cat','>','x'], saves the trouble of figuring out technique to split it later | |
*/ | |
char **split_line_from_shell(char* line, int* count){ | |
char* token; | |
char **args = malloc(MAXSIZE * sizeof(char*)); | |
char* special; | |
int i = 0; | |
int idx; | |
*count = 0; | |
token = strtok(line," "); | |
while(token != NULL){ | |
special = FindTheSpecialChar(token); | |
if(special != NULL){ | |
idx = FindIndexOfSpecialChar(token,special); | |
if(idx == -1){ | |
args[i++] = token; | |
*count= *count + 1; | |
token = strtok(NULL," "); | |
} | |
else if(idx == 0){ | |
args[i++] = special; | |
token++; | |
*count= *count + 1; | |
} | |
else if(idx==strlen(token)-1){ | |
token[strlen(token)-1] = '\0'; | |
args[i++] = token; | |
args[i++] = special; | |
*count= *count + 2; | |
token = strtok(NULL," "); | |
} | |
else { | |
char* SliceFirstHalf = malloc(MAXSIZE * sizeof(char*)); | |
char* SliceSecondHalf = malloc(MAXSIZE * sizeof(char*)); | |
strcpy(SliceFirstHalf,token); | |
strcpy(SliceSecondHalf,token); | |
// Get First Half | |
SliceFirstHalf[idx] = '\0'; | |
args[i++] = SliceFirstHalf; | |
// Add in special | |
args[i++] = special; | |
// Get Second Half | |
SliceSecondHalf = strstr(SliceSecondHalf,special); | |
SliceSecondHalf++; | |
token = SliceSecondHalf; | |
*count= *count + 2; | |
} | |
} else{ | |
args[i++] = token; | |
*count= *count + 1; | |
token = strtok(NULL," "); | |
} | |
} | |
return args; | |
} | |
/* | |
Evaluate Args after Splitting | |
Use: This is where we figure out things like piping, input/output redirection, and setting up things for execution | |
Note: fileredirect[0,1] --> 0 = input, 1 = output. pipeargs = 3d array where each pipe is an array of arguements | |
*/ | |
char **eval_args_from_shell(char** args, int* count, int* special_args, char** fileredirect, char*** pipeargs){ | |
char ** new_args= malloc(MAXSIZE * sizeof(char*)); | |
int i; | |
int idx = 0; | |
int idx2 = 0; | |
char *val; | |
char *nextval; | |
if(args[0] == NULL) return NULL; | |
for(i = 0; i < *count; i++){ | |
val = args[i]; | |
if(i == *count-1 && !strcmp(val,"&\0")){ // Check for & in end | |
special_args[0] = 1; | |
} | |
else if(!strcmp(val,"&\0")) {printf("ERROR: Ampersand can only appear at the end of input"); return NULL;} // Check for & otherwise | |
else if(CountX(val,'>') > 0 ){ // Check for > | |
if(CountX(val,'>') > 1 || special_args[1] > 1){ | |
printf("ERROR: With >, too many redirects \n"); | |
return NULL; | |
} else { | |
if(args[i+1] == NULL){printf("ERROR: No output file detected \n");return NULL;} | |
nextval = args[i+1]; | |
if(CountX(nextval,'>') > 0){printf("ERROR: With >, too many redirects \n");return NULL;} | |
special_args[1] = special_args[1] + 1; | |
fileredirect[1] = args[++i]; | |
} | |
} else if(CountX(val,'<') > 0 ){ // Check for < | |
if(CountX(val,'<') > 1 || special_args[2] > 1){ | |
printf("ERROR With <, too many redirects \n"); | |
return NULL; | |
} else { | |
nextval = args[i+1]; | |
if(args[i+1] == NULL){printf("ERROR: No input file detected \n");return NULL;} | |
if(CountX(nextval,'<') > 0){printf("ERROR With <, too many redirects \n");return NULL;} | |
special_args[2] = special_args[2] + 1; | |
fileredirect[0] = args[++i]; | |
} | |
} else if(CountX(val,'|') > 0 ){ // Check for | | |
idx2 = 0; | |
while(i+1 < *count && args[i+1][0] != '|' && args[i+1] != NULL){ | |
if(!strcmp(args[i+1],"<\0")){ | |
printf("ERROR: Input redirection must be the first command \n"); | |
return NULL; | |
} | |
if(!strcmp(args[i+1],">\0")){ | |
if(args[i+2] == NULL || FindTheSpecialChar(args[i+2]) != NULL || args[i+3] != NULL){ | |
printf("ERROR: output redirection must be the last command \n"); | |
return NULL; | |
}else{ | |
special_args[1] = special_args[1] + 1; | |
fileredirect[1] = args[i+2]; | |
i = i + 2; | |
} | |
} else { | |
pipeargs[special_args[3]][idx2++] = args[++i]; | |
} | |
} | |
special_args[3] = special_args[3] + 1; | |
} | |
else{ | |
new_args[idx++]=val; | |
} | |
} | |
return new_args; | |
} | |
/* | |
Launch a shell process | |
Use: Launching a shell process with specified args, file redirection, and flags for piping | |
Note: Piping is done in conjunction with execute shell function | |
*/ | |
static int launch_shell_process(char **args, int* special_args, char** fileredirect, int inputfd, int islast, int ispipe){ | |
pid_t pid; | |
int status; | |
int inputfile,outputfile; | |
inputfile = dup(inputfd); | |
outputfile = dup(1); | |
int piping[2]; | |
pipe(piping); | |
pid = fork(); | |
if (pid == 0) { | |
// Setup for piping | |
if(inputfd == 0 && islast ==0 ){ | |
dup2(piping[1],outputfile); | |
} else if (inputfd != 0 && islast == 0){ | |
dup2(inputfd,inputfile); | |
dup2(piping[1],outputfile); | |
} else { | |
dup2(inputfd,inputfile); | |
} | |
// end setup for piping | |
if(special_args[1] > 0 && islast){ // > -- Redirect output | |
if(special_args[1] > 1){ | |
printf("ERROR: TOO MANY REDIRECTS"); | |
return -1; | |
} | |
else { | |
outputfile = creat(fileredirect[1], 0600); | |
} | |
} | |
if(special_args[2] > 0 && inputfd == 0){ // > -- Redirect input | |
if(special_args[2] > 1){ | |
printf("ERROR: TOO MANY REDIRECTS"); | |
return -1; | |
} | |
else { | |
inputfile = open(fileredirect[0], O_RDONLY); | |
} | |
} | |
dup2(inputfile, STDIN_FILENO); | |
dup2(outputfile, STDOUT_FILENO); | |
if (execvp(args[0], args) == -1) {perror("ERROR: "); } | |
exit(-1); | |
} else if(pid > 0){ | |
if(!ispipe && special_args[0] == 0) { | |
do { | |
waitpid(pid, &status, WUNTRACED); | |
} | |
while (!WIFEXITED(status) && !WIFSIGNALED(status)); | |
} | |
if(inputfd != 0) close(inputfd); | |
close(piping[1]); | |
if(islast) close(piping[0]); | |
} | |
return piping[0]; | |
} | |
/* | |
Execute Shell | |
Use: Execute Shell, Decide between builtins, Piping, or regular execution | |
Note: Piping in conjunction with flags and launch shell process function | |
*/ | |
int execute_shell(char **args, int *special_args, char** fileredirect,char*** pipeargs){ | |
int i; | |
if (strcmp(args[0],"cd") == 0) { // For CD because it won't work with FORK | |
launch_cd(args); | |
return 1; | |
} | |
else if(special_args[3] > 0){ | |
int numpipes = special_args[3]; | |
int pipeindex = 0; | |
int readpipe = 0; | |
int isfirst = 1; | |
while(numpipes > 0){ | |
if(isfirst){ | |
readpipe = launch_shell_process(args,special_args,fileredirect,readpipe,0,1); | |
} | |
else{ | |
readpipe = launch_shell_process(pipeargs[pipeindex++],special_args,fileredirect,readpipe,0,1); | |
} | |
numpipes--; | |
isfirst = 0; | |
} | |
launch_shell_process(pipeargs[pipeindex++],special_args,fileredirect,readpipe,1,1); | |
for (i = 0; i < special_args[3]+1; ++i) | |
wait(NULL); | |
} | |
else | |
{ | |
return launch_shell_process(args,special_args,fileredirect,0,1,0); | |
} | |
return 0; | |
} | |
int main(int argc, char **argv){ | |
// Initilization | |
int i,j,count,useprompt; | |
char* line; | |
char** args; | |
char** fileredirect = malloc(2 * sizeof(char*)); | |
char*** pipeargs = (char ***)malloc( MAXSIZE * sizeof(char**) ); | |
for (i = 0; i< MAXSIZE; i++) { | |
pipeargs[i] = (char **) malloc(MAXSIZE*sizeof(char *)); | |
} | |
count = 0; // Number of arguements passed to shell | |
int special_args[4]= {0,0,0,0}; | |
if(argc > 1){ | |
if(!strcmp(argv[1],"-n")){ | |
useprompt = 0; | |
}else{ | |
useprompt = 1; // Prompt | |
} | |
} else{ | |
useprompt = 1; // Prompt | |
} | |
// Loop | |
while(1){ | |
for(i = 0; i < 4; i++){ | |
special_args[i] = 0; | |
} | |
for (i = 0; i< MAXSIZE; i++) { | |
pipeargs[i] = (char **) malloc(MAXSIZE*sizeof(char *)); | |
} | |
count = 0; | |
if(useprompt) printf("my_shell$ "); // Prompt | |
line = read_line_from_shell(line); // Read | |
args = split_line_from_shell(line,&count); // Parse | |
args = eval_args_from_shell(args,&count,special_args, fileredirect,pipeargs); // Evaluate | |
if(args != NULL) execute_shell(args,special_args,fileredirect,pipeargs); // Proceed | |
CleanUp(); // Cleanup | |
} // Loop | |
// Free stuff | |
free(line); | |
for(i = 0;i<MAXSIZE;i++){ | |
free(args[i]); | |
} | |
free(args); | |
for(i = 0;i<MAXSIZE;i++){ | |
for(j = 0; j<MAXSIZE;j++){ | |
free(pipeargs[i][j]); | |
} | |
} | |
for(i = 0;i<MAXSIZE;i++){ | |
free(pipeargs[i]); | |
} | |
free(pipeargs); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment