diff options
Diffstat (limited to '42sh/src/exec')
| -rw-r--r-- | 42sh/src/exec/Makefile.am | 15 | ||||
| -rw-r--r-- | 42sh/src/exec/ast_eval.c | 20 | ||||
| -rw-r--r-- | 42sh/src/exec/ast_eval.h | 24 | ||||
| -rw-r--r-- | 42sh/src/exec/ast_exec_functions.c | 491 | ||||
| -rw-r--r-- | 42sh/src/exec/ast_exec_functions.h | 77 | ||||
| -rw-r--r-- | 42sh/src/exec/ast_exec_redirs.c | 149 | ||||
| -rw-r--r-- | 42sh/src/exec/ast_exec_redirs.h | 11 |
7 files changed, 787 insertions, 0 deletions
diff --git a/42sh/src/exec/Makefile.am b/42sh/src/exec/Makefile.am new file mode 100644 index 0000000..a6a9134 --- /dev/null +++ b/42sh/src/exec/Makefile.am @@ -0,0 +1,15 @@ +lib_LIBRARIES = libeval.a + +libeval_a_SOURCES = \ + ast_exec_functions.c \ + ast_exec_functions.h \ + ast_eval.h \ + ast_eval.c \ + ast_exec_redirs.h \ + ast_exec_redirs.c + +libeval_a_CPPFLAGS = -I$(top_srcdir)/src + +libeval_a_CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla -pedantic + +noinst_LIBRARIES = libeval.a diff --git a/42sh/src/exec/ast_eval.c b/42sh/src/exec/ast_eval.c new file mode 100644 index 0000000..db152f8 --- /dev/null +++ b/42sh/src/exec/ast_eval.c @@ -0,0 +1,20 @@ +#include "exec/ast_eval.h" + +#include "exec/ast_exec_functions.h" + +static const exec_f exec_node_ltable[] = { + [AST_LIST] = exec_ast_list, [AST_IF] = exec_ast_if, + [AST_COMMAND] = exec_ast_command, [AST_LOGICAL] = exec_ast_logical, + [AST_PIPELINE] = exec_ast_pipe, [AST_REDIRECTION] = exec_ast_redirection, + [AST_WHILE] = exec_ast_while, [AST_ASSIGN] = exec_ast_assign, + [AST_FOR] = exec_ast_for, [AST_SUBSHELL] = exec_ast_subshell +}; + +int eval_ast(struct ast *ast) +{ + if (!ast) + { + return SHELL_TRUE; + } + return exec_node_ltable[ast->type](ast); +} diff --git a/42sh/src/exec/ast_eval.h b/42sh/src/exec/ast_eval.h new file mode 100644 index 0000000..785df5a --- /dev/null +++ b/42sh/src/exec/ast_eval.h @@ -0,0 +1,24 @@ +#ifndef AST_EVAL_H +#define AST_EVAL_H + +#include "ast/ast.h" + +/** + * @brief Shell true value, this is not the builtin true. + */ +#define SHELL_TRUE 0 + +/** + * @brief Shell false value, this is not the builtin false. + */ +#define SHELL_FALSE 1 + +/** + * @brief Returns the exit code of the given AST. + * @param ast The AST to evaluate. It is given as a `struct ast*` + * and uses an inheritance-like principle. + * An AST can be of any type from the `enum ast_node`. + */ +int eval_ast(struct ast *ast); + +#endif /* ! AST_EVAL_H */ diff --git a/42sh/src/exec/ast_exec_functions.c b/42sh/src/exec/ast_exec_functions.c new file mode 100644 index 0000000..9885997 --- /dev/null +++ b/42sh/src/exec/ast_exec_functions.c @@ -0,0 +1,491 @@ +#define _POSIX_C_SOURCE 200809L + +#include "exec/ast_exec_functions.h" + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "ast/ast_accessors.h" +#include "ast/ast_redirect.h" +#include "exec/ast_exec_redirs.h" +#include "lexer/expansion.h" +#include "utils/env.h" + +#define XCODE_TOENV(R) \ + char buf[16] = { 0 }; \ + sprintf(buf, "%d", (R)); \ + env_set("?", buf) + +static char **convert_args(struct string **args) +{ + size_t l = 0; + while (args[l]) + { + l++; + } + + char **c_args = calloc(l + 1, sizeof(char *)); + if (!c_args) + { + errx(EXIT_FAILURE, "convert_args: calloc failed."); + } + + for (size_t i = 0; i < l; i++) + { + c_args[i] = calloc(args[i]->length + 1, sizeof(char)); + if (!c_args[i]) + { + errx(EXIT_FAILURE, "convert_args: calloc failed."); + } + c_args[i] = strncpy(c_args[i], args[i]->data, args[i]->length); + } + + return c_args; +} + +static void free_args(char **args) +{ + size_t i = 0; + while (args[i]) + { + free(args[i]); + i++; + } + free(args); +} + +static builtin fetch_builtin(struct string *s) +{ + if (!s) + { + return NULL; + } + if (STRINGS_ARE_EQUAL(s->data, "false")) + { + return my_false; + } + if (STRINGS_ARE_EQUAL(s->data, "true")) + { + return my_true; + } + if (STRINGS_ARE_EQUAL(s->data, "echo")) + { + return echo; + } + if (STRINGS_ARE_EQUAL(s->data, ".")) + { + return dot; + } + if (STRINGS_ARE_EQUAL(s->data, "exit")) + { + return my_exit; + } + if (STRINGS_ARE_EQUAL(s->data, "cd")) + { + return cd; + } + if (STRINGS_ARE_EQUAL(s->data, "export")) + { + return export; + } + return NULL; +} + +static int exec_program(struct string **args) +{ + char **argv = convert_args(args); + if (!argv[0]) + { + free_args(argv); + return 127; + } + pid_t p = fork(); + if (!p) + { + execvp(argv[0], argv); + if (errno == ENOEXEC) + { + fprintf(stderr, + "exec_program: the file \'%s\' is not executable.\n", + argv[0]); + free_args(argv); + exit(126); + } + fprintf(stderr, "exec_program: command \'%s\' not found.\n", argv[0]); + free_args(argv); + exit(127); + } + else + { + int status = 0; + waitpid(p, &status, 0); + free_args(argv); + if (WIFEXITED(status)) + { + return WEXITSTATUS(status); + } + return SHELL_FALSE; + } +} + +static struct string **_copy_args(struct string **args) +{ + size_t len = 0; + while (args[len]) + { + len++; + } + struct string **cpy = calloc(len + 1, sizeof(struct string *)); + for (size_t i = 0; i < len; i++) + { + cpy[i] = string_deepcopy(args[i]); + } + return cpy; +} + +static void _free_str_args(struct string **args) +{ + size_t i = 0; + while (args[i]) + { + string_free(args[i]); + i++; + } + free(args); +} + +int exec_ast_command(struct ast *ast) +{ + union ast_caster ast_cast; + ast_cast.ast = ast; + struct string **cpy = _copy_args(ast_cast.ast_c->args); + if (!ast_cast.ast_c->args) + { + _free_str_args(cpy); + fprintf(stderr, "exec_ast_command: invalid args for a command exec.\n"); + XCODE_TOENV(SHELL_FALSE); + return SHELL_FALSE; + } + + size_t i = 0; + size_t j = 0; + while (ast_cast.ast_c->args[i]) + { + struct string *cv = expand_word(ast_cast.ast_c->args[i]); + if (cv->length > 0) + { + string_free(ast_cast.ast_c->args[j]); + ast_cast.ast_c->args[j++] = cv; + } + else + { + string_free(cv); + } + i++; + } + while (ast_cast.ast_c->args[j]) + { + string_free(ast_cast.ast_c->args[j]); + ast_cast.ast_c->args[j++] = NULL; + } + if (i == 0) + { + // No args ! + XCODE_TOENV(SHELL_TRUE); + return SHELL_TRUE; + } + builtin f = fetch_builtin(ast_cast.ast_c->args[0]); + int r = SHELL_TRUE; + if (f != NULL) + { + r = (*f)(ast_cast.ast_c->args + 1); + } + else + { + r = exec_program(ast_cast.ast_c->args); + } + _free_str_args(ast_cast.ast_c->args); + ast_cast.ast_c->args = cpy; + XCODE_TOENV(r); + return r; +} + +int exec_ast_list(struct ast *ast) +{ + union ast_caster ast_cast; + ast_cast.ast = ast; + int status = SHELL_TRUE; + for (size_t i = 0; i < ast_cast.ast_l->nb_children; i++) + { + status = eval_ast(get_i(ast_cast.ast, i)); + } + XCODE_TOENV(status); + return status; +} + +int exec_ast_if(struct ast *ast) +{ + int k = SHELL_TRUE; + if (eval_ast(get_i(ast, 0)) == SHELL_TRUE) + { + k = eval_ast(get_i(ast, 1)); + } + else + { + k = eval_ast(get_i(ast, 2)); + } + XCODE_TOENV(k); + return k; +} + +int exec_ast_logical(struct ast *ast) +{ + union ast_caster ast_cast; + ast_cast.ast = ast; + int k = SHELL_TRUE; + switch (ast_cast.ast_lo->type) + { + case TOKEN_NEG: + k = !eval_ast(get_left(ast)); + break; + case TOKEN_AND: + if (eval_ast(get_left(ast)) == SHELL_TRUE) + { + k = eval_ast(get_right(ast)); + break; + } + k = SHELL_FALSE; + break; + case TOKEN_OR: + if (eval_ast(get_left(ast)) == SHELL_FALSE) + { + k = eval_ast(get_right(ast)); + break; + } + k = SHELL_TRUE; + break; + default: + errx(SHELL_FALSE, + "exec_ast_logical: invalid token type for logical operation. Got: " + "%d", + ast_cast.ast_lo->type); + } + XCODE_TOENV(k); + return k; +} + +int exec_ast_pipe(struct ast *ast) +{ + union ast_caster ast_cast; + ast_cast.ast = ast; + + struct ast *left = ast_cast.ast_p->left; + struct ast *right = ast_cast.ast_p->right; + if (!left || !right) + { + XCODE_TOENV(EXIT_FAILURE); + return EXIT_FAILURE; + } + + int fds[2]; + if (pipe(fds) == -1) + { + XCODE_TOENV(EXIT_FAILURE); + return EXIT_FAILURE; + } + + pid_t p1 = fork(); + if (!p1) + { + close(fds[0]); + dup2(fds[1], STDOUT_FILENO); + int r1 = eval_ast(left); + exit(r1); + } + + pid_t p2 = fork(); + if (!p2) + { + close(fds[1]); + dup2(fds[0], STDIN_FILENO); + int r2 = eval_ast(right); + exit(r2); + } + + close(fds[0]); + close(fds[1]); + + int status = 0; + waitpid(p1, &status, 0); + waitpid(p2, &status, 0); + + if (WIFEXITED(status)) + { + int k = WEXITSTATUS(status); + XCODE_TOENV(k); + return k; + } + XCODE_TOENV(SHELL_FALSE); + return SHELL_FALSE; +} + +inline static bool _is_a_std_fd(int fd) +{ + return fd >= STDIN_FILENO && fd <= STDERR_FILENO; +} + +static int _exec_ast_redir(struct ast_redirection *ast, int left_fd, + int right_fd) +{ + // Save std fds + int cpy_stdin = dup(STDIN_FILENO); + int cpy_stdout = dup(STDOUT_FILENO); + int cpy_stderr = dup(STDERR_FILENO); + if (right_fd == CLOSE_FD) + { + close(left_fd); + } + else + { + dup2(right_fd, left_fd); + } + + int r = eval_ast(ast->expr); + + if (!_is_a_std_fd(right_fd)) + { + close(right_fd); + } + if (!_is_a_std_fd(left_fd)) + { + close(left_fd); + } + + // Restore std fds + dup2(cpy_stdin, STDIN_FILENO); + dup2(cpy_stdout, STDOUT_FILENO); + dup2(cpy_stderr, STDERR_FILENO); + XCODE_TOENV(r); + return r; +} + +int exec_ast_redirection(struct ast *ast) +{ + union ast_caster ast_cast; + ast_cast.ast = ast; + + int left_fd = 0; + int right_fd = 0; + find_fds(ast_cast.ast_r->redirect, &left_fd, &right_fd); + if (left_fd == BAD_FD || right_fd == BAD_FD) + { + fprintf(stderr, "redirection: bad fd.\n"); + XCODE_TOENV(SHELL_FALSE); + return SHELL_FALSE; + } + + int k = _exec_ast_redir(ast_cast.ast_r, left_fd, right_fd); + XCODE_TOENV(k); + return k; +} + +int exec_ast_while(struct ast *ast) +{ + int k = SHELL_TRUE; + while (true) + { + int r = eval_ast(((struct ast_while *)ast)->cond); + if (r != SHELL_TRUE) + { + break; + } + k = eval_ast(((struct ast_while *)ast)->body); + } + XCODE_TOENV(k); + return k; +} + +int exec_ast_assign(struct ast *ast) +{ + struct ast_assign *ast_a = (struct ast_assign *)ast; + struct string *expanded = expand_word(ast_a->val); + env_set(ast_a->name->data, expanded->data); + string_free(expanded); + XCODE_TOENV(SHELL_TRUE); + return SHELL_TRUE; +} + +int exec_ast_for(struct ast *ast) +{ + if (!get_left(ast)) + { + XCODE_TOENV(SHELL_TRUE); + return SHELL_TRUE; + } + struct string **args = ((struct ast_command *)get_left(ast))->args; + struct string *name = ((struct ast_for *)ast)->var; + int k = SHELL_TRUE; + size_t i = 0; + while (args[i]) + { + struct string *value = args[i]; + i++; + env_set(name->data, value->data); + k = eval_ast(get_right(ast)); + } + XCODE_TOENV(k); + return k; +} + +static int _exec_fork(struct ast *ast) +{ + pid_t child = fork(); + + if (child == -1) + { + XCODE_TOENV(SHELL_FALSE); + return SHELL_FALSE; + } + else if (child) + { + // Parent process + + int status; + // Why did I put 0 last time I used fork(2) + waitpid(child, &status, 0); + + if (!WIFEXITED(status)) + { + XCODE_TOENV(SHELL_FALSE); + return SHELL_FALSE; + } + + return WEXITSTATUS(status); + } + else + { + // Child process + + int ret = eval_ast(ast); + // Need to exit or else we will have to exit from the main function + // So we will execute the remainder of the ast twice + // Which is not what we want + exit(ret); + } +} + +int exec_ast_subshell(struct ast *ast) +{ + struct ast *sub = get_left(ast); + int fork_res = _exec_fork(sub); + // TODO:The exit codes might not be the correct one + XCODE_TOENV(fork_res); + return fork_res; +} diff --git a/42sh/src/exec/ast_exec_functions.h b/42sh/src/exec/ast_exec_functions.h new file mode 100644 index 0000000..ba2fbcd --- /dev/null +++ b/42sh/src/exec/ast_exec_functions.h @@ -0,0 +1,77 @@ +#ifndef AST_EXEC_FUNCTIONS_H +#define AST_EXEC_FUNCTIONS_H + +#include "builtins/builtins.h" +#include "exec/ast_eval.h" + +#define BASE_RW_SIZE 256 + +typedef int (*exec_f)(struct ast *ast); + +/** + * @brief Evaluates the given AST and return the exit code of it. + * A command can be a shell builtin or an external binary file called with + * `execvp`. + * @param ast An `AST_COMMAND` AST to evaluate. It is given as a `struct ast*` + * and uses inheritance-like principle. + */ +int exec_ast_command(struct ast *ast); + +/** + * @brief Evaluates the given AST and return the exit code of it. + * It performs a lazy evaluation. + * @param ast An `AST_LOGICAL` AST to evaluate. It is given as a `struct ast*` + * and uses inheritance-like principle. + */ +int exec_ast_logical(struct ast *ast); + +/** + * @brief Evaluates the given AST and return the exit code of the last + * `AST_COMMAND`. + * @param ast An `AST_LIST` AST to evaluate. It is given as a `struct ast*` + * and uses inheritance-like principle. + */ +int exec_ast_list(struct ast *ast); + +/** + * @brief Evaluates the given AST and return the exit code of it. + * It performs a lazy evaluation. + * @param ast An `AST_IF` AST to evaluate. It is given as a `struct ast*` + * and uses inheritance-like principle. + */ +int exec_ast_if(struct ast *ast); + +/** + * @brief Evaluates the given AST and return the exit code of it. + * It redirects the standard output of `ast->left` to the standard input of + * `ast->right`. + * @param ast An `AST_PIPELINE` AST to evaluate. It is given as a `struct ast*` + * and uses inheritance-like principle. + */ +int exec_ast_pipe(struct ast *ast); + +/** + * @brief Evaluates the given AST and return the exit code of it. + * It gets the redirection details from `ast->redir` which is a `struct + * redirect`. + * @param ast An `AST_REDIRECTION` AST to evaluate. It is given as a `struct + * ast*` and uses inheritance-like principle. + */ +int exec_ast_redirection(struct ast *ast); + +int exec_ast_while(struct ast *ast); + +int exec_ast_assign(struct ast *ast); + +int exec_ast_for(struct ast *ast); + +/** + * @brief Evaluates the given AST and return the exit code of it. + * It gets the ast corresponding to the subshell and executes it in a new + * environment thanks to the fork(2) function. + * @param ast An `AST_SUBSHELL` AST to evaluate It is given as a `struct ast*` + * and uses inheritance-like principle. + */ +int exec_ast_subshell(struct ast *ast); + +#endif /* ! AST_EXEC_FUNCTIONS_H */ diff --git a/42sh/src/exec/ast_exec_redirs.c b/42sh/src/exec/ast_exec_redirs.c new file mode 100644 index 0000000..5cd013c --- /dev/null +++ b/42sh/src/exec/ast_exec_redirs.c @@ -0,0 +1,149 @@ +#define _POSIX_C_SOURCE 200809L + +#include "exec/ast_exec_redirs.h" + +#include <err.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "ast/ast.h" +#include "ast/ast_accessors.h" +#include "ast/ast_redirect.h" +#include "utils/env.h" +#include "utils/libstring.h" + +inline static long _get_open_max(void) +{ + return sysconf(_SC_OPEN_MAX); +} + +static bool _file_exists(char *filename) +{ + struct stat s; + return stat(filename, &s) == 0; +} + +inline static bool _is_a_digit(char c) +{ + return c >= '0' && c <= '9'; +} + +static bool _is_a_number(struct string *s) +{ + size_t i = 0; + while (i < s->length && _is_a_digit(s->data[i])) + { + i++; + } + return i == s->length; +} + +/** + * @brief Assigns the fd described by s to out_fd if s + * describes an integer which is less than _SC_OPEN_MAX. + */ +static void _assign_if_valid_int_fd(struct string *s, int *out_fd) +{ + if (_is_a_number(s)) + { + int fd = atoi(s->data); + if (fd < _get_open_max()) + { + *out_fd = fd; + return; + } + } + *out_fd = BAD_FD; +} + +/** + * @brief Assigns the fd described by s to out_fd if s + * describes an integer which is less than _SC_OPEN_MAX. + * s can also be exactly '-', in which case out_fd contains + * the value CLOSE_FD. + */ +static void _assign_if_valid_fd_and(struct string *s, int *out_fd) +{ + if (_is_a_number(s)) + { + int fd = atoi(s->data); + int cpy_fd = dup(fd); + *out_fd = cpy_fd; + return; + } + if (s->length == 1 && s->data[0] == '-') + { + *out_fd = CLOSE_FD; + return; + } + *out_fd = BAD_FD; +} + +void find_fds(struct redirect r, int *left_fd, int *right_fd) +{ + char *rd = r.redir->data; + static const int filemode = 0644; + + // Default value is no valid left fd is passed in `r`. + *left_fd = BAD_FD; + *right_fd = BAD_FD; + + if (STRINGS_ARE_EQUAL(rd, ">")) + { + // Sec. 2.7.2: noclobber must avoid overwritting on an existing file + if (env_get("noclobber") != NULL && _file_exists(r.file->data)) + { + fprintf(stderr, "redirection: Unable to overwrite on %s.\n", + r.file->data); + return; + } + *right_fd = open(r.file->data, O_WRONLY | O_CREAT | O_TRUNC, filemode); + _assign_if_valid_int_fd(r.fd, left_fd); + } + if (STRINGS_ARE_EQUAL(rd, ">>")) + { + *right_fd = open(r.file->data, O_WRONLY | O_CREAT | O_APPEND, filemode); + _assign_if_valid_int_fd(r.fd, left_fd); + } + if (STRINGS_ARE_EQUAL(rd, ">|")) + { + // Sec. 2.7.2: this redir bypasses noclobber + *right_fd = open(r.file->data, O_WRONLY | O_CREAT | O_TRUNC, filemode); + _assign_if_valid_int_fd(r.fd, left_fd); + } + if (STRINGS_ARE_EQUAL(rd, ">&")) + { + _assign_if_valid_int_fd(r.fd, left_fd); + _assign_if_valid_fd_and(r.file, right_fd); + if (*right_fd == BAD_FD) + { + // Sure why not after all, + // Because when it is about bash posix + // 'undefined' in the SCL means: + // OH <&{filename} is AMBIGUOUS + // BUT >&{filename} is PERFECTLY FINE + *right_fd = + open(r.file->data, O_WRONLY | O_CREAT | O_TRUNC, filemode); + } + } + if (STRINGS_ARE_EQUAL(rd, "<")) + { + *right_fd = open(r.file->data, O_RDONLY); + _assign_if_valid_int_fd(r.fd, left_fd); + } + if (STRINGS_ARE_EQUAL(rd, "<&")) + { + _assign_if_valid_int_fd(r.fd, left_fd); + _assign_if_valid_fd_and(r.file, right_fd); + } + if (STRINGS_ARE_EQUAL(rd, "<>")) + { + *right_fd = open(r.file->data, O_RDWR | O_CREAT, filemode); + _assign_if_valid_int_fd(r.fd, left_fd); + } +} diff --git a/42sh/src/exec/ast_exec_redirs.h b/42sh/src/exec/ast_exec_redirs.h new file mode 100644 index 0000000..4fd32de --- /dev/null +++ b/42sh/src/exec/ast_exec_redirs.h @@ -0,0 +1,11 @@ +#ifndef AST_EXEC_REDIRS_H +#define AST_EXEC_REDIRS_H + +#include "ast/ast_redirect.h" + +#define BAD_FD -1 +#define CLOSE_FD -2 + +void find_fds(struct redirect r, int *left_fd, int *right_fd); + +#endif /* ! AST_EXEC_REDIRS_H */ |
