Merge branch 'feature/executor-gl' into solo

This commit is contained in:
2026-02-12 21:26:12 +01:00
30 changed files with 1633 additions and 353 deletions

View File

@@ -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:

View File

@@ -3,10 +3,10 @@
# ::: :::::::: #
# Makefile :+: :+: :+: #
# +:+ +:+ +:+ #
# By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ #
# By: marcnava <marcnava@student.42madrid.com +#+ +:+ +#+ #
# +#+#+#+#+#+ +#+ #
# Created: 2025/07/30 20:22:21 by sede-san #+# #+# #
# Updated: 2026/02/05 21:06:52 by sede-san ### ########.fr #
# Updated: 2026/02/09 22:44:34 by marcnava ### ########.fr #
# #
# **************************************************************************** #
@@ -17,7 +17,7 @@ NAME = minishell
# ************************** Compilation variables *************************** #
CC = cc
CFLAGS = -Wall -Wextra -Werror
CFLAGS = -Wall -Wextra #-Werror
HEADERS = -I $(INCLUDE_PATH) $(LIBS_INCLUDE)
ifeq ($(DEBUG), lldb) # debug with LLDB

1
adios Normal file
View File

@@ -0,0 +1 @@
dfsa

177
allowed.txt Normal file
View File

@@ -0,0 +1,177 @@
[minishell_allowed]
readline
rl_clear_history
rl_on_new_line
rl_replace_line
rl_redisplay
add_history
printf
malloc
free
write
access
open
read
close
fork
wait
waitpid
wait3
wait4
signal
sigaction
sigemptyset
sigaddset
kill
exit
getcwd
chdir
stat
lstat
fstat
unlink
execve
dup
dup2
pipe
opendir
readdir
closedir
strerror
perror
isatty
ttyname
ttyslot
ioctl
getenv
tcsetattr
tcgetattr
tgetent
tgetflag
tgetnum
tgetstr
tgoto
tputs
[libft]
ft_atoi
ft_atoi_base
ft_atol
ft_bzero
ft_calloc
ft_cdlstadd_back
ft_cdlstadd_front
ft_cdlstclear
ft_cdlstdelone
ft_cdlstiter
ft_cdlstlast
ft_cdlstmap
ft_cdlstnew
ft_cdlstsize
ft_clstadd_back
ft_clstadd_front
ft_clstclear
ft_clstdelone
ft_clstiter
ft_clstlast
ft_clstmap
ft_clstnew
ft_clstsize
ft_dlstadd_back
ft_dlstadd_front
ft_dlstclear
ft_dlstdelone
ft_dlstiter
ft_dlstlast
ft_dlstmap
ft_dlstnew
ft_dlstsize
ft_eputchar
ft_eputendl
ft_eputnbr
ft_eputstr
ft_free
ft_free_split
ft_hashmap_clear
ft_hashmap_clear_keys
ft_hashmap_contains_key
ft_hashmap_entries
ft_hashmap_get
ft_hashmap_hashstr
ft_hashmap_new
ft_hashmap_put
ft_hashmap_remove
ft_hashmap_strcmp
ft_hashstr
ft_iabs
ft_imin
ft_isalnum
ft_isalpha
ft_isascii
ft_iscntrl
ft_isdigit
ft_islower
ft_isprint
ft_isspace
ft_isupper
ft_itoa
ft_itoa_base
ft_lstadd_back
ft_lstadd_front
ft_lstclear
ft_lstclear_nodes
ft_lstdelone
ft_lstiter
ft_lstlast
ft_lstmap
ft_lstnew
ft_lstsize
ft_ltoa
ft_memchr
ft_memcmp
ft_memcpy
ft_memmove
ft_memset
ft_nsplit
ft_pow
ft_putchar
ft_putchar_fd
ft_putendl
ft_putendl_fd
ft_putnbr
ft_putnbr_fd
ft_putstr
ft_putstr_fd
ft_realloc
ft_split
ft_strchr
ft_strcmp
ft_strdup
ft_strisnum
ft_striteri
ft_strjoin
ft_strlcat
ft_strlcpy
ft_strlen
ft_strmapi
ft_strncmp
ft_strncpy
ft_strnjoin
ft_strnstr
ft_strrchr
ft_strtrim
ft_substr
ft_swap
ft_tolower
ft_toupper
ft_uitoa
ft_uitoa_base
ft_ultoa_base
[get_next_line]
get_next_line
[ft_printf]
ft_eprintf
ft_fprintf
ft_printf

151
docs/builtins_fixes.md Normal file
View File

@@ -0,0 +1,151 @@
# Correcciones en Builtins
Este documento resume los fallos detectados en los builtins actuales y la
solucion aplicada en codigo.
## 1) Infraestructura de builtins (`include/builtins.h`, `src/builtins/builtins.c`)
### Fallo
- Uso inconsistente de tipos (`u_int8_t`/`unsigned char` frente a `uint8_t`).
- `set_builtins` no comprobaba fallos de registro por builtin (duplicado de
clave o insercion en hashmap), pudiendo dejar estado parcial.
- `is_builtin` podia dereferenciar punteros nulos.
### Por que fallaba
- `u_int8_t` no es el tipo estandar C99 y depende de plataforma/headers.
- Si falla una insercion, la tabla quedaba inicializada parcialmente sin
rollback.
- En errores de inicializacion, consultar `is_builtin` podia romper.
### Solucion
- Unificacion de firmas a `uint8_t`.
- Nuevo helper `register_builtin()` con validacion tras `ft_hashmap_put`.
- Si falla cualquier alta: limpieza de `minishell->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 <no_numerico>` 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.

1
hola Normal file
View File

@@ -0,0 +1 @@
fds

View File

@@ -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 */

View File

@@ -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 */

View File

@@ -19,6 +19,8 @@
# define READ_PIPE 0
# define WRITE_PIPE 1
# define PIPE_ERROR -1
# define FORK_ERROR -1
typedef struct s_pipeline
{
@@ -26,16 +28,33 @@ typedef struct s_pipeline
int pipefd[2];
} t_pipeline;
/******************************************************************************/
/* Functions */
/******************************************************************************/
// executor.c
# define PIPE_ERROR -1
# define FORK_ERROR -1
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 */

1
out Normal file
View File

@@ -0,0 +1 @@
23

View File

@@ -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));
}

View File

@@ -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);
}

View File

@@ -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
){

View File

@@ -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
){

58
src/builtins/env/env.c vendored Normal file
View File

@@ -0,0 +1,58 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* env.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/09 22:05:00 by codex #+# #+# */
/* Updated: 2026/02/09 22:05:00 by codex ### ########.fr */
/* */
/* ************************************************************************** */
#include "builtins.h"
static void print_entry(t_map_entry *entry);
static void print_env(t_minishell *msh);
uint8_t builtin_env(
t_command cmd,
t_minishell *msh
)
{
if (cmd.argc > 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);
}

View File

@@ -11,7 +11,19 @@
/* ************************************************************************** */
#include "builtins.h"
#include <stdint.h>
#include <limits.h>
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,
@@ -20,27 +32,77 @@ uint8_t builtin_exit(
{
uint8_t exit_status;
if (isatty(STDIN_FILENO))
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 (!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);
}

View File

@@ -0,0 +1,79 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* export.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/09 22:05:00 by codex #+# #+# */
/* Updated: 2026/02/09 22:05:00 by codex ### ########.fr */
/* */
/* ************************************************************************** */
#include "builtins.h"
static uint8_t is_valid_identifier(const char *arg, size_t name_len);
static uint8_t export_one(char *arg, t_minishell *msh);
uint8_t builtin_export(
t_command cmd,
t_minishell *msh
)
{
uint8_t status;
size_t i;
if (cmd.argc == 1)
return (builtin_env(cmd, msh));
status = EXIT_SUCCESS;
i = 0;
while (cmd.argv[++i] != NULL)
if (export_one(cmd.argv[i], msh) != EXIT_SUCCESS)
status = EXIT_FAILURE;
return (status);
}
static uint8_t is_valid_identifier(
const char *arg,
size_t name_len
)
{
size_t i;
if (name_len == 0 || arg == NULL)
return (0);
if (!ft_isalpha(arg[0]) && arg[0] != '_')
return (0);
i = 0;
while (++i < name_len)
if (!ft_isalnum(arg[i]) && arg[i] != '_')
return (0);
return (1);
}
static uint8_t export_one(
char *arg,
t_minishell *msh
)
{
char *eq_pos;
char *name;
size_t name_len;
eq_pos = ft_strchr(arg, '=');
name_len = ft_strlen(arg);
if (eq_pos != NULL)
name_len = (size_t)(eq_pos - arg);
if (!is_valid_identifier(arg, name_len))
return (ft_eprintf("minishell: export: `%s': not a valid identifier\n",
arg), EXIT_FAILURE);
name = ft_substr(arg, 0, name_len);
if (name == NULL)
return (EXIT_FAILURE);
if (eq_pos != NULL)
set_env(name, eq_pos + 1, msh);
else
set_env(name, "", msh);
free(name);
return (EXIT_SUCCESS);
}

View File

@@ -12,15 +12,21 @@
#include "builtins.h"
u_int8_t builtin_pwd(
uint8_t builtin_pwd(
t_command cmd,
t_minishell *msh
){
char cwd[PATH_MAX];
char *cwd;
(void)cmd;
(void)msh;
if (getcwd(cwd, PATH_MAX) != NULL)
cwd = getcwd(NULL, 0);
if (cwd == NULL)
{
perror("minishell: pwd");
return (EXIT_FAILURE);
}
ft_putendl(cwd);
free(cwd);
return (EXIT_SUCCESS);
}

View File

@@ -0,0 +1,61 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* unset.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/09 22:05:00 by codex #+# #+# */
/* Updated: 2026/02/09 22:05:00 by codex ### ########.fr */
/* */
/* ************************************************************************** */
#include "builtins.h"
static uint8_t is_valid_identifier(const char *arg);
static uint8_t unset_one(char *arg, t_minishell *msh);
uint8_t builtin_unset(
t_command cmd,
t_minishell *msh
)
{
uint8_t status;
size_t i;
status = EXIT_SUCCESS;
i = 0;
while (cmd.argv[++i] != NULL)
if (unset_one(cmd.argv[i], msh) != EXIT_SUCCESS)
status = EXIT_FAILURE;
return (status);
}
static uint8_t is_valid_identifier(
const char *arg
)
{
size_t i;
if (arg == NULL || *arg == '\0')
return (0);
if (!ft_isalpha(arg[0]) && arg[0] != '_')
return (0);
i = 0;
while (arg[++i] != '\0')
if (!ft_isalnum(arg[i]) && arg[i] != '_')
return (0);
return (1);
}
static uint8_t unset_one(
char *arg,
t_minishell *msh
)
{
if (!is_valid_identifier(arg))
return (ft_eprintf("minishell: unset: `%s': not a valid identifier\n",
arg), EXIT_FAILURE);
unset_env(arg, msh);
return (EXIT_SUCCESS);
}

View File

@@ -0,0 +1,86 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* command_exec.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/11 00:00:00 by sede-san #+# #+# */
/* Updated: 2026/02/11 00:00:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "executor.h"
#include "builtins.h"
#include <errno.h>
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);
}

View File

@@ -6,198 +6,72 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/08 19:10:47 by sede-san #+# #+# */
/* Updated: 2026/02/08 21:32:03 by sede-san ### ########.fr */
/* Updated: 2026/02/11 00:00:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "executor.h"
#include "builtins.h"
static inline uint8_t execute_builtin(
const t_command *command,
t_minishell *minishell
static void init_exec_state(
t_exec_state *state,
t_list *command_list
)
{
const t_builtin_func builtin
= ft_hashmap_get(minishell->builtins, command->path);
return (builtin(*command, minishell));
state->exit_status = EXIT_SUCCESS;
state->pipeline.prev_read_fd = -1;
state->current_command = command_list;
state->last_child_pid = -1;
}
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,
static bool fork_current_command(
t_exec_state *state,
t_minishell *minishell
)
{
pid_t pid;
pid = 0;
if (is_fork_required(current_command, minishell))
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);
}

View File

@@ -0,0 +1,77 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* path_resolver.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/11 00:00:00 by sede-san #+# #+# */
/* Updated: 2026/02/11 00:00:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "executor.h"
static bool is_path_explicit(
const char *command_name
)
{
return (command_name != NULL && ft_strchr(command_name, '/') != NULL);
}
static char *resolve_path_from_env(
const char *command_name,
t_minishell *minishell
)
{
char *command_path;
char **path_env;
char *path_value;
size_t i;
path_value = get_env("PATH", minishell);
if (path_value == NULL)
return (NULL);
path_env = ft_split(path_value, ':');
if (path_env == NULL)
return (NULL);
command_path = NULL;
i = -1;
while (!command_path && path_env[++i] != NULL)
{
command_path = ft_strnjoin(3, path_env[i], "/", command_name);
if (command_path != NULL && access(command_path, X_OK) != EXIT_SUCCESS)
{
free(command_path);
command_path = NULL;
}
}
ft_free_split(path_env);
return (command_path);
}
static char *resolve_explicit_path(
const char *command_name
)
{
if (access(command_name, F_OK) != EXIT_SUCCESS)
return (NULL);
if (access(command_name, X_OK) != EXIT_SUCCESS)
return (NULL);
return (ft_strdup(command_name));
}
char *executor_resolve_command_path(
const t_command *command,
t_minishell *minishell
)
{
const char *command_name;
if (command == NULL || command->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));
}

View File

@@ -0,0 +1,73 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* pipeline_helpers.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/11 00:00:00 by sede-san #+# #+# */
/* Updated: 2026/02/11 00:00:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "executor.h"
#include "builtins.h"
int executor_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);
}
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]);
}
}

View File

@@ -0,0 +1,112 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* process_helpers.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/11 00:00:00 by sede-san #+# #+# */
/* Updated: 2026/02/11 00:00:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "executor.h"
void executor_child_process(
t_list *current_command,
t_pipeline *pipeline,
t_minishell *minishell
)
{
uint8_t exit_status;
t_command *command;
command = current_command->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);
}

111
src/executor/redirections.c Normal file
View File

@@ -0,0 +1,111 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* redirections.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/12 00:00:00 by sede-san #+# #+# */
/* Updated: 2026/02/12 00:00:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "executor.h"
#include <errno.h>
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);
}
}

View File

@@ -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);
}

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/22 18:37:38 by sede-san #+# #+# */
/* Updated: 2026/02/12 20:55:49 by sede-san ### ########.fr */
/* Updated: 2026/02/12 21:25:39 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -488,57 +488,3 @@ t_list *ft_lstfind(
}
return (NULL);
}
// void print_command_info(
// t_command *command
// )
// {
// printf("Command:\n");
// printf(" argc: %d\n", command->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;
// }

View File

@@ -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;

View File

@@ -0,0 +1,41 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* environment_unset.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/09 22:16:00 by codex #+# #+# */
/* Updated: 2026/02/09 22:16:00 by codex ### ########.fr */
/* */
/* ************************************************************************** */
#include "minishell.h"
#include "core.h"
void unset_env(
const char *env_name,
t_minishell *msh
){
t_hashmap *new_env;
t_list *entries;
t_list *current;
t_map_entry *entry;
new_env = ft_hashmap_new(32, ft_hashmap_hashstr, ft_hashmap_strcmp);
if (new_env == NULL)
return ;
entries = ft_hashmap_entries(msh->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;
}

204
tests/builtins_edge_cases.sh Executable file
View File

@@ -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