Builtins fixed
The builtins wasnt protected, now all data received is protected, the hashmap addition is protected and added functionality of env, export and unset (not implemented in this version). Added fixed details documentation in docs/builtins_fixes.md generated by codex and created tests/builtins_edge_cases.sh to test all the builtins to work correctly
This commit is contained in:
151
docs/builtins_fixes.md
Normal file
151
docs/builtins_fixes.md
Normal 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.
|
||||
@@ -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 */
|
||||
|
||||
@@ -107,4 +107,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 */
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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(error);
|
||||
*status = EXIT_FAILURE;
|
||||
return (NULL);
|
||||
}
|
||||
*status = EXIT_SUCCESS;
|
||||
return (path);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
){
|
||||
|
||||
@@ -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
58
src/builtins/env/env.c
vendored
Normal 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(entry->key);
|
||||
ft_putchar('=');
|
||||
if (entry->value != NULL)
|
||||
ft_putstr(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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
79
src/builtins/export/export.c
Normal file
79
src/builtins/export/export.c
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
61
src/builtins/unset/unset.c
Normal file
61
src/builtins/unset/unset.c
Normal 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);
|
||||
}
|
||||
@@ -118,7 +118,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;
|
||||
|
||||
41
src/variables/environment_unset.c
Normal file
41
src/variables/environment_unset.c
Normal 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
204
tests/builtins_edge_cases.sh
Executable 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
|
||||
Reference in New Issue
Block a user