diff --git a/AGENTS.md b/AGENTS.md index b07a7fa..3e09296 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,6 +2,8 @@ ## Project Structure & Module Organization - `src/` holds the shell implementation, with submodules for `builtins/`, `executor/`, `parser/`, and `variables/`. +- `src/builtins/` currently includes: `cd`, `echo`, `env`, `exit`, `export`, `pwd`, `unset`. +- `src/executor/` includes pipeline/process orchestration plus file redirections in `redirections.c`. - `include/` contains public headers used across modules. - `lib/` is populated at build time with third-party 42 libraries (libft, get_next_line, ft_printf, ft_args). - `docs/` stores project references and manual test notes (see `docs/tests.md`). @@ -19,11 +21,46 @@ - The codebase follows 42 **Norminette v4** rules. Run `norminette *.c *.h` (or on specific files) before submitting changes. - Keep file names lowercase with underscores (e.g., `src/builtins/echo/echo.c`). - Keep headers in `include/` and expose only what modules need. +- Before adding or changing code, check `allowed.txt` to know which functions are permitted. +- Any function not listed in `allowed.txt` is not allowed in this project. + +## Parser & Lexer Functionality (Current `src/parser`) +- Runtime entrypoint is `parse(line, minishell)` from `src/minishell.c` (`readline -> parse -> execute`). +- `parse` calls `lex(line)` and then converts token lists to `t_command` nodes with `parse_tokens`. +- `command_new` builds one command from tokens up to `TOKEN_PIPE`. +- `words_add` stores consecutive `TOKEN_WORD` tokens in `command->argv` and increments `argc`. +- `expand_envs` is currently a TODO (no `$VAR` expansion is applied in parser stage). +- Redirection tokens are converted into `t_redirection` and stored in `t_command.redirections`; heredocs are stored in `t_command.heredocs`. +- Path resolution is handled in executor (`executor_resolve_command_path`) before `execve`. +- `src/parser/lexer.c` provides a separate lexer (`lex`) that tokenizes into `TOKEN_WORD`, `TOKEN_PIPE`, `TOKEN_REDIRECT_IN`, `TOKEN_REDIRECT_OUT`, `TOKEN_APPEND`, and `TOKEN_HEREDOC`. +- The lexer tracks single/double quote context so metacharacters inside quotes remain part of words. +- Meta runs are read as contiguous chunks in `read_token` (for example, repeated `|`/`<`/`>` are captured as one token value). +- Current parser flow consumes lexer output directly. + +## Executor Redirections (Current `src/executor`) +- File redirections are applied in `src/executor/redirections.c` via `open` + `dup2` before command execution. +- Supported file redirections: input `<`, output truncate `>`, output append `>>`. +- Redirections are applied for both forked commands (child path) and single builtins executed in parent. +- Parent-builtin redirections save/restore `STDIN_FILENO` and `STDOUT_FILENO` after builtin execution. + +## Parser & Lexer Known Gaps +- Heredoc tokens are parsed and stored, but runtime heredoc execution/input feeding is still pending in executor. +- No explicit unmatched-quote syntax error handling is implemented in parser/lexer path. ## Testing Guidelines - There is no automated test runner. Use manual checks in `docs/tests.md` and basic shell behavior checks (pipes, redirects, builtins). +- A local builtin edge-case script exists at `tests/builtins_edge_cases.sh` (expects a compiled `./minishell`). - When debugging memory issues, run under valgrind and use the suppression file in `valgrind/readline.supp`. +## Builtins Status +- `cd`: handles `HOME` fallback, `cd -` via `OLDPWD`, updates `PWD`/`OLDPWD`, and returns failure on invalid usage (`too many arguments`, missing `HOME`/`OLDPWD`). +- `echo`: supports repeated `-n` flags (`-n`, `-nnn`) and prints remaining args preserving spaces between tokens. +- `env`: prints current environment as `KEY=VALUE`; returns failure when called with extra arguments. +- `exit`: validates numeric argument (with overflow checks), returns `1` on `too many arguments` without exiting, and exits with `2` on non-numeric argument. +- `export`: supports `NAME=VALUE` and `NAME` (stored as empty value), validates identifiers, and returns failure when any identifier is invalid. +- `pwd`: prints working directory using dynamic `getcwd` and returns failure if `getcwd` fails. +- `unset`: validates identifiers and removes matching variables from the environment map. + ## Commit & Pull Request Guidelines - Commit messages in this repo use a simple `type: summary` format (examples: `update: ...`, `fix: ...`). Keep summaries short and specific. - For PRs, include: diff --git a/Makefile b/Makefile index bb21283..91d7211 100644 --- a/Makefile +++ b/Makefile @@ -3,10 +3,10 @@ # ::: :::::::: # # Makefile :+: :+: :+: # # +:+ +:+ +:+ # -# By: sede-san builtins` y retorno de + error. +- Guardas nulas en `is_builtin`. + +## 2) `cd` (`src/builtins/cd/cd.c`) + +### Fallo +- `cd` con demasiados argumentos devolvia `2` (bash devuelve `1`). +- `cd` sin `HOME` acababa llamando a `chdir(NULL)`. +- El manejo de error usaba comprobaciones invertidas (`access`) y codigos + incorrectos. +- No se actualizaban `PWD` y `OLDPWD` tras `chdir` exitoso. +- `cd -` (usar `OLDPWD`) no estaba soportado. + +### Por que fallaba +- Codigos de salida incompatibles con el comportamiento esperado del shell. +- `HOME` no definido no se controlaba antes del `chdir`. +- La logica de `access` estaba al reves y mezclaba condiciones. +- Variables de entorno del directorio quedaban desincronizadas. +- Faltaba resolver el caso especial de `-` hacia `OLDPWD`. + +### Solucion +- Refactor en `resolve_cd_path()` para validar argumentos y `HOME`. +- Retorno `EXIT_FAILURE` en `too many arguments` y `HOME not set`. +- Error de `chdir` simplificado a `perror("minishell: cd")` + retorno `1`. +- Actualizacion de `OLDPWD` y `PWD` mediante `getcwd(NULL, 0)` + `set_env()`. +- Soporte de `cd -`: usa `OLDPWD`, valida `OLDPWD not set` e imprime el nuevo + directorio tras el cambio. + +## 3) `exit` (`src/builtins/exit/exit.c`) + +### Fallo +- Habia un `printf` de debug en ejecucion real. +- `exit ` devolvia `2` pero no cerraba el shell. +- `exit n m` devolvia `2`; en bash es `1` y no sale del shell. +- Validacion numerica basada en `ft_strisnum` sin control de overflow. +- Se mostraba `exit` incluso en contexto no interactivo. + +### Por que fallaba +- Debug residual contamina salida. +- Semantica de `exit` incompleta respecto a bash. +- Valores fuera de rango podian tratarse como validos por conversion directa. +- Mensaje `exit` debe mostrarse solo en shell interactivo. + +### Solucion +- Eliminado debug print. +- Nuevo flujo `resolve_exit_status()`: + - Sin argumentos: usa `msh->exit_status`. + - Argumento no numerico o fuera de `long`: mensaje + `numeric argument required`, `msh->exit = true`, estado `2`. + - Demasiados argumentos: mensaje de error y estado `1`, sin salir. +- Parser numerico propio (`get_uint8_from_num` + `has_overflow`) con soporte + de signo y control de overflow. +- `ft_eputendl("exit")` solo si `isatty(STDIN_FILENO)`. + +## 4) `pwd` (`src/builtins/pwd/pwd.c`) + +### Fallo +- Si `getcwd` fallaba, el builtin devolvia `EXIT_SUCCESS`. +- Uso de buffer fijo (`PATH_MAX`) menos robusto para rutas largas. + +### Por que fallaba +- El shell reportaba exito aunque no pudiera obtener el directorio. +- Un buffer fijo puede truncar o fallar en escenarios de rutas profundas. + +### Solucion +- Cambio a `getcwd(NULL, 0)` con memoria dinamica. +- Si falla, `perror("minishell: pwd")` y retorno `EXIT_FAILURE`. +- `free()` del buffer dinamico tras imprimir. + +## 5) `echo` (`src/builtins/echo/echo.c`, `src/builtins/echo/echo_def.c`) + +### Cambio aplicado +- Ajuste de tipos de retorno auxiliares a `uint8_t` para mantener consistencia + con `builtins.h`. + +### Nota +- No se detectaron fallos funcionales criticos adicionales en la logica actual + de `echo` durante esta revision. + +## 6) Builtins faltantes: `env`, `export`, `unset` + +### Fallo +- Los builtins `env`, `export` y `unset` no estaban implementados ni + registrados en `set_builtins`. + +### Por que fallaba +- Comandos basicos de shell no existian en la tabla de builtins. +- Cualquier prueba/flujo que dependiera de gestion de variables exportadas + fallaba (listar, crear y eliminar variables de entorno). + +### Solucion +- Nuevos builtins implementados: + - `src/builtins/env/env.c` + - `src/builtins/export/export.c` + - `src/builtins/unset/unset.c` +- Registro en `src/builtins/builtins.c` (tabla ampliada de 4 a 7 entradas). +- Nuevos prototipos en `include/builtins.h`. +- Soporte de borrado real de entorno mediante `unset_env`: + - Declaracion en `include/core.h` + - Implementacion en `src/variables/environment_unset.c` + +## 7) Comportamiento aplicado en los nuevos builtins + +### `env` +- Si recibe argumentos, devuelve error (`minishell: env: too many arguments`) + y estado `1`. +- Sin argumentos, lista `KEY=VALUE` de las variables de entorno actuales. + +### `export` +- `export NAME=VALUE`: crea/actualiza la variable. +- `export NAME`: crea/actualiza con valor vacio (`NAME=`) para que aparezca + en `env`. +- Identificadores invalidos (`1A=2`, etc.) devuelven error + `not a valid identifier` y estado `1`. +- Sin argumentos, reutiliza el listado de `env`. + +### `unset` +- Elimina variables validas del entorno en memoria. +- Identificadores invalidos devuelven error + `not a valid identifier` y estado `1`. +- Si el identificador es valido y no existe, no falla (comportamiento shell). + +## Validacion realizada + +- `norminette` ejecutado sobre los archivos modificados: `OK`. +- Build completa no ejecutable en este entorno por falta de acceso de red al + clonado de librerias (`github.com`), por lo que no se pudo validar runtime + del binario en esta sesion. diff --git a/hola b/hola new file mode 100644 index 0000000..8093d87 --- /dev/null +++ b/hola @@ -0,0 +1 @@ +fds diff --git a/include/builtins.h b/include/builtins.h index c8dac83..01ae629 100644 --- a/include/builtins.h +++ b/include/builtins.h @@ -17,7 +17,7 @@ # include "minishell.h" # include "core.h" -typedef unsigned char (*t_builtin_func)(t_command cmd, t_minishell *minishell); +typedef uint8_t (*t_builtin_func)(t_command cmd, t_minishell *minishell); /******************************************************************************/ /* Functions */ @@ -25,24 +25,36 @@ typedef unsigned char (*t_builtin_func)(t_command cmd, t_minishell *minishell) /* builtins.c */ -extern u_int8_t set_builtins(t_minishell *minishell); +extern uint8_t set_builtins(t_minishell *minishell); -extern u_int8_t is_builtin(const char *command_name, t_minishell *minishell); +extern uint8_t is_builtin(const char *command_name, t_minishell *minishell); /* cd.c */ -extern u_int8_t builtin_cd(t_command cmd, t_minishell *minishell); +extern uint8_t builtin_cd(t_command cmd, t_minishell *minishell); /* echo.c */ -extern u_int8_t builtin_echo(t_command cmd, t_minishell *minishell); +extern uint8_t builtin_echo(t_command cmd, t_minishell *minishell); /* exit.c */ -extern u_int8_t builtin_exit(t_command cmd, t_minishell *minishell); +extern uint8_t builtin_exit(t_command cmd, t_minishell *minishell); /* pwd.c */ -extern u_int8_t builtin_pwd(t_command cmd, t_minishell *minishell); +extern uint8_t builtin_pwd(t_command cmd, t_minishell *minishell); + +/* env.c */ + +extern uint8_t builtin_env(t_command cmd, t_minishell *minishell); + +/* export.c */ + +extern uint8_t builtin_export(t_command cmd, t_minishell *minishell); + +/* unset.c */ + +extern uint8_t builtin_unset(t_command cmd, t_minishell *minishell); #endif /* BUILTINS_H */ diff --git a/include/core.h b/include/core.h index b333412..dddc0fb 100644 --- a/include/core.h +++ b/include/core.h @@ -82,12 +82,6 @@ typedef struct s_minishell bool exit; } t_minishell; -typedef struct s_redirection -{ - t_token_type type; - char *target; -} t_redirection; - /** * @brief Structure representing a single command in the shell * @@ -142,4 +136,6 @@ extern void free_envp(char **envp); extern char *get_env(const char *env_name, t_minishell *msh); +extern void unset_env(const char *env_name, t_minishell *msh); + #endif /* CORE_H */ diff --git a/include/executor.h b/include/executor.h index f2a49f4..d91de93 100644 --- a/include/executor.h +++ b/include/executor.h @@ -19,23 +19,42 @@ # define READ_PIPE 0 # define WRITE_PIPE 1 - -typedef struct s_pipeline -{ - int prev_read_fd; - int pipefd[2]; -} t_pipeline; - - -/******************************************************************************/ -/* Functions */ -/******************************************************************************/ - -// executor.c - # define PIPE_ERROR -1 # define FORK_ERROR -1 -extern uint8_t execute(t_list *command, t_minishell *minishell); +typedef struct s_pipeline +{ + int prev_read_fd; + int pipefd[2]; +} t_pipeline; + +typedef struct s_exec_state +{ + uint8_t exit_status; + t_pipeline pipeline; + t_list *current_command; + pid_t last_child_pid; +} t_exec_state; + +extern uint8_t execute(t_list *command, t_minishell *minishell); +extern uint8_t executor_execute_command(t_command *cmd, t_minishell *msh); +extern char *executor_resolve_command_path(const t_command *cmd, + t_minishell *msh); +extern bool executor_is_builtin_command(const t_command *cmd, + t_minishell *msh); +extern int executor_create_pipe_if_needed(t_list *node, t_pipeline *pl); +extern bool executor_is_fork_required(t_list *node, const t_pipeline *pl, + t_minishell *msh); +extern void executor_setup_child_input(t_pipeline *pipeline); +extern void executor_setup_child_output(t_list *node, t_pipeline *pl); +extern void executor_child_process(t_list *node, t_pipeline *pl, + t_minishell *msh); +extern void executor_parent_cleanup(t_list *node, t_pipeline *pl); +extern uint8_t executor_wait_for_children(pid_t last_child_pid); +extern void executor_cmdfree(t_command *command); +extern bool executor_apply_redirections(const t_command *command, + int *saved_stdin, int *saved_stdout); +extern void executor_restore_redirections(int saved_stdin, + int saved_stdout); #endif /* EXECUTOR_H */ diff --git a/out b/out new file mode 100644 index 0000000..4099407 --- /dev/null +++ b/out @@ -0,0 +1 @@ +23 diff --git a/src/builtins/builtins.c b/src/builtins/builtins.c index 0d8c89b..3dd536b 100644 --- a/src/builtins/builtins.c +++ b/src/builtins/builtins.c @@ -12,23 +12,53 @@ #include "builtins.h" -u_int8_t set_builtins( - t_minishell *minishell -) { - minishell->builtins - = ft_hashmap_new(4, ft_hashmap_hashstr, ft_hashmap_strcmp); - if (minishell->builtins == NULL) +static uint8_t register_builtin( + t_minishell *minishell, + const char *name, + t_builtin_func builtin +) +{ + char *key; + + key = ft_strdup(name); + if (key == NULL) return (0); - ft_hashmap_put(minishell->builtins, ft_strdup("cd"), builtin_cd); - ft_hashmap_put(minishell->builtins, ft_strdup("echo"), builtin_echo); - ft_hashmap_put(minishell->builtins, ft_strdup("exit"), builtin_exit); - ft_hashmap_put(minishell->builtins, ft_strdup("pwd"), builtin_pwd); + ft_hashmap_put(minishell->builtins, key, builtin); + if (!ft_hashmap_contains_key(minishell->builtins, name)) + { + free(key); + return (0); + } return (1); } -u_int8_t is_builtin( +uint8_t set_builtins( + t_minishell *minishell +) { + minishell->builtins + = ft_hashmap_new(7, ft_hashmap_hashstr, ft_hashmap_strcmp); + if (minishell->builtins == NULL) + return (0); + if (!register_builtin(minishell, "cd", builtin_cd) + || !register_builtin(minishell, "echo", builtin_echo) + || !register_builtin(minishell, "env", builtin_env) + || !register_builtin(minishell, "exit", builtin_exit) + || !register_builtin(minishell, "export", builtin_export) + || !register_builtin(minishell, "pwd", builtin_pwd) + || !register_builtin(minishell, "unset", builtin_unset)) + { + ft_hashmap_clear_keys(&minishell->builtins); + return (0); + } + return (1); +} + +uint8_t is_builtin( const char *command_name, t_minishell *minishell ) { + if (command_name == NULL || minishell == NULL + || minishell->builtins == NULL) + return (0); return (ft_hashmap_contains_key(minishell->builtins, command_name)); } diff --git a/src/builtins/cd/cd.c b/src/builtins/cd/cd.c index 2f508de..cb1979f 100644 --- a/src/builtins/cd/cd.c +++ b/src/builtins/cd/cd.c @@ -12,43 +12,104 @@ #include "builtins.h" -static u_int8_t handle_error(t_command cmd, t_minishell *msh, char *path); +static uint8_t handle_error(void); +static void update_pwd_vars(t_minishell *msh, char *oldpwd); +static char *get_path_from_env( + t_minishell *msh, + const char *env_name, + const char *error, + uint8_t *status + ); +static char *resolve_cd_path( + t_command cmd, + t_minishell *msh, + uint8_t *status + ); -u_int8_t builtin_cd( +uint8_t builtin_cd( t_command cmd, t_minishell *msh ){ char *path; + char *oldpwd; + uint8_t status; + bool show_pwd; - if (cmd.argc > 2) - { - ft_eputendl("minishell: cd: too many arguments"); - return (2); - } - else if (cmd.argc == 1) - path = get_env("HOME", msh); - else - path = cmd.argv[1]; + path = resolve_cd_path(cmd, msh, &status); + if (status != EXIT_SUCCESS) + return (status); + show_pwd = (cmd.argc == 2 && ft_strcmp(cmd.argv[1], "-") == 0); + oldpwd = getcwd(NULL, 0); if (chdir(path) == -1) - return (handle_error(cmd, msh, path)); + { + free(oldpwd); + return (handle_error()); + } + update_pwd_vars(msh, oldpwd); + if (show_pwd && get_env("PWD", msh) != NULL) + ft_putendl(get_env("PWD", msh)); + free(oldpwd); return (EXIT_SUCCESS); } -static u_int8_t handle_error( +static uint8_t handle_error(void) +{ + perror("minishell: cd"); + return (EXIT_FAILURE); +} + +static void update_pwd_vars( + t_minishell *msh, + char *oldpwd +){ + char *newpwd; + + if (oldpwd != NULL) + set_env("OLDPWD", oldpwd, msh); + newpwd = getcwd(NULL, 0); + if (newpwd != NULL) + { + set_env("PWD", newpwd, msh); + free(newpwd); + } +} + +static char *resolve_cd_path( t_command cmd, t_minishell *msh, - char *path + uint8_t *status ){ - u_int8_t exit_code; - - (void)msh; - exit_code = 0; - if (access(path, F_OK) != -1) - // No such file or directory - exit_code = 1; - else if (access(path, X_OK) == -1) - // Permission denied - exit_code = 2; - perror(cmd.argv[0]); - return (exit_code); + if (cmd.argc > 2) + { + ft_eputendl("minishell: cd: too many arguments"); + *status = EXIT_FAILURE; + return (NULL); + } + if (cmd.argc == 2 && ft_strcmp(cmd.argv[1], "-") == 0) + return (get_path_from_env(msh, "OLDPWD", + "minishell: cd: OLDPWD not set", status)); + if (cmd.argc == 1) + return (get_path_from_env(msh, "HOME", + "minishell: cd: HOME not set", status)); + *status = EXIT_SUCCESS; + return (cmd.argv[1]); +} + +static char *get_path_from_env( + t_minishell *msh, + const char *env_name, + const char *error, + uint8_t *status +){ + char *path; + + path = get_env(env_name, msh); + if (path == NULL) + { + ft_eputendl((char *)error); + *status = EXIT_FAILURE; + return (NULL); + } + *status = EXIT_SUCCESS; + return (path); } diff --git a/src/builtins/echo/echo.c b/src/builtins/echo/echo.c index 5a8a729..567235f 100644 --- a/src/builtins/echo/echo.c +++ b/src/builtins/echo/echo.c @@ -13,7 +13,7 @@ #include "builtins.h" #include "echo_def.h" -u_int8_t builtin_echo( +uint8_t builtin_echo( t_command cmd, t_minishell *msh ){ diff --git a/src/builtins/echo/echo_def.c b/src/builtins/echo/echo_def.c index fd60d0d..f5e5f65 100644 --- a/src/builtins/echo/echo_def.c +++ b/src/builtins/echo/echo_def.c @@ -13,7 +13,7 @@ #include "echo_def.h" static void assign_flags(t_args *args, t_command cmd, size_t *i); -static u_int8_t is_valid_flag(t_args *args, char *flag); +static uint8_t is_valid_flag(t_args *args, char *flag); static void assign_default_flags( t_args *args @@ -51,7 +51,7 @@ static void assign_flags( } } -static u_int8_t is_valid_flag( +static uint8_t is_valid_flag( t_args *args, char *flag ){ diff --git a/src/builtins/env/env.c b/src/builtins/env/env.c new file mode 100644 index 0000000..e79d06e --- /dev/null +++ b/src/builtins/env/env.c @@ -0,0 +1,58 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* env.c :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: sede-san 1) + { + ft_eputendl("minishell: env: too many arguments"); + return (EXIT_FAILURE); + } + print_env(msh); + return (EXIT_SUCCESS); +} + +static void print_entry( + t_map_entry *entry +) +{ + ft_putstr((char *)entry->key); + ft_putchar('='); + if (entry->value != NULL) + ft_putstr((char *)entry->value); + ft_putchar('\n'); +} + +static void print_env( + t_minishell *msh +) +{ + t_list *entries; + t_list *current; + + entries = ft_hashmap_entries(msh->variables.environment); + current = entries; + while (current != NULL) + { + print_entry((t_map_entry *)current->content); + current = current->next; + } + ft_lstclear_nodes(&entries); +} diff --git a/src/builtins/exit/exit.c b/src/builtins/exit/exit.c index 9d32e0d..a2aff19 100644 --- a/src/builtins/exit/exit.c +++ b/src/builtins/exit/exit.c @@ -11,7 +11,19 @@ /* ************************************************************************** */ #include "builtins.h" -#include +#include + +static uint8_t get_uint8_from_num(const char *arg, uint8_t *status); +static uint8_t has_overflow( + uint64_t n, + char digit, + uint64_t limit + ); +static uint8_t resolve_exit_status( + t_command cmd, + t_minishell *msh, + uint8_t *exit_status + ); uint8_t builtin_exit( t_command cmd, @@ -19,28 +31,78 @@ uint8_t builtin_exit( ) { uint8_t exit_status; - - ft_eputendl("exit"); - if (cmd.argc == 1) - exit_status = msh->exit_status; - else if (!ft_strisnum(cmd.argv[1])) - { - ft_eprintf( - "minishell: exit: %s: numeric argument required\n", - cmd.argv[1]); - return (2); - } - else if (cmd.argc > 2) - { - ft_eputendl("exit: too many arguments"); - return (2); - } - else - exit_status = (uint8_t)ft_atol(cmd.argv[1]); - printf("builtin_exit: exit_status=%d\n", exit_status); // Debug print - - msh->exit = 1; + if (isatty(STDIN_FILENO)) + ft_eputendl("exit"); + if (!resolve_exit_status(cmd, msh, &exit_status)) + return (msh->exit_status); + msh->exit = true; msh->exit_status = exit_status; return (exit_status); } + +static uint8_t resolve_exit_status( + t_command cmd, + t_minishell *msh, + uint8_t *exit_status +){ + if (cmd.argc == 1) + *exit_status = msh->exit_status; + else if (!get_uint8_from_num(cmd.argv[1], exit_status)) + { + ft_eprintf("minishell: exit: %s: numeric argument required\n", + cmd.argv[1]); + msh->exit = true; + msh->exit_status = 2; + return (0); + } + else if (cmd.argc > 2) + { + ft_eputendl("minishell: exit: too many arguments"); + msh->exit_status = EXIT_FAILURE; + return (0); + } + return (1); +} + +static uint8_t get_uint8_from_num( + const char *arg, + uint8_t *status +){ + uint64_t n; + uint64_t limit; + int sign; + + if (arg == NULL || *arg == '\0') + return (0); + n = 0; + sign = 1; + if (*arg == '+' || *arg == '-') + if (*arg++ == '-') + sign = -1; + if (*arg == '\0') + return (0); + limit = LONG_MAX; + if (sign == -1) + limit = (uint64_t)LONG_MAX + 1; + while (*arg != '\0') + { + if (!ft_isdigit(*arg) || has_overflow(n, *arg, limit)) + return (0); + n = (n * 10) + (*arg++ - '0'); + } + *status = (uint8_t)(n * sign); + return (1); +} + +static uint8_t has_overflow( + uint64_t n, + char digit, + uint64_t limit +){ + if (n > (limit / 10)) + return (1); + if (n == (limit / 10) && (uint64_t)(digit - '0') > (limit % 10)) + return (1); + return (0); +} diff --git a/src/builtins/export/export.c b/src/builtins/export/export.c new file mode 100644 index 0000000..e082ea7 --- /dev/null +++ b/src/builtins/export/export.c @@ -0,0 +1,79 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* export.c :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: sede-san + +static uint8_t resolve_execve_status(void) +{ + if (errno == ENOENT) + return (127); + if (errno == EACCES || errno == ENOEXEC || errno == EISDIR) + return (126); + return (EXIT_FAILURE); +} + +static uint8_t execute_builtin( + const t_command *command, + t_minishell *minishell +) +{ + const t_builtin_func builtin + = ft_hashmap_get(minishell->builtins, command->argv[0]); + + return (builtin(*command, minishell)); +} + +static void handle_execve_error( + const t_command *command, + char **envp +) +{ + uint8_t exit_status; + + exit_status = resolve_execve_status(); + free_envp(envp); + perror(command->path); + exit(exit_status); +} + +static void execute_external_command( + const t_command *command, + t_minishell *minishell +) +{ + char **envp; + + envp = get_envp(minishell); + if (envp == NULL) + { + perror("get_envp"); + exit(EXIT_FAILURE); + } + execve(command->path, command->argv, envp); + handle_execve_error(command, envp); +} + +uint8_t executor_execute_command( + t_command *command, + t_minishell *minishell +) +{ + if (command == NULL || command->argv == NULL || command->argv[0] == NULL) + return (EXIT_SUCCESS); + if (executor_is_builtin_command(command, minishell)) + return (execute_builtin(command, minishell)); + if (command->path != NULL) + free(command->path); + command->path = executor_resolve_command_path(command, minishell); + if (command->path == NULL) + { + ft_eprintf("minishell: %s: command not found\n", command->argv[0]); + return (127); + } + execute_external_command(command, minishell); + return (EXIT_FAILURE); +} diff --git a/src/executor/executor.c b/src/executor/executor.c index 9011705..645b106 100644 --- a/src/executor/executor.c +++ b/src/executor/executor.c @@ -6,198 +6,72 @@ /* By: sede-san exit_status = EXIT_SUCCESS; + state->pipeline.prev_read_fd = -1; + state->current_command = command_list; + state->last_child_pid = -1; +} + +static bool fork_current_command( + t_exec_state *state, t_minishell *minishell ) { - const t_builtin_func builtin - = ft_hashmap_get(minishell->builtins, command->path); + pid_t pid; - return (builtin(*command, minishell)); -} - -static void handle_execve_error( - const t_command *command, - char **envp -) -{ - free_envp(envp); - perror(command->path); - exit(EXIT_FAILURE); -} - -static void execute_external_command( - const t_command *command, - t_minishell *minishell -) -{ - char **envp; - - envp = get_envp(minishell); - if (envp == NULL) - { - perror("get_envp"); - exit(EXIT_FAILURE); - } - execve(command->path, command->argv, envp); - handle_execve_error(command, envp); -} - -static uint8_t execute_command( - const t_command *command, - t_minishell *minishell -) -{ - if (is_builtin(command->path, minishell)) - return (execute_builtin(command, minishell)); - else - { - execute_external_command(command, minishell); - return (EXIT_FAILURE); //! should never reach here - } -} - -static void cmdfree_argv( - char **argv -) -{ - size_t i; - - if (argv == NULL) - return ; - i = 0; - while (argv[i] != NULL) - { - free(argv[i]); - i++; - } - free(argv); -} - -static void cmdfree( - t_command *command -) -{ - if (command == NULL) - return ; - cmdfree_argv(command->argv); - free(command->path); - free(command); -} - -static int create_pipe_if_needed( - t_list *current_command, - t_pipeline *pipeline -) -{ - if (!current_command->next) - return (0); - if (pipe(pipeline->pipefd) == PIPE_ERROR) - return (perror("pipe"), PIPE_ERROR); - return (0); -} - -static bool is_fork_required( - t_list *current_command, - t_minishell *minishell -) { - const t_command *command = current_command->content; - - return (current_command->next != NULL - || !is_builtin(command->path, minishell)); -} - -static pid_t fork_process( - t_list *current_command, - t_minishell *minishell -) -{ - pid_t pid; - - pid = 0; - if (is_fork_required(current_command, minishell)) - pid = fork(); + pid = fork(); if (pid == FORK_ERROR) - perror("fork"); - return (pid); + return (perror("fork"), state->exit_status = EXIT_FAILURE, false); + if (pid == 0) + executor_child_process(state->current_command, &state->pipeline, + minishell); + state->last_child_pid = pid; + return (true); } -static void setup_child_input( - t_pipeline *pipeline -) -{ - if (pipeline->prev_read_fd != -1) - { - dup2(pipeline->prev_read_fd, STDIN_FILENO); - close(pipeline->prev_read_fd); - } -} - -static void setup_child_output( - t_list *current_command, - t_pipeline *pipeline -) -{ - if (current_command->next) - { - dup2(pipeline->pipefd[WRITE_PIPE], STDOUT_FILENO); - close(pipeline->pipefd[READ_PIPE]); - close(pipeline->pipefd[WRITE_PIPE]); - } -} - -static void child_process( - t_list *current_command, - t_pipeline *pipeline, +static bool run_current_command( + t_exec_state *state, t_minishell *minishell ) { - uint8_t exit_status; - const t_command *command = current_command->content; + bool should_fork; + t_command *command; + int saved_stdin; + int saved_stdout; - setup_child_input(pipeline); - setup_child_output(current_command, pipeline); - exit_status = execute_command(command, minishell); - if (is_fork_required(current_command, minishell)) - exit(exit_status); -} - -static void parent_cleanup( - t_list *current_command, - t_pipeline *pipeline -) -{ - if (pipeline->prev_read_fd != -1) - close(pipeline->prev_read_fd); - if (current_command->next) + if (executor_create_pipe_if_needed(state->current_command, + &state->pipeline) == PIPE_ERROR) + return (state->exit_status = EXIT_FAILURE, false); + should_fork = executor_is_fork_required(state->current_command, + &state->pipeline, minishell); + if (should_fork) { - close(pipeline->pipefd[WRITE_PIPE]); - pipeline->prev_read_fd = pipeline->pipefd[READ_PIPE]; + if (!fork_current_command(state, minishell)) + return (false); } else - pipeline->prev_read_fd = -1; -} - -static uint8_t wait_for_children(void) -{ - uint8_t exit_status; - int status; - - exit_status = EXIT_SUCCESS; - while (wait(&status) > 0) { - if (WIFEXITED(status)) - exit_status = WEXITSTATUS(status); + command = state->current_command->content; + if (!executor_apply_redirections(command, &saved_stdin, &saved_stdout)) + state->exit_status = EXIT_FAILURE; + else + state->exit_status = executor_execute_command(command, minishell); + executor_restore_redirections(saved_stdin, saved_stdout); } - return (exit_status); + executor_parent_cleanup(state->current_command, &state->pipeline); + state->current_command = state->current_command->next; + return (true); } uint8_t execute( @@ -205,26 +79,17 @@ uint8_t execute( t_minishell *minishell ) { - uint8_t exit_status; - t_pipeline pipeline; - t_list *current_command; - pid_t pid; + t_exec_state state; - pipeline.prev_read_fd = -1; - current_command = command_list; - while (current_command) + init_exec_state(&state, command_list); + while (state.current_command) { - if (create_pipe_if_needed(current_command, &pipeline) == PIPE_ERROR) + if (!run_current_command(&state, minishell)) break ; - pid = fork_process(current_command, minishell); - if (pid == FORK_ERROR) - break ; - if (pid == 0) - child_process(current_command, &pipeline, minishell); - parent_cleanup(current_command, &pipeline); - current_command = current_command->next; } - exit_status = wait_for_children(); - ft_lstclear(&command_list, (void (*)(void *))cmdfree); - return (exit_status); + if (state.last_child_pid > 0) + state.exit_status = executor_wait_for_children(state.last_child_pid); + minishell->exit_status = state.exit_status; + ft_lstclear(&command_list, (void (*)(void *))executor_cmdfree); + return (state.exit_status); } diff --git a/src/executor/path_resolver.c b/src/executor/path_resolver.c new file mode 100644 index 0000000..67cb80e --- /dev/null +++ b/src/executor/path_resolver.c @@ -0,0 +1,77 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* path_resolver.c :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: sede-san argv == NULL || command->argv[0] == NULL) + return (NULL); + command_name = command->argv[0]; + if (is_path_explicit(command_name)) + return (resolve_explicit_path(command_name)); + return (resolve_path_from_env(command_name, minishell)); +} diff --git a/src/executor/pipeline_helpers.c b/src/executor/pipeline_helpers.c new file mode 100644 index 0000000..9136868 --- /dev/null +++ b/src/executor/pipeline_helpers.c @@ -0,0 +1,73 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* pipeline_helpers.c :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: sede-san next) + return (0); + if (pipe(pipeline->pipefd) == PIPE_ERROR) + return (perror("pipe"), PIPE_ERROR); + return (0); +} + +bool executor_is_builtin_command( + const t_command *command, + t_minishell *minishell +) +{ + if (command == NULL || command->argv == NULL || command->argv[0] == NULL) + return (false); + return (is_builtin(command->argv[0], minishell)); +} + +bool executor_is_fork_required( + t_list *current_command, + const t_pipeline *pipeline, + t_minishell *minishell +) +{ + const t_command *command; + + command = current_command->content; + return (pipeline->prev_read_fd != -1 || current_command->next != NULL + || !executor_is_builtin_command(command, minishell)); +} + +void executor_setup_child_input( + t_pipeline *pipeline +) +{ + if (pipeline->prev_read_fd != -1) + { + dup2(pipeline->prev_read_fd, STDIN_FILENO); + close(pipeline->prev_read_fd); + } +} + +void executor_setup_child_output( + t_list *current_command, + t_pipeline *pipeline +) +{ + if (current_command->next) + { + dup2(pipeline->pipefd[WRITE_PIPE], STDOUT_FILENO); + close(pipeline->pipefd[READ_PIPE]); + close(pipeline->pipefd[WRITE_PIPE]); + } +} diff --git a/src/executor/process_helpers.c b/src/executor/process_helpers.c new file mode 100644 index 0000000..e34fc1e --- /dev/null +++ b/src/executor/process_helpers.c @@ -0,0 +1,112 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* process_helpers.c :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: sede-san content; + executor_setup_child_input(pipeline); + executor_setup_child_output(current_command, pipeline); + if (!executor_apply_redirections(command, NULL, NULL)) + exit(EXIT_FAILURE); + exit_status = executor_execute_command(command, minishell); + exit(exit_status); +} + +void executor_parent_cleanup( + t_list *current_command, + t_pipeline *pipeline +) +{ + if (pipeline->prev_read_fd != -1) + close(pipeline->prev_read_fd); + if (current_command->next) + { + close(pipeline->pipefd[WRITE_PIPE]); + pipeline->prev_read_fd = pipeline->pipefd[READ_PIPE]; + } + else + pipeline->prev_read_fd = -1; +} + +uint8_t executor_wait_for_children( + pid_t last_child_pid +) +{ + uint8_t exit_status; + int status; + pid_t pid; + + exit_status = EXIT_SUCCESS; + pid = wait(&status); + while (last_child_pid > 0 && pid > 0) + { + if (pid == last_child_pid) + { + if (WIFEXITED(status)) + exit_status = WEXITSTATUS(status); + else if (WIFSIGNALED(status)) + exit_status = 128 + WTERMSIG(status); + } + pid = wait(&status); + } + return (exit_status); +} + +static void cmdfree_argv( + char **argv +) +{ + size_t i; + + if (argv == NULL) + return ; + i = 0; + while (argv[i] != NULL) + { + free(argv[i]); + i++; + } + free(argv); +} + +static void cmdfree_redirection( + t_redirection *redirection +) +{ + if (redirection == NULL) + return ; + free(redirection->target); + free(redirection); +} + +void executor_cmdfree( + t_command *command +) +{ + if (command == NULL) + return ; + cmdfree_argv(command->argv); + free(command->path); + ft_lstclear(&command->redirections, + (void (*)(void *))cmdfree_redirection); + ft_lstclear(&command->heredocs, (void (*)(void *))cmdfree_redirection); + free(command); +} diff --git a/src/executor/redirections.c b/src/executor/redirections.c new file mode 100644 index 0000000..5ce94c2 --- /dev/null +++ b/src/executor/redirections.c @@ -0,0 +1,111 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* redirections.c :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: sede-san + +static int open_redirection_target( + const t_redirection *redirection +) +{ + if (redirection->type == TOKEN_REDIRECT_IN) + return (open(redirection->target, O_RDONLY)); + if (redirection->type == TOKEN_REDIRECT_OUT) + return (open(redirection->target, O_WRONLY | O_CREAT | O_TRUNC, 0644)); + if (redirection->type == TOKEN_APPEND) + return (open(redirection->target, O_WRONLY | O_CREAT | O_APPEND, 0644)); + errno = EINVAL; + return (-1); +} + +static bool backup_stream_if_needed( + const t_redirection *redirection, + int *saved_stdin, + int *saved_stdout +) +{ + if (redirection->type == TOKEN_REDIRECT_IN && saved_stdin != NULL + && *saved_stdin == -1) + { + *saved_stdin = dup(STDIN_FILENO); + if (*saved_stdin == -1) + return (perror("dup"), false); + } + if ((redirection->type == TOKEN_REDIRECT_OUT + || redirection->type == TOKEN_APPEND) && saved_stdout != NULL + && *saved_stdout == -1) + { + *saved_stdout = dup(STDOUT_FILENO); + if (*saved_stdout == -1) + return (perror("dup"), false); + } + return (true); +} + +bool executor_apply_redirections( + const t_command *command, + int *saved_stdin, + int *saved_stdout +) +{ + t_list *node; + t_redirection *redirection; + int fd; + + if (saved_stdin != NULL) + *saved_stdin = -1; + if (saved_stdout != NULL) + *saved_stdout = -1; + if (command == NULL || command->redirections == NULL) + return (true); + node = command->redirections; + while (node != NULL) + { + redirection = (t_redirection *)node->content; + if (redirection == NULL || redirection->target == NULL) + return (false); + if (!backup_stream_if_needed(redirection, saved_stdin, saved_stdout)) + return (false); + fd = open_redirection_target(redirection); + if (fd == -1) + return (perror(redirection->target), false); + if (redirection->type == TOKEN_REDIRECT_IN + && dup2(fd, STDIN_FILENO) == -1) + return (close(fd), perror("dup2"), false); + if ((redirection->type == TOKEN_REDIRECT_OUT + || redirection->type == TOKEN_APPEND) + && dup2(fd, STDOUT_FILENO) == -1) + return (close(fd), perror("dup2"), false); + close(fd); + node = node->next; + } + return (true); +} + +void executor_restore_redirections( + int saved_stdin, + int saved_stdout +) +{ + if (saved_stdin != -1) + { + if (dup2(saved_stdin, STDIN_FILENO) == -1) + perror("dup2"); + close(saved_stdin); + } + if (saved_stdout != -1) + { + if (dup2(saved_stdout, STDOUT_FILENO) == -1) + perror("dup2"); + close(saved_stdout); + } +} diff --git a/src/minishell.c b/src/minishell.c index 75560c9..387d24a 100644 --- a/src/minishell.c +++ b/src/minishell.c @@ -46,7 +46,7 @@ void minishell_run( { add_history(line); commands = parse(line, minishell); - execute(commands, minishell); + minishell->exit_status = execute(commands, minishell); } free(line); } diff --git a/src/parser/parser.c b/src/parser/parser.c index c97d3ff..cebfad9 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -6,7 +6,7 @@ /* By: sede-san argc); -// printf(" argv: ["); -// for (int i = 0; i < command->argc; i++) -// { -// printf("%s", command->argv[i]); -// if (i < command->argc - 1) -// printf(", "); -// } -// printf("]\n"); -// printf(" path: %s\n", command->path); -// printf(" redirections:\n"); -// t_list *redirection_node = command->redirections; -// while (redirection_node != NULL) -// { -// t_redirection *redirection = (t_redirection *)redirection_node->content; -// printf(" type: %d, target: %s\n", redirection->type, redirection->target); -// redirection_node = redirection_node->next; -// } -// printf(" heredocs:\n"); -// t_list *heredoc_node = command->heredocs; -// while (heredoc_node != NULL) -// { -// t_redirection *heredoc = (t_redirection *)heredoc_node->content; -// printf(" type: %d, target: %s\n", heredoc->type, heredoc->target); -// heredoc_node = heredoc_node->next; -// } -// } - -// int main(int argc, char const *argv[], char **envp) -// { -// t_minishell minishell; -// t_list *commands; -// char *line; - -// if (argc != 2) -// return (EXIT_FAILURE); - -// minishell_init(&minishell, envp); - -// line = ft_strdup(argv[1]); -// commands = parse(line, NULL); -// ft_lstiter(commands, (void (*)(void *))print_command_info); -// if (line != NULL) -// free(line); -// if (commands != NULL) -// ft_lstclear(&commands, (void (*)(void *))command_clear); -// return 0; -// } diff --git a/src/variables/environment.c b/src/variables/environment.c index 747db01..3461a4a 100644 --- a/src/variables/environment.c +++ b/src/variables/environment.c @@ -32,26 +32,37 @@ void set_envp( char **envp, t_minishell *msh ) { - char **splitted_env; + char *equal_sign; + char *key; + char *value; + if (msh == NULL || envp == NULL) + return ; msh->variables.environment = ft_hashmap_new(32, ft_hashmap_hashstr, ft_hashmap_strcmp); - if (msh == NULL) - { - ft_hashmap_clear(&msh->variables.environment, free); + if (msh->variables.environment == NULL) return ; - } while (*envp != NULL) { - splitted_env = ft_split(*envp, '='); - if (splitted_env == NULL) + equal_sign = ft_strchr(*envp, '='); + if (equal_sign == NULL) { + key = ft_strdup(*envp); + value = ft_strdup(""); + } + else + { + key = ft_substr(*envp, 0, equal_sign - *envp); + value = ft_strdup(equal_sign + 1); + } + if (key == NULL || value == NULL) + { + free(key); + free(value); ft_hashmap_clear(&msh->variables.environment, free); return ; } - ft_hashmap_put(msh->variables.environment, - ft_strdup(splitted_env[0]), ft_strdup(splitted_env[1])); - ft_free_split(splitted_env); + ft_hashmap_put(msh->variables.environment, key, value); envp++; } } @@ -118,7 +129,9 @@ char **get_envp( size_t i; env_list = ft_hashmap_entries(msh->variables.environment); - envp = (char **)malloc((msh->variables.environment->size + 1) * sizeof(char *)); + envp = (char **)malloc( + (msh->variables.environment->size + 1) * sizeof(char *) + ); if (envp != NULL) { i = 0; diff --git a/src/variables/environment_unset.c b/src/variables/environment_unset.c new file mode 100644 index 0000000..feae147 --- /dev/null +++ b/src/variables/environment_unset.c @@ -0,0 +1,41 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* environment_unset.c :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: sede-san variables.environment); + current = entries; + while (current != NULL) + { + entry = current->content; + if (ft_strcmp(entry->key, env_name) != 0) + ft_hashmap_put(new_env, + ft_strdup(entry->key), ft_strdup(entry->value)); + current = current->next; + } + ft_lstclear_nodes(&entries); + ft_hashmap_clear(&msh->variables.environment, free); + msh->variables.environment = new_env; +} diff --git a/tests/builtins_edge_cases.sh b/tests/builtins_edge_cases.sh new file mode 100755 index 0000000..f0e6477 --- /dev/null +++ b/tests/builtins_edge_cases.sh @@ -0,0 +1,204 @@ +#!/usr/bin/env bash + +set -u + +ROOT_DIR="$(cd -- "$(dirname -- "$0")/.." && pwd)" +MSH_BIN="$ROOT_DIR/minishell" +TMP_DIR="$(mktemp -d /tmp/minishell-builtins-tests.XXXXXX)" + +PASS_COUNT=0 +FAIL_COUNT=0 +CASE_OUT="" +CASE_ERR="" +CASE_EC=0 +CASE_NAME="" +CASE_FAILED=0 + +cleanup() { + rm -rf "$TMP_DIR" +} + +pass() { + printf "PASS: %s\n" "$1" + PASS_COUNT=$((PASS_COUNT + 1)) +} + +fail() { + printf "FAIL: %s\n" "$1" + FAIL_COUNT=$((FAIL_COUNT + 1)) + CASE_FAILED=1 +} + +assert_exit_code() { + local expected="$1" + if [ "$CASE_EC" -ne "$expected" ]; then + fail "$CASE_NAME -> exit code esperado $expected, obtenido $CASE_EC" + fi +} + +assert_err_contains() { + local text="$1" + if ! grep -Fq -- "$text" "$CASE_ERR"; then + fail "$CASE_NAME -> stderr no contiene: $text" + fi +} + +assert_out_contains() { + local text="$1" + if ! grep -Fq -- "$text" "$CASE_OUT"; then + fail "$CASE_NAME -> stdout no contiene: $text" + fi +} + +assert_out_regex_count() { + local regex="$1" + local expected="$2" + local count + + count="$(grep -Ec -- "$regex" "$CASE_OUT" || true)" + if [ "$count" -ne "$expected" ]; then + fail "$CASE_NAME -> regex [$regex] esperado $expected, obtenido $count" + fi +} + +run_case() { + local name="$1" + local input="$2" + local expected_exit="$3" + local clean_env="${4:-0}" + + CASE_NAME="$name" + CASE_OUT="$TMP_DIR/$name.out" + CASE_ERR="$TMP_DIR/$name.err" + CASE_EC=0 + CASE_FAILED=0 + + if [ "$clean_env" -eq 1 ]; then + printf "%b" "$input" | env -i PATH="$PATH" "$MSH_BIN" >"$CASE_OUT" 2>"$CASE_ERR" + else + printf "%b" "$input" | "$MSH_BIN" >"$CASE_OUT" 2>"$CASE_ERR" + fi + CASE_EC=$? + assert_exit_code "$expected_exit" + if [ "$CASE_FAILED" -eq 0 ]; then + pass "$CASE_NAME" + fi +} + +run_pwd_deleted_dir_case() { + local tmpd + + tmpd="$TMP_DIR/pwd_deleted_dir" + mkdir -p "$tmpd" + run_case "pwd_deleted_dir" \ + "cd $tmpd\n/bin/rmdir $tmpd\npwd\nexit\n" \ + 1 + assert_err_contains "minishell: pwd:" + if [ "$CASE_FAILED" -eq 0 ]; then + pass "$CASE_NAME assertions" + fi +} + +trap cleanup EXIT + +if [ ! -x "$MSH_BIN" ]; then + printf "Compilando minishell...\n" + if ! make -C "$ROOT_DIR" >/dev/null; then + printf "No se pudo compilar minishell\n" + exit 1 + fi +fi + +run_case "exit_non_numeric" "exit abc\n" 2 +assert_err_contains "numeric argument required" +if [ "$CASE_FAILED" -eq 0 ]; then + pass "$CASE_NAME assertions" +fi + +run_case "exit_sign_only" "exit +\n" 2 +assert_err_contains "numeric argument required" +if [ "$CASE_FAILED" -eq 0 ]; then + pass "$CASE_NAME assertions" +fi + +run_case "exit_negative_wrap" "exit -1\n" 255 + +run_case "exit_too_many_args" "exit 7 8\npwd\nexit\n" 0 +assert_err_contains "too many arguments" +assert_out_contains "$ROOT_DIR" +if [ "$CASE_FAILED" -eq 0 ]; then + pass "$CASE_NAME assertions" +fi + +run_case "cd_home_not_set" "cd\nexit\n" 1 1 +assert_err_contains "HOME not set" +if [ "$CASE_FAILED" -eq 0 ]; then + pass "$CASE_NAME assertions" +fi + +run_case "cd_invalid_path" "cd /definitely/not/here\nexit\n" 1 +assert_err_contains "No such file or directory" +if [ "$CASE_FAILED" -eq 0 ]; then + pass "$CASE_NAME assertions" +fi + +run_case "cd_dash_roundtrip" "cd /tmp\ncd -\npwd\nexit\n" 0 +assert_out_contains "$ROOT_DIR" +if [ "$CASE_FAILED" -eq 0 ]; then + pass "$CASE_NAME assertions" +fi + +run_pwd_deleted_dir_case + +run_case "echo_flags" "echo -nn hi\necho hi-there\nexit\n" 0 +assert_out_contains "himinishell >" +assert_out_contains "hi-there" +if [ "$CASE_FAILED" -eq 0 ]; then + pass "$CASE_NAME assertions" +fi + +run_case "env_builtin_with_arg" "env EXTRA\nexit\n" 1 +assert_err_contains "too many arguments" +if [ "$CASE_FAILED" -eq 0 ]; then + pass "$CASE_NAME assertions" +fi + +run_case "export_unset_cycle" \ + "export FOO=bar\n/usr/bin/env\nunset FOO\n/usr/bin/env\nexit\n" \ + 0 +assert_out_regex_count "^FOO=bar$" 1 +if [ "$CASE_FAILED" -eq 0 ]; then + pass "$CASE_NAME assertions" +fi + +run_case "env_builtin_lists_exported" \ + "export FOO=bar\nenv\nunset FOO\nenv\nexit\n" \ + 0 +assert_out_regex_count "^FOO=bar$" 1 +if [ "$CASE_FAILED" -eq 0 ]; then + pass "$CASE_NAME assertions" +fi + +run_case "export_invalid_identifier" "export 1A=2\nexit\n" 1 +assert_err_contains "not a valid identifier" +if [ "$CASE_FAILED" -eq 0 ]; then + pass "$CASE_NAME assertions" +fi + +run_case "unset_invalid_identifier" "unset 1A\nexit\n" 1 +assert_err_contains "not a valid identifier" +if [ "$CASE_FAILED" -eq 0 ]; then + pass "$CASE_NAME assertions" +fi + +run_case "export_without_value" "unset ZZZ\nexport ZZZ\n/usr/bin/env\nexit\n" 0 +assert_out_regex_count "^ZZZ=$" 1 +if [ "$CASE_FAILED" -eq 0 ]; then + pass "$CASE_NAME assertions" +fi + +printf "\nResumen tests builtins: PASS=%d FAIL=%d\n" "$PASS_COUNT" "$FAIL_COUNT" +if [ "$FAIL_COUNT" -ne 0 ]; then + exit 1 +fi +exit 0