1 Commits

Author SHA1 Message Date
1c418dded0 Merge pull request 'save commit' (#1) from solo into feature/executor
Reviewed-on: #1
2026-02-09 20:48:16 +01:00
19 changed files with 85 additions and 1044 deletions

View File

@@ -2,7 +2,6 @@
## Project Structure & Module Organization ## Project Structure & Module Organization
- `src/` holds the shell implementation, with submodules for `builtins/`, `executor/`, `parser/`, and `variables/`. - `src/` holds the shell implementation, with submodules for `builtins/`, `executor/`, `parser/`, and `variables/`.
- `src/builtins/` currently includes: `cd`, `echo`, `env`, `exit`, `export`, `pwd`, `unset`.
- `include/` contains public headers used across modules. - `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). - `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`). - `docs/` stores project references and manual test notes (see `docs/tests.md`).
@@ -20,23 +19,11 @@
- The codebase follows 42 **Norminette v4** rules. Run `norminette *.c *.h` (or on specific files) before submitting changes. - 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 file names lowercase with underscores (e.g., `src/builtins/echo/echo.c`).
- Keep headers in `include/` and expose only what modules need. - 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.
## Testing Guidelines ## Testing Guidelines
- There is no automated test runner. Use manual checks in `docs/tests.md` and basic shell behavior checks (pipes, redirects, builtins). - 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`. - 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 & Pull Request Guidelines
- Commit messages in this repo use a simple `type: summary` format (examples: `update: ...`, `fix: ...`). Keep summaries short and specific. - Commit messages in this repo use a simple `type: summary` format (examples: `update: ...`, `fix: ...`). Keep summaries short and specific.
- For PRs, include: - For PRs, include:

View File

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

View File

@@ -1,177 +0,0 @@
[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

View File

@@ -1,151 +0,0 @@
# 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.

View File

@@ -17,7 +17,7 @@
# include "minishell.h" # include "minishell.h"
# include "core.h" # include "core.h"
typedef uint8_t (*t_builtin_func)(t_command cmd, t_minishell *minishell); typedef unsigned char (*t_builtin_func)(t_command cmd, t_minishell *minishell);
/******************************************************************************/ /******************************************************************************/
/* Functions */ /* Functions */
@@ -25,36 +25,24 @@ typedef uint8_t (*t_builtin_func)(t_command cmd, t_minishell *minishell);
/* builtins.c */ /* builtins.c */
extern uint8_t set_builtins(t_minishell *minishell); extern u_int8_t set_builtins(t_minishell *minishell);
extern uint8_t is_builtin(const char *command_name, t_minishell *minishell); extern u_int8_t is_builtin(const char *command_name, t_minishell *minishell);
/* cd.c */ /* cd.c */
extern uint8_t builtin_cd(t_command cmd, t_minishell *minishell); extern u_int8_t builtin_cd(t_command cmd, t_minishell *minishell);
/* echo.c */ /* echo.c */
extern uint8_t builtin_echo(t_command cmd, t_minishell *minishell); extern u_int8_t builtin_echo(t_command cmd, t_minishell *minishell);
/* exit.c */ /* exit.c */
extern uint8_t builtin_exit(t_command cmd, t_minishell *minishell); extern u_int8_t builtin_exit(t_command cmd, t_minishell *minishell);
/* pwd.c */ /* pwd.c */
extern uint8_t builtin_pwd(t_command cmd, t_minishell *minishell); extern u_int8_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 */ #endif /* BUILTINS_H */

View File

@@ -88,9 +88,9 @@ typedef struct s_command
/* minishell.c */ /* minishell.c */
extern void minishell_init(t_minishell *minishell, char **envp); extern int minishell_init(t_minishell *minishell, char **envp);
extern void minishell_run(t_minishell *minishell); extern uint8_t minishell_run(t_minishell *minishell);
extern void minishell_clear(t_minishell *minishell); extern void minishell_clear(t_minishell *minishell);
@@ -107,6 +107,4 @@ extern void free_envp(char **envp);
extern char *get_env(const char *env_name, t_minishell *msh); 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 */ #endif /* CORE_H */

View File

@@ -12,53 +12,23 @@
#include "builtins.h" #include "builtins.h"
static uint8_t register_builtin( u_int8_t set_builtins(
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, key, builtin);
if (!ft_hashmap_contains_key(minishell->builtins, name))
{
free(key);
return (0);
}
return (1);
}
uint8_t set_builtins(
t_minishell *minishell t_minishell *minishell
) { ) {
minishell->builtins minishell->builtins
= ft_hashmap_new(7, ft_hashmap_hashstr, ft_hashmap_strcmp); = ft_hashmap_new(4, ft_hashmap_hashstr, ft_hashmap_strcmp);
if (minishell->builtins == NULL) if (minishell->builtins == NULL)
return (0); return (0);
if (!register_builtin(minishell, "cd", builtin_cd) ft_hashmap_put(minishell->builtins, ft_strdup("cd"), builtin_cd);
|| !register_builtin(minishell, "echo", builtin_echo) ft_hashmap_put(minishell->builtins, ft_strdup("echo"), builtin_echo);
|| !register_builtin(minishell, "env", builtin_env) ft_hashmap_put(minishell->builtins, ft_strdup("exit"), builtin_exit);
|| !register_builtin(minishell, "exit", builtin_exit) ft_hashmap_put(minishell->builtins, ft_strdup("pwd"), builtin_pwd);
|| !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); return (1);
} }
uint8_t is_builtin( u_int8_t is_builtin(
const char *command_name, const char *command_name,
t_minishell *minishell t_minishell *minishell
) { ) {
if (command_name == NULL || minishell == NULL
|| minishell->builtins == NULL)
return (0);
return (ft_hashmap_contains_key(minishell->builtins, command_name)); return (ft_hashmap_contains_key(minishell->builtins, command_name));
} }

View File

@@ -12,104 +12,43 @@
#include "builtins.h" #include "builtins.h"
static uint8_t handle_error(void); static u_int8_t handle_error(t_command cmd, t_minishell *msh, char *path);
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
);
uint8_t builtin_cd( u_int8_t builtin_cd(
t_command cmd, t_command cmd,
t_minishell *msh t_minishell *msh
){ ){
char *path; char *path;
char *oldpwd;
uint8_t status;
bool show_pwd;
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)
{
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 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,
uint8_t *status
){
if (cmd.argc > 2) if (cmd.argc > 2)
{ {
ft_eputendl("minishell: cd: too many arguments"); ft_eputendl("minishell: cd: too many arguments");
*status = EXIT_FAILURE; return (2);
return (NULL);
} }
if (cmd.argc == 2 && ft_strcmp(cmd.argv[1], "-") == 0) else if (cmd.argc == 1)
return (get_path_from_env(msh, "OLDPWD", path = get_env("HOME", msh);
"minishell: cd: OLDPWD not set", status)); else
if (cmd.argc == 1) path = cmd.argv[1];
return (get_path_from_env(msh, "HOME", if (chdir(path) == -1)
"minishell: cd: HOME not set", status)); return (handle_error(cmd, msh, path));
*status = EXIT_SUCCESS; return (EXIT_SUCCESS);
return (cmd.argv[1]);
} }
static char *get_path_from_env( static u_int8_t handle_error(
t_command cmd,
t_minishell *msh, t_minishell *msh,
const char *env_name, char *path
const char *error,
uint8_t *status
){ ){
char *path; u_int8_t exit_code;
path = get_env(env_name, msh); (void)msh;
if (path == NULL) exit_code = 0;
{ if (access(path, F_OK) != -1)
ft_eputendl((char *)error); // No such file or directory
*status = EXIT_FAILURE; exit_code = 1;
return (NULL); else if (access(path, X_OK) == -1)
} // Permission denied
*status = EXIT_SUCCESS; exit_code = 2;
return (path); perror(cmd.argv[0]);
return (exit_code);
} }

View File

@@ -13,7 +13,7 @@
#include "builtins.h" #include "builtins.h"
#include "echo_def.h" #include "echo_def.h"
uint8_t builtin_echo( u_int8_t builtin_echo(
t_command cmd, t_command cmd,
t_minishell *msh t_minishell *msh
){ ){

View File

@@ -13,7 +13,7 @@
#include "echo_def.h" #include "echo_def.h"
static void assign_flags(t_args *args, t_command cmd, size_t *i); static void assign_flags(t_args *args, t_command cmd, size_t *i);
static uint8_t is_valid_flag(t_args *args, char *flag); static u_int8_t is_valid_flag(t_args *args, char *flag);
static void assign_default_flags( static void assign_default_flags(
t_args *args t_args *args
@@ -51,7 +51,7 @@ static void assign_flags(
} }
} }
static uint8_t is_valid_flag( static u_int8_t is_valid_flag(
t_args *args, t_args *args,
char *flag char *flag
){ ){

View File

@@ -1,58 +0,0 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* 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,19 +11,7 @@
/* ************************************************************************** */ /* ************************************************************************** */
#include "builtins.h" #include "builtins.h"
#include <limits.h> #include <stdint.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( uint8_t builtin_exit(
t_command cmd, t_command cmd,
@@ -32,77 +20,27 @@ uint8_t builtin_exit(
{ {
uint8_t exit_status; uint8_t exit_status;
if (isatty(STDIN_FILENO)) ft_eputendl("exit");
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) if (cmd.argc == 1)
*exit_status = msh->exit_status; exit_status = msh->exit_status;
else if (!get_uint8_from_num(cmd.argv[1], exit_status)) else if (!ft_strisnum(cmd.argv[1]))
{ {
ft_eprintf("minishell: exit: %s: numeric argument required\n", ft_eprintf(
"minishell: exit: %s: numeric argument required\n",
cmd.argv[1]); cmd.argv[1]);
msh->exit = true; return (2);
msh->exit_status = 2;
return (0);
} }
else if (cmd.argc > 2) else if (cmd.argc > 2)
{ {
ft_eputendl("minishell: exit: too many arguments"); ft_eputendl("exit: too many arguments");
msh->exit_status = EXIT_FAILURE; return (2);
return (0);
} }
return (1); else
} exit_status = (uint8_t)ft_atol(cmd.argv[1]);
static uint8_t get_uint8_from_num( printf("builtin_exit: exit_status=%d\n", exit_status); // Debug print
const char *arg,
uint8_t *status
){
uint64_t n;
uint64_t limit;
int sign;
if (arg == NULL || *arg == '\0') msh->exit = 1;
return (0); msh->exit_status = exit_status;
n = 0; return (exit_status);
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

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

View File

@@ -1,61 +0,0 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* 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

@@ -26,8 +26,8 @@ static void set_argv(t_command *command, char *line, t_minishell *minishell);
static void expand_envs(char *arg, t_minishell *minishell); static void expand_envs(char *arg, t_minishell *minishell);
static char **lst_to_argv(t_list *argv_list); static char **lst_to_argv(t_list *argv_list);
static void set_argc(t_command *command); static void set_argc(t_command *command);
// static void set_infile(t_command *command); static void set_infile(t_command *command);
// static void set_outfile(t_command *command); static void set_outfile(t_command *command);
static void set_path(t_command *command, t_minishell *minishell); static void set_path(t_command *command, t_minishell *minishell);
static u_int8_t path_is_solved(char *cmd_name, t_minishell *msh); static u_int8_t path_is_solved(char *cmd_name, t_minishell *msh);
static char *solve_path(char *command_name, t_minishell *minishell); static char *solve_path(char *command_name, t_minishell *minishell);
@@ -37,7 +37,7 @@ t_list *parse(
t_minishell *minishell t_minishell *minishell
) { ) {
t_list *commands; t_list *commands;
// t_list *tokens; t_list *tokens;
t_command *command; t_command *command;
char *command_str; char *command_str;
size_t i; size_t i;
@@ -48,7 +48,7 @@ t_list *parse(
i = 0; i = 0;
while (line[i] != '\0') while (line[i] != '\0')
{ {
// tokens = tokenize(); tokens = tokenize();
command_str = extract_next_command(line, &i); command_str = extract_next_command(line, &i);
if (command_str != NULL) if (command_str != NULL)
{ {
@@ -155,8 +155,8 @@ static t_command *cmdnew(
return (NULL); return (NULL);
} }
set_argc(command); set_argc(command);
// set_infile(command); set_infile(command);
// set_outfile(command); set_outfile(command);
set_path(command, minishell); set_path(command, minishell);
return (command); return (command);
} }
@@ -233,19 +233,19 @@ static void set_argc(
command->argc = argc; command->argc = argc;
} }
// static void set_infile( static void set_infile(
// t_command *command t_command *command
// ) { ) {
// // test_infile // test_infile
// command->infile = -1; command->infile = -1;
// } }
// static void set_outfile( static void set_outfile(
// t_command *command t_command *command
// ) { ) {
// // test_outfile // test_outfile
// command->outfile = STDOUT_FILENO; command->outfile = STDOUT_FILENO;
// } }
static void set_path( static void set_path(
t_command *command, t_command *command,

View File

@@ -118,9 +118,7 @@ char **get_envp(
size_t i; size_t i;
env_list = ft_hashmap_entries(msh->variables.environment); env_list = ft_hashmap_entries(msh->variables.environment);
envp = (char **)malloc( envp = (char **)malloc((msh->variables.environment->size + 1) * sizeof(char *));
(msh->variables.environment->size + 1) * sizeof(char *)
);
if (envp != NULL) if (envp != NULL)
{ {
i = 0; i = 0;

View File

@@ -1,41 +0,0 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* 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;
}

View File

@@ -1,204 +0,0 @@
#!/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