#include "execution.h" #include #include #include #include #include #include #include #include #include "../utils/ast/ast.h" // --- Helpers --- /** * @brief converts a linked list of command arguments to an argv array. Don't * forget to free the result * * @param command_list Linked list of command arguments * @return char** Array of command arguments suitable for execvp. Terminated by * NULL */ static char **list_to_argv(struct list *command_list) { size_t len = 0; struct list *cur = command_list; while (cur) { len++; cur = cur->next; } char **argv = calloc(len + 1, sizeof(char *)); if (!argv) { return NULL; } cur = command_list; for (size_t i = 0; i < len; i++) { argv[i] = (char *)cur->data; cur = cur->next; } argv[len] = NULL; return argv; } // --- Builtins --- static int builtin_echo(char **argv) { bool newline = true; int i = 1; if (argv[1] && strcmp(argv[1], "-n") == 0) { newline = false; i++; } for (; argv[i]; i++) { printf("%s", argv[i]); if (argv[i + 1]) printf(" "); } if (newline) printf("\n"); fflush(stdout); return 0; } static int builtin_true(char **argv) { (void)argv; return 0; } static int builtin_false(char **argv) { (void)argv; return 1; } static int builtin_exit(char **argv) { int exit_val = 0; if (argv[1]) exit_val = atoi(argv[1]); exit(exit_val); return exit_val; } static int builtin_cd(char **argv) { const char *path = argv[1]; if (!path) { path = getenv("HOME"); if (!path) { fprintf(stderr, "cd: HOME not set\n"); return 1; } } if (chdir(path) != 0) { perror("cd"); return 1; } return 0; } /** * @brief Tries to execute a builtin command if the command matches a builtin * * @param argv Array of command arguments * @return int Exit status of the builtin command, or -1 if not a builtin */ static int try_builtin(char **argv) { if (!argv || !argv[0]) return 0; if (strcmp(argv[0], "echo") == 0) return builtin_echo(argv); if (strcmp(argv[0], "true") == 0) return builtin_true(argv); if (strcmp(argv[0], "false") == 0) return builtin_false(argv); if (strcmp(argv[0], "exit") == 0) return builtin_exit(argv); if (strcmp(argv[0], "cd") == 0) return builtin_cd(argv); return -1; } // --- Execution Core --- /** * @brief Executes a command represented by an ast_command structure * * @param command The command to execute * @return int The exit status of the command */ static int exec_command(struct ast_command *command) { if (!command || !(command->command)) { return 1; } char **argv = list_to_argv(command->command); if (!argv || !(argv[0])) { free(argv); return 0; } int builtin_ret = try_builtin(argv); if (builtin_ret != -1) { free(argv); return builtin_ret; } pid_t pid = fork(); if (pid < 0) { perror("fork"); free(argv); return 1; } if (pid == 0) { execvp(argv[0], argv); perror("execvp"); _exit(127); } int status = 0; waitpid(pid, &status, 0); free(argv); if (WIFEXITED(status)) { return WEXITSTATUS(status); } return 1; } /** * @brief Executes the AST returned by the parser * * @param ast The AST to execute * @return int The exit status of the last executed command. */ int execution(struct ast *ast) { if (!ast) { return 0; } switch (ast->type) { case AST_END: { return 0; } case AST_CMD: { struct ast_command *command = ast_get_command(ast); return exec_command(command); } case AST_IF: { struct ast_if *if_node = ast_get_if(ast); int cond = execution(if_node->condition); if (cond == 0) // True { return execution(if_node->then_clause); } else // False { return execution(if_node->else_clause); } } case AST_LIST: { struct ast_list *list_node = ast_get_list(ast); struct list *cur = list_node->children; int ret = 0; while (cur) { struct ast *child = (struct ast *)cur->data; ret = execution(child); cur = cur->next; } return ret; } case AST_AND_OR: { struct ast_and_or *ao_node = ast_get_and_or(ast); int left_ret = execution(ao_node->left); if (ao_node->type == AST_AND_OR_TYPE_AND) { if (left_ret == 0) return execution(ao_node->right); return left_ret; } else // OR { if (left_ret != 0) return execution(ao_node->right); return left_ret; } } case AST_REDIR: { struct ast_redir *redir = ast_get_redir(ast); int fd_target = redir->io_number; if (fd_target == -1) { if (redir->type == AST_REDIR_TYPE_LESS || redir->type == AST_REDIR_TYPE_DLESS || redir->type == AST_REDIR_TYPE_LESSAND) fd_target = 0; else fd_target = 1; } int saved_fd = dup(fd_target); int new_fd = -1; int flags = 0; int mode = 0644; if (redir->type == AST_REDIR_TYPE_GREAT || redir->type == AST_REDIR_TYPE_CLOBBER) { flags = O_WRONLY | O_CREAT | O_TRUNC; new_fd = open(redir->filename, flags, mode); } else if (redir->type == AST_REDIR_TYPE_DGREAT) { flags = O_WRONLY | O_CREAT | O_APPEND; new_fd = open(redir->filename, flags, mode); } else if (redir->type == AST_REDIR_TYPE_LESS) { flags = O_RDONLY; new_fd = open(redir->filename, flags); } else if (redir->type == AST_REDIR_TYPE_GREATAND || redir->type == AST_REDIR_TYPE_LESSAND) { // Simple fd duplication new_fd = atoi(redir->filename); // Verify new_fd is valid? dup2 will check. if (dup2(new_fd, fd_target) == -1) { perror("dup2"); if (saved_fd != -1) close(saved_fd); return 1; } new_fd = -2; // Mark as "already duped" } if (new_fd == -1) { perror("open"); if (saved_fd != -1) close(saved_fd); return 1; } if (new_fd != -2) { if (dup2(new_fd, fd_target) == -1) { perror("dup2"); close(new_fd); if (saved_fd != -1) close(saved_fd); return 1; } close(new_fd); } int ret = execution(redir->child); if (saved_fd != -1) { dup2(saved_fd, fd_target); close(saved_fd); } else { close(fd_target); } return ret; } default: { return 127; } } }