Skip to content

Instantly share code, notes, and snippets.

@justgage
Created May 17, 2016 06:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save justgage/7ad93abb94763e3e3a9c19e0f0c329f9 to your computer and use it in GitHub Desktop.
Save justgage/7ad93abb94763e3e3a9c19e0f0c329f9 to your computer and use it in GitHub Desktop.
A simple Linux shell
/***********************************************************************
* 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