//Add your code here #include<stdio.h> #include<string.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> #include<ctype.h> #include<errno.h> #include<fcntl.h> #define MAXCOM 1000 // max length of command line #define MAX_ARGS 100 // max number of commands to be supported // function for finding ampersand (parallel commands) int findParallelCmds(char* str, char** parallelStr) { int i; // to be used as parallel cmds counter for (i = 0; i < MAX_ARGS; i++) { parallelStr[i] = strsep(&str, "&"); //printf("i value here: %d, cmd: %s\n", i, parallelStr[i]); if (parallelStr[i] == NULL) { break; } } if (i > 1) return i; else { return 0; // returns zero if no pipe is found. } } int checkRedirection(char *str, char *filename) { // Find the position of the > operator in the command char *p = strchr(str, '>'); if (p == NULL) { return 0; // valid: no ">" found } else if (p == str) { return -1; // invalid: empty cmd with ">" } else { // The > operator was found, extract the filename //filename = strtok(p + 1, " "); char *f = strtok(p + 1, " "); if (f == NULL) { return -2; // invalid: no file specified } // get the next token p = strtok(NULL, " \t\r\n"); if (p != NULL) { return -3; // invalid: more than one file injected } //printf("next: %s\n", p); strcpy(filename, f); // Remove any leading or trailing whitespace from the filename filename = strtok(filename, " \t\r\n"); // Extract the remaining command args str = strtok(str, ">"); } return 1; // valid redirection } // function to handle built-in commands // if 0 is returned, it means that none of // the built-in commands was executed int useBuiltInCmds(int argc, char **args) { int noOfBuiltInCmds = 3; int chosenCmd = 0; char* listOfBuiltInCmds[noOfBuiltInCmds]; listOfBuiltInCmds[0] = "exit"; listOfBuiltInCmds[1] = "cd"; listOfBuiltInCmds[2] = "path"; for (int i = 0; i < noOfBuiltInCmds; i++) { if (strcmp(args[0], listOfBuiltInCmds[i]) == 0) { chosenCmd = i + 1; break; } } switch (chosenCmd) { case 1: // exit if (argc > 1) { char error_message[30] = "An error has occurred\n"; write(STDERR_FILENO, error_message, strlen(error_message)); return 1; } exit(0); case 2: // cd if (argc == 1 || argc > 2) { char error_message[30] = "An error has occurred\n"; write(STDERR_FILENO, error_message, strlen(error_message)); return 1; } if (chdir(args[1]) != 0) { char error_message[30] = "An error has occurred\n"; write(STDERR_FILENO, error_message, strlen(error_message)); } return 1; case 3: // path if (argc == 1) { // set PATH to an empty string if no path passed in setenv("PATH", "", 1); } else { // allocate initial buffer memory for added path // initialize the buffer with a null terminator char *newPath = (char*) malloc(1); newPath[0] = '\0'; // concatinate the old path to the new path separated by a colon char *oldPath = getenv("PATH"); // get the current search path newPath = realloc(newPath, strlen(newPath) + strlen(oldPath) + 2); newPath = strcat(newPath, oldPath); newPath = strcat(newPath, ":"); // concatinate new input paths for (int i = 1; i < argc; i++) { if (strlen(newPath) + strlen(args[i]) > strlen(newPath)) { newPath = realloc(newPath, (strlen(newPath) + strlen(args[i])) * 2 + 1); } newPath = strcat(newPath, args[i]); newPath = strcat(newPath, ":"); } newPath[strlen(newPath) - 1] = '\0'; // replace the last colon with a null terminator setenv("PATH", newPath, 1); // set the PATH environment variable free(newPath); newPath = NULL; } return 1; default: break; } return 0; } // Function where the system command execv is executed void execvArgs(char **args) { // Forking a child pid_t pid = fork(); if (pid == -1) { char error_message[30] = "An error has occurred\n"; write(STDERR_FILENO, error_message, strlen(error_message)); return; } else if (pid == 0) { char *path = getenv("PATH"); // get the current search path char *p = strtok(path, ":"); // tokenize the search path while (p != NULL) { // allocate memory for the executable path char *exe = malloc(strlen(p) + strlen(args[0]) + 2); // construct the full path to the executable sprintf(exe, "%s/%s", p, args[0]); // check if the executable exists and is executable if (access(exe, X_OK) == 0) { if (execv(exe, args) < 0) { fprintf(stderr, "%s\n", strerror(errno)); } exit(0); } // free the allocated memory free(exe); exe = NULL; // get the next token p = strtok(NULL, ":"); } // if path is NULL, kill child process char error_message[30] = "An error has occurred\n"; write(STDERR_FILENO, error_message, strlen(error_message)); exit(0); } else { // waiting for child to terminate wait(NULL); return; } } // function for parsing parallel command words int parseSpaceParallel(char **parallelStr, int **parallelArgsCounter, char ***parallelArgs) { return 1; } // function for parsing single command words int parseSpace(char *str, int argsCounter, char **args) { int i; for (i = 0; i < MAX_ARGS; i++) { args[i] = strsep(&str, " \t\n"); if (args[i] == NULL) { break; } if (strlen(args[i]) == 0) { i--; } } return i; } void executeSingleCmd(char *str, char *filename, char** args) { int argsCounter = 0; // number of arguments, including the cmd arg // single cmd found // base on redirection codes for cases int redirection_code = checkRedirection(str, filename); argsCounter = parseSpace(str, argsCounter, args); if (argsCounter == 0) { // only whitespace found return; } if (redirection_code == 1) { // redirection // save the original file descriptor for stdout/stderr int orig_stdout_fd = dup(STDOUT_FILENO); int orig_stderr_fd = dup(STDERR_FILENO); // open a file for writing and redirect to it int fd; fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) { char error_message[30] = "An error has occurred\n"; write(STDERR_FILENO, error_message, strlen(error_message)); } // redirect stderr to stdout if (dup2(fd, STDOUT_FILENO) == -1 || dup2(fd, STDERR_FILENO) == -1) { char error_message[30] = "An error has occurred\n"; write(STDERR_FILENO, error_message, strlen(error_message)); } // check with built-in first if (useBuiltInCmds(argsCounter, args) == 0) { // did not execute with built-in cmd execvArgs(args); } // reverse redirection after finish dup2(orig_stderr_fd, STDERR_FILENO); dup2(orig_stdout_fd, STDOUT_FILENO); // close file close(fd); } else if (redirection_code == 0) { // no redirection // check with built-in first if (useBuiltInCmds(argsCounter, args) == 0) { // did not execute with built-in cmd execvArgs(args); } } else { // errors with cmd char error_message[30] = "An error has occurred\n"; write(STDERR_FILENO, error_message, strlen(error_message)); } } // this function executes when the user does not put // any argument after ./wish, meaning they use stdin void takeStdInput(int argsCounter, char **args, int **parallelArgsCounter, char ***parallelArgs) { while(1) { // print shell prompt to terminal printf("wish> "); // variables and buffers char str[MAXCOM]; // ptr to store command line input string char filename[MAXCOM]; // for getline() char *line = NULL; // buffer to store each line read size_t len = 0; // length of the line read ssize_t read; // number of characters read by getline // take input line by line while ((read = getline(&line, &len, stdin)) != -1) { // remove newline character from command string if (line[strlen(line) - 1] == '\n') { line[strlen(line) - 1] = '\0'; } if (strlen(line) > 0) { // check for empty string // copy to char array strcpy(str, line); } // Free the line buffer free(line); // Reset the line pointer and size for the next line line = NULL; len = 0; break; } if (strlen(str) == 0) { // check for empty string continue; } // check for parallel cmds char *parallelStr[MAX_ARGS]; int parallelCmdsNum = findParallelCmds(str, parallelStr); if (parallelCmdsNum) { // for (int i=0; i<parallelCmdsNum; i++) { // printf("%s\n", parallelStr[i]); // executeSingleCmd(parallelStr[i], filename, parallelArgs[i]); // } // XXX need to work on parallel // parallel cmds found, return a number greater than 0 //parseSpaceParallel(parallelStr, parallelArgsCounter, parallelArgs); } else { executeSingleCmd(str, filename, args); } } } // function to execute given batch file void takeBatchInput(char *inputFile, int argsCounter, char **args, int **parallelArgsCounter, char ***parallelArgs) { FILE * fp = fopen(inputFile, "r"); if (fp == NULL) { char error_message[30] = "An error has occurred\n"; write(STDERR_FILENO, error_message, strlen(error_message)); exit(1); // bad batch file err } char filename[MAXCOM]; // store output filename if specified char *line = NULL; // buffer to store each line read size_t len = 0; // length of the line read ssize_t read; // number of characters read by getline // take input line by line while ((read = getline(&line, &len, fp)) != -1) { // remove newline character from command string if (line[strlen(line) - 1] == '\n') { line[strlen(line) - 1] = '\0'; } // check for empty string if (strlen(line) == 0) { continue; } // check for parallel cmds char *parallelStr[MAX_ARGS]; int parallelCmdsNum = findParallelCmds(line, parallelStr); if (parallelCmdsNum) { // parallel cmds found, return a number greater than 0 parseSpaceParallel(parallelStr, parallelArgsCounter, parallelArgs); } else { // single cmd found // base on redirection codes for cases int redirection_code = checkRedirection(line, filename); if (redirection_code == 1) { // redirection // save the original file descriptor for stdout/stderr int orig_stdout_fd = dup(STDOUT_FILENO); int orig_stderr_fd = dup(STDERR_FILENO); // open a file for writing and redirect to it int fd; fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) { char error_message[30] = "An error has occurred\n"; write(STDERR_FILENO, error_message, strlen(error_message)); } // redirect stderr to stdout if (dup2(fd, STDOUT_FILENO) == -1 || dup2(fd, STDERR_FILENO) == -1) { char error_message[30] = "An error has occurred\n"; write(STDERR_FILENO, error_message, strlen(error_message)); } // execute commands argsCounter = parseSpace(line, argsCounter, args); if (argsCounter == 0) { // only whitespace found continue; } if (useBuiltInCmds(argsCounter, args) == 0) { // did not execute with built-in cmd execvArgs(args); } // reverse redirection after finish dup2(orig_stderr_fd, STDERR_FILENO); dup2(orig_stdout_fd, STDOUT_FILENO); // close file close(fd); } else if (redirection_code == 0) { // no redirection // execute commands argsCounter = parseSpace(line, argsCounter, args); if (argsCounter == 0) { // only whitespace found continue; } if (useBuiltInCmds(argsCounter, args) == 0) { // did not execute with built-in cmd execvArgs(args); } } else { // errors with cmd char error_message[30] = "An error has occurred\n"; write(STDERR_FILENO, error_message, strlen(error_message)); } } } // Free the line buffer free(line); // Reset the line pointer and size for the next line line = NULL; len = 0; // close input file fclose(fp); } int main(int argc, char *argv[]) { setenv("PATH", "/bin", 1); // initialize shell path to default char *args[MAX_ARGS]; // ptr to a list of arguments int argsCounter = 0; // number of arguments, including the cmd arg char **parallelArgs[MAX_ARGS]; int *parallelArgsCounter[MAX_ARGS]; // If given more than ./wish and a file if (argc > 2) { char error_message[30] = "An error has occurred\n"; write(STDERR_FILENO, error_message, strlen(error_message)); exit(1); } // If given just ./wish -> prompt input else if (argc == 1) { takeStdInput(argsCounter, args, parallelArgsCounter, parallelArgs); } // If given ./wish and a batch file -> read and execute commands in file else { takeBatchInput(argv[1], argsCounter, args, parallelArgsCounter, parallelArgs); } return 0; }