//Add your code here #include #include #include #include #include #include #include #include #include #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. } } // function to check if redirection is requested 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, ":"); } // replace the last colon with a null terminator newPath[strlen(newPath) - 1] = '\0'; 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 where the system command execv is executed void execMultipleArgs(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 // XXX need to check if this is still necessary!!!! // char error_message[30] = "An error has occurred\n"; // write(STDERR_FILENO, error_message, strlen(error_message)); // exit(0); } } // 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; } // function for parsing parallel command words int parseSpaceParallel(int counter, char **parallelStr, int *parallelArgsCounter, char ***parallelArgs) { int i; for (i = 0; i < counter; i++) { parallelArgs[i] = (char **)malloc(strlen(parallelStr[i]) * 2 * sizeof(char *) + 1); int j; for (j = 0; j < MAX_ARGS; j++) { parallelArgs[i][j] = strsep(¶llelStr[i], " \t\n"); if (parallelArgs[i][j] == NULL) { break; } if (strlen(parallelArgs[i][j]) == 0) { j--; } } parallelArgsCounter[i] = j; } return 0; } void execMultipleCmd(char *str, char *filename, char** args) { int argsCounter = 0; // number of arguments, including the cmd arg // 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 execMultipleArgs(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 execMultipleArgs(args); } else { // errors with cmd char error_message[30] = "An error has occurred\n"; write(STDERR_FILENO, error_message, strlen(error_message)); } } // function to execute single command void execSingleCmd(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(char **args, int *parallelArgsCounter, char **parallelArgs[MAX_ARGS]) { while(1) { // print shell prompt to terminal printf("wish> "); // 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'; } break; } // check for empty string if (strlen(line) == 0) continue; // check for parallel cmds char *parallelStr[MAX_ARGS]; int parallelCmdsNum = findParallelCmds(line, parallelStr); if (parallelCmdsNum) { char filename[MAXCOM]; for (int j = 0; j < parallelCmdsNum; j++) { parallelArgs[j] = (char **)malloc(MAX_ARGS * 2 * sizeof(char *) + 1); execMultipleCmd(parallelStr[j], filename, parallelArgs[j]); } for (int j = 0; j < parallelCmdsNum; j++) { wait(NULL); } } else { char filename[MAXCOM]; execSingleCmd(line, filename, args); } // Free the line buffer free(line); // Reset the line pointer and size for the next line line = NULL; len = 0; } } // function to execute given batch file void takeBatchInput(char *inputFile, char **args, int *parallelArgsCounter, char **parallelArgs[MAX_ARGS]) { 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 *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) { char filename[MAXCOM]; for (int j = 0; j < parallelCmdsNum; j++) { parallelArgs[j] = (char **)malloc(MAX_ARGS * 2 * sizeof(char *) + 1); execMultipleCmd(parallelStr[j], filename, parallelArgs[j]); } for (int j = 0; j < parallelCmdsNum; j++) { wait(NULL); } } else { char filename[MAXCOM]; // store output filename if specified execSingleCmd(line, filename, args); } } // 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 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(args, parallelArgsCounter, parallelArgs); } // If given ./wish and a batch file -> read and execute commands in file else { takeBatchInput(argv[1], args, parallelArgsCounter, parallelArgs); } return 0; }