Skip to content

Instantly share code, notes, and snippets.

@YahiaBakour
Last active January 20, 2020 17:03
Show Gist options
  • Save YahiaBakour/7fde9c2b903006326fea102dcb99a52a to your computer and use it in GitHub Desktop.
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 -
/*
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