/****************************************
* Title : ECS 150 HW 1
* Author: NAUSHEEN SUJELA
****************************************/
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MAX_CMD_SZ 512
#define MAX_NUM_ARGS 16
#define MAX_NUM_CMDS_IN_PIPE 10
/**
* Command struct
* Used as a reference to create command struct:
* https://stackoverflow.com/questions/10162152/how-to-work-with-string-fields-in-a-c-struct
*/
typedef struct {
/* Basics */
char *program;
char *args[MAX_NUM_ARGS+1];
char *toPrint[MAX_NUM_ARGS+1];
int numArguments;
int printCount;
/* Input + output redirection */
bool hasInputRedirection;
bool hasOutputRedirection;
char *fin;
char *fout;
/* Background commands */
bool hasBackgroundCommand;
char *backgroundCommand;
} command;
/* Pipe struct */
typedef struct {
int pipeCount;
char *all_commands[MAX_NUM_CMDS_IN_PIPE + 1];
} completePipe;
/* Creates pipe structure to hold all pipe details */
completePipe *createPipe(int numCmds, char *all_cmds[MAX_NUM_CMDS_IN_PIPE + 1]) {
completePipe *p = malloc(MAX_CMD_SZ * MAX_NUM_CMDS_IN_PIPE);
p->pipeCount = numCmds;
/* Fill up the commands array of the pipe. */
for(int i = 0; i < numCmds; i++) {
p->all_commands[i] = all_cmds[i];
}
return p;
}
/* Creates command structure to hold all command details */
command *createCommand(char *program, char* args[MAX_NUM_ARGS+1], char *toPrint[MAX_NUM_ARGS+1], int numArgs, int printCount,
bool hasInputRedirection, bool hasOutputRedirection, char *fin, char *fout) {
command *c = malloc(MAX_CMD_SZ);
c->program = program;
int i, j;
/* Full command to be printed at the end */
for(j = 0; j < printCount; j++) {
c->toPrint[j] = toPrint[j];
}
/* Fill up the arguments array to be passed into exec */
for(i = 0; i < numArgs; i++) {
c->args[i] = args[i];
}
/* Null-terminate both arrays */
c->args[i] = 0;
c->toPrint[j] = 0;
/* Pass in other necessary information */
c->numArguments = numArgs;
c->printCount = printCount;
c->hasInputRedirection = hasInputRedirection;
c->hasOutputRedirection = hasOutputRedirection;
c->fin = fin; // This will be NULL if there is no input/output file
c->fout = fout;
// if(c->hasOutputRedirection) {
// printf("Output file has been stored. It's called %s\n", fout);
// }
return c;
}
/* Removes trailing newline */
void removeTrailingNewline(char *str) {
if ( (strlen(str) > 0) && (str[strlen(str) - 1] == '\n')) {
str[strlen (str) - 1] = '\0';
}
}
/* Finds a particular character in a char array */
bool findElement(char *symbol, char *str) {
char *c = str;
while (*c) {
if (strchr (symbol, *c))
return true;
c++;
}
return false;
}
/**
* Credit for removeChar function:
* https://stackoverflow.com/questions/5457608/how-to-remove-the-character-at-a-given-index-from-a-string-in-c
*/
void removeChar(char *str, char toRemove) {
char *src, *dst;
for (src = dst = str; *src != '\0'; src++) {
*dst = *src;
if (*dst != toRemove) dst++;
}
*dst = '\0';
}
/* Remove a substring from a string */
void removeSubstring(char *s,const char *toremove)
{
while( (s = strstr(s,toremove)) )
memmove(s,s+strlen(toremove),1+strlen(s+strlen(toremove)));
}
/**
* Credit for strtok_single function:
* https://stackoverflow.com/questions/8705844/need-to-know-when-no-data-appears-between-two-token-separators-using-strtok
* Used to tokenize over single instance of a delimiter.
*/
char *strtok_single (char * str, char const * delims)
{
static char * src = NULL;
char * p, * ret = 0;
if (str != NULL)
src = str;
if (src == NULL)
return NULL;
if ((p = strpbrk (src, delims)) != NULL) {
*p = 0;
ret = src;
src = ++p;
} else if (*src) {
ret = src;
src = NULL;
}
return ret;
}
/* Redirects input to specified file descriptor. */
void redirectInput(char *file, char *args[MAX_NUM_ARGS]) {
int fd;
if (open(file, O_RDWR)) {
fd = open(file, O_RDWR);
dup2(fd, STDIN_FILENO);
close(fd);
}
}
/* Redirects output to specified file descriptor. */
void redirectOutput(char *file, char *args[MAX_NUM_ARGS]) {
// printf("Redirecting output...\n");
int fd;
if (open(file, O_RDWR|O_CREAT, 0666)) {
// printf("FILE EXISTS. REDIRECTING OUTPUT TO FILE NOW...\n");
fd = open(file, O_RDWR|O_CREAT, 0666);
// printf("fd: %d\n", fd);
dup2(fd, STDOUT_FILENO);
close(fd);
}
}
/* ------------- PIPE + COMMAND PARSERS ------------- */
/* Parse user input over | and create a pipe. */
completePipe *pipeHandler(char *complete_user_input) {
/* Copy user input to a buffer to parse for pipes. */
char *copy_pipe = (char *)malloc(strlen(complete_user_input) + 1);
strcpy(copy_pipe, complete_user_input);
char *store_commands[MAX_NUM_CMDS_IN_PIPE + 1];
/* Parsing for pipes. */
char *pipe_tok = strtok(copy_pipe, "|");
int i = 0;
while (pipe_tok != NULL) {
// puts(pipe_tok);
store_commands[i] = malloc(strlen(pipe_tok) + 1);
strcpy(store_commands[i], pipe_tok);
pipe_tok = strtok(NULL, "|");
i++;
}
completePipe *pipe = createPipe(i, store_commands);
return pipe;
}
/**
* Parse and process a command. Parse over >, <, and whitespace.
* Build instance of command struct based on parsed information.
*/
command *cmdHandler(char *cmd, char *all_args[MAX_NUM_ARGS + 1]) {
/* Full command to be printed at the end */
char *print_arr[MAX_NUM_ARGS+1];
/* Preliminary variables */
char *fin = NULL;
char *fout = NULL;
bool inputRedirectionActivated = false;
bool outputRedirectionActivated = false;
/* Copy cmd to a buffer to parse for input */
char *copy_input = (char *)malloc(strlen(cmd) + 1);
strcpy(copy_input, cmd);
/* Copy cmd to a buffer to parse for output */
char *copy_output = (char *)malloc(strlen(cmd) + 1);
strcpy(copy_output, cmd);
/* Parsing for input redirection. */
char *input_tok = strtok(copy_input, "<");
if (input_tok != NULL) {
// printf("Token: %s\n", tok);
input_tok = strtok(NULL, "<");
if (input_tok != NULL) {
inputRedirectionActivated = true;
fin = input_tok;
removeSubstring(fin, " ");
// printf("Token: %s\n", fin);
}
}
/* Parsing for output redirection */
char *output_tok = strtok(copy_output, ">");
if (output_tok != NULL) {
// printf("Token: %s\n", output_tok);
output_tok = strtok(NULL, ">");
if (output_tok != NULL) {
outputRedirectionActivated = true;
fout = output_tok;
removeSubstring(fout, " ");
// printf("Token: %s\n", fout);
}
}
/* Begin parsing over whitespace */
char delims[] = " ";
// printf("TEST TO MAKE SURE CMD REMAINS UNALTERED: ");
// printf("%s\n", cmd);
char *token = strtok(cmd, delims);
int i = 0;
int j = 0;
while (token != NULL && i < MAX_NUM_ARGS) {
// printf("TOKEN: %s\n", token)
/* Add "<" and ">" symbols to print arr but not args arr */
if (strcmp(token, "<") == 0 || strcmp(token, ">") == 0) {
print_arr[j] = malloc(strlen(token) + 1);
strcpy(print_arr[j], token);
j++;
// printf("SEPARATED < \n");
token = strtok(NULL, " ");
}
// bool hasBg = false;
// char *bg;
// else if (findElement("&", token)) {
// hasBg = true;
// bg = malloc(strlen(token)); // not + 1 since we'll be removing &
// token.removeSubstring("&");
// strcpy(bg, token);
// token = strtok(NULL, " ");
// }
// WE'RE TRYING TO GET THE ARGUMENT BEFORE <, <, AND THE ARGUMENT AFTER
else if (findElement("<", token)) {
// printf("NOT SEPARATED < \n");
char* ptr = malloc(strlen(token) + 1);
/* Gets position of < in string */
int pos;
ptr = strstr(token, "<");
pos = token - ptr;
// If <file.txt
if (strstr(token, fin) && pos == 0) {
// printf("CASE <file.txt\n");
all_args[i] = malloc(strlen(fin) + 1);
strcpy(all_args[i], fin);
i++;
print_arr[j] = malloc(strlen(token) + 1);
strcpy(print_arr[j], token);
j++;
token = strtok(NULL, " ");
}
// Else if word<file.txt
else if (strstr(token, fin) && pos != 0) {
// printf("CASE word<file.txt\n");
all_args[i] = malloc(strlen(token) + 1);
strcpy(all_args[i], token);
removeChar(all_args[i], '<');
removeSubstring(all_args[i], fin);
// printf("AFTER REMOVING SUBSTRING: %s\n", all_args[i]);
i++;
print_arr[j] = malloc(strlen(token) + 1);
strcpy(print_arr[j], token);
j++;
all_args[i] = malloc(strlen(token) + 1);
strcpy(all_args[i], fin);
// printf("Just added: %s\n", all_args[i]);
// printf("%s\n", all_args[i]);
i++;
token = strtok(NULL, " ");
}
// Else if word<
else if (!strstr(token, fin)) {
// printf("CASE word<\n");
all_args[i] = malloc(strlen(token) + 1);
strcpy(all_args[i], token);
removeChar(all_args[i], '<');
// printf("AFTER REMOVING CHAR: ");
// printf("%s\n", all_args[i]);
i++;
print_arr[j] = malloc(strlen(token) + 1);
strcpy(print_arr[j], token);
j++;
token = strtok(NULL, " ");
}
continue;
}
// Copy all tokens to printed array
print_arr[j] = malloc(strlen(token) + 1);
strcpy(print_arr[j], token);
j++;
// Copy token to arguments array if it is not a >, <, or output file
if (outputRedirectionActivated == true && strcmp(fout, token) == 0) {
token = strtok(NULL, " ");
continue;
}
all_args[i] = malloc(strlen(token) + 1);
strcpy(all_args[i], token);
i++;
// Move on to the next token
token = strtok(NULL, " ");
}
// Null-terminate the arguments array.
// This is why all_args contains an extra element.
all_args[i] = 0;
print_arr[j] = 0;
command *com = createCommand(all_args[0], all_args, print_arr, i, j,
inputRedirectionActivated, outputRedirectionActivated, fin, fout);
// printf("Outpur redirection debugging: %d\t%s\n", com->hasInputRedirection, com->fout);
return com;
}
/* Built-in exit command */
int myExit(bool activeStatus) {
activeStatus = false;
fprintf(stderr, "Bye...\n");
return EXIT_SUCCESS;
}
/* Built-in pwd command */
void myPwd() {
char current_dir[1024];
if (getcwd(current_dir, 1024) != NULL) {
fprintf(stdout, "%s\n", current_dir);
}
else {
perror("pwd error");
}
}
/* Built-in cd command */
void myCd(char* arg) {
if (arg == NULL) {
// printf("Going to HOME directory\n");
chdir(getenv("HOME"));
}
else if (strcmp(arg, "..") == 0) {
// printf("Going to previous directory\n");
chdir("..");
}
else {
// Check if specified directory exists
DIR *checkDir = opendir(arg);
if (checkDir) {
// printf("Going to specified directory\n");
chdir(arg);
} else {
fprintf(stderr, "Error: no such directory\n");
}
}
}
/* ------------ PIPING FUNCTION ------------ */
void runPipe (completePipe *p) {
int numCommands = p->pipeCount; // numCommands must be >= 2
int status;
int i = 0;
pid_t pid;
int pipefds[2*numCommands];
/* Have the parent process create the pipes from the start */
for (i = 0; i < numCommands; i++) {
if(pipe(pipefds + i*2) < 0) {
perror("runPipe malfunction");
exit(EXIT_FAILURE);
}
}
int j = 0;
int k = 0;
while (k < numCommands) {
pid = fork();
/* CHILD PROCESS */
if (pid == 0){
printf("Inside a child process\n");
// If not last command:
if (k != numCommands) {
if(dup2(pipefds[j + 1], 1) < 0){
perror("dup2 malfunction");
exit(EXIT_FAILURE);
}
}
// If not first command:
if(k != 0){
if(dup2(pipefds[j-2], 0) < 0){
perror("dup2 malfunction");///j-2 0 j+1 1
exit(EXIT_FAILURE);
}
}
// Close pipes after dup2
for(int m = 0; m < 2*numCommands; m++){
close(pipefds[i]);
}
// Create command
char *all_args[MAX_NUM_ARGS + 1];
command *command = cmdHandler(p->all_commands[k], all_args);
if( execvp(command->program, command->args) < 0 ) {
perror("Pipe execvp failure");
exit(EXIT_FAILURE);
}
} // END CHILD
else if (pid < 0) {
perror("fork/pid error");
exit(EXIT_FAILURE);
}
k++;
j = j + 2;
}
/* Parent is in charge of closing the pipes; waits for child processes to finish */
for(i = 0; i < 2 * numCommands; i++){
close(pipefds[i]);
}
for(i = 0; i < numCommands + 1; i++)
wait(&status);
}
/* ---------------------------------------- MAIN PROGRAM ---------------------------------------- */
int main(int argc, char *argv[])
{
int status;
bool active = true;
int start = 0;
while (active == true) {
if (start == 0) {
char *startInstructions = "----------\nWelcome to Nausheen's Shell!\nHere, you can try out a variety of standard terminal commands. E.g. pwd, ls, mkdir, cd, touch, cat, and echo.\n----------\n";
fprintf(stdout, "%s\n", startInstructions);
start = 1;
}
char *prompt = "sshell$ ";
fprintf(stdout, "%s", prompt);
char cmd[MAX_CMD_SZ];
fgets(cmd, MAX_CMD_SZ, stdin); // <-- could be a piped command
/*
* Prints command line to stdout if fgets just read from a file and not
* the terminal (which is the case with the test script)
*/
if (!isatty(STDIN_FILENO)) {
printf("%s", cmd);
fflush(stdout);
}
/* Remove newline. */
removeTrailingNewline(cmd);
// completePipe *the_pipe = pipeHandler(cmd);
// if (the_pipe->pipeCount > 1) {
// runPipe(the_pipe);
// continue;
// }
/* Parse, process, and execute each individual command in the pipe. */
char *all_args[MAX_NUM_ARGS + 1];
command *command = cmdHandler(cmd, all_args);
/* -- HANDLE BUILT-IN COMMANDS -- */
// --- EXIT ---
if (strcmp(command->program, "exit") == 0) {
myExit(active); // exit
break;
}
// --- CD ---
else if (strcmp(command->program, "cd") == 0) {
myCd(all_args[1]); // cd
fprintf(stderr, "%s", "+ completed '");
// Print command + statement of completion
for(int k = 0; k < command->printCount; k++) {
if (k == command->printCount-1) {
fprintf(stderr, "%s", command->toPrint[k]);
}
else fprintf(stderr, "%s ", command->toPrint[k]);
}
fprintf(stderr, "%s%d%s", "' [", WEXITSTATUS(status), "]\n");
continue;
}
// --- PWD ---
else if (strcmp(command->program, "pwd") == 0) {
myPwd(); // pwd
fprintf(stderr, "%s", "+ completed '");
// Print command + statement of completion
for(int k = 0; k < command->printCount; k++) {
if (k == command->printCount-1) {
fprintf(stderr, "%s", command->toPrint[k]);
}
else fprintf(stderr, "%s ", command->toPrint[k]);
}
fprintf(stderr, "%s%d%s", "' [", WEXITSTATUS(status), "]\n");
continue;
}
/* Fork the process.*/
fflush(stdout);
pid_t process_ID = fork(); // fork() returns child process ID to parent process
/* ------- THE CHILD PROCESS -------- */
/* This process takes care of commands with input redirection, output redirection,
and all "regular" commands to be executed using an execvp system call. */
if(process_ID == 0) {
/* --- BEGIN: Executing a command with INPUT redirection --- */
if (command->hasInputRedirection && command->fin!=NULL) {
/* Check if input file exists */
int fileInCheck = open(command->fin, O_RDWR);
// printf("Filecheck %d\n", fileCheck);
if (fileInCheck == -1) {
fprintf(stderr, "Error: cannot open input file: %s\n", command->fin);
break;
}
redirectInput(command->fin, command->args);
execvp(command->program, command->args);
fprintf(stderr, "Error: cannot open input file: %s\n", command->fin);
break;
}
else if (command->hasInputRedirection && command->fin == NULL) {
fprintf(stderr, "Error: no input file\n");
}
/* --- END: Executing a command with INPUT redirection --- */
/* --- BEGIN: Executing a command with OUTPUT redirection --- */
else if (command->hasOutputRedirection && command->fout!=NULL) {
/* Check if output file exists */
int fileOutCheck = open(command->fout, O_RDWR|O_CREAT, 0666);
if (fileOutCheck == -1) {
fprintf(stderr, "Error: cannot open output file: %s\n", command->fin);
break;
}
redirectOutput(command->fout, command->args);
execvp(command->program, command->args);
fprintf(stderr, "Error: cannot open output file: %s\n", command->fin);
break;
}
else if (command->hasOutputRedirection && command->fout == NULL) {
fprintf(stderr, "Error: no output file\n");
}
/* --- END: Executing a command with OUTPUT redirection --- */
execvp(command->program, command->args);
fprintf(stdout, "Error: command not found\n");
_exit(1);
}
/* ----- RETURN TO THE PARENT PROCESS ----- */
else {
/* Handle background commands */
if (findElement("&", command->program))
waitpid(process_ID, &status, 0);
if ( (process_ID = wait(&status)) == -1) {
perror("Wait error");
}
// If child process exited successfully:
else if ( WIFEXITED(status) != 0 ) {
fprintf(stderr, "%s", "+ completed '");
for(int k = 0; k < command->printCount; k++) {
if (k == command->printCount-1) {
fprintf(stderr, "%s", command->toPrint[k]);
}
else fprintf(stderr, "%s ", command->toPrint[k]);
}
fprintf(stderr, "%s%d%s", "' [", WEXITSTATUS(status), "]\n");
}
}
}
return EXIT_SUCCESS;
}