Created
May 17, 2016 06:36
-
-
Save justgage/7ad93abb94763e3e3a9c19e0f0c329f9 to your computer and use it in GitHub Desktop.
A simple Linux shell
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
/*********************************************************************** | |
* Program: | |
* Lab UnixShell | |
* Brother Jones, CS 345 | |
* Author: | |
* Gage Peterson | |
* Summary: | |
* This is a tiny unix shell made for my operating systems class | |
************************************************************************/ | |
#include <signal.h> | |
#include <stdio.h> | |
#include <iostream> | |
#include <stdlib.h> | |
#include <string> | |
#include <string.h> | |
#include <sys/types.h> | |
#include <sys/wait.h> | |
#include <unistd.h> | |
#include <vector> | |
#define MAX_LINE 80 /* 80 chars per line, per command, should be enough. */ | |
#define BUFFER_SIZE 255 | |
#define HIST_SIZE 10 // how large the history for the shell should be | |
using namespace std; | |
vector<string> history; | |
bool grab_hist = false; | |
bool is_char_arg(char shouldBe, char* args[]) | |
{ | |
char* arg = args[0]; | |
if (arg != NULL && arg[0] == shouldBe) | |
return true; | |
else | |
return false; | |
} | |
/*********************************************************************** | |
* Prints the history of the commands | |
***********************************************************************/ | |
void print_hist() | |
{ | |
const char* message = "\nhistory\n---------------\n"; | |
write(STDOUT_FILENO, message, strlen(message)); | |
int hsize = history.size(); | |
for (int i = min(HIST_SIZE, hsize); i > 0; i--) | |
{ | |
// writes and reads needed because it's in a signal handler :P | |
const char* next_cmd = history[hsize - i].c_str(); | |
write(STDOUT_FILENO, " - ", 4); | |
write(STDOUT_FILENO, next_cmd, strlen(next_cmd)); | |
write(STDOUT_FILENO, "\n", 2); | |
} | |
} | |
/*********************************************************************** | |
* This will prompt for which command you want to run in the history | |
***********************************************************************/ | |
void prompt_hist() | |
{ | |
const char* message = "enter num to execute (starting at 1 from the bottom):"; | |
char* input; | |
// writes and reads needed because it's in a signal handler :P | |
write(STDOUT_FILENO, message, strlen(message)); | |
grab_hist = true; | |
} | |
void handle_SIGQUIT( int junk ) | |
{ | |
if (history.size() > 0) { | |
print_hist(); | |
prompt_hist(); | |
return; | |
} else { | |
const char* message = "Sorry, no history...\n > "; | |
write(STDOUT_FILENO, message, strlen(message)); | |
} | |
} | |
/** | |
* setup() reads in the next command line, separating it into distinct tokens | |
* using whitespace as delimiters. | |
* | |
* setup() modifies the inputBuffer creating a set of null-terminated strings | |
* and places pointers into the args[] array that point to the beginning of | |
* each argument. A NULL pointer is placed in the args[] array indicating | |
* the end of the argument list. This is what is needed for using execvp(). | |
* | |
* A ^d input at the beginning of a line, by a user, exits the shell. | |
*/ | |
void setup(char inputBuffer[], char *args[], int *background) | |
{ | |
int length; /* # of characters in the command line */ | |
int i; /* loop index for accessing inputBuffer array */ | |
int start; /* index where beginning of next command parameter is */ | |
int ct; /* index of where to place the next parameter into args[] */ | |
ct = 0; | |
/* read what the user enters on the command line */ | |
length = read(STDIN_FILENO, inputBuffer, MAX_LINE); | |
start = -1; /* Used as a flag to indicate that we are looking | |
* for the start of the command or an argument if | |
* it is a -1, or stores the starting position of | |
* the command or argument to be put in args[]. | |
*/ | |
if (length == 0) | |
exit(0); /* ^d was entered, end of user command stream */ | |
if (length < 0) | |
{ | |
perror("error reading the command"); | |
exit(-1); /* terminate with error code of -1 */ | |
} | |
/* examine every character in the inputBuffer */ | |
for (i=0;i<length;i++) | |
{ | |
switch (inputBuffer[i]) | |
{ | |
case ' ': | |
case '\t' : /* argument separators */ | |
if(start != -1) /* found the end of the command or arg */ | |
{ | |
args[ct] = &inputBuffer[start]; /* set up pointer in args[] */ | |
ct++; | |
} | |
inputBuffer[i] = '\0'; /* add a null char; make a C string */ | |
start = -1; | |
break; | |
case '\n': /* should be the final char examined */ | |
if (start != -1) /* if in the command or an argument */ | |
{ | |
args[ct] = &inputBuffer[start]; /* set up pointer in args[] */ | |
ct++; | |
} | |
inputBuffer[i] = '\0'; | |
args[ct] = NULL; /* no more arguments to this command */ | |
break; | |
default : /* some other character */ | |
if (start == -1 && inputBuffer[i] != '&') | |
start = i; /* starting position of the command or arg */ | |
if (inputBuffer[i] == '&') | |
{ | |
*background = 1; | |
inputBuffer[i] = '\0'; | |
} else { | |
*background = 0; | |
} | |
} /* end of switch */ | |
} /* end of for loop looking at every character */ | |
args[ct] = NULL; /* just in case the input line was > 80 */ | |
} | |
void redo(char* args[], int temp) | |
{ | |
// Make sure the user's number is in the range of the history | |
if (temp > 0 && temp <= history.size()) { | |
const char* histCommand = history[history.size() - temp].c_str(); | |
cout << histCommand << endl; | |
strcpy(args[0], histCommand); | |
} else { | |
printf("Sorry the number has to between the range of 1-%d, you put: `%d`\n", | |
min(HIST_SIZE, (int) history.size()), | |
temp); | |
} | |
} | |
int main(void) | |
{ | |
char inputBuffer[MAX_LINE]; /* buffer to hold the command entered */ | |
int background; /* equals 1 if a command is followed by '&' */ | |
char *args[MAX_LINE/2]; /* command line (of 80) has max of 40 arguments */ | |
struct sigaction handler; | |
handler.sa_handler = handle_SIGQUIT; /* function the handler will call */ | |
handler.sa_flags = SA_RESTART; | |
sigaction(SIGQUIT, &handler, NULL); /* handler to call for quit signal */ | |
while (1) /* Program terminates normally inside setup */ | |
{ | |
background = 0; | |
printf("λ > "); | |
fflush(stdout); | |
setup(inputBuffer, args, &background); /* get next command */ | |
// grab the history after you push CTRL+backslash | |
if (grab_hist == true) { | |
int temp = atoi(args[0]); | |
redo(args, temp); | |
grab_hist = false; | |
} | |
// (1) fork a child process | |
if (is_char_arg('r', args)) { | |
printf("you entered the redo command\n"); | |
} | |
int pid = fork(); | |
if (pid == -1) { // ERROR forking the process | |
printf("Error forking the process. Maybe we should have used a spoon...\n"); | |
break; // quit | |
} | |
if (pid == 0) { // CHILD process | |
int error = execvp(args[0], args) == -1; | |
if (error) { | |
printf("Sorry, I couldn't find: `%s` Maybe a typo?\n", args[0]); | |
} | |
break; // quit | |
} else { // PARENT process | |
// Wait or continue based on background | |
if (background != 1) { | |
waitpid(pid, NULL, 0); | |
} | |
if (args[0] != NULL) { | |
string tmp = args[0]; | |
history.push_back(tmp); | |
} | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment