Files
school_archives/OSU Coursework/CS 344 - Operating Systems I/Assignment 3 - smallsh/smallsh.c

386 lines
13 KiB
C

/////////////////////////////
////////// SMALLSH //////////
/////// Corwin Perren ///////
/////////////////////////////
//////////////////////////////////////////
////////// Includes and Defines //////////
//////////////////////////////////////////
//Includes
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <stdbool.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
//Defines
#define INPUT_LENGTH_MAX 2048
#define INPUT_ARGS_MAX 513 //Includes one extra for the program itself
#define BACKGROUND_WAIT_COUNT 100000
/////////////////////////////////////////
////////// Function Prototypes //////////
/////////////////////////////////////////
void interrupt_signal_handler(int signal_number);
void terminate_signal_handler(int signal_number);
void check_background_processes(int* status);
bool is_blank_input_string(char* input_string);
int split_input_to_array(char* input_string, char** output_array);
void clean_newline(char* input_string);
void clean_array(char** to_clean);
bool check_if_run_in_background(char** arguments_array, int* args_count);
bool check_if_output_redirect(char** arguments_array, int* args_count, \
char* output_filename);
bool check_if_input_redirect(char** arguments_array, int* args_count, \
char* input_filename);
void clean_extra_args(char** arguments_array, int args_count);
//////////////////////////
////////// Main //////////
//////////////////////////
int main() {
////////// SMALLSH Management Variables //////////
char user_input_string[INPUT_LENGTH_MAX];
int input_arg_count = 0;
char** user_input_array = malloc(INPUT_ARGS_MAX * sizeof(char*));
static int smallsh_status = 0;
//Assign initial pointers to NULL so cleaning functions are happy
//This is for user_input_array
for(int i = 0 ; i < INPUT_ARGS_MAX ; i++){
user_input_array[i] = NULL;
}
////////// SMALLSH Process Spawn Variables //////////
bool spawn_in_background = false;
bool redirect_output = false;
bool redirect_input = false;
char output_filename[INPUT_LENGTH_MAX];
char input_filename[INPUT_LENGTH_MAX];
int spawn_id = 0;
////////// SMALLSH Initialization //////////
//Assign signal handlers
signal(SIGINT, interrupt_signal_handler);
signal(SIGTERM, terminate_signal_handler);
////////// Print welcome screen //////////
printf("----------------------\n");
printf("Welcome to Small Shell\n");
printf("http://caperren.com\n");
printf("----------------------\n");
////////// Main program code //////////
while(1){
//Reset process management variables
spawn_in_background = false;
redirect_output = false;
redirect_input = false;
//Process child processes
check_background_processes(&smallsh_status);
//Print prompt
printf(": ");
fflush(stdout);
//Clear input buffer and read in new command
memset(user_input_string, '\0', INPUT_LENGTH_MAX);
fgets(user_input_string, INPUT_LENGTH_MAX, stdin);
//Check if input is blank
if(is_blank_input_string(user_input_string)){
continue;
}
//Clean off newline
clean_newline(user_input_string);
//Break input string into an array of the arguments
input_arg_count = split_input_to_array(user_input_string, \
user_input_array);
//Check what kind of input we got
if(strcmp(user_input_array[0], "exit") == 0){
//We should clean up malloc'd memory and exit
clean_array(user_input_array);
free(user_input_array);
exit(EXIT_SUCCESS);
}else if(strcmp(user_input_array[0], "status") == 0){
//Print our exit status variable, and continue
printf("exit value %d\n", WEXITSTATUS(smallsh_status));
fflush(stdout);
continue;
}else if(strcmp(user_input_array[0], "cd") == 0){
//Check if we're going to the home directory, or somewhere else
if((user_input_array[1] == NULL) || \
(strcmp(user_input_array[1], "~/") == 0) || \
(strcmp(user_input_array[1], "~") == 0)){
//Change to the user's home directory as requested
chdir(getenv("HOME"));
}else{
//Change to the user's requested directory, error is not
//accessible, or doesn't exist
int chdir_result = chdir(user_input_array[1]);
if(chdir_result == -1){
printf("Cannot change to directory \"%s\"\n", \
user_input_array[1]);
fflush(stdout);
}
}
continue;
}else if(user_input_array[0][0] == '#'){
//We got a comment line, so do nothing and continue
continue;
}
//If we've gotten here, that means the command given is one we're
//going to be spawning using an exec call.
//First check is to see whether it should be foreground or not.
spawn_in_background = check_if_run_in_background(user_input_array, \
&input_arg_count);
//Now check whether or not we need to redirect output
redirect_output = check_if_output_redirect(user_input_array, \
&input_arg_count, \
output_filename);
//Then the same, but for redirection of input from file
redirect_input = check_if_input_redirect(user_input_array, \
&input_arg_count, \
input_filename);
//Clean out these input/output args we don't want passed to our process
clean_extra_args(user_input_array, input_arg_count);
//Time to fork for our new process
spawn_id = fork();
if(spawn_id == 0){
//We're the child process, get ready to execute.
if(spawn_in_background){
//If we're supposed to be in the background, set stdin and
//stdout file descriptors for the process to /dev/null
int null_rw = open("/dev/null", O_RDWR);
int null_read = open("/dev/null", O_RDONLY);
dup2(null_read, 0);
dup2(null_rw, 1);
}
if(redirect_input){
//Even if background, if we redirect input, attempt to open
//file and pass it in
int input_fd = open(input_filename, O_RDONLY, 0644);
if(input_fd < 0){
printf("File %s cannot be accessed.\n", input_filename);
fflush(stdout);
continue;
}
dup2(input_fd, 0);
}
if(redirect_output){
//Even if background, attempt to make output file and redirect
int output_fd = open(output_filename, \
O_WRONLY | O_CREAT | O_TRUNC, 0644);
if(output_fd < 0){
printf("File %s cannot be accessed.\n", input_filename);
fflush(stdout);
continue;
}
dup2(output_fd, 1);
}
//Execute the command, including searching the path variable
execvp(user_input_array[0], user_input_array);
printf("Failed to run program: %s\n", user_input_array[0]);
fflush(stdout);
exit(EXIT_FAILURE);
}else{
//We're the parent
if(spawn_in_background){
//Print the process id, and return to prompt
printf("background pid is %d\n", spawn_id);
fflush(stdout);
}else{
//Wait for the process to die, as this is foreground
spawn_id = waitpid(spawn_id, &smallsh_status, 0);
}
}
}
exit(EXIT_SUCCESS);
}
///////////////////////////////////////////
////////// Function Declarations //////////
///////////////////////////////////////////
void interrupt_signal_handler(int signal_number){
printf("terminated by signal %d\n", signal_number);
signal(SIGINT, interrupt_signal_handler);
}
void terminate_signal_handler(int signal_number){
printf("terminated by signal %d\n", signal_number);
exit(EXIT_FAILURE);
signal(SIGTERM, terminate_signal_handler);
}
void check_background_processes(int* status){
int temp_pid = 0;
int temp_count = 0;
//Loop through a bunch of times and print out child exit statuses
while(temp_count < BACKGROUND_WAIT_COUNT){
temp_pid = waitpid(-1, status, WNOHANG);
if(temp_pid > 0){
if(WIFEXITED(*status)){
printf("process %d exited with exit value %d", temp_pid, \
WEXITSTATUS(*status));
}
if(WIFSIGNALED(*status)){
printf("process %d terminated by signal %d", temp_pid, \
WTERMSIG(*status));
}
printf("\n");
fflush(stdout);
}
temp_count++;
}
}
bool is_blank_input_string(char* input_string){
//This does a simple check as to whether we have no input on a line
int length = strlen(input_string);
char current_char = '\0';
for(int i = 0 ; i < length ; i++){
current_char = input_string[i];
if((current_char != ' ') && (current_char != '\0') && \
(current_char != '\n')){
return false;
}
}
return true;
}
int split_input_to_array(char* input_string, char** output_array){
int args_count = 0;
char* token_result;
//Clean the array first so we don't memory leak
clean_array(output_array);
for(args_count = 0 ; args_count < INPUT_ARGS_MAX ; args_count++){
//Get the current tokenizer result
if(args_count == 0){
token_result = strtok(input_string, " ");
}else{
token_result = strtok(NULL, " ");
}
//Check if we're done parsing input string
if(token_result == NULL){
break;
}
//Allocate space for next string
output_array[args_count] = malloc((strlen(token_result) + 1) * \
sizeof(char));
//Copy the current string into this allocated space
strcpy(output_array[args_count], token_result);
}
return args_count;
}
void clean_newline(char* input_string){
input_string[strlen(input_string) - 1] = '\0';
}
void clean_array(char** to_clean){
for(int i = 0 ; i < INPUT_ARGS_MAX ; i++){
if(to_clean[i] != NULL){
free(to_clean[i]);
to_clean[i] = NULL;
}else{
break;
}
}
}
bool check_if_run_in_background(char** arguments_array, int* args_count){
if(strcmp(arguments_array[*args_count-1], "&") == 0){
*args_count -= 1;
return true;
}else{
return false;
}
}
bool check_if_output_redirect(char** arguments_array, int* args_count, \
char* output_filename){
memset(output_filename, '\0', INPUT_LENGTH_MAX);
//Check for output redirect. If found, copy the filename and lower
//argument count so we don't process it later
for(int i = 0 ; i < *args_count ; i++){
if(strcmp(arguments_array[i], ">") == 0){
strcpy(output_filename, arguments_array[i + 1]);
*args_count -= 2;
return true;
}
}
return false;
}
bool check_if_input_redirect(char** arguments_array, int* args_count, \
char* input_filename){
memset(input_filename, '\0', INPUT_LENGTH_MAX);
//Check for input redirect. If found, copy the filename and lower
//argument count so we don't process it later
for(int i = 0 ; i < *args_count ; i++){
if(strcmp(arguments_array[i], "<") == 0){
strcpy(input_filename, arguments_array[i + 1]);
*args_count -= 2;
return true;
}
}
return false;
}
void clean_extra_args(char** arguments_array, int args_count){
//This clean up the rest on the input line, after our redirects if they
//happened
for(int i = args_count ; i < INPUT_ARGS_MAX ; i++){
if(arguments_array[i] != NULL){
free(arguments_array[i]);
arguments_array[i] = NULL;
}else{
break;
}
}
}