4 Commits

Author SHA1 Message Date
marcnava-42cursus
d39eca2c94 Fixed compilation 2026-02-09 22:48:12 +01:00
marcnava-42cursus
084fa4759c Created allowed.txt to track allowed functions for minishell and updated AGENTS.md to tell codex to not use any forbidden functions, also updated AGENTS.md to add all builtins changes 2026-02-09 22:37:09 +01:00
marcnava-42cursus
778e0c0481 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
2026-02-09 22:08:45 +01:00
280fa51f94 save commit 2026-02-09 20:47:43 +01:00
50 changed files with 4404 additions and 284 deletions

48
AGENTS.md Normal file
View File

@@ -0,0 +1,48 @@
# Repository Guidelines
## 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`.
- `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`).
- `build/` is the object output directory generated by `make`.
## Build, Test, and Development Commands
- `make` or `make all`: build `minishell` (auto-clones required libs into `lib/`).
- `make clean`: remove objects in `build/`.
- `make fclean`: remove objects and the `minishell` binary (also cleans libs).
- `make re`: full rebuild.
- `./minishell`: run locally after a build.
- `make DEBUG=lldb` or `make DEBUG=valgrind` or `make DEBUG=address`: rebuild with debug/ASan-friendly flags.
## Coding Style & Naming Conventions
- 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.
## 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:
1. What changed and why.
2. How to test (commands or manual steps).
3. Notes on any parser/executor/builtin behavior changes.
## Configuration Tips
- The project depends on `readline`; ensure your system has `libreadline-dev` or equivalent before building.

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: 2025/12/01 14:05:56 by sede-san ### ########.fr #
# Updated: 2026/02/09 22:44:34 by marcnava ### ########.fr #
# #
# **************************************************************************** #
@@ -17,15 +17,15 @@ 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
CFLAGS += -g3
CFLAGS = -g3
else ifeq ($(DEBUG), valgrind) # debug with valgrind
CFLAGS += -g3
CFLAGS = -g3
else ifeq ($(DEBUG), address) # use AdressSanitize
CFLAGS += -fsanitize=address -g3
CFLAGS = -fsanitize=address -g3
else # apply optimization flags if no debugging is being done
CFLAGS += -O3
endif

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.

View File

@@ -0,0 +1,77 @@
# Minishell - Resumen para defensa
Este documento es un guion breve para explicar el proyecto con claridad.
## 1. Que es minishell
- Un shell interactivo basico: lee comandos, los parsea y los ejecuta.
- Objetivo didactico: procesos, pipes, fds, señales y parsing.
## 2. Flujo general (en 20 segundos)
1. Prompt con readline.
2. Lexer divide la linea en tokens validos (respetando comillas).
3. Parser crea comandos, argv y redirecciones.
4. Expansion de variables ($VAR, $?).
5. Resolver PATH.
6. Ejecutar con fork/execve y pipes.
7. Actualizar exit status.
## 3. Puntos clave que suelen preguntar
### Comillas
- Comilla simple: todo literal, no expansion.
- Comilla doble: expansion de $VAR, pero no metacaracteres.
- Si una comilla no se cierra: error y no se ejecuta nada.
### Redirecciones
- <, >, >>, <<.
- Se resuelven antes de ejecutar el comando.
- Heredoc: lectura hasta delimitador, sin historial.
### Pipes
- Conecto stdout del comando i con stdin del comando i+1.
- Uso pipe() y dup2() en el child.
- El padre cierra fds que no usa.
### Builtins
- echo -n, cd, pwd, export, unset, env, exit.
- Si no hay pipeline, se ejecutan en el padre para afectar el estado.
- En pipeline, se ejecutan en el child.
### Variables
- $VAR: entorno.
- $?: ultimo exit status.
### Senales
- Una sola variable global para la senal.
- ctrl-C: nueva linea y prompt.
- ctrl-D: salir del shell.
- ctrl-\: no hace nada.
## 4. Ejemplos rapidos para demostrar
1. Pipes
- `ls | wc -l`
2. Redirecciones
- `echo hola > out.txt`
- `cat < out.txt`
3. Heredoc
- `cat << EOF`
- escribir lineas
- `EOF`
4. Expansion
- `echo $HOME`
- `echo $?`
## 5. Errores y robustez
- No ejecuto si hay errores de parseo.
- Mensajes con perror cuando fallan syscalls.
- exit_status siempre actualizado.
## 6. Mensajes finales recomendados
- "He separado parsing y ejecucion para que el codigo sea mantenible y
la defensa mas clara".
- "Sigo el comportamiento de bash como referencia".

292
docs/guia_obligatoria_es.md Normal file
View File

@@ -0,0 +1,292 @@
# Minishell - Guia de estructura y pasos (parte obligatoria)
Objetivo de este documento
- Proponer una estructura de archivos y datos.
- Describir el proceso paso a paso como tutorial.
- Servir de guion para defensa (explicaciones claras y ordenadas).
Este documento no implementa nada. Solo define el plan y el por que.
---
## 1. Vision general del flujo
El shell se puede explicar como una tuberia de fases:
1. Lectura interactiva (prompt + historial).
2. Tokenizacion (lexing) con comillas y metacaracteres.
3. Parseo (construccion de comandos, redirecciones, pipes).
4. Expansion de variables ($VAR y $?).
5. Preparacion de ejecucion (resolucion de rutas, heredoc).
6. Ejecucion (builtins o execve) con pipes y redirecciones.
7. Gestion de exit status y señales.
Esta separacion permite explicar en defensa cada pieza por separado y justificar
las decisiones tecnicas.
---
## 2. Propuesta de estructuras de datos
Estas estructuras son solo guia. Adapta nombres a tu estilo y Norminette.
### 2.1 Token (lexer)
Representa una unidad del input (palabra, pipe, redireccion, etc.).
- enum e_tokentype
- TOK_WORD
- TOK_PIPE
- TOK_REDIR_IN (<)
- TOK_REDIR_OUT (>)
- TOK_REDIR_APPEND (>>)
- TOK_HEREDOC (<<)
- struct s_token
- char *text
- t_tokentype type
- struct s_token *next
Uso en defensa: el lexer separa el input en unidades, respetando comillas.
### 2.2 Redireccion
Guarda los datos de redireccion por comando.
- enum e_redirtype
- REDIR_IN
- REDIR_OUT
- REDIR_APPEND
- REDIR_HEREDOC
- struct s_redir
- t_redirtype type
- char *target
- int fd
- struct s_redir *next
Notas:
- target es el filename o delimitador de heredoc.
- fd se resuelve en fase de preparacion (open o pipe temporal).
### 2.3 Comando
- struct s_command
- char **argv
- int argc
- char *path
- t_redir *redirs
### 2.4 Pipeline
Una lista de comandos en orden, unidos por pipes.
- struct s_pipeline
- t_command **cmds
- size_t count
---
## 3. Lexer: reglas y pasos
Reglas clave del subject:
- No interpretar comillas sin cerrar.
- Comilla simple: no se expanden variables ni metacaracteres.
- Comilla doble: se expanden variables, pero se respetan caracteres normales.
- Metacaracteres: |, <, >, <<, >> separan tokens.
Pasos recomendados:
1. Recorrer la linea caracter a caracter.
2. Mantener estado: in_single, in_double.
3. Cuando no estas en comillas, detectar metacaracteres y cortar tokens.
4. Construir TOK_WORD con el texto exacto (sin eliminar comillas aun).
5. Si llegas a fin de linea con in_single o in_double activo, error de parseo.
Explicacion para defensa:
- El lexer no sabe de ejecucion, solo separa en tokens validos.
- El manejo de comillas se hace aqui para respetar la sintaxis del shell.
---
## 4. Parser: construccion de comandos
Objetivo: transformar tokens en una estructura ejecutable.
Pasos:
1. Recorrer lista de tokens.
2. Cada TOK_PIPE cierra un comando actual y abre el siguiente.
3. TOK_WORD se agrega a argv.
4. TOK_REDIR_* consume el siguiente token (debe ser TOK_WORD) como target.
5. Construir lista de redirecciones para cada comando.
6. Validar errores: pipe inicial/final, redireccion sin target, etc.
Explicacion para defensa:
- El parser aplica reglas de orden y construye una estructura clara.
- Separar argv y redirecciones evita mezclar logica en executor.
---
## 5. Expansion de variables
Reglas:
- $VAR se sustituye por getenv/tabla interna.
- $? se sustituye por el exit_status anterior.
- En comilla simple no se expande.
- En comilla doble si se expande.
Proceso recomendado:
1. Durante tokenizacion, guardar el texto con sus comillas o bien
marcar segmentos con estado de comillas.
2. En expansion, recorrer cada palabra y reemplazar $...
3. Si variable no existe, reemplazar por string vacio.
4. Eliminar comillas despues de la expansion.
Explicacion para defensa:
- La expansion es una fase separada para no complicar el parser.
- $?, variable especial, refleja el estado de la ultima ejecucion.
---
## 6. Redirecciones y heredoc
Redirecciones basicas:
- <: open(file, O_RDONLY)
- >: open(file, O_WRONLY | O_CREAT | O_TRUNC)
- >>: open(file, O_WRONLY | O_CREAT | O_APPEND)
Heredoc (<<):
1. Leer lineas hasta delimitador exacto.
2. Guardar el contenido en un pipe o fichero temporal.
3. Usar el extremo de lectura como STDIN del comando.
4. No guardar el contenido en historial.
Explicacion para defensa:
- Las redirecciones se resuelven antes de ejecutar el proceso.
- Heredoc es una fuente especial de entrada.
---
## 7. Resolucion de comandos y PATH
Reglas:
- Si argv[0] es una ruta absoluta o relativa (/, ./, ../), usarla tal cual.
- Si no, buscar en PATH separando por ':'.
- Si es builtin, no se necesita path real.
Proceso:
1. Detectar builtin.
2. Si no builtin y no es ruta, recorrer PATH y usar access().
3. Guardar path en t_command->path.
---
## 8. Ejecucion
Caso 1: comando unico builtin
- Ejecutar en el proceso padre para que pueda modificar estado del shell
(ej: cd, export, unset, exit).
Caso 2: pipeline o comando externo
- Usar fork + execve.
- Crear pipes entre comandos.
- Aplicar redirecciones antes de ejecutar.
Proceso para pipeline:
1. Para cada comando, crear pipe si hay siguiente.
2. fork.
3. En child: dup2 para redirecciones y pipes, luego ejecutar.
4. En parent: cerrar FDs innecesarios y seguir.
5. Esperar procesos, guardar exit status del ultimo comando.
Explicacion para defensa:
- Las pipes conectan stdout del comando i con stdin del comando i+1.
- Los builtins dentro de pipeline se ejecutan en child.
---
## 9. Builtins obligatorios
- echo con -n
- cd (ruta relativa o absoluta)
- pwd
- export (sin opciones)
- unset (sin opciones)
- env (sin opciones o argumentos)
- exit
Notas de defensa:
- export/unset trabajan sobre la tabla de variables del shell.
- env imprime variables de entorno.
- exit debe actualizar exit_status y terminar el loop principal.
---
## 10. Senales
Requisitos interactivos:
- ctrl-C: imprime nueva linea y muestra prompt.
- ctrl-D: termina el shell.
- ctrl-\: no hace nada.
Regla del subject:
- Solo una variable global para indicar la senal recibida.
Proceso:
1. Definir una variable global int g_signal.
2. Configurar handlers con sigaction.
3. En handler: actualizar g_signal y escribir un '\n' si procede.
4. En el loop principal: si g_signal indica SIGINT, resetear lineas de readline.
---
## 11. Manejo de errores y salida
- Mostrar errores con perror o mensajes consistentes.
- Si parseo falla, no ejecutar nada.
- Mantener exit_status actualizado.
Explicacion en defensa:
- Un shell robusto evita ejecutar comandos parcialmente parseados.
- exit_status es clave para $?.
---
## 12. Checklist para defensa (guion rapido)
1. Explico el flujo completo: lectura -> lexer -> parser -> expansion -> exec.
2. Explico como manejo comillas y metacaracteres.
3. Explico como construyo argv y redirecciones.
4. Explico expansion de $VAR y $?.
5. Explico pipes y redirecciones con dup2.
6. Explico por que los builtins se ejecutan en parent o child.
7. Explico manejo de senales y la variable global unica.
8. Explico exit_status y comportamiento de $?.
---
## 13. Sugerencia de estructura de archivos
- include/
- minishell.h
- core.h (estructuras globales y estado)
- parser.h (tokens, parser)
- executor.h
- builtins.h
- src/
- core/ (init, signals, util)
- parser/ (lexer.c, parser.c, expand.c)
- executor/ (executor.c, redirs.c)
- builtins/ (echo, cd, pwd, exit, env, export, unset)
- variables/ (environment.c)
- minishell.c (loop principal)
- main.c
Esto es solo una guia; no es obligatorio seguirla al pie de la letra.
---
## 14. Consejos para la defensa
- Usa bash como referencia de comportamiento.
- Demuestra un par de ejemplos: pipe, redireccion y expansion.
- Si algo falla, explica que el parser previene ejecucion parcial.
- Recalca el manejo correcto de ctrl-C y ctrl-\.

341
executor_new_previous.c Normal file
View File

@@ -0,0 +1,341 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* executor_new.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/28 13:03:44 by sede-san #+# #+# */
/* Updated: 2026/02/08 19:05:37 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "executor.h"
#include "builtins.h"
#include <stdint.h>
#include <errno.h>
static void handle_execve_error();
static void handle_parent_process(
int *prev_read_fd,
t_list *current_command,
int pipefd[2]
);
static int handle_pipeline(
t_list *current_command,
int pipefd[2],
int *exit_status
);
static inline uint8_t execute_builtin(
t_command *command,
t_minishell *minishell
);
static void execute_external_command(
t_command *command,
t_minishell *minishell
);
static pid_t handle_fork(
t_list *current_command,
t_minishell *minishell
);
static uint8_t execute_command(
t_command *command,
t_minishell *minishell
);
static inline bool can_execute_command(t_command *command, t_minishell *minishell);
static inline bool command_exists(t_command *command, t_minishell *minishell);
static void cmdfree(t_command *command);
static void cmdfree_argv(char **argv);
# define WRITE_PIPE 1
# define READ_PIPE 0
# define CHILD_PID 0
u_int8_t execute(
t_list *command_list,
t_minishell *minishell
) {
t_list *current_command;
int prev_read_fd;
int exit_status;
int pipefd[2];
pid_t pid;
prev_read_fd = -1;
exit_status = EXIT_SUCCESS;
current_command = command_list;
while (current_command != NULL)
{
if (handle_pipeline(current_command, pipefd, &exit_status) == EXIT_FAILURE)
break ;
pid = handle_fork(current_command, minishell);
if (pid == -1)
{
perror("fork");
exit_status = EXIT_FAILURE;
break ;
}
if (pid == CHILD_PID)
handle_child_process(prev_read_fd, current_command, pipefd, minishell);
handle_parent_process(&prev_read_fd, current_command, pipefd);
current_command = current_command->next;
}
ft_lstclear(&command_list, (void (*)(void *))cmdfree);
while (wait(&exit_status) > 0)
{
if (WIFEXITED(exit_status))
exit_status = WEXITSTATUS(exit_status);
}
return (exit_status);
}
static int handle_pipeline(
t_list *current_command,
int pipefd[2],
int *exit_status
) {
if (current_command->next) // create pipe if needed
{
if (pipe(pipefd) == -1)
{
perror("pipe");
*exit_status = EXIT_FAILURE;
return (EXIT_FAILURE);
}
}
return (EXIT_SUCCESS);
}
static void handle_parent_process(
int *prev_read_fd,
t_list *current_command,
int pipefd[2]
) {
if (*prev_read_fd != -1)
close(*prev_read_fd);
if (current_command->next)
{
close(pipefd[WRITE_PIPE]); // parent does not write
*prev_read_fd = pipefd[READ_PIPE]; // pass read end forward
}
else
*prev_read_fd = -1;
}
static pid_t handle_fork(
t_list *current_command,
t_minishell *minishell
) {
pid_t pid;
const t_command *command = (t_command *)current_command->content;
pid = 0;
if (current_command->next != NULL
|| !is_builtin(command->path, minishell))
pid = fork();
return (pid);
}
void handle_child_process(int prev_read_fd, t_list *current_command, int pipefd[2], t_minishell *minishell)
{
redirect_pipes(prev_read_fd, current_command, pipefd);
execute_command((t_command *)current_command->content, minishell);
}
void redirect_pipes(int prev_read_fd, t_list *current_command, int pipefd[2])
{
redirect_input_pipe(prev_read_fd);
redirect_output_pipe(current_command, pipefd);
}
void redirect_output_pipe(t_list * current_command, int pipefd[2])
{
if (current_command->next)
{
dup2(pipefd[WRITE_PIPE], STDOUT_FILENO);
close(pipefd[READ_PIPE]);
close(pipefd[WRITE_PIPE]);
}
}
void redirect_input_pipe(int prev_read_fd)
{
if (prev_read_fd != -1)
{
dup2(prev_read_fd, STDIN_FILENO);
close(prev_read_fd);
}
}
static inline bool can_execute_command(
t_command *command,
t_minishell *minishell
) {
if (!is_builtin(command->path, minishell)
&& access(command->path, X_OK) != EXIT_SUCCESS)
return (false);
return (true);
}
static inline bool command_exists(
t_command *command,
t_minishell *minishell
) {
if (!is_builtin(command->path, minishell)
&& access(command->path, F_OK) != EXIT_SUCCESS)
return (false);
return (true);
}
static uint8_t execute_command(
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 inline uint8_t execute_builtin(
t_command *command,
t_minishell *minishell
) {
const t_builtin_func builtin
= ft_hashmap_get(minishell->builtins, command->path);
return (builtin(*command, minishell));
}
static void execute_external_command(
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();
}
static void handle_execve_error() {
perror("execve");
exit(EXIT_FAILURE);
}
// static inline bool is_piped(
// t_command *command
// ) {
// return (command->piped_to != NULL || command->piped_from != NULL);
// }
// static u_int8_t handle_child_process(
// t_command *command,
// t_minishell *minishell
// ) {
// if (!redirect_pipes(command))
// exit(EXIT_FAILURE);
// execute_command(command, minishell);
// exit(EXIT_FAILURE);
// }
// static int redirect_pipe(
// int from,
// int to
// ) {
// if (dup2(from, to) == -1)
// return (-1);
// close(from);
// return (0);
// }
// static int redirect_pipes(
// t_command *command
// ) {
// if (command->piped_from &&
// redirect_pipe(command->piped_from->outfile, STDIN_FILENO) == -1)
// {
// perror("dup2");
// return (0);
// }
// if (command->piped_to &&
// redirect_pipe(command->outfile, STDOUT_FILENO) == -1)
// {
// perror("dup2");
// return (0);
// }
// return (1);
// }
// static void show_error(
// t_command *command,
// t_minishell *minishell
// ) {
// if (!command_exists(command, minishell))
// {
// ft_eprintf("minishell: %s: command not found\n", command->argv[0]);
// minishell->exit_status = 127;
// }
// else if (!can_execute_command(command, minishell))
// {
// ft_eprintf("minishell: %s: %s\n", command->path, strerror(errno));
// minishell->exit_status = errno;
// }
// }
static void cmdfree(
t_command *command
) {
if (command == NULL)
return ;
cmdfree_argv(command->argv);
free(command->path);
free(command);
}
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 debug_print_command_info(
// t_command *command
// ) {
// size_t i;
// if (command == NULL)
// {
// printf("Command is NULL\n");
// return ;
// }
// printf("Command info:\n");
// printf(" Path: %s\n", command->path);
// printf(" Argc: %d\n", command->argc);
// printf(" Argv:\n");
// for (i = 0; i < (size_t)command->argc; i++)
// printf(" arg[%zu]: %s\n", i, command->argv[i]);
// printf(" Infile FD: %d\n", command->infile);
// printf(" Outfile FD: %d\n", command->outfile);
// printf("--------------------------\n");
// }

256
executor_old.c Normal file
View File

@@ -0,0 +1,256 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* executor.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/28 13:03:44 by sede-san #+# #+# */
/* Updated: 2026/02/08 03:42:11 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "executor.h"
#include "builtins.h"
#include <errno.h>
static inline bool can_execute_command(t_command *command, t_minishell *minishell);
static inline bool command_exists(t_command *command, t_minishell *minishell);
static u_int8_t execute_command(t_command *command, t_minishell *minishell);
static u_int8_t execute_command(t_command *command, t_minishell *minishell);
// static inline bool is_piped(t_command *command);
static void cmdfree(t_command *command);
static void cmdfree_argv(char **argv);
# define WRITE_PIPE 1
# define READ_PIPE 0
# define CHILD_PID 0
u_int8_t execute(
t_list *command_list,
t_minishell *minishell
) {
t_list *current_command;
int prev_read_fd;
pid_t pid;
u_int8_t exit_status;
int child_exit_status;
prev_read_fd = -1;
exit_status = EXIT_SUCCESS;
current_command = command_list;
while (current_command != NULL)
{
t_command *command = (t_command *)current_command->content;
int pipefd[2];
/* Create pipe ONLY if there is a next command */
if (current_command->next)
{
if (pipe(pipefd) == -1)
{
perror("pipe");
exit_status = EXIT_FAILURE;
break ;
}
}
// create fork
pid = 0;
if (current_command->next != NULL
|| !is_builtin(command->path, minishell))
pid = fork();
// handle fork error
if (pid == -1)
{
perror("fork");
return (EXIT_FAILURE);
}
// handle child process
if (pid == CHILD_PID)
{
/* If we have input from previous pipe */
if (prev_read_fd != -1)
{
dup2(prev_read_fd, STDIN_FILENO);
close(prev_read_fd);
}
/* If we pipe output to next command */
if (current_command->next)
{
dup2(pipefd[WRITE_PIPE], STDOUT_FILENO);
close(pipefd[READ_PIPE]);
close(pipefd[WRITE_PIPE]);
}
execute_command(command, minishell); // child process exits here
}
// handle parent process
waitpid(pid, &child_exit_status, 0); // wait for child to finish
if (prev_read_fd != -1)
close(prev_read_fd);
if (current_command->next)
{
close(pipefd[WRITE_PIPE]); /* parent does not write */
prev_read_fd = pipefd[READ_PIPE]; /* pass read end forward */
}
else
prev_read_fd = -1;
// continue executing
current_command = current_command->next;
}
ft_lstclear(&command_list, (void (*)(void *))cmdfree);
exit_status = child_exit_status;
return (exit_status);
}
static inline bool can_execute_command(
t_command *command,
t_minishell *minishell
) {
if (!is_builtin(command->path, minishell)
&& access(command->path, X_OK) != EXIT_SUCCESS)
return (false);
return (true);
}
static inline bool command_exists(
t_command *command,
t_minishell *minishell
) {
if (!is_builtin(command->path, minishell)
&& access(command->path, F_OK) != EXIT_SUCCESS)
return (false);
return (true);
}
static u_int8_t execute_command(
t_command *command,
t_minishell *minishell
)
{
char **envp;
t_builtin_func builtin;
if (is_builtin(command->path, minishell))
{
builtin = ft_hashmap_get(minishell->builtins, command->path);
return (builtin(*command, minishell));
}
envp = get_envp(minishell);
execve(command->path, command->argv, envp);
// handle error if execve fails
perror("execve");
free_envp(envp);
exit(EXIT_FAILURE);
}
// static inline bool is_piped(
// t_command *command
// ) {
// return (command->piped_to != NULL || command->piped_from != NULL);
// }
// static u_int8_t handle_child_process(
// t_command *command,
// t_minishell *minishell
// ) {
// if (!redirect_pipes(command))
// exit(EXIT_FAILURE);
// execute_command(command, minishell);
// exit(EXIT_FAILURE);
// }
// static int redirect_pipe(
// int from,
// int to
// ) {
// if (dup2(from, to) == -1)
// return (-1);
// close(from);
// return (0);
// }
// static int redirect_pipes(
// t_command *command
// ) {
// if (command->piped_from &&
// redirect_pipe(command->piped_from->outfile, STDIN_FILENO) == -1)
// {
// perror("dup2");
// return (0);
// }
// if (command->piped_to &&
// redirect_pipe(command->outfile, STDOUT_FILENO) == -1)
// {
// perror("dup2");
// return (0);
// }
// return (1);
// }
// static void show_error(
// t_command *command,
// t_minishell *minishell
// ) {
// if (!command_exists(command, minishell))
// {
// ft_eprintf("minishell: %s: command not found\n", command->argv[0]);
// minishell->exit_status = 127;
// }
// else if (!can_execute_command(command, minishell))
// {
// ft_eprintf("minishell: %s: %s\n", command->path, strerror(errno));
// minishell->exit_status = errno;
// }
// }
static void cmdfree(
t_command *command
) {
if (command == NULL)
return ;
cmdfree_argv(command->argv);
free(command->path);
free(command);
}
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 debug_print_command_info(
// t_command *command
// ) {
// size_t i;
// if (command == NULL)
// {
// printf("Command is NULL\n");
// return ;
// }
// printf("Command info:\n");
// printf(" Path: %s\n", command->path);
// printf(" Argc: %d\n", command->argc);
// printf(" Argv:\n");
// for (i = 0; i < (size_t)command->argc; i++)
// printf(" arg[%zu]: %s\n", i, command->argv[i]);
// printf(" Infile FD: %d\n", command->infile);
// printf(" Outfile FD: %d\n", command->outfile);
// printf("--------------------------\n");
// }

View File

@@ -6,19 +6,18 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/29 22:09:51 by sede-san #+# #+# */
/* Updated: 2025/12/01 17:53:57 by sede-san ### ########.fr */
/* Updated: 2026/02/08 19:42:50 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#ifndef BUILTINS_H
# define BUILTINS_H
# include "libft.h"
# include "minishell.h"
# include "ft_args.h"
# include "minishell.h"
# include "core.h"
typedef unsigned char (*t_builtin_func)(t_command cmd, t_minishell *msh);
typedef uint8_t (*t_builtin_func)(t_command cmd, t_minishell *minishell);
/******************************************************************************/
/* Functions */
@@ -26,24 +25,36 @@ typedef unsigned char (*t_builtin_func)(t_command cmd, t_minishell *msh);
/* builtins.c */
extern u_int8_t set_builtins(t_minishell *msh);
extern uint8_t set_builtins(t_minishell *minishell);
extern u_int8_t is_builtin(const char *command_name, t_minishell *msh);
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 *msh);
extern uint8_t builtin_cd(t_command cmd, t_minishell *minishell);
/* echo.c */
extern u_int8_t builtin_echo(t_command cmd, t_minishell *msh);
extern uint8_t builtin_echo(t_command cmd, t_minishell *minishell);
/* exit.c */
extern u_int8_t builtin_exit(t_command cmd, t_minishell *msh);
extern uint8_t builtin_exit(t_command cmd, t_minishell *minishell);
/* pwd.c */
extern u_int8_t builtin_pwd(t_command cmd, t_minishell *msh);
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

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/22 19:10:13 by sede-san #+# #+# */
/* Updated: 2025/12/01 17:02:10 by sede-san ### ########.fr */
/* Updated: 2026/02/09 18:45:41 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -50,8 +50,8 @@ typedef struct s_minishell
{
t_variables variables;
t_hashmap *builtins;
u_int8_t exit_status;
u_int8_t exit;
uint8_t exit_status;
bool exit;
} t_minishell;
/**
@@ -69,17 +69,28 @@ typedef struct s_command
int argc;
char **argv;
char *path;
t_list *redirections;
} t_command;
/**
* @brief Default shell prompt string
*/
# define DEFAULT_PS1 "minishell > "
/**
* @brief Default secondary prompt string for multiline input
*/
# define DEFAULT_PS2 "> "
/******************************************************************************/
/* Functions */
/******************************************************************************/
/* minishell.c */
extern int minishell_init(t_minishell *minishell, char **envp);
extern void minishell_init(t_minishell *minishell, char **envp);
extern u_int8_t minishell_run(t_minishell *minishell);
extern void minishell_run(t_minishell *minishell);
extern void minishell_clear(t_minishell *minishell);
@@ -96,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 */

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/28 13:08:33 by sede-san #+# #+# */
/* Updated: 2025/10/28 14:20:23 by sede-san ### ########.fr */
/* Updated: 2026/02/08 21:32:56 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -14,6 +14,18 @@
# define EXECUTOR_H
# include "minishell.h"
# include "core.h"
# include <stdint.h>
# define READ_PIPE 0
# define WRITE_PIPE 1
typedef struct s_pipeline
{
int prev_read_fd;
int pipefd[2];
} t_pipeline;
/******************************************************************************/
/* Functions */
@@ -21,6 +33,9 @@
// executor.c
extern u_int8_t execute(t_command command, t_minishell *minishell);
# define PIPE_ERROR -1
# define FORK_ERROR -1
extern uint8_t execute(t_list *command, t_minishell *minishell);
#endif /* EXECUTOR_H */

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/20 16:35:10 by sede-san #+# #+# */
/* Updated: 2025/10/30 12:20:08 by sede-san ### ########.fr */
/* Updated: 2026/02/09 18:38:16 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -16,10 +16,8 @@
# include "libft.h"
# include "ft_printf.h"
# include "chardefs.h"
# include "core.h"
# include "parser.h"
# include "executor.h"
# include "builtins.h"
# include <stdbool.h>
# include <stdint.h>
# include <readline/readline.h> // readline(3), rl_clear_history(),
// rl_on_new_line(), rl_replace_line(),
// rl_redisplay()

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/22 19:03:51 by sede-san #+# #+# */
/* Updated: 2025/10/30 12:44:51 by sede-san ### ########.fr */
/* Updated: 2026/02/09 20:36:19 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -14,6 +14,46 @@
# define PARSER_H
# include "minishell.h"
# include "core.h"
# include "builtins.h"
# define PIPE_STR "|"
# define REDIRECT_IN_STR "<"
# define REDIRECT_OUT_STR ">"
# define APPEND_STR ">>"
# define HEREDOC_STR "<<"
# define TOKENS_COUNT 5
typedef enum e_token_type
{
TOKEN_WORD,
TOKEN_PIPE,
TOKEN_REDIRECT_IN,
TOKEN_REDIRECT_OUT,
TOKEN_APPEND,
TOKEN_HEREDOC
} t_token_type;
typedef struct s_token
{
t_token_type type;
char *value;
} t_token;
typedef enum e_redirection_type
{
REDIRECT_IN,
REDIRECT_OUT,
APPEND,
HEREDOC
} t_redirection_type;
typedef struct s_redirection
{
t_redirection_type type;
char *target;
} t_redirection;
/******************************************************************************/
/* Functions */
@@ -21,6 +61,6 @@
// parser.c
extern t_command parse(char *line, t_minishell *minishell);
extern t_list *parse(char *line, t_minishell *minishell);
#endif /* PARSER_H */

27
minishell-codex/Makefile Normal file
View File

@@ -0,0 +1,27 @@
NAME := minishell
CC := cc
CFLAGS := -Wall -Wextra -Werror -g
INCLUDES := -Iinclude
READLINE_LIBS := -lreadline -lncurses
SRCS := $(shell find src -name '*.c')
OBJS := $(SRCS:src/%.c=build/%.o)
all: $(NAME)
$(NAME): $(OBJS)
$(CC) $(CFLAGS) $(OBJS) $(READLINE_LIBS) -o $(NAME)
build/%.o: src/%.c
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
clean:
rm -rf build
fclean: clean
rm -f $(NAME)
re: fclean all
.PHONY: all clean fclean re

View File

@@ -0,0 +1,72 @@
# Minishell - Guion de defensa (version codex)
Este guion esta alineado con la estructura real en `minishell-codex/`.
## 1. Explicacion corta del proyecto
- Minishell es un interprete de comandos interactivo.
- Implementa pipes, redirecciones, variables y builtins basicos.
- Se basa en el flujo: lectura -> lexer -> parser -> expansion -> ejecucion.
## 2. Flujo completo (paso a paso)
1. `readline()` muestra el prompt y devuelve la linea.
2. `lex_line()` divide la linea en tokens (`TOK_WORD`, `TOK_PIPE`, redirecciones).
3. `parse_tokens()` construye la pipeline con comandos y redirecciones.
4. `expand_pipeline()` aplica expansion de `$VAR` y `$?` respetando comillas.
5. `execute_pipeline()` resuelve `PATH`, prepara heredocs y ejecuta.
## 3. Estructuras clave
- `t_token`: tipo y texto de tokens (`minishell-codex/include/minishell.h`).
- `t_command`: argv, redirecciones, path.
- `t_pipeline`: lista de comandos.
- `t_redir`: tipo, target y fd.
- `t_shell`: estado global (env, exit_status, flags).
## 4. Lexer (por que esta separado)
- Maneja comillas y metacaracteres sin mezclar con ejecucion.
- Detecta errores de comillas sin cerrar.
- Facilita el parseo posterior.
## 5. Parser
- Convierte tokens en comandos reales.
- Cada `TOK_PIPE` crea un nuevo comando.
- Redirecciones se guardan en lista separada (`t_redir`).
- Valida errores (pipe sin comando, redireccion sin destino).
## 6. Expansion
- `expand_pipeline()` recorre argv y targets de redireccion.
- Reglas:
- En comilla simple no se expande.
- En comilla doble si se expande.
- `$?` es el exit status anterior.
## 7. Redirecciones y heredoc
- `apply_redirections()` abre y hace `dup2()`.
- `prepare_heredocs()` genera un pipe con el contenido.
- Heredoc no se mete en el historial.
## 8. Ejecucion y pipes
- Si hay un solo builtin, se ejecuta en el padre.
- Si hay pipeline, todos se forkean.
- Se conectan con `pipe()` y `dup2()`.
- Se espera a todos, y el exit status es el del ultimo comando.
## 9. Builtins
- Implementados en `src/builtins/builtins.c`.
- `echo`, `cd`, `pwd`, `env`, `export`, `unset`, `exit`.
- `export` valida identificadores y permite `KEY=VALUE`.
## 10. Señales
- Una sola global: `g_signal`.
- `ctrl-C`: limpia linea y muestra prompt.
- `ctrl-\`: se ignora en interactivo.
- En child se restauran señales por defecto.
## 11. Ejemplos rapidos para demostrar
- Pipes: `ls | wc -l`
- Redireccion: `echo hola > out.txt`
- Heredoc: `cat << EOF` -> texto -> `EOF`
- Expansion: `echo $HOME`, `echo $?`
## 12. Mensaje final recomendado
"Separar lexer, parser, expansion y ejecucion me permitio mantener el codigo claro
y replicar el comportamiento de bash para el minimo requerido por el subject."

View File

@@ -0,0 +1,59 @@
# Minishell - Checklist de pruebas manuales
Ejecuta en `minishell-codex/`:
- `make`
- `./minishell`
## 1. Prompt y salida
- Iniciar y salir con `ctrl-D`.
- `exit` debe cerrar el shell con el ultimo status.
## 2. Comandos simples
- `ls`
- `pwd`
- `echo hola`
## 3. Builtins
- `echo -n hola` (sin salto de linea)
- `cd /` luego `pwd`
- `export TEST=42` luego `env | grep TEST`
- `unset TEST` luego `env | grep TEST` (no debe aparecer)
- `env` sin argumentos
- `exit 2`
## 4. Expansion
- `echo $HOME`
- `echo $?` despues de un comando que falle (ej: `ls noexiste`)
- `echo '$HOME'` (no expande)
- `echo "$HOME"` (si expande)
## 5. Pipes
- `ls | wc -l`
- `echo hola | cat`
- `cat /etc/passwd | grep root | wc -l`
## 6. Redirecciones
- `echo hola > out.txt` y luego `cat out.txt`
- `echo 1 >> out.txt` y luego `cat out.txt`
- `cat < out.txt`
## 7. Heredoc
- `cat << EOF`
- escribir varias lineas
- `EOF`
- Ver que se imprime todo lo escrito.
## 8. Comillas
- `echo "a b c"` (una sola palabra)
- `echo 'a b c'` (una sola palabra)
- `echo "a 'b' c"`
## 9. Errores de parseo
- `| ls` (no debe ejecutar)
- `echo hola >` (error)
- `echo "hola` (comillas sin cerrar)
## 10. Senales
- `ctrl-C` en prompt: debe limpiar linea y mostrar prompt nuevo.
- `sleep 5` y `ctrl-C`: debe interrumpir el proceso.
- `ctrl-\` no debe imprimir nada en prompt interactivo.

View File

@@ -0,0 +1,136 @@
#ifndef MINISHELL_H
#define MINISHELL_H
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <termios.h>
#include <readline/readline.h>
#include <readline/history.h>
#define MS_PROMPT "minishell> "
extern int g_signal;
typedef enum e_tokentype
{
TOK_WORD,
TOK_PIPE,
TOK_REDIR_IN,
TOK_REDIR_OUT,
TOK_REDIR_APPEND,
TOK_HEREDOC
} t_tokentype;
typedef struct s_token
{
char *text;
t_tokentype type;
struct s_token *next;
} t_token;
typedef enum e_redirtype
{
REDIR_IN,
REDIR_OUT,
REDIR_APPEND,
REDIR_HEREDOC
} t_redirtype;
typedef struct s_redir
{
t_redirtype type;
char *target;
int fd;
struct s_redir *next;
} t_redir;
typedef struct s_command
{
char **argv;
int argc;
char *path;
t_redir *redirs;
} t_command;
typedef struct s_pipeline
{
t_command **cmds;
size_t count;
} t_pipeline;
typedef struct s_env
{
char *key;
char *value;
struct s_env *next;
} t_env;
typedef struct s_shell
{
t_env *env;
int exit_status;
int last_status;
int exit_requested;
int interactive;
} t_shell;
/* core */
void ms_init(t_shell *sh, char **envp);
void ms_loop(t_shell *sh);
void ms_cleanup(t_shell *sh);
void ms_setup_signals(t_shell *sh);
void ms_set_child_signals(void);
void ms_set_heredoc_signals(void);
/* parser */
t_token *lex_line(const char *line, int *error);
void free_tokens(t_token *toks);
t_pipeline *parse_tokens(t_token *toks, int *error);
void free_pipeline(t_pipeline *p);
int expand_pipeline(t_pipeline *p, t_shell *sh);
/* executor */
int execute_pipeline(t_pipeline *p, t_shell *sh);
int prepare_heredocs(t_pipeline *p, t_shell *sh);
int apply_redirections(t_command *cmd, int *saved_stdin, int *saved_stdout);
void restore_redirections(int saved_stdin, int saved_stdout);
char *resolve_path(const char *cmd, t_shell *sh);
/* env */
void env_init(t_shell *sh, char **envp);
void env_clear(t_shell *sh);
char *env_get(t_shell *sh, const char *key);
int env_set(t_shell *sh, const char *key, const char *value);
int env_unset(t_shell *sh, const char *key);
char **env_to_envp(t_shell *sh);
void env_free_envp(char **envp);
void env_print(t_shell *sh);
/* builtins */
int is_builtin(const char *name);
int exec_builtin(t_command *cmd, t_shell *sh);
/* utils */
int ms_is_space(int c);
int ms_is_alpha(int c);
int ms_is_alnum(int c);
int ms_is_digit(int c);
char *ms_strdup(const char *s);
char *ms_strndup(const char *s, size_t n);
char *ms_strjoin(const char *a, const char *b);
char *ms_strjoin3(const char *a, const char *b, const char *c);
char *ms_substr(const char *s, size_t start, size_t len);
char **ms_split(const char *s, char delim);
void ms_free_split(char **sp);
char *ms_itoa(int n);
#endif

View File

@@ -0,0 +1,221 @@
#include "minishell.h"
static int builtin_echo(t_command *cmd, t_shell *sh);
static int builtin_cd(t_command *cmd, t_shell *sh);
static int builtin_pwd(t_command *cmd, t_shell *sh);
static int builtin_env(t_command *cmd, t_shell *sh);
static int builtin_export(t_command *cmd, t_shell *sh);
static int builtin_unset(t_command *cmd, t_shell *sh);
static int builtin_exit(t_command *cmd, t_shell *sh);
int is_builtin(const char *name)
{
if (!name)
return 0;
return (strcmp(name, "echo") == 0 || strcmp(name, "cd") == 0
|| strcmp(name, "pwd") == 0 || strcmp(name, "env") == 0
|| strcmp(name, "export") == 0 || strcmp(name, "unset") == 0
|| strcmp(name, "exit") == 0);
}
int exec_builtin(t_command *cmd, t_shell *sh)
{
if (strcmp(cmd->argv[0], "echo") == 0)
return builtin_echo(cmd, sh);
if (strcmp(cmd->argv[0], "cd") == 0)
return builtin_cd(cmd, sh);
if (strcmp(cmd->argv[0], "pwd") == 0)
return builtin_pwd(cmd, sh);
if (strcmp(cmd->argv[0], "env") == 0)
return builtin_env(cmd, sh);
if (strcmp(cmd->argv[0], "export") == 0)
return builtin_export(cmd, sh);
if (strcmp(cmd->argv[0], "unset") == 0)
return builtin_unset(cmd, sh);
if (strcmp(cmd->argv[0], "exit") == 0)
return builtin_exit(cmd, sh);
return 1;
}
static int builtin_echo(t_command *cmd, t_shell *sh)
{
int i = 1;
int newline = 1;
(void)sh;
while (cmd->argv[i] && cmd->argv[i][0] == '-' && cmd->argv[i][1] == 'n')
{
int j = 2;
while (cmd->argv[i][j] == 'n')
j++;
if (cmd->argv[i][j] != '\0')
break;
newline = 0;
i++;
}
while (cmd->argv[i])
{
printf("%s", cmd->argv[i]);
if (cmd->argv[i + 1])
printf(" ");
i++;
}
if (newline)
printf("\n");
return 0;
}
static int builtin_pwd(t_command *cmd, t_shell *sh)
{
char buf[4096];
(void)cmd;
(void)sh;
if (getcwd(buf, sizeof(buf)))
printf("%s\n", buf);
return 0;
}
static int builtin_cd(t_command *cmd, t_shell *sh)
{
char *path;
char cwd[4096];
if (cmd->argc > 2)
{
fprintf(stderr, "minishell: cd: too many arguments\n");
return 1;
}
if (cmd->argc == 1)
path = env_get(sh, "HOME");
else
path = cmd->argv[1];
if (!path)
{
fprintf(stderr, "minishell: cd: HOME not set\n");
return 1;
}
if (getcwd(cwd, sizeof(cwd)))
env_set(sh, "OLDPWD", cwd);
if (chdir(path) != 0)
{
perror("minishell: cd");
return 1;
}
if (getcwd(cwd, sizeof(cwd)))
env_set(sh, "PWD", cwd);
return 0;
}
static int builtin_env(t_command *cmd, t_shell *sh)
{
if (cmd->argc > 1)
{
fprintf(stderr, "minishell: env: too many arguments\n");
return 1;
}
env_print(sh);
return 0;
}
static int valid_identifier(const char *s)
{
int i = 0;
if (!s || !ms_is_alpha((unsigned char)s[0]))
return 0;
while (s[i])
{
if (!ms_is_alnum((unsigned char)s[i]))
return 0;
i++;
}
return 1;
}
static int builtin_export(t_command *cmd, t_shell *sh)
{
int i = 1;
if (cmd->argc == 1)
{
env_print(sh);
return 0;
}
while (cmd->argv[i])
{
char *eq = strchr(cmd->argv[i], '=');
if (eq)
{
char *key = ms_strndup(cmd->argv[i], (size_t)(eq - cmd->argv[i]));
char *val = ms_strdup(eq + 1);
if (!valid_identifier(key))
fprintf(stderr, "minishell: export: `%s': not a valid identifier\n", cmd->argv[i]);
else
env_set(sh, key, val);
free(key);
free(val);
}
else
{
if (!valid_identifier(cmd->argv[i]))
fprintf(stderr, "minishell: export: `%s': not a valid identifier\n", cmd->argv[i]);
else
env_set(sh, cmd->argv[i], "");
}
i++;
}
return 0;
}
static int builtin_unset(t_command *cmd, t_shell *sh)
{
int i = 1;
while (cmd->argv[i])
{
env_unset(sh, cmd->argv[i]);
i++;
}
return 0;
}
static int builtin_exit(t_command *cmd, t_shell *sh)
{
long code = sh->exit_status;
int i = 0;
if (sh->interactive)
printf("exit\n");
if (cmd->argc == 1)
{
sh->exit_requested = 1;
sh->exit_status = (int)(code & 0xFF);
return sh->exit_status;
}
if (cmd->argc > 2)
{
fprintf(stderr, "minishell: exit: too many arguments\n");
return 1;
}
if (cmd->argv[1][i] == '+' || cmd->argv[1][i] == '-')
i++;
if (cmd->argv[1][i] == '\0')
{
fprintf(stderr, "minishell: exit: %s: numeric argument required\n", cmd->argv[1]);
sh->exit_requested = 1;
sh->exit_status = 2;
return 2;
}
while (cmd->argv[1][i])
{
if (!ms_is_digit((unsigned char)cmd->argv[1][i]))
{
fprintf(stderr, "minishell: exit: %s: numeric argument required\n", cmd->argv[1]);
sh->exit_requested = 1;
sh->exit_status = 2;
return 2;
}
i++;
}
code = strtol(cmd->argv[1], NULL, 10);
sh->exit_requested = 1;
sh->exit_status = (int)(code & 0xFF);
return sh->exit_status;
}

View File

@@ -0,0 +1,18 @@
#include "minishell.h"
void ms_init(t_shell *sh, char **envp)
{
memset(sh, 0, sizeof(*sh));
sh->interactive = isatty(STDIN_FILENO);
sh->exit_status = 0;
sh->last_status = 0;
sh->exit_requested = 0;
env_init(sh, envp);
ms_setup_signals(sh);
}
void ms_cleanup(t_shell *sh)
{
env_clear(sh);
rl_clear_history();
}

View File

@@ -0,0 +1,46 @@
#include "minishell.h"
static void handle_line(t_shell *sh, char *line)
{
int error = 0;
t_token *toks = NULL;
t_pipeline *p = NULL;
toks = lex_line(line, &error);
if (error)
{
free_tokens(toks);
return;
}
p = parse_tokens(toks, &error);
free_tokens(toks);
if (error || !p)
{
free_pipeline(p);
return;
}
if (expand_pipeline(p, sh) != 0)
{
free_pipeline(p);
return;
}
sh->exit_status = execute_pipeline(p, sh);
sh->last_status = sh->exit_status;
free_pipeline(p);
}
void ms_loop(t_shell *sh)
{
char *line;
while (!sh->exit_requested)
{
line = readline(MS_PROMPT);
if (!line)
break;
if (line[0] != '\0')
add_history(line);
handle_line(sh, line);
free(line);
}
}

View File

@@ -0,0 +1,46 @@
#include "minishell.h"
int g_signal = 0;
static void sigint_handler(int sig)
{
g_signal = sig;
write(STDOUT_FILENO, "\n", 1);
rl_on_new_line();
rl_replace_line("", 0);
rl_redisplay();
}
static void sigquit_handler(int sig)
{
g_signal = sig;
(void)sig;
}
void ms_setup_signals(t_shell *sh)
{
struct sigaction sa_int;
struct sigaction sa_quit;
(void)sh;
memset(&sa_int, 0, sizeof(sa_int));
memset(&sa_quit, 0, sizeof(sa_quit));
sa_int.sa_handler = sigint_handler;
sa_quit.sa_handler = sigquit_handler;
sigemptyset(&sa_int.sa_mask);
sigemptyset(&sa_quit.sa_mask);
sigaction(SIGINT, &sa_int, NULL);
sigaction(SIGQUIT, &sa_quit, NULL);
}
void ms_set_child_signals(void)
{
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
}
void ms_set_heredoc_signals(void)
{
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_IGN);
}

171
minishell-codex/src/env/env.c vendored Normal file
View File

@@ -0,0 +1,171 @@
#include "minishell.h"
static t_env *env_new(const char *key, const char *value)
{
t_env *n = (t_env *)calloc(1, sizeof(t_env));
if (!n)
return NULL;
n->key = ms_strdup(key);
n->value = value ? ms_strdup(value) : ms_strdup("");
if (!n->key || !n->value)
{
free(n->key);
free(n->value);
free(n);
return NULL;
}
return n;
}
void env_init(t_shell *sh, char **envp)
{
int i;
char *eq;
sh->env = NULL;
if (!envp)
return;
i = 0;
while (envp[i])
{
eq = strchr(envp[i], '=');
if (eq)
{
char *key = ms_strndup(envp[i], (size_t)(eq - envp[i]));
char *val = ms_strdup(eq + 1);
env_set(sh, key, val);
free(key);
free(val);
}
i++;
}
}
void env_clear(t_shell *sh)
{
t_env *cur = sh->env;
t_env *next;
while (cur)
{
next = cur->next;
free(cur->key);
free(cur->value);
free(cur);
cur = next;
}
sh->env = NULL;
}
char *env_get(t_shell *sh, const char *key)
{
t_env *cur = sh->env;
while (cur)
{
if (strcmp(cur->key, key) == 0)
return cur->value;
cur = cur->next;
}
return NULL;
}
int env_set(t_shell *sh, const char *key, const char *value)
{
t_env *cur = sh->env;
t_env *prev = NULL;
while (cur)
{
if (strcmp(cur->key, key) == 0)
{
char *dup = ms_strdup(value ? value : "");
if (!dup)
return 1;
free(cur->value);
cur->value = dup;
return 0;
}
prev = cur;
cur = cur->next;
}
cur = env_new(key, value);
if (!cur)
return 1;
if (prev)
prev->next = cur;
else
sh->env = cur;
return 0;
}
int env_unset(t_shell *sh, const char *key)
{
t_env *cur = sh->env;
t_env *prev = NULL;
while (cur)
{
if (strcmp(cur->key, key) == 0)
{
if (prev)
prev->next = cur->next;
else
sh->env = cur->next;
free(cur->key);
free(cur->value);
free(cur);
return 0;
}
prev = cur;
cur = cur->next;
}
return 0;
}
char **env_to_envp(t_shell *sh)
{
char **envp;
int count = 0;
t_env *cur = sh->env;
int i = 0;
while (cur)
{
count++;
cur = cur->next;
}
envp = (char **)calloc((size_t)count + 1, sizeof(char *));
if (!envp)
return NULL;
cur = sh->env;
while (cur)
{
char *kv = ms_strjoin3(cur->key, "=", cur->value);
envp[i++] = kv;
cur = cur->next;
}
envp[i] = NULL;
return envp;
}
void env_free_envp(char **envp)
{
int i = 0;
if (!envp)
return;
while (envp[i])
free(envp[i++]);
free(envp);
}
void env_print(t_shell *sh)
{
t_env *cur = sh->env;
while (cur)
{
if (cur->value)
printf("%s=%s\n", cur->key, cur->value);
cur = cur->next;
}
}

View File

@@ -0,0 +1,134 @@
#include "minishell.h"
static int exec_external(t_command *cmd, t_shell *sh)
{
char **envp = env_to_envp(sh);
if (!envp)
return 1;
execve(cmd->path, cmd->argv, envp);
perror(cmd->path);
env_free_envp(envp);
return 126;
}
static int run_command_child(t_command *cmd, t_shell *sh)
{
int saved_in, saved_out;
if (apply_redirections(cmd, &saved_in, &saved_out) != 0)
return 1;
if (is_builtin(cmd->argv[0]))
return exec_builtin(cmd, sh);
return exec_external(cmd, sh);
}
static int run_command_parent_builtin(t_command *cmd, t_shell *sh)
{
int saved_in, saved_out;
int status;
if (apply_redirections(cmd, &saved_in, &saved_out) != 0)
return 1;
status = exec_builtin(cmd, sh);
restore_redirections(saved_in, saved_out);
return status;
}
static int setup_pipes(int idx, int count, int pipefd[2])
{
if (idx + 1 >= count)
return 0;
if (pipe(pipefd) == -1)
return 1;
return 0;
}
static void setup_child_fds(int idx, int count, int prev_read, int pipefd[2])
{
if (prev_read != -1)
{
dup2(prev_read, STDIN_FILENO);
close(prev_read);
}
if (idx + 1 < count)
{
dup2(pipefd[1], STDOUT_FILENO);
close(pipefd[0]);
close(pipefd[1]);
}
}
static void close_parent_fds(int idx, int count, int *prev_read, int pipefd[2])
{
if (*prev_read != -1)
close(*prev_read);
if (idx + 1 < count)
{
close(pipefd[1]);
*prev_read = pipefd[0];
}
else
*prev_read = -1;
}
int execute_pipeline(t_pipeline *p, t_shell *sh)
{
int i;
int prev_read = -1;
pid_t *pids;
int status = 0;
if (!p || p->count == 0)
return 0;
if (prepare_heredocs(p, sh) != 0)
return sh->exit_status;
for (size_t k = 0; k < p->count; k++)
{
if (!p->cmds[k]->argv || !p->cmds[k]->argv[0])
return 1;
free(p->cmds[k]->path);
p->cmds[k]->path = resolve_path(p->cmds[k]->argv[0], sh);
}
if (p->count == 1 && is_builtin(p->cmds[0]->argv[0]))
return run_command_parent_builtin(p->cmds[0], sh);
pids = (pid_t *)calloc(p->count, sizeof(pid_t));
if (!pids)
return 1;
for (i = 0; i < (int)p->count; i++)
{
int pipefd[2] = {-1, -1};
if (setup_pipes(i, (int)p->count, pipefd))
break;
pids[i] = fork();
if (pids[i] == 0)
{
ms_set_child_signals();
setup_child_fds(i, (int)p->count, prev_read, pipefd);
if (!p->cmds[i]->path)
{
fprintf(stderr, "minishell: %s: command not found\n", p->cmds[i]->argv[0]);
exit(127);
}
status = run_command_child(p->cmds[i], sh);
exit(status);
}
close_parent_fds(i, (int)p->count, &prev_read, pipefd);
}
for (i = 0; i < (int)p->count; i++)
{
int wstatus = 0;
if (pids[i] > 0)
{
waitpid(pids[i], &wstatus, 0);
if (i == (int)p->count - 1)
{
if (WIFEXITED(wstatus))
status = WEXITSTATUS(wstatus);
else if (WIFSIGNALED(wstatus))
status = 128 + WTERMSIG(wstatus);
}
}
}
free(pids);
return status;
}

View File

@@ -0,0 +1,41 @@
#include "minishell.h"
static int has_slash(const char *s)
{
return (s && strchr(s, '/'));
}
char *resolve_path(const char *cmd, t_shell *sh)
{
char *path_env;
char **parts;
char *candidate;
int i;
if (!cmd)
return NULL;
if (is_builtin(cmd))
return ms_strdup(cmd);
if (has_slash(cmd))
return ms_strdup(cmd);
path_env = env_get(sh, "PATH");
if (!path_env)
return NULL;
parts = ms_split(path_env, ':');
if (!parts)
return NULL;
i = 0;
while (parts[i])
{
candidate = ms_strjoin3(parts[i], "/", cmd);
if (candidate && access(candidate, X_OK) == 0)
{
ms_free_split(parts);
return candidate;
}
free(candidate);
i++;
}
ms_free_split(parts);
return NULL;
}

View File

@@ -0,0 +1,108 @@
#include "minishell.h"
static int open_redir(t_redir *r)
{
if (r->type == REDIR_IN)
return open(r->target, O_RDONLY);
if (r->type == REDIR_OUT)
return open(r->target, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (r->type == REDIR_APPEND)
return open(r->target, O_WRONLY | O_CREAT | O_APPEND, 0644);
return -1;
}
int prepare_heredocs(t_pipeline *p, t_shell *sh)
{
size_t i;
g_signal = 0;
for (i = 0; i < p->count; i++)
{
t_redir *r = p->cmds[i]->redirs;
while (r)
{
if (r->type == REDIR_HEREDOC)
{
int fds[2];
char *line;
ms_set_heredoc_signals();
if (pipe(fds) == -1)
return 1;
while (1)
{
line = readline("> ");
if (!line)
break;
if (strcmp(line, r->target) == 0)
{
free(line);
break;
}
write(fds[1], line, strlen(line));
write(fds[1], "\n", 1);
free(line);
}
close(fds[1]);
r->fd = fds[0];
ms_setup_signals(sh);
if (g_signal == SIGINT)
{
sh->exit_status = 130;
return 1;
}
}
r = r->next;
}
}
return 0;
}
int apply_redirections(t_command *cmd, int *saved_stdin, int *saved_stdout)
{
t_redir *r = cmd->redirs;
*saved_stdin = -1;
*saved_stdout = -1;
while (r)
{
int fd = -1;
if (r->type == REDIR_HEREDOC)
fd = r->fd;
else
fd = open_redir(r);
if (fd == -1)
{
perror(r->target);
return 1;
}
if (r->type == REDIR_IN || r->type == REDIR_HEREDOC)
{
if (*saved_stdin == -1)
*saved_stdin = dup(STDIN_FILENO);
dup2(fd, STDIN_FILENO);
}
else
{
if (*saved_stdout == -1)
*saved_stdout = dup(STDOUT_FILENO);
dup2(fd, STDOUT_FILENO);
}
if (r->type != REDIR_HEREDOC)
close(fd);
r = r->next;
}
return 0;
}
void restore_redirections(int saved_stdin, int saved_stdout)
{
if (saved_stdin != -1)
{
dup2(saved_stdin, STDIN_FILENO);
close(saved_stdin);
}
if (saved_stdout != -1)
{
dup2(saved_stdout, STDOUT_FILENO);
close(saved_stdout);
}
}

View File

@@ -0,0 +1,17 @@
#include "minishell.h"
int main(int argc, char **argv, char **envp)
{
t_shell sh;
(void)argv;
if (argc != 1)
{
fprintf(stderr, "Usage: ./minishell\n");
return 1;
}
ms_init(&sh, envp);
ms_loop(&sh);
ms_cleanup(&sh);
return sh.exit_status;
}

View File

@@ -0,0 +1,113 @@
#include "minishell.h"
static void buf_append(char **buf, size_t *len, size_t *cap, const char *s)
{
size_t slen;
char *newbuf;
if (!s)
return;
slen = strlen(s);
if (*len + slen + 1 > *cap)
{
*cap = (*len + slen + 1) * 2;
newbuf = (char *)realloc(*buf, *cap);
if (!newbuf)
return;
*buf = newbuf;
}
memcpy(*buf + *len, s, slen);
*len += slen;
(*buf)[*len] = '\0';
}
static void buf_append_char(char **buf, size_t *len, size_t *cap, char c)
{
char tmp[2];
tmp[0] = c;
tmp[1] = '\0';
buf_append(buf, len, cap, tmp);
}
static char *expand_word(const char *word, t_shell *sh)
{
int in_single = 0;
int in_double = 0;
size_t i = 0;
char *out = NULL;
size_t len = 0, cap = 0;
while (word && word[i])
{
if (word[i] == '\'' && !in_double)
{
in_single = !in_single;
i++;
continue;
}
if (word[i] == '"' && !in_single)
{
in_double = !in_double;
i++;
continue;
}
if (word[i] == '$' && !in_single)
{
if (word[i + 1] == '?')
{
char *v = ms_itoa(sh->last_status);
buf_append(&out, &len, &cap, v);
free(v);
i += 2;
continue;
}
if (ms_is_alpha((unsigned char)word[i + 1]))
{
size_t j = i + 1;
while (word[j] && ms_is_alnum((unsigned char)word[j]))
j++;
char *name = ms_strndup(word + i + 1, j - (i + 1));
char *val = env_get(sh, name);
buf_append(&out, &len, &cap, val ? val : "");
free(name);
i = j;
continue;
}
}
buf_append_char(&out, &len, &cap, word[i]);
i++;
}
if (!out)
out = ms_strdup("");
return out;
}
int expand_pipeline(t_pipeline *p, t_shell *sh)
{
size_t i;
int j;
for (i = 0; i < p->count; i++)
{
for (j = 0; p->cmds[i]->argv && p->cmds[i]->argv[j]; j++)
{
char *neww = expand_word(p->cmds[i]->argv[j], sh);
free(p->cmds[i]->argv[j]);
p->cmds[i]->argv[j] = neww;
}
if (p->cmds[i]->redirs)
{
t_redir *r = p->cmds[i]->redirs;
while (r)
{
if (r->target)
{
char *nt = expand_word(r->target, sh);
free(r->target);
r->target = nt;
}
r = r->next;
}
}
}
return 0;
}

View File

@@ -0,0 +1,131 @@
#include "minishell.h"
static t_token *token_new(t_tokentype type, const char *text, size_t len)
{
t_token *t = (t_token *)calloc(1, sizeof(t_token));
if (!t)
return NULL;
t->type = type;
if (text)
t->text = ms_strndup(text, len);
return t;
}
static void token_add(t_token **head, t_token *new)
{
t_token *cur;
if (!new)
return;
if (!*head)
{
*head = new;
return;
}
cur = *head;
while (cur->next)
cur = cur->next;
cur->next = new;
}
void free_tokens(t_token *toks)
{
t_token *n;
while (toks)
{
n = toks->next;
free(toks->text);
free(toks);
toks = n;
}
}
static int is_meta(char c)
{
return (c == '|' || c == '<' || c == '>');
}
static int read_word(const char *line, size_t *i, t_token **out)
{
size_t start = *i;
int in_single = 0;
int in_double = 0;
while (line[*i])
{
if (line[*i] == '\'' && !in_double)
in_single = !in_single;
else if (line[*i] == '"' && !in_single)
in_double = !in_double;
else if (!in_single && !in_double)
{
if (ms_is_space(line[*i]) || is_meta(line[*i]))
break;
}
(*i)++;
}
if (in_single || in_double)
return 1;
*out = token_new(TOK_WORD, line + start, *i - start);
return 0;
}
t_token *lex_line(const char *line, int *error)
{
t_token *toks = NULL;
size_t i = 0;
*error = 0;
while (line[i])
{
if (ms_is_space(line[i]))
{
i++;
continue;
}
if (line[i] == '|')
{
token_add(&toks, token_new(TOK_PIPE, "|", 1));
i++;
continue;
}
if (line[i] == '<')
{
if (line[i + 1] == '<')
{
token_add(&toks, token_new(TOK_HEREDOC, "<<", 2));
i += 2;
}
else
{
token_add(&toks, token_new(TOK_REDIR_IN, "<", 1));
i++;
}
continue;
}
if (line[i] == '>')
{
if (line[i + 1] == '>')
{
token_add(&toks, token_new(TOK_REDIR_APPEND, ">>", 2));
i += 2;
}
else
{
token_add(&toks, token_new(TOK_REDIR_OUT, ">", 1));
i++;
}
continue;
}
{
t_token *w = NULL;
if (read_word(line, &i, &w))
{
*error = 1;
free_tokens(toks);
return NULL;
}
token_add(&toks, w);
}
}
return toks;
}

View File

@@ -0,0 +1,175 @@
#include "minishell.h"
static t_command *command_new(void)
{
t_command *cmd = (t_command *)calloc(1, sizeof(t_command));
return cmd;
}
static void command_add_arg(t_command *cmd, const char *text)
{
char **new_argv;
int i;
if (!cmd || !text)
return;
i = cmd->argc;
new_argv = (char **)calloc((size_t)i + 2, sizeof(char *));
if (!new_argv)
return;
for (int j = 0; j < i; j++)
new_argv[j] = cmd->argv[j];
new_argv[i] = ms_strdup(text);
new_argv[i + 1] = NULL;
free(cmd->argv);
cmd->argv = new_argv;
cmd->argc += 1;
}
static void command_add_redir(t_command *cmd, t_redirtype type, const char *target)
{
t_redir *r = (t_redir *)calloc(1, sizeof(t_redir));
t_redir *cur;
if (!r)
return;
r->type = type;
r->target = ms_strdup(target);
r->fd = -1;
r->next = NULL;
if (!cmd->redirs)
{
cmd->redirs = r;
return;
}
cur = cmd->redirs;
while (cur->next)
cur = cur->next;
cur->next = r;
}
static void free_redirs(t_redir *r)
{
t_redir *n;
while (r)
{
n = r->next;
if (r->fd != -1)
close(r->fd);
free(r->target);
free(r);
r = n;
}
}
void free_pipeline(t_pipeline *p)
{
size_t i;
if (!p)
return;
for (i = 0; i < p->count; i++)
{
if (p->cmds[i])
{
for (int j = 0; p->cmds[i]->argv && p->cmds[i]->argv[j]; j++)
free(p->cmds[i]->argv[j]);
free(p->cmds[i]->argv);
free(p->cmds[i]->path);
free_redirs(p->cmds[i]->redirs);
free(p->cmds[i]);
}
}
free(p->cmds);
free(p);
}
static int pipeline_add_cmd(t_pipeline *p, t_command *cmd)
{
t_command **new_cmds;
size_t i;
new_cmds = (t_command **)calloc(p->count + 1, sizeof(t_command *));
if (!new_cmds)
return 1;
for (i = 0; i < p->count; i++)
new_cmds[i] = p->cmds[i];
new_cmds[p->count] = cmd;
free(p->cmds);
p->cmds = new_cmds;
p->count += 1;
return 0;
}
t_pipeline *parse_tokens(t_token *toks, int *error)
{
t_pipeline *p;
t_command *cmd;
t_token *cur;
*error = 0;
p = (t_pipeline *)calloc(1, sizeof(t_pipeline));
if (!p)
return NULL;
cmd = command_new();
if (!cmd)
{
free(p);
return NULL;
}
cur = toks;
while (cur)
{
if (cur->type == TOK_PIPE)
{
if (cmd->argc == 0)
{
*error = 1;
free_pipeline(p);
free(cmd);
return NULL;
}
pipeline_add_cmd(p, cmd);
cmd = command_new();
if (!cmd)
{
*error = 1;
free_pipeline(p);
return NULL;
}
cur = cur->next;
continue;
}
if (cur->type == TOK_REDIR_IN || cur->type == TOK_REDIR_OUT
|| cur->type == TOK_REDIR_APPEND || cur->type == TOK_HEREDOC)
{
if (!cur->next || cur->next->type != TOK_WORD)
{
*error = 1;
free_pipeline(p);
free(cmd);
return NULL;
}
if (cur->type == TOK_REDIR_IN)
command_add_redir(cmd, REDIR_IN, cur->next->text);
else if (cur->type == TOK_REDIR_OUT)
command_add_redir(cmd, REDIR_OUT, cur->next->text);
else if (cur->type == TOK_REDIR_APPEND)
command_add_redir(cmd, REDIR_APPEND, cur->next->text);
else if (cur->type == TOK_HEREDOC)
command_add_redir(cmd, REDIR_HEREDOC, cur->next->text);
cur = cur->next->next;
continue;
}
if (cur->type == TOK_WORD)
command_add_arg(cmd, cur->text);
cur = cur->next;
}
if (cmd->argc == 0)
{
*error = 1;
free_pipeline(p);
free(cmd);
return NULL;
}
pipeline_add_cmd(p, cmd);
return p;
}

View File

@@ -0,0 +1,6 @@
#include "minishell.h"
int ms_is_space(int c) { return (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f'); }
int ms_is_alpha(int c) { return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_'); }
int ms_is_digit(int c) { return (c >= '0' && c <= '9'); }
int ms_is_alnum(int c) { return (ms_is_alpha(c) || ms_is_digit(c)); }

View File

@@ -0,0 +1,67 @@
#include "minishell.h"
static size_t count_parts(const char *s, char delim)
{
size_t count = 0;
int in = 0;
while (*s)
{
if (*s == delim)
in = 0;
else if (!in)
{
in = 1;
count++;
}
s++;
}
return count;
}
char **ms_split(const char *s, char delim)
{
char **out;
size_t parts;
size_t i = 0;
size_t start = 0;
size_t len = 0;
int in = 0;
if (!s)
return NULL;
parts = count_parts(s, delim);
out = (char **)calloc(parts + 1, sizeof(char *));
if (!out)
return NULL;
while (s[i])
{
if (s[i] == delim)
{
if (in)
{
out[len++] = ms_strndup(s + start, i - start);
in = 0;
}
}
else if (!in)
{
in = 1;
start = i;
}
i++;
}
if (in)
out[len++] = ms_strndup(s + start, i - start);
out[len] = NULL;
return out;
}
void ms_free_split(char **sp)
{
size_t i = 0;
if (!sp)
return;
while (sp[i])
free(sp[i++]);
free(sp);
}

View File

@@ -0,0 +1,100 @@
#include "minishell.h"
char *ms_strdup(const char *s)
{
char *out;
size_t len;
if (!s)
return NULL;
len = strlen(s);
out = (char *)malloc(len + 1);
if (!out)
return NULL;
memcpy(out, s, len);
out[len] = '\0';
return out;
}
char *ms_strndup(const char *s, size_t n)
{
char *out;
if (!s)
return NULL;
out = (char *)malloc(n + 1);
if (!out)
return NULL;
memcpy(out, s, n);
out[n] = '\0';
return out;
}
char *ms_substr(const char *s, size_t start, size_t len)
{
size_t slen;
if (!s)
return NULL;
slen = strlen(s);
if (start >= slen)
return ms_strdup("");
if (start + len > slen)
len = slen - start;
return ms_strndup(s + start, len);
}
char *ms_strjoin(const char *a, const char *b)
{
size_t la;
size_t lb;
char *out;
if (!a || !b)
return NULL;
la = strlen(a);
lb = strlen(b);
out = (char *)malloc(la + lb + 1);
if (!out)
return NULL;
memcpy(out, a, la);
memcpy(out + la, b, lb);
out[la + lb] = '\0';
return out;
}
char *ms_strjoin3(const char *a, const char *b, const char *c)
{
char *ab;
char *abc;
ab = ms_strjoin(a, b);
if (!ab)
return NULL;
abc = ms_strjoin(ab, c);
free(ab);
return abc;
}
char *ms_itoa(int n)
{
char buf[32];
int i;
int neg;
long nb;
nb = n;
neg = (nb < 0);
if (neg)
nb = -nb;
i = 30;
buf[31] = '\0';
if (nb == 0)
buf[i--] = '0';
while (nb > 0)
{
buf[i--] = (char)('0' + (nb % 10));
nb /= 10;
}
if (neg)
buf[i--] = '-';
return ms_strdup(&buf[i + 1]);
}

View File

@@ -6,28 +6,59 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/12/01 17:04:57 by sede-san #+# #+# */
/* Updated: 2025/12/01 17:54:26 by sede-san ### ########.fr */
/* Updated: 2026/02/08 19:51:38 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "minishell.h"
#include "builtins.h"
u_int8_t set_builtins(
t_minishell *msh
) {
msh->builtins = ft_hashmap_new(4, ft_hashmap_hashstr, ft_hashmap_strcmp);
if (msh->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(msh->builtins, ft_strdup("cd"), builtin_cd);
ft_hashmap_put(msh->builtins, ft_strdup("echo"), builtin_echo);
ft_hashmap_put(msh->builtins, ft_strdup("exit"), builtin_exit);
ft_hashmap_put(msh->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(
const char *command_name,
t_minishell *msh
uint8_t set_builtins(
t_minishell *minishell
) {
return (ft_hashmap_contains_key(msh->builtins, command_name));
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

@@ -6,39 +6,103 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/30 01:20:48 by sede-san #+# #+# */
/* Updated: 2025/12/01 19:15:08 by sede-san ### ########.fr */
/* Updated: 2026/02/08 21:16:18 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "builtins.h"
#include <limits.h>
u_int8_t builtin_exit(
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,
t_minishell *msh
){
)
{
uint8_t exit_status;
if (isatty(STDIN_FILENO))
ft_eputendl("exit");
if (cmd.argc == 1)
{
msh->exit = 1;
// return the last exit_status, if none 0 is returned
if (!resolve_exit_status(cmd, msh, &exit_status))
return (msh->exit_status);
}
else if (!ft_strisnum(cmd.argv[1]))
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_eputstr("exit: ");
ft_eputendl(cmd.argv[1]);
ft_eputendl(": numeric argument required");
return (2);
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("exit: too many arguments");
return (2);
}
else
{
msh->exit = 1;
// cast to u_int8_t causes to return a value between 0 and 255
return ((u_int8_t)ft_atol(cmd.argv[1]));
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,12 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* command.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/09 18:40:03 by sede-san #+# #+# */
/* Updated: 2026/02/09 18:40:04 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */

View File

@@ -5,133 +5,226 @@
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/28 13:03:44 by sede-san #+# #+# */
/* Updated: 2025/12/02 09:07:28 by sede-san ### ########.fr */
/* Created: 2026/02/08 19:10:47 by sede-san #+# #+# */
/* Updated: 2026/02/08 21:32:03 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "executor.h"
#include "builtins.h"
static char *solve_path(char *cmd_name, t_minishell *msh);
static u_int8_t path_is_solved(char *cmd_name, t_minishell *msh);
static void handle_child(t_command *cmd, t_minishell *msh);
static void handle_parent(pid_t child_pid, t_command *cmd, t_minishell *msh);
static inline uint8_t execute_builtin(
const t_command *command,
t_minishell *minishell
)
{
const t_builtin_func builtin
= ft_hashmap_get(minishell->builtins, command->path);
u_int8_t execute(
t_command cmd,
t_minishell *msh
) {
pid_t child_pid;
cmd.path = solve_path(cmd.argv[0], msh);
if (!cmd.path)
{
ft_eprintf("minishell: %s: command not found\n", cmd.argv[0]);
return (msh->exit_status = 127, msh->exit_status);
}
if (!is_builtin(cmd.path, msh) && access(cmd.path, X_OK) != EXIT_SUCCESS)
{
ft_eputstr("minishell: ");
perror(cmd.path);
return (msh->exit_status = 126, msh->exit_status);
}
child_pid = 0;
if (!is_builtin(cmd.path, msh))
child_pid = fork();
if (child_pid == -1)
perror("minishell");
else if (child_pid == 0)
handle_child(&cmd, msh);
else
handle_parent(child_pid, &cmd, msh);
return (msh->exit_status);
return (builtin(*command, minishell));
}
static char *solve_path(
char *cmd_name,
t_minishell *msh
){
char *cmd_path;
char **path;
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 (path_is_solved(cmd_name, msh))
// return a copy to avoid double free on parent
return (ft_strdup(cmd_name));
path = ft_split(get_env("PATH", msh), COLON);
if (!path)
return (NULL);
cmd_path = NULL;
i = -1;
while (!cmd_path && path[++i])
if (argv == NULL)
return ;
i = 0;
while (argv[i] != NULL)
{
cmd_path = ft_strnjoin(3, path[i], "/", cmd_name);
if (!cmd_path)
return (NULL);
/**
* If a command exists but user has no execution permission
* the command is shown as non existant instead of showing the
* last ocurrence found
*
* TLDR: bash shows 'Permission denied'
* and minishell 'command not found'
*
* TEST: execute an existing command without permission to do so
*/
if (access(cmd_path, X_OK) != EXIT_SUCCESS)
ft_free((void **)&cmd_path);
free(argv[i]);
i++;
}
ft_free_split((char **)path);
if (!cmd_path)
return (NULL);
return (cmd_path);
free(argv);
}
static u_int8_t path_is_solved(
char *cmd_name,
t_minishell *msh
){
return (ft_strncmp(cmd_name, "/", 1) == 0
|| (cmd_name[1] && ft_strncmp(cmd_name, "./", 2) == 0)
|| (cmd_name[2] && ft_strncmp(cmd_name, "../", 3) == 0)
|| is_builtin(cmd_name, msh)
);
static void cmdfree(
t_command *command
)
{
if (command == NULL)
return ;
cmdfree_argv(command->argv);
free(command->path);
free(command);
}
static void handle_child(
t_command *cmd,
t_minishell *msh
){
char **envp;
t_builtin_func builtin;
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);
}
if (is_builtin(cmd->argv[0], msh))
static bool is_fork_required(
t_list *current_command,
t_minishell *minishell
) {
const t_command *command = current_command->content;
return (current_command->next != NULL
|| !is_builtin(command->path, minishell));
}
static pid_t fork_process(
t_list *current_command,
t_minishell *minishell
)
{
pid_t pid;
pid = 0;
if (is_fork_required(current_command, minishell))
pid = fork();
if (pid == FORK_ERROR)
perror("fork");
return (pid);
}
static void setup_child_input(
t_pipeline *pipeline
)
{
if (pipeline->prev_read_fd != -1)
{
builtin = ft_hashmap_get(msh->builtins, cmd->argv[0]);
builtin(*cmd, msh);
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,
t_minishell *minishell
)
{
uint8_t exit_status;
const t_command *command = current_command->content;
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)
{
close(pipeline->pipefd[WRITE_PIPE]);
pipeline->prev_read_fd = pipeline->pipefd[READ_PIPE];
}
else
{
envp = get_envp(msh);
execve(cmd->path, cmd->argv, envp);
free_envp(envp);
}
pipeline->prev_read_fd = -1;
}
static void handle_parent(
pid_t child_pid,
t_command *cmd,
t_minishell *msh
){
if (waitpid(child_pid, (int *)&msh->exit_status, 0) == EXIT_SUCCESS)
static uint8_t wait_for_children(void)
{
uint8_t exit_status;
int status;
exit_status = EXIT_SUCCESS;
while (wait(&status) > 0)
{
// handle success
if (WIFEXITED(status))
exit_status = WEXITSTATUS(status);
}
else
{
// handle error
}
ft_free((void **)&cmd->path);
ft_free_split(cmd->argv);
return (exit_status);
}
uint8_t execute(
t_list *command_list,
t_minishell *minishell
)
{
uint8_t exit_status;
t_pipeline pipeline;
t_list *current_command;
pid_t pid;
pipeline.prev_read_fd = -1;
current_command = command_list;
while (current_command)
{
if (create_pipe_if_needed(current_command, &pipeline) == PIPE_ERROR)
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);
}

View File

@@ -3,14 +3,15 @@
/* ::: :::::::: */
/* main.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: padan-pe <padan-pe@student.42.fr> +#+ +:+ +#+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/20 16:34:42 by sede-san #+# #+# */
/* Updated: 2025/10/23 17:10:17 by padan-pe ### ########.fr */
/* Updated: 2026/02/09 18:46:21 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "minishell.h"
#include "core.h"
int main(
int argc,
@@ -18,19 +19,14 @@ int main(
char **envp
){
t_minishell minishell;
u_int8_t exit_status;
if (argc != 1 || argv[1] || !envp)
{
printf("Usage: ./minishell\n");
return (EXIT_FAILURE);
}
if (!minishell_init(&minishell, envp))
{
printf("Error: %s\n", "failed to initialize minishell");
return (EXIT_FAILURE);
}
exit_status = minishell_run(&minishell);
minishell_init(&minishell, envp);
minishell_run(&minishell);
minishell_clear(&minishell);
return (exit_status);
return (minishell.exit_status);
}

View File

@@ -6,13 +6,16 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/20 20:51:33 by sede-san #+# #+# */
/* Updated: 2025/12/01 19:02:18 by sede-san ### ########.fr */
/* Updated: 2026/02/09 18:48:34 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "minishell.h"
#include "core.h"
#include "parser.h"
#include "executor.h"
int minishell_init(
void minishell_init(
t_minishell *minishell,
char **envp
){
@@ -20,32 +23,34 @@ int minishell_init(
set_envp(envp, minishell);
set_builtins(minishell);
if (minishell->variables.environment == NULL || minishell->builtins == NULL)
{
minishell_clear(minishell);
return (0);
}
return (1);
}
u_int8_t minishell_run(
void minishell_run(
t_minishell *minishell
){
char *line;
t_command command;
t_list *commands;
line = NULL;
while (minishell->exit == 0)
if (minishell == NULL)
{
line = readline("minishell > ");
if (*line)
minishell->exit_status = EXIT_FAILURE;
return ;
}
while (!minishell->exit)
{
line = readline(DEFAULT_PS1);
if (line != NULL)
{
if (*line != '\0')
{
add_history(line);
command = parse(line, minishell);
execute(command, minishell);
commands = parse(line, minishell);
execute(commands, minishell);
}
free(line);
}
ft_free((void **)&line);
}
return (minishell->exit_status);
}
void minishell_clear(
@@ -56,5 +61,4 @@ void minishell_clear(
ft_hashmap_clear(&minishell->variables.environment, free);
if (minishell->builtins != NULL)
ft_hashmap_clear_keys(&minishell->builtins);
ft_bzero(minishell, sizeof(t_minishell));
}

79
src/parser/lexer.c Normal file
View File

@@ -0,0 +1,79 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* lexer.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/09 18:56:41 by sede-san #+# #+# */
/* Updated: 2026/02/09 20:42:50 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "core.h"
#include "parser.h"
static t_token *tokenize(const char *line, size_t *start);
static t_token_type get_token_type(const char *str);
t_list *lex(
const char *line
) {
t_list *tokens;
t_token *token;
size_t i;
tokens = NULL;
i = 0;
while (line[i] != '\0')
{
// ignore spaces
while (ft_isspace(line[i]))
i++;
// create token
token = tokenize(line, &i);
// add token to list
if (token != NULL)
ft_lstadd_back(&tokens, ft_lstnew(token));
}
return (tokens);
}
static t_token *tokenize(const char *line, size_t *start) {
t_token *token;
t_token_type type;
token = NULL;
if (line == NULL || line[*start] == '\0')
return (NULL);
type = get_token_type(line + *start);
(void)type;
// if (type != TOKEN_WORD)
// token = token_new(type, NULL);
// else
// token = read_word(line, start);
// if (token == NULL)
// (*start) += ft_strlen(token->value);
return (token);
}
static t_token_type get_token_type(const char *str)
{
size_t i;
static const t_map_entry tokens[TOKENS_COUNT] = {
{PIPE_STR, (void *)TOKEN_PIPE},
{REDIRECT_IN_STR, (void *)TOKEN_REDIRECT_IN},
{REDIRECT_OUT_STR, (void *)TOKEN_REDIRECT_OUT},
{APPEND_STR, (void *)TOKEN_APPEND},
{HEREDOC_STR, (void *)TOKEN_HEREDOC}
};
i = 0;
while (i < TOKENS_COUNT)
{
if (ft_strcmp(str, tokens[i].key) == 0)
return ((t_token_type)tokens[i].value);
i++;
}
return (TOKEN_WORD);
}

View File

@@ -6,73 +6,295 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/22 18:37:38 by sede-san #+# #+# */
/* Updated: 2025/10/23 22:53:35 by sede-san ### ########.fr */
/* Updated: 2026/02/09 18:50:43 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "minishell.h"
#include "parser.h"
static char **expand_envs(char **argv);
static int count_argv(char **argv);
// parse exclusive
static char *extract_next_command(char *line, size_t *index);
static char *trim_whitespaces(char *line, size_t *start, size_t *end);
// static void set_pipes(t_list *commands);
char *parse(
// common
static void find_boundary(char *line, size_t *index, char bound_char);
// command exclusive
static t_command *cmdnew(char *line, t_minishell *minishell);
static void set_argv(t_command *command, char *line, t_minishell *minishell);
static void expand_envs(char *arg, t_minishell *minishell);
static char **lst_to_argv(t_list *argv_list);
static void set_argc(t_command *command);
// static void set_infile(t_command *command);
// static void set_outfile(t_command *command);
static void set_path(t_command *command, t_minishell *minishell);
static u_int8_t path_is_solved(char *cmd_name, t_minishell *msh);
static char *solve_path(char *command_name, t_minishell *minishell);
t_list *parse(
char *line,
t_minishell *minishell
){
t_command command;
) {
t_list *commands;
// t_list *tokens;
t_command *command;
char *command_str;
size_t i;
if (!line || !*line)
if (line == NULL)
return (NULL);
(void)minishell;
command.argv = expand_envs(ft_split(line, SPACE));
command.argc = count_argv(command.argv);
if (!command.argc)
return (NULL);
////////////////////////////////////////////////////////////////////////////////
//* DEBUG
// int i = -1;
// while (command.argv[++i])
// printf("argv[%i]: %s\n", i, command.argv[i]);
////////////////////////////////////////////////////////////////////////////////
commands = NULL;
i = 0;
while (line[i] != '\0')
{
// tokens = tokenize();
command_str = extract_next_command(line, &i);
if (command_str != NULL)
{
command = cmdnew(command_str, minishell);
free(command_str);
if (command != NULL)
ft_lstadd_back(&commands, ft_lstnew(command));
}
}
// set_pipes(commands);
return (commands);
}
// static void set_pipes(
// t_list *commands
// ) {
// t_list *current_command;
// t_list *previous_command;
// t_list *next_command;
// t_command *command;
// previous_command = NULL;
// current_command = commands;
// while (current_command != NULL)
// {
// command = (t_command *)current_command->content;
// if (previous_command != NULL)
// command->piped_from = (t_command *)previous_command->content;
// next_command = current_command->next;
// if (next_command != NULL)
// command->piped_to = (t_command *)next_command->content;
// previous_command = current_command;
// current_command = current_command->next;
// }
// }
static char *extract_next_command(
char *line,
size_t *index
) {
char *command_str;
size_t start;
size_t end;
start = *index;
find_boundary(line, index, '|');
end = *index;
command_str = trim_whitespaces(line, &start, &end);
while (line[*index] == '|' || ft_isspace(line[*index]))
(*index)++;
return (command_str);
}
static void find_boundary(
char *line,
size_t *index,
char bound_char
) {
bool in_single_quote;
bool in_double_quote;
in_single_quote = false;
in_double_quote = false;
while (line[*index] != '\0')
{
if (line[*index] == '\'' && !in_double_quote)
in_single_quote = !in_single_quote;
else if (line[*index] == '"' && !in_single_quote)
in_double_quote = !in_double_quote;
if (line[*index] == bound_char && !in_single_quote && !in_double_quote)
break ;
(*index)++;
}
}
static char *trim_whitespaces(
char *line,
size_t *start,
size_t *end
) {
while (*start < *end && ft_isspace(line[*start]))
(*start)++;
while (*end > *start && ft_isspace(line[*end - 1]))
(*end)--;
if (*end > *start)
return (ft_substr(line, *start, *end - *start));
return (NULL);
}
static char **expand_envs(
char **argv
){
int i;
char *env;
static t_command *cmdnew(
char *line,
t_minishell *minishell
) {
t_command *command;
if (!argv)
command = (t_command *)ft_calloc(1, sizeof(t_command));
if (!command)
return (NULL);
else if (!*argv) // check if ft_split returned and empty matrix
// resolve_heredoc
set_argv(command, line, minishell);
if (!command->argv)
{
ft_free_split(argv);
free(command);
return (NULL);
}
i = -1;
while (argv[++i])
set_argc(command);
// set_infile(command);
// set_outfile(command);
set_path(command, minishell);
return (command);
}
static void set_argv(
t_command *command,
char *line,
t_minishell *minishell
) {
t_list *argv_list;
char *arg;
size_t i;
size_t start;
size_t end;
if (line == NULL)
return ;
i = 0;
argv_list = NULL;
while (line[i] != '\0')
{
if (!ft_strchr(argv[i], DOLLAR)
|| (ft_strchr(argv[i], DOLLAR) && ft_strchr(argv[i], SINGLE_QUOTE) && ft_strchr(argv[i] + (ft_strchr(argv[i], SINGLE_QUOTE) + 1 - argv[i]), SINGLE_QUOTE))) // env is surrounded by single quote
continue ;
env = getenv(ft_strchr(argv[i], DOLLAR) + 1);
free(argv[i]);
if (env)
argv[i] = ft_strdup(env);
else
argv[i] = ft_strdup("");
start = i;
find_boundary(line, &i, ' ');
end = i;
arg = trim_whitespaces(line, &start, &end);
expand_envs(arg, minishell);
if (arg != NULL)
ft_lstadd_back(&argv_list, ft_lstnew(arg));
while (ft_isspace(line[i]))
i++;
}
command->argv = lst_to_argv(argv_list);
ft_lstclear(&argv_list, free);
}
static void expand_envs(
char *arg,
t_minishell *minishell
) {
// TODO
(void)arg;
(void)minishell;
}
static char **lst_to_argv(
t_list *argv_list
) {
char **argv;
t_list *current_arg;
size_t i;
argv = (char **)ft_calloc(ft_lstsize(argv_list) + 1, sizeof(char *));
if (!argv)
return (NULL);
i = 0;
current_arg = argv_list;
while (current_arg != NULL)
{
argv[i] = ft_strdup((char *)current_arg->content);
i++;
current_arg = current_arg->next;
}
return (argv);
}
static int count_argv(
char **argv
){
int i;
static void set_argc(
t_command *command
) {
int argc;
i = 0;
while (argv[i])
i++;
return (i);
argc = 0;
while (command->argv[argc] != NULL)
argc++;
command->argc = argc;
}
// static void set_infile(
// t_command *command
// ) {
// // test_infile
// command->infile = -1;
// }
// static void set_outfile(
// t_command *command
// ) {
// // test_outfile
// command->outfile = STDOUT_FILENO;
// }
static void set_path(
t_command *command,
t_minishell *minishell
) {
char *command_path;
char *command_name;
command_name = command->argv[0];
if (!path_is_solved(command_name, minishell))
command_path = solve_path(command_name, minishell);
else
command_path = ft_strdup(command_name);
command->path = command_path;
}
static char *solve_path(
char *command_name,
t_minishell *minishell
){
char *command_path;
char **path_env;
size_t i;
path_env = ft_split(get_env("PATH", minishell), ':');
if (!path_env)
return (NULL);
command_path = NULL;
i = -1;
while (!command_path && path_env[++i])
{
command_path = ft_strnjoin(3, path_env[i], "/", command_name);
if (command_path != NULL && access(command_path, F_OK) != EXIT_SUCCESS)
{
free(command_path);
command_path = NULL;
}
}
ft_free_split(path_env);
return (command_path);
}
static u_int8_t path_is_solved(
char *command_name,
t_minishell *minishell
){
return (ft_strncmp(command_name, "/", 1) == 0
|| (command_name[1] && ft_strncmp(command_name, "./", 2) == 0)
|| (command_name[2] && ft_strncmp(command_name, "../", 3) == 0)
|| is_builtin(command_name, minishell)
);
}

View File

@@ -6,11 +6,12 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/12/01 09:12:39 by sede-san #+# #+# */
/* Updated: 2025/12/01 17:27:57 by sede-san ### ########.fr */
/* Updated: 2026/02/08 19:44:15 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "minishell.h"
#include "core.h"
/**
* @brief Parses and stores environment variables from envp array into a hashmap
@@ -118,7 +119,8 @@ char **get_envp(
env_list = ft_hashmap_entries(msh->variables.environment);
envp = (char **)malloc(
(msh->variables.environment->size + 1) * sizeof(char *));
(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