diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/command.c | 103 | ||||
| -rw-r--r-- | src/hush.c | 106 | ||||
| -rw-r--r-- | src/lexer.c | 225 |
3 files changed, 434 insertions, 0 deletions
diff --git a/src/command.c b/src/command.c new file mode 100644 index 0000000..73a2027 --- /dev/null +++ b/src/command.c @@ -0,0 +1,103 @@ +/* + * + */ + +#include "command.h" +#include "config.h" +#include "lexer.h" +#include <stdio.h> +#include <unistd.h> + +command *commands_from_tokens(token *parsed_cmdstr, size_t *cnt) { + + command *cmd_head = calloc(1, sizeof(command)); + command *cmd = cmd_head; + size_t arg_cnt = 1; + + *cnt = 0; + + token *prev_tkn = NULL; + + for (token *tkn = parsed_cmdstr; tkn; prev_tkn = tkn, tkn = tkn->next) { + if (tkn->type == TKN_COMMAND) { + cmd->command = tkn->text; + cmd->args[0] = tkn->text; + arg_cnt = 1; + (*cnt)++; + } else if (tkn->type == TKN_ARG) { + cmd->args[arg_cnt++] = tkn->text; + } else if (tkn->type == TKN_FILENAME) { + if (prev_tkn->type == TKN_IN_REDIR) { + cmd->infile = tkn->text; + } else { + cmd->outfile = tkn->text; + } + } else if (tkn->type == TKN_PIPE) { + cmd->next = calloc(1, sizeof(command)); + cmd->next->read_pipe = cmd->pipe; + cmd = cmd->next; + } + + } + + return cmd_head; +} + +void print_commands(FILE *file, command *cmds) { + for (command *cmd=cmds; cmd; cmd = cmd->next) { + fprintf(file, "Command: %s\n", cmd->command); + fprintf(file, "\t"); + for (size_t i=0; i<MAX_ARGUMENT_CNT; i++) { + fprintf(file, "%s ", cmd->args[i]); + } + fprintf(file, "\n\tInfile: %s\n\tOutfile: %s\n", cmd->infile, cmd->outfile); + } +} + +void destroy_commands(command *cmds) { + command **cmd_ptr = &cmds->next; + while (*cmd_ptr) { + free(cmds); + cmds = *cmd_ptr; + cmd_ptr = &cmds->next; + } +} + +pid_t execute_command(command *cmd) { + if (cmd->next) { + pipe(cmd->pipe); + } + + pid_t res = fork(); + if (res == 0) { + if (cmd->infile) { + if (!freopen(cmd->infile, "r", stdin)) { + perror("Could not open input file"); + exit(EXIT_FAILURE); + } + } else if (cmd->read_pipe) { + dup2(*(cmd->read_pipe), STDIN_FILENO); + close(*(cmd->read_pipe)); + } + + if (cmd->outfile) { + if (!freopen(cmd->outfile, "w", stdout)) { + perror("Could not open output file"); + exit(EXIT_FAILURE); + } + } else if (cmd->next) { + dup2(cmd->pipe[1], STDOUT_FILENO); + close(cmd->pipe[1]); + } + + + int res = execvp(cmd->command, cmd->args); + perror("Could not run command"); + exit(EXIT_FAILURE); + } else if (res < 0) { + perror("Could not run command"); + } + + cmd->pid = res; + return res; +} diff --git a/src/hush.c b/src/hush.c new file mode 100644 index 0000000..478103f --- /dev/null +++ b/src/hush.c @@ -0,0 +1,106 @@ +/* + * hush -- the HU Shell + * + * A minimal shell for CISC 301, Operating Systems + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/wait.h> + +#include "config.h" +#include "lexer.h" +#include "command.h" + +FILE *open_input(int argc, char **argv) { + FILE *input_file = (argc > 1) ? fopen(argv[1], "r") : stdin; + if (!input_file) { + fprintf(stderr, "ERROR: Failed to open input file %s\n", argv[1]); + exit(EXIT_FAILURE); + } + + return input_file; +} + +static size_t get_command_len(char *cmdstr) { + size_t len = strlen(cmdstr); + if (cmdstr[len - 1] != '\n') { + len = 0; + } + + return len; +} + +int main(int argc, char **argv) { + FILE *input_file = open_input(argc, argv); + char buffer[MAX_LINE_LEN]; + + fprintf(stdout, "$ "); + while (fgets(buffer, MAX_LINE_LEN, input_file)) { + char *cmdstr = trim(buffer); + if (!cmdstr) { + fprintf(stderr, "ERROR: Specified command is too long\n"); + goto draw_prompt; + } + + if (strlen(cmdstr) == 0) { + goto draw_prompt; + } + + token *parsed_cmd; + if (!(parsed_cmd = parse_command(cmdstr))) { + fprintf(stderr, "ERROR: Failed to parse command\n"); + goto draw_prompt; + } + + { + token *error; + if ((error = validate_command(parsed_cmd))) { + fprintf(stderr, "ERROR: Invalid syntax near %s\n", error->text); + print_parsed_command(stdout, parsed_cmd); + goto free_tokens; + } + } + + // print_parsed_command(stdout, parsed_cmd); + + if (parsed_cmd->type == TKN_VARKEY) { + /* handle variable creation */ + goto free_tokens; + } + + size_t commands; + command *cmds; + if (!(cmds = commands_from_tokens(parsed_cmd, &commands ))) { + fprintf(stderr, "ERROR: Unable to create commands from tokens"); + goto free_tokens; + } + + // print_commands(stdout, cmds); + + for (command *cmd = cmds; cmd; cmd = cmd->next) { + pid_t result = execute_command(cmd); + if (result < 0) { + break; + } + } + + for (command *cmd = cmds; cmd; cmd = cmd->next) { + if (cmd->pid > 0) { + waitpid(cmd->pid, NULL, 0); + } + } + + free_tokens: + destroy_tokens(parsed_cmd); + + free_commands: + destroy_commands(cmds); + + draw_prompt: + fprintf(stdout, "$ "); + } +} diff --git a/src/lexer.c b/src/lexer.c new file mode 100644 index 0000000..a6af008 --- /dev/null +++ b/src/lexer.c @@ -0,0 +1,225 @@ +/* + * + */ + +#include "lexer.h" +#include "config.h" + +enum parser_state { + LEX_COMMAND, + LEX_IFILE, + LEX_OFILE, + LEX_ARGUMENT, + LEX_VARIABLE +}; + +static char *token_tostr(enum token_type type) { + switch (type) { + case TKN_FILENAME: + return "FILENAME"; + case TKN_COMMAND: + return "COMMAND"; + case TKN_PIPE: + return "PIPE"; + case TKN_IN_REDIR: + return "INPUT REDIR"; + case TKN_OUT_REDIR: + return "OUT REDIR"; + case TKN_ARG: + return "ARGUMENT"; + case TKN_INVALID: + return "INVALID"; + case TKN_VARKEY: + return "VARKEY"; + case TKN_VARVAL: + return "VARVAL"; + default: + return "INVALID"; + } +} + +static token *token_create(char *text) { + token *tkn = malloc(sizeof(token)); + if (!tkn) { + return NULL; + } + + tkn->text = text; + tkn->type = TKN_INVALID; + tkn->next = NULL; + + return tkn; +} + +char *trim(char *str) { + if (!str) { + return NULL; + } + + /* skip any leading whitespace */ + for (; isspace(*str); str++) + ; + + /* skip any tailing whitespace */ + size_t len = strlen(str); + char *str_p = str + len; + for (; isspace(*str_p) && str_p >= str; str_p--) { + *str_p = '\0'; + } + + return str; +} + +static enum token_type token_type_from_state(enum parser_state state) { + switch (state) { + case LEX_ARGUMENT: + return TKN_ARG; + case LEX_IFILE: + case LEX_OFILE: + return TKN_FILENAME; + case LEX_COMMAND: + return TKN_COMMAND; + case LEX_VARIABLE: + return TKN_VARVAL; + default: + return TKN_INVALID; + } +} + +token *parse_command(char *cmdstr) { + + token *tkn_head = token_create(cmdstr); + token **tkn = &tkn_head; + enum parser_state state = LEX_COMMAND; + bool previous_whitespace = false; + + do { + /* determine if parser state should change due to special characters */ + if (*cmdstr == '<') { + (*tkn)->type = TKN_IN_REDIR; + state = LEX_IFILE; + goto advance_token; + } else if (*cmdstr == '>') { + (*tkn)->type = TKN_OUT_REDIR; + state = LEX_OFILE; + goto advance_token; + } else if (*cmdstr == '|') { + (*tkn)->type = TKN_PIPE; + state = LEX_COMMAND; + goto advance_token; + } else if (*cmdstr == '=') { + (*tkn)->type = TKN_VARKEY; + state = LEX_VARIABLE; + goto advance_token; + } else if (isspace(*cmdstr)) { + (*tkn)->type = token_type_from_state(state); + + if (state == LEX_VARIABLE) { + state = LEX_COMMAND; + } else if (state == LEX_COMMAND) { + state = LEX_ARGUMENT; + } + + goto advance_token; + } + + /* otherwise, maintain current state and move to next character */ + continue; + + advance_token: + *cmdstr = '\0'; + + /* advance over any extra whitespace between tokens */ + while (isspace(*(cmdstr + 1))) { + cmdstr++; + } + + if (*(cmdstr + 1)) { + if (!((*tkn)->next = token_create(cmdstr + 1))) { + fprintf(stderr, "ERROR: Out of memory\n"); + exit(EXIT_FAILURE); + } + tkn = &(*tkn)->next; + } + + } while (*(++cmdstr)); + + return tkn_head; +} + +token *validate_command(token *cmd) { + token *prev = NULL; + size_t arg_cnt = 0; + bool infile_defined = false; + bool outfile_defined = false; + bool looking_for_file = false; + + for (token *tkn = cmd; tkn; tkn = tkn->next) { + if (!prev) { + if (tkn->type != TKN_COMMAND && tkn->type != TKN_VARKEY) { + return tkn; + } + } else if (prev->type == TKN_VARVAL) { + return tkn; + } else if (tkn->type == TKN_ARG) { + arg_cnt++; + if ((prev->type != TKN_COMMAND && prev->type != TKN_ARG) || + arg_cnt > MAX_ARGUMENT_CNT) { + return tkn; + } + } else if (tkn->type == TKN_FILENAME) { + if (prev->type == TKN_IN_REDIR && !infile_defined) { + infile_defined = true; + looking_for_file = false; + } else if (prev->type == TKN_OUT_REDIR && !outfile_defined) { + outfile_defined = true; + looking_for_file = false; + } else { + return tkn; + } + } else if (tkn->type == TKN_COMMAND) { + if (prev->type != TKN_PIPE) { + return tkn; + } + + arg_cnt = 0; + } else if (tkn->type == TKN_IN_REDIR || tkn->type == TKN_OUT_REDIR) { + if (prev->type == TKN_PIPE || prev->type == TKN_IN_REDIR || + prev->type == TKN_OUT_REDIR) { + return tkn; + } + + looking_for_file = true; + } else if (tkn->type == TKN_INVALID) { + return tkn; + } else if (tkn->type == TKN_VARVAL) { + if (prev->type != TKN_VARKEY) { + return tkn; + } + } + + if (looking_for_file && + (tkn->type != TKN_IN_REDIR && tkn->type != TKN_OUT_REDIR)) { + return tkn; + } + + prev = tkn; + } + + return NULL; +} + +void print_parsed_command(FILE *file, token *cmd) { + for (token *tkn = cmd; tkn; tkn = tkn->next) { + fprintf(file, "%s\t%s\n", tkn->text, token_tostr(tkn->type)); + } +} + +void destroy_tokens(token *tkns) { + token **tkn_ptr = &tkns->next; + while (*tkn_ptr) { + free(tkns); + tkns = *tkn_ptr; + tkn_ptr = &tkns->next; + } +} |