47 Commits

Author SHA1 Message Date
3b7a11c185 chore: removed unused files 2026-02-14 17:55:37 +01:00
5dc7de73b3 update: updated readme 2026-02-14 17:53:44 +01:00
640c22d366 update: updated readme detailed info 2026-02-14 17:37:38 +01:00
856bc8243f update: added more internal variables 2026-02-14 17:37:22 +01:00
fbd2349060 chore: added minishell version macro 2026-02-14 17:37:09 +01:00
marcnava-42cursus
77a704a09a norma arreglada 2026-02-14 15:12:25 +01:00
marcnava-42cursus
39e5719183 terminado 2026-02-14 15:04:25 +01:00
marcnava-42cursus
c1e622947d fixed heredoc expansion 2026-02-14 14:37:56 +01:00
marcnava-42cursus
001709139b Merge branch 'fix/variable_expansion' into solo 2026-02-14 14:27:52 +01:00
marcnava-42cursus
73ed56aa16 heredoc and builtins fix 2026-02-14 14:26:58 +01:00
5852e8618f multiple fixes 2026-02-14 13:48:15 +01:00
marcnava-42cursus
ba40670ace Fixed variable expansion errors 2026-02-14 13:33:44 +01:00
marcnava-42cursus
dd6101edec A POR TODAAS 2026-02-14 06:14:59 +01:00
e2b734cf0c save commit 2026-02-14 05:48:18 +01:00
32b3bd72b5 fix: prompt was not being requested to hashmap 2026-02-14 02:05:49 +01:00
c95703b42b update: replaced set_env by set_var in export builtin 2026-02-14 02:02:52 +01:00
f4cfae1107 feat: added internal variables 2026-02-14 02:01:10 +01:00
marcnava-42cursus
6453abfda3 Fixed all norme from lexer, parser. minishell and builtins 2026-02-14 00:02:03 +01:00
marcnava-42cursus
7862f3e131 chore: stop tracking minishell-codex 2026-02-13 22:17:55 +01:00
marcnava-42cursus
637391470b Fixed exit using old code + updates 2026-02-13 22:05:25 +01:00
marcnava-42cursus
217505e3b0 Restored exit but adding isatty 2026-02-13 21:54:00 +01:00
5c33afb20a update: removed minishell_tester submodule 2026-02-13 21:05:30 +01:00
3f17f7789c update: added isatty support and prompt usage 2026-02-13 20:18:26 +01:00
5df1520224 chore: added testers 2026-02-13 18:42:54 +01:00
marcnava-42cursus
c166d0f77a feat: Added signals
Added functionality of the signals ctrl+d, ctrl+c and ctr+\
2026-02-13 18:11:48 +01:00
3e31447073 fix: fixed crash on non-existent variables
index was being moved to -1 if token was only the variable, this
probably works too for variables without value
2026-02-13 16:42:50 +01:00
3ead1ad547 chore: updated docs to v10.0 2026-02-13 16:38:36 +01:00
c99e0a17a3 update: renamed variable 2026-02-13 15:08:41 +01:00
dc772d10c2 fix: syntax error on pipe at start was not being detected 2026-02-13 13:48:28 +01:00
e5d7fcf050 fix: fixed leak on variable expansion 2026-02-13 13:33:32 +01:00
d248819dfe fix: commands list was not being freed if the command line ended in pipe
error was reported but commands were being executed as the list was not
being freed
2026-02-13 09:29:24 +01:00
8f92cf269c fix: fixed crash when reading variables with underscore 2026-02-13 08:20:27 +01:00
8353d3f64f chore: removed unused and test files 2026-02-13 08:18:25 +01:00
marcnava-42cursus
4756edb727 Added errors file after merge 2026-02-12 22:03:59 +01:00
d06e49274b Merge branch 'feature/executor-gl' into solo 2026-02-12 21:26:12 +01:00
marcnava-42cursus
ff5edf543f Updated AGENTS file with redirections options 2026-02-12 20:32:12 +01:00
marcnava-42cursus
518ec87e60 Deleted tester 2026-02-12 20:28:29 +01:00
marcnava-42cursus
6bc2eab19b feat: Redirections applied
Now redirections work.
> redirects to a file
< reads the content of a file and set it to the input of a command
>> append the output of a command into a file
2026-02-12 20:28:05 +01:00
marcnava-42cursus
3c7ee5b161 Merge branch 'feature/builtins-gl' into feature/executor-gl
# Conflicts:
#	src/parser/parser.c
2026-02-12 18:36:05 +01:00
marcnava-42cursus
d1aad3ed20 Merge branch 'solo' into feature/executor-gl
# Conflicts:
#	src/parser/parser.c
2026-02-12 18:34:26 +01:00
marcnava-42cursus
df6ed1c5cc Executor working, need to validate with the parser fixed, all norme fixed 2026-02-11 02:29:45 +01:00
marcnava-42cursus
e02613253a Fixed command exit status and exec bugs, read relative and absolute paths and correctly resolved 2026-02-11 02:17:40 +01:00
marcnava-42cursus
ae578867b2 Moved find command in path to executor, fixed env save code 2026-02-11 02:09:12 +01:00
marcnava-42cursus
328737c557 Updated AGENTS with info of the current (this branch) parser status, need to be updated again when parser works correctly 2026-02-11 01:38:41 +01:00
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
93 changed files with 4127 additions and 3793 deletions

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "tests/42_minishell_tester"]
path = tests/42_minishell_tester
url = https://github.com/zstenger93/42_minishell_tester.git

View File

@@ -1,35 +0,0 @@
# Repository Guidelines
## Project Structure & Module Organization
- `src/` holds the shell implementation, with submodules for `builtins/`, `executor/`, `parser/`, and `variables/`.
- `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.
## Testing Guidelines
- There is no automated test runner. Use manual checks in `docs/tests.md` and basic shell behavior checks (pipes, redirects, builtins).
- When debugging memory issues, run under valgrind and use the suppression file in `valgrind/readline.supp`.
## 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: 2026/02/05 21:06:52 by sede-san ### ########.fr #
# Updated: 2026/02/09 22:44:34 by marcnava ### ########.fr #
# #
# **************************************************************************** #
@@ -17,7 +17,7 @@ NAME = minishell
# ************************** Compilation variables *************************** #
CC = cc
CFLAGS = -Wall -Wextra -Werror
CFLAGS = -Wall -Wextra #-Werror
HEADERS = -I $(INCLUDE_PATH) $(LIBS_INCLUDE)
ifeq ($(DEBUG), lldb) # debug with LLDB

View File

@@ -1,3 +1,4 @@
<!-- *This project has been made by sede-san and padan-pe* -->
<div align="center">
<!-- Project badge -->
@@ -30,7 +31,21 @@
> The purpose of this project is to create a simple bash-like shell.
DETAILED INFO
**minishell** is a minimal bash-like shell implementation written in C. It provides essential shell functionality including command execution, piping, input/output redirection, and built-in commands.
### Key Features
- **Command Execution**: Execute external programs with argument parsing and path resolution.
- **Pipelines**: Connect multiple commands using the pipe operator (`|`).
- **Redirections**: Support for input (`<`), output (`>`), and append (`>>`) file redirections.
- **Built-in Commands**: `cd`, `echo`, `env`, `exit`, `export`, `pwd`, `unset`.
- **Environment Variables**: Access and manage shell environment variables.
- **Interactive Shell**: Read-Evaluate-Print Loop (REPL) powered by GNU Readline.
- **Quote Handling**: Proper handling of single and double quotes for literal strings.
### Development
The codebase adheres to **Norminette v4** standards and uses a modular architecture separating parsing, execution, and built-in command logic. Build artifacts are automatically generated; refer to `AGENTS.md` for detailed development guidelines.
For detailed info, refer to this project [subject](docs/en.subject.pdf).
@@ -88,7 +103,20 @@ For detailed info, refer to this project [subject](docs/en.subject.pdf).
### Basic Usage
INSTRUCTIONS
1. **Execute the shell**
```bash
./minishell
```
2. **Interact with it like if it was bash**
```bash
echo "Hello, World!"
...
cat groceries.txt | grep tomatoes
...
echo "Greetings $USER!" > greetings.txt
```
## 📏 Norminette

View File

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

Binary file not shown.

Binary file not shown.

View File

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

View File

@@ -1,17 +0,0 @@
### Multiple redirections
```
cmd > file1 > file2
```
or
```
cmd >> file1 >> file2
```
Should create both `file1` and `file2` but store the output in `file2`
### Environment variables
#### SHLVL
Must be increased by 1 if executed from any other shell or 1 if executed

View File

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

View File

@@ -1,256 +0,0 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* 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,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/29 22:09:51 by sede-san #+# #+# */
/* Updated: 2026/02/08 19:42:50 by sede-san ### ########.fr */
/* Updated: 2026/02/14 01:15:34 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -16,8 +16,9 @@
# include "ft_args.h"
# include "minishell.h"
# include "core.h"
# include "variables.h"
typedef unsigned char (*t_builtin_func)(t_command cmd, t_minishell *minishell);
typedef uint8_t (*t_builtin_func)(t_command cmd, t_minishell *minishell);
/******************************************************************************/
/* Functions */
@@ -25,24 +26,36 @@ typedef unsigned char (*t_builtin_func)(t_command cmd, t_minishell *minishell)
/* builtins.c */
extern u_int8_t set_builtins(t_minishell *minishell);
extern uint8_t set_builtins(t_minishell *minishell);
extern u_int8_t is_builtin(const char *command_name, t_minishell *minishell);
extern uint8_t is_builtin(const char *command_name, t_minishell *minishell);
/* cd.c */
extern u_int8_t builtin_cd(t_command cmd, t_minishell *minishell);
extern uint8_t builtin_cd(t_command cmd, t_minishell *minishell);
/* echo.c */
extern u_int8_t builtin_echo(t_command cmd, t_minishell *minishell);
extern uint8_t builtin_echo(t_command cmd, t_minishell *minishell);
/* exit.c */
extern u_int8_t builtin_exit(t_command cmd, t_minishell *minishell);
extern uint8_t builtin_exit(t_command cmd, t_minishell *minishell);
/* pwd.c */
extern u_int8_t builtin_pwd(t_command cmd, t_minishell *minishell);
extern uint8_t builtin_pwd(t_command cmd, t_minishell *minishell);
/* env.c */
extern uint8_t builtin_env(t_command cmd, t_minishell *minishell);
/* export.c */
extern uint8_t builtin_export(t_command cmd, t_minishell *minishell);
/* unset.c */
extern uint8_t builtin_unset(t_command cmd, t_minishell *minishell);
#endif /* BUILTINS_H */

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/22 19:10:13 by sede-san #+# #+# */
/* Updated: 2026/02/12 20:19:23 by sede-san ### ########.fr */
/* Updated: 2026/02/14 01:25:43 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -34,8 +34,8 @@ typedef enum e_token_type
typedef struct s_token
{
t_token_type type;
char *value;
} t_token;
char *value;
} t_token;
typedef enum e_redirection_type
{
@@ -48,8 +48,11 @@ typedef enum e_redirection_type
typedef struct s_redirection
{
t_token_type type;
int io_number;
char *target;
} t_redirection;
bool heredoc_expand;
bool heredoc_ready;
} t_redirection;
/**
* @brief Structure that holds both environment and internal variables
@@ -63,7 +66,7 @@ typedef struct s_redirection
typedef struct s_variables
{
t_hashmap *environment;
// char **internal;
t_hashmap *internal;
} t_variables;
/**
@@ -82,12 +85,6 @@ typedef struct s_minishell
bool exit;
} t_minishell;
typedef struct s_redirection
{
t_token_type type;
char *target;
} t_redirection;
/**
* @brief Structure representing a single command in the shell
*
@@ -129,6 +126,16 @@ extern void minishell_run(t_minishell *minishell);
extern void minishell_clear(t_minishell *minishell);
extern void minishell_set_interactive_signals(void);
extern void minishell_set_execution_signals(void);
extern void minishell_set_child_signals(void);
extern void minishell_set_heredoc_signals(void);
extern bool minishell_consume_sigint(void);
/* environment.c */
extern void set_envp(char **envp, t_minishell *msh);
@@ -140,6 +147,7 @@ extern char **get_envp(t_minishell *msh);
extern void free_envp(char **envp);
extern char *get_env(const char *env_name, t_minishell *msh);
extern void handle_sigint_status(t_minishell *minishell);
extern bool handle_eof(char *line, t_minishell *minishell);
#endif /* CORE_H */

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/10 22:28:01 by sede-san #+# #+# */
/* Updated: 2026/02/10 23:24:16 by sede-san ### ########.fr */
/* Updated: 2026/02/14 01:11:38 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -17,7 +17,8 @@
# include "core.h"
# include "parser.h"
extern void syntax_error_unexpected_token(t_token *token);
extern void syntax_error_unexpected_token(t_token *token);
extern void malloc_error(void);
extern void command_not_found_error(const char *command);
#endif /* ERRORS_H */

View File

@@ -19,23 +19,46 @@
# define READ_PIPE 0
# define WRITE_PIPE 1
typedef struct s_pipeline
{
int prev_read_fd;
int pipefd[2];
} t_pipeline;
/******************************************************************************/
/* Functions */
/******************************************************************************/
// executor.c
# define PIPE_ERROR -1
# define FORK_ERROR -1
extern uint8_t execute(t_list *command, t_minishell *minishell);
typedef struct s_pipeline
{
int prev_read_fd;
int pipefd[2];
} t_pipeline;
typedef struct s_exec_state
{
uint8_t exit_status;
t_pipeline pipeline;
t_list *current_command;
pid_t last_child_pid;
} t_exec_state;
extern uint8_t execute(t_list *command, t_minishell *minishell);
extern uint8_t executor_execute_command(t_command *cmd, t_minishell *msh);
extern char *executor_resolve_command_path(const t_command *cmd,
t_minishell *msh);
extern bool executor_is_builtin_command(const t_command *cmd,
t_minishell *msh);
extern int executor_create_pipe_if_needed(t_list *node, t_pipeline *pl);
extern bool executor_is_fork_required(t_list *node, const t_pipeline *pl,
t_minishell *msh);
extern void executor_setup_child_input(t_pipeline *pipeline);
extern void executor_setup_child_output(t_list *node, t_pipeline *pl);
extern void executor_child_process(t_list *node, t_pipeline *pl,
t_minishell *msh);
extern void executor_parent_cleanup(t_list *node, t_pipeline *pl);
extern uint8_t executor_wait_for_children(pid_t last_child_pid);
extern void executor_cmdfree(t_command *command);
extern bool executor_apply_redirections(const t_command *command,
int *saved_stdin, int *saved_stdout, int *saved_stderr);
extern void executor_restore_redirections(int saved_stdin,
int saved_stdout, int saved_stderr);
extern bool executor_prepare_heredocs(t_list *command_list,
t_minishell *minishell, uint8_t *exit_status);
extern char *executor_expand_heredoc_line(const char *line,
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: 2026/02/10 22:12:58 by sede-san ### ########.fr */
/* Updated: 2026/02/14 15:17:38 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -15,6 +15,7 @@
# include "libft.h"
# include "ft_printf.h"
# include "get_next_line.h"
# include "chardefs.h"
# include <stdbool.h>
# include <stdint.h>
@@ -41,4 +42,6 @@
# include <term.h> // tgetent(3), tgetflag(3), tgetnum(3),
// tgetstr(3), tgoto(3), tputs(3)
# define MINISHELL_VERSION "1.0.0"
#endif /* MINISHELL_H */

View File

@@ -23,19 +23,31 @@
// parser.c
extern t_list *parse(char *line, t_minishell *minishell);
extern t_list *parse(char *line, t_minishell *minishell);
// lexer.c
extern t_list *lex(const char *line);
t_token_type get_token_type(const char *str);
t_token *token_new(t_token_type type, char *text);
t_token *read_token(t_token_type type, const char *line,
size_t *i);
t_token *read_word(const char *line, size_t *i);
extern void token_clear(t_token *token);
extern t_list *lex(const char *line);
extern void token_clear(t_token *token);
extern t_command *command_new(t_list **tokens);
extern void command_clear(t_command *command);
extern void command_add_tokens(t_command **command, t_list **tokens);
extern bool is_pipe(t_token *token);
extern bool is_redirection(t_token *token);
void redirection_add(t_list **tokens, t_token *token, t_command **command);
void words_add(t_list **tokens, t_command **command);
extern bool is_redirection(t_token *token);
bool parser_token_is_fd_prefix(t_list *token_node,
int *io_number);
void redirection_add_with_fd(t_list **tokens, t_token *token,
t_command **command, int io_number);
void words_add(t_list **tokens, t_command **command);
void expand(t_list **commands, t_minishell *minishell);
void redirection_clear(t_redirection *redirection);
#endif /* PARSER_H */

43
include/variables.h Normal file
View File

@@ -0,0 +1,43 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* variables.h :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/13 21:45:35 by sede-san #+# #+# */
/* Updated: 2026/02/14 01:24:49 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#ifndef VARIABLES_H
# define VARIABLES_H
# include "minishell.h"
# include "core.h"
// variables.c
extern char *get_var(const char *name, t_minishell *minishell);
extern void set_var(const char *name, char *value, t_minishell *minishell);
extern void unset_var(const char *name, t_minishell *minishell);
// environment.c
extern char *get_env(const char *name, t_minishell *minishell);
extern void set_env(const char *name, char *value, t_minishell *minishell);
extern void unset_env(const char *name, t_minishell *minishell);
extern void set_envp(char **envp, t_minishell *minishell);
extern char **get_envp(t_minishell *minishell);
extern void free_envp(char **envp);
// internal.c
extern char *get_int(const char *name, t_minishell *minishell);
extern void set_int(const char *name, char *value, t_minishell *minishell);
extern void unset_int(const char *name, t_minishell *minishell);
extern void set_intp(t_minishell *minishell);
#endif /* VARIABLES_H */

View File

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

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

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

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

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

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

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

@@ -1,46 +0,0 @@
#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);
}

View File

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

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

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

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

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

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

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

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

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

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

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

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

View File

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

View File

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

View File

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

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

@@ -0,0 +1,59 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* 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_eprintf("minishell: env: %s: No such file or directory\n",
cmd.argv[1]);
return (127);
}
print_env(msh);
return (EXIT_SUCCESS);
}
static void print_entry(
t_map_entry *entry
)
{
ft_putstr((char *)entry->key);
ft_putchar('=');
if (entry->value != NULL)
ft_putstr((char *)entry->value);
ft_putchar('\n');
}
static void print_env(
t_minishell *msh
)
{
t_list *entries;
t_list *current;
entries = ft_hashmap_entries(msh->variables.environment);
current = entries;
while (current != NULL)
{
print_entry((t_map_entry *)current->content);
current = current->next;
}
ft_lstclear_nodes(&entries);
}

View File

@@ -11,36 +11,47 @@
/* ************************************************************************** */
#include "builtins.h"
#include <stdint.h>
static bool exit_arg_is_invalid(const char *arg)
{
if (arg == NULL)
return (true);
if (arg[0] == '\0')
return (true);
if ((*arg == '+' || *arg == '-') && arg[1] == '\0')
return (true);
if (!ft_strisnum(arg))
return (true);
return (false);
}
uint8_t builtin_exit(
t_command cmd,
t_minishell *msh
)
{
uint8_t exit_status;
ft_eputendl("exit");
if (isatty(STDIN_FILENO))
ft_eputendl("exit");
if (cmd.argc == 1)
exit_status = msh->exit_status;
else if (!ft_strisnum(cmd.argv[1]))
{
ft_eprintf(
"minishell: exit: %s: numeric argument required\n",
msh->exit = 1;
return (msh->exit_status);
}
if (exit_arg_is_invalid(cmd.argv[1]))
{
ft_eprintf("minishell: exit: %s: numeric argument required\n",
cmd.argv[1]);
return (2);
msh->exit = 1;
msh->exit_status = 2;
return (msh->exit_status);
}
else if (cmd.argc > 2)
if (cmd.argc > 2)
{
ft_eputendl("exit: too many arguments");
return (2);
ft_eputendl("minishell: exit: too many arguments");
msh->exit_status = EXIT_FAILURE;
return (msh->exit_status);
}
else
exit_status = (uint8_t)ft_atol(cmd.argv[1]);
printf("builtin_exit: exit_status=%d\n", exit_status); // Debug print
msh->exit = 1;
msh->exit_status = exit_status;
return (exit_status);
msh->exit_status = (uint8_t)ft_atol(cmd.argv[1]);
return (msh->exit_status);
}

View File

@@ -0,0 +1,151 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* export.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/09 22:05:00 by codex #+# #+# */
/* Updated: 2026/02/14 02:02:14 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "builtins.h"
static uint8_t export_one(char *arg, t_minishell *msh);
static uint8_t print_exported(t_minishell *msh);
static void **entries_to_array(t_list *lst, size_t *count,
bool add_oldpwd, t_map_entry *oldpwd_entry);
static bool print_sorted_entries(t_map_entry **entries, size_t count);
bool export_is_valid_identifier(const char *arg, size_t name_len);
void export_parse_assignment(char *arg, char **eq_pos,
size_t *name_len, bool *append);
uint8_t export_set_assigned_value(const char *name, char *eq_pos,
bool append, t_minishell *msh);
bool export_print_declaration(const char *name, const char *value);
void export_sort_entries(t_map_entry **entries, size_t count);
uint8_t builtin_export(
t_command cmd,
t_minishell *msh
)
{
uint8_t status;
uint8_t result;
size_t i;
if (cmd.argc == 1)
return (print_exported(msh));
status = EXIT_SUCCESS;
i = 0;
while (cmd.argv[++i] != NULL)
{
result = export_one(cmd.argv[i], msh);
if (result == 2)
return (2);
if (result != EXIT_SUCCESS)
status = EXIT_FAILURE;
}
return (status);
}
static uint8_t export_one(
char *arg,
t_minishell *msh
)
{
char *eq_pos;
char *name;
size_t name_len;
bool append;
if (arg[0] == '-' && arg[1] != '\0')
return (ft_eprintf("minishell: export: %s: invalid option\n", arg),
ft_eputendl("export: usage: export [name[=value] ...]"), 2);
export_parse_assignment(arg, &eq_pos, &name_len, &append);
if (!export_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 (export_set_assigned_value(name, eq_pos, append, msh) != EXIT_SUCCESS)
return (free(name), EXIT_FAILURE);
free(name);
return (EXIT_SUCCESS);
}
static uint8_t print_exported(
t_minishell *msh
)
{
t_list *entries;
t_map_entry **sorted_entries;
t_map_entry oldpwd_entry;
size_t count;
entries = ft_hashmap_entries(msh->variables.environment);
if (entries == NULL)
return (EXIT_SUCCESS);
oldpwd_entry = (t_map_entry){"OLDPWD", NULL};
sorted_entries = (t_map_entry **)entries_to_array(entries, &count,
!ft_hashmap_contains_key(msh->variables.environment, "OLDPWD"),
&oldpwd_entry);
ft_lstclear_nodes(&entries);
if (sorted_entries == NULL)
return (EXIT_FAILURE);
export_sort_entries(sorted_entries, count);
if (!print_sorted_entries(sorted_entries, count))
return (free(sorted_entries), EXIT_FAILURE);
return (free(sorted_entries), EXIT_SUCCESS);
}
static void **entries_to_array(
t_list *entries,
size_t *count,
bool add_oldpwd,
t_map_entry *oldpwd_entry
)
{
void **array;
t_list *node;
t_map_entry *entry;
size_t i;
*count = (size_t)ft_lstsize(entries) + (size_t)add_oldpwd;
array = (void **)malloc(sizeof(void *) * (*count));
if (array == NULL)
return (NULL);
i = 0;
node = entries;
while (node != NULL)
{
entry = (t_map_entry *)node->content;
if (entry != NULL && ft_strcmp((char *)entry->key, "_") != 0)
array[i++] = entry;
node = node->next;
}
if (add_oldpwd)
array[i++] = oldpwd_entry;
*count = i;
return (array);
}
static bool print_sorted_entries(
t_map_entry **entries,
size_t count
)
{
size_t i;
i = 0;
while (i < count)
{
if (!export_print_declaration((char *)entries[i]->key,
(char *)entries[i]->value))
return (false);
i++;
}
return (true);
}

View File

@@ -0,0 +1,40 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* export_sort.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 23:55:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 23:55:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "builtins.h"
void export_sort_entries(
t_map_entry **entries,
size_t count
)
{
size_t i;
size_t j;
t_map_entry *tmp;
i = 0;
while (i < count)
{
j = i + 1;
while (j < count)
{
if (ft_strcmp((char *)entries[i]->key, (char *)entries[j]->key) > 0)
{
tmp = entries[i];
entries[i] = entries[j];
entries[j] = tmp;
}
j++;
}
i++;
}
}

View File

@@ -0,0 +1,129 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* export_utils.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 15:20:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 15:20:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "builtins.h"
bool export_is_valid_identifier(
const char *arg,
size_t name_len
)
{
size_t i;
if (name_len == 0 || arg == NULL)
return (false);
if (!ft_isalpha(arg[0]) && arg[0] != '_')
return (false);
i = 0;
while (++i < name_len)
if (!ft_isalnum(arg[i]) && arg[i] != '_')
return (false);
return (true);
}
void export_parse_assignment(
char *arg,
char **eq_pos,
size_t *name_len,
bool *append
)
{
char *plus_eq;
*append = false;
*eq_pos = ft_strchr(arg, '=');
*name_len = ft_strlen(arg);
plus_eq = ft_strnstr(arg, "+=", *name_len);
if (plus_eq != NULL)
{
*append = true;
*name_len = (size_t)(plus_eq - arg);
*eq_pos = plus_eq + 1;
}
else if (*eq_pos != NULL)
*name_len = (size_t)(*eq_pos - arg);
}
uint8_t export_set_assigned_value(
const char *name,
char *eq_pos,
bool append,
t_minishell *msh
)
{
char *current;
char *joined;
if (append)
{
current = get_var((char *)name, msh);
if (current == NULL)
current = "";
joined = ft_strnjoin(2, current, eq_pos + 1);
if (joined == NULL)
return (EXIT_FAILURE);
set_var(name, joined, msh);
free(joined);
return (EXIT_SUCCESS);
}
if (eq_pos != NULL)
set_var(name, eq_pos + 1, msh);
else
set_var(name, "", msh);
return (EXIT_SUCCESS);
}
char *export_escape_value(
const char *value
)
{
char *escaped;
size_t i;
size_t j;
escaped = (char *)malloc((ft_strlen(value) * 2) + 1);
if (escaped == NULL)
return (NULL);
i = 0;
j = 0;
while (value[i] != '\0')
{
if (value[i] == '\\' || value[i] == '\"' || value[i] == '$')
escaped[j++] = '\\';
escaped[j++] = value[i++];
}
escaped[j] = '\0';
return (escaped);
}
bool export_print_declaration(
const char *name,
const char *value
)
{
char *escaped;
ft_putstr("declare -x ");
ft_putstr((char *)name);
if (value != NULL)
{
escaped = export_escape_value(value);
if (escaped == NULL)
return (false);
ft_putstr("=\"");
ft_putstr(escaped);
ft_putstr("\"");
free(escaped);
}
ft_putchar('\n');
return (true);
}

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)
ft_putendl(cwd);
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,80 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* 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);
static bool is_option(const char *arg);
uint8_t builtin_unset(
t_command cmd,
t_minishell *msh
)
{
uint8_t status;
uint8_t result;
size_t i;
status = EXIT_SUCCESS;
i = 0;
while (cmd.argv[++i] != NULL)
{
result = unset_one(cmd.argv[i], msh);
if (result == 2)
return (2);
if (result != 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_option(arg))
{
ft_eprintf("minishell: unset: %s: invalid option\n", arg);
ft_eputendl("unset: usage: unset [name ...]");
return (2);
}
if (!is_valid_identifier(arg))
return (EXIT_SUCCESS);
unset_env(arg, msh);
return (EXIT_SUCCESS);
}
static bool is_option(
const char *arg
)
{
return (arg != NULL && arg[0] == '-' && arg[1] != '\0');
}

View File

@@ -9,4 +9,3 @@
/* Updated: 2026/02/09 18:40:04 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */

56
src/core/signals.c Normal file
View File

@@ -0,0 +1,56 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* signals.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/13 00:00:00 by sede-san #+# #+# */
/* Updated: 2026/02/13 00:00:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "core.h"
#include "signals_internal.h"
int g_signal = 0;
static void sigint_handler_interactive(int signal)
{
g_signal = signal;
write(STDOUT_FILENO, "\n", 1);
rl_on_new_line();
rl_replace_line("", 0);
rl_redisplay();
}
void minishell_set_interactive_signals(void)
{
struct sigaction action;
ft_bzero(&action, sizeof(action));
action.sa_handler = sigint_handler_interactive;
sigemptyset(&action.sa_mask);
sigaction(SIGINT, &action, NULL);
action.sa_handler = SIG_IGN;
sigaction(SIGQUIT, &action, NULL);
}
void minishell_set_execution_signals(void)
{
struct sigaction action;
ft_bzero(&action, sizeof(action));
action.sa_handler = SIG_IGN;
sigemptyset(&action.sa_mask);
sigaction(SIGINT, &action, NULL);
sigaction(SIGQUIT, &action, NULL);
}
bool minishell_consume_sigint(void)
{
if (g_signal != SIGINT)
return (false);
g_signal = 0;
return (true);
}

24
src/core/signals_child.c Normal file
View File

@@ -0,0 +1,24 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* signals_child.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 18:20:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 18:20:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "core.h"
void minishell_set_child_signals(void)
{
struct sigaction action;
ft_bzero(&action, sizeof(action));
action.sa_handler = SIG_DFL;
sigemptyset(&action.sa_mask);
sigaction(SIGINT, &action, NULL);
sigaction(SIGQUIT, &action, NULL);
}

View File

@@ -0,0 +1,32 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* signals_heredoc.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 23:40:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 23:40:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "core.h"
#include "signals_internal.h"
static void sigint_handler_heredoc(int signal)
{
g_signal = signal;
write(STDOUT_FILENO, "\n", 1);
}
void minishell_set_heredoc_signals(void)
{
struct sigaction action;
ft_bzero(&action, sizeof(action));
action.sa_handler = sigint_handler_heredoc;
sigemptyset(&action.sa_mask);
sigaction(SIGINT, &action, NULL);
action.sa_handler = SIG_IGN;
sigaction(SIGQUIT, &action, NULL);
}

View File

@@ -0,0 +1,18 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* signals_internal.h :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 23:40:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 23:40:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#ifndef SIGNALS_INTERNAL_H
# define SIGNALS_INTERNAL_H
extern int g_signal;
#endif

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/10 22:22:06 by sede-san #+# #+# */
/* Updated: 2026/02/12 02:55:36 by sede-san ### ########.fr */
/* Updated: 2026/02/14 06:58:05 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -28,4 +28,11 @@ void syntax_error_unexpected_token(
void malloc_error(void)
{
ft_eprintf("minishell: %s\n", strerror(ENOMEM));
}
}
void command_not_found_error(
const char *command
)
{
ft_eprintf("minishell: %s: command not found\n", command);
}

View File

@@ -0,0 +1,85 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* command_exec.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/11 00:00:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 13:12:58 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "executor.h"
#include "builtins.h"
#include "errors.h"
#include <errno.h>
static uint8_t resolve_execve_status(void)
{
if (errno == ENOENT)
return (127);
if (errno == EACCES || errno == ENOEXEC || errno == EISDIR)
return (126);
return (EXIT_FAILURE);
}
static uint8_t execute_builtin(
const t_command *command,
t_minishell *minishell
)
{
const t_builtin_func builtin
= ft_hashmap_get(minishell->builtins, command->argv[0]);
return (builtin(*command, minishell));
}
static void handle_execve_error(
const t_command *command,
char **envp
)
{
uint8_t exit_status;
size_t i;
exit_status = resolve_execve_status();
i = 0;
while (envp[i] != NULL)
free(envp[i++]);
free(envp);
perror(command->path);
exit(exit_status);
}
static void execute_external_command(
const t_command *command,
t_minishell *minishell
)
{
char **envp;
envp = get_envp(minishell);
if (envp == NULL)
return (malloc_error(), exit(EXIT_FAILURE));
execve(command->path, command->argv, envp);
handle_execve_error(command, envp);
}
uint8_t executor_execute_command(
t_command *command,
t_minishell *minishell
)
{
if (command == NULL || command->argv == NULL || command->argv[0] == NULL)
return (EXIT_SUCCESS);
if (executor_is_builtin_command(command, minishell))
return (execute_builtin(command, minishell));
if (command->path != NULL)
free(command->path);
command->path = executor_resolve_command_path(command, minishell);
if (command->path == NULL)
return (command_not_found_error(command->argv[0]), 127);
execute_external_command(command, minishell);
return (EXIT_FAILURE);
}

View File

@@ -0,0 +1,57 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* command_free.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/13 00:00:00 by sede-san #+# #+# */
/* Updated: 2026/02/13 00:00:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "executor.h"
static void cmdfree_argv(
char **argv
)
{
size_t i;
if (argv == NULL)
return ;
i = 0;
while (argv[i] != NULL)
{
free(argv[i]);
i++;
}
free(argv);
}
static void cmdfree_redirection(
t_redirection *redirection
)
{
if (redirection == NULL)
return ;
if (redirection->type == TOKEN_HEREDOC && redirection->heredoc_ready
&& redirection->target != NULL)
unlink(redirection->target);
free(redirection->target);
free(redirection);
}
void executor_cmdfree(
t_command *command
)
{
if (command == NULL)
return ;
cmdfree_argv(command->argv);
free(command->path);
ft_lstclear(&command->redirections,
(void (*)(void *))cmdfree_redirection);
ft_lstclear(&command->heredocs, (void (*)(void *))cmdfree_redirection);
free(command);
}

View File

@@ -6,198 +6,81 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/08 19:10:47 by sede-san #+# #+# */
/* Updated: 2026/02/08 21:32:03 by sede-san ### ########.fr */
/* Updated: 2026/02/14 13:10:43 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "executor.h"
#include "builtins.h"
static inline uint8_t execute_builtin(
const t_command *command,
static void init_exec_state(
t_exec_state *state,
t_list *command_list
)
{
state->exit_status = EXIT_SUCCESS;
state->pipeline.prev_read_fd = -1;
state->current_command = command_list;
state->last_child_pid = -1;
}
static bool fork_current_command(
t_exec_state *state,
t_minishell *minishell
)
{
const t_builtin_func builtin
= ft_hashmap_get(minishell->builtins, command->path);
pid_t pid;
return (builtin(*command, minishell));
}
static void handle_execve_error(
const t_command *command,
char **envp
)
{
free_envp(envp);
perror(command->path);
exit(EXIT_FAILURE);
}
static void execute_external_command(
const t_command *command,
t_minishell *minishell
)
{
char **envp;
envp = get_envp(minishell);
if (envp == NULL)
{
perror("get_envp");
exit(EXIT_FAILURE);
}
execve(command->path, command->argv, envp);
handle_execve_error(command, envp);
}
static uint8_t execute_command(
const t_command *command,
t_minishell *minishell
)
{
if (is_builtin(command->path, minishell))
return (execute_builtin(command, minishell));
else
{
execute_external_command(command, minishell);
return (EXIT_FAILURE); //! should never reach here
}
}
static void cmdfree_argv(
char **argv
)
{
size_t i;
if (argv == NULL)
return ;
i = 0;
while (argv[i] != NULL)
{
free(argv[i]);
i++;
}
free(argv);
}
static void cmdfree(
t_command *command
)
{
if (command == NULL)
return ;
cmdfree_argv(command->argv);
free(command->path);
free(command);
}
static int create_pipe_if_needed(
t_list *current_command,
t_pipeline *pipeline
)
{
if (!current_command->next)
return (0);
if (pipe(pipeline->pipefd) == PIPE_ERROR)
return (perror("pipe"), PIPE_ERROR);
return (0);
}
static bool is_fork_required(
t_list *current_command,
t_minishell *minishell
) {
const t_command *command = current_command->content;
return (current_command->next != NULL
|| !is_builtin(command->path, minishell));
}
static pid_t fork_process(
t_list *current_command,
t_minishell *minishell
)
{
pid_t pid;
pid = 0;
if (is_fork_required(current_command, minishell))
pid = fork();
pid = fork();
if (pid == FORK_ERROR)
perror("fork");
return (pid);
return (perror("fork"), state->exit_status = EXIT_FAILURE, false);
if (pid == 0)
executor_child_process(state->current_command, &state->pipeline,
minishell);
state->last_child_pid = pid;
return (true);
}
static void setup_child_input(
t_pipeline *pipeline
)
{
if (pipeline->prev_read_fd != -1)
{
dup2(pipeline->prev_read_fd, STDIN_FILENO);
close(pipeline->prev_read_fd);
}
}
static void setup_child_output(
t_list *current_command,
t_pipeline *pipeline
)
{
if (current_command->next)
{
dup2(pipeline->pipefd[WRITE_PIPE], STDOUT_FILENO);
close(pipeline->pipefd[READ_PIPE]);
close(pipeline->pipefd[WRITE_PIPE]);
}
}
static void child_process(
t_list *current_command,
t_pipeline *pipeline,
static void run_command_in_parent(
t_exec_state *state,
t_minishell *minishell
)
{
uint8_t exit_status;
const t_command *command = current_command->content;
t_command *command;
int saved_stdin;
int saved_stdout;
int saved_stderr;
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);
command = state->current_command->content;
if (!executor_apply_redirections(command, &saved_stdin, &saved_stdout,
&saved_stderr))
state->exit_status = EXIT_FAILURE;
else
state->exit_status = executor_execute_command(command, minishell);
executor_restore_redirections(saved_stdin, saved_stdout, saved_stderr);
}
static void parent_cleanup(
t_list *current_command,
t_pipeline *pipeline
static bool run_current_command(
t_exec_state *state,
t_minishell *minishell
)
{
if (pipeline->prev_read_fd != -1)
close(pipeline->prev_read_fd);
if (current_command->next)
bool should_fork;
if (executor_create_pipe_if_needed(state->current_command,
&state->pipeline) == PIPE_ERROR)
return (state->exit_status = EXIT_FAILURE, false);
should_fork = executor_is_fork_required(state->current_command,
&state->pipeline, minishell);
if (should_fork)
{
close(pipeline->pipefd[WRITE_PIPE]);
pipeline->prev_read_fd = pipeline->pipefd[READ_PIPE];
if (!fork_current_command(state, minishell))
return (false);
}
else
pipeline->prev_read_fd = -1;
}
static uint8_t wait_for_children(void)
{
uint8_t exit_status;
int status;
exit_status = EXIT_SUCCESS;
while (wait(&status) > 0)
{
if (WIFEXITED(status))
exit_status = WEXITSTATUS(status);
}
return (exit_status);
run_command_in_parent(state, minishell);
executor_parent_cleanup(state->current_command, &state->pipeline);
state->current_command = state->current_command->next;
return (true);
}
uint8_t execute(
@@ -205,26 +88,25 @@ uint8_t execute(
t_minishell *minishell
)
{
uint8_t exit_status;
t_pipeline pipeline;
t_list *current_command;
pid_t pid;
t_exec_state state;
pipeline.prev_read_fd = -1;
current_command = command_list;
while (current_command)
init_exec_state(&state, command_list);
if (!executor_prepare_heredocs(command_list, minishell, &state.exit_status))
{
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;
minishell->exit_status = state.exit_status;
ft_lstclear(&command_list, (void (*)(void *))executor_cmdfree);
return (state.exit_status);
}
exit_status = wait_for_children();
ft_lstclear(&command_list, (void (*)(void *))cmdfree);
return (exit_status);
minishell_set_execution_signals();
while (state.current_command)
{
if (!run_current_command(&state, minishell))
break ;
}
if (state.last_child_pid > 0)
state.exit_status = executor_wait_for_children(state.last_child_pid);
minishell_set_interactive_signals();
minishell->exit_status = state.exit_status;
ft_lstclear(&command_list, (void (*)(void *))executor_cmdfree);
return (state.exit_status);
}

65
src/executor/heredoc.c Normal file
View File

@@ -0,0 +1,65 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* heredoc.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 17:50:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 17:50:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "heredoc_internal.h"
#include "errors.h"
static bool prepare_heredoc_redirection(t_redirection *rd,
t_minishell *minishell, uint8_t *exit_status)
{
char *path;
int fd;
path = NULL;
fd = executor_heredoc_open_tmp(&path);
if (fd == -1)
return (perror("open"), *exit_status = EXIT_FAILURE, false);
if (!executor_heredoc_fill_tmp(fd, rd, minishell, exit_status))
return (executor_heredoc_discard_tmp(fd, path), false);
return (executor_heredoc_finalize_tmp(rd, fd, path, exit_status));
}
static bool process_command_heredocs(t_command *command, t_minishell *minishell,
uint8_t *exit_status)
{
t_list *redir_node;
t_redirection *rd;
redir_node = command->redirections;
while (redir_node != NULL)
{
rd = (t_redirection *)redir_node->content;
if (rd->type == TOKEN_HEREDOC
&& !prepare_heredoc_redirection(rd, minishell, exit_status))
return (false);
redir_node = redir_node->next;
}
return (true);
}
bool executor_prepare_heredocs(t_list *command_list, t_minishell *minishell,
uint8_t *exit_status)
{
t_command *command;
*exit_status = EXIT_SUCCESS;
minishell_set_heredoc_signals();
while (command_list != NULL)
{
command = (t_command *)command_list->content;
if (!process_command_heredocs(command, minishell, exit_status))
return (minishell_set_interactive_signals(), false);
command_list = command_list->next;
}
minishell_set_interactive_signals();
return (true);
}

View File

@@ -0,0 +1,107 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* heredoc_expand.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 17:30:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 17:30:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "executor.h"
#include "variables.h"
#include "errors.h"
static bool is_var_char(char c)
{
return (ft_isalnum(c) || c == '_');
}
static bool append_text(char **result, const char *value)
{
char *joined;
joined = ft_strnjoin(2, *result, (char *)value);
if (joined == NULL)
{
free(*result);
*result = NULL;
return (false);
}
free(*result);
*result = joined;
return (true);
}
static char *expand_variable(const char *line, size_t *i, t_minishell *msh)
{
char *name;
char *value;
size_t start;
(*i)++;
if (line[*i] == '?')
return ((*i)++, ft_itoa(msh->exit_status));
if (line[*i] == '\0' || !is_var_char(line[*i]))
return (ft_strdup("$"));
start = *i;
while (line[*i] != '\0' && is_var_char(line[*i]))
(*i)++;
name = ft_substr(line, start, *i - start);
if (name == NULL)
return (NULL);
value = get_var(name, msh);
free(name);
if (value == NULL)
value = "";
return (ft_strdup(value));
}
static char *expand_literal_char(const char *line, size_t *i,
bool *in_single, bool *in_double)
{
char value[2];
if (line[*i] == '\'' && !*in_double)
*in_single = !*in_single;
else if (line[*i] == '\"' && !*in_single)
*in_double = !*in_double;
value[0] = line[*i];
value[1] = '\0';
(*i)++;
return (ft_strdup(value));
}
char *executor_expand_heredoc_line(
const char *line,
t_minishell *minishell
)
{
char *result;
char *expanded;
size_t i;
bool in_single;
bool in_double;
result = ft_strdup("");
if (result == NULL)
return (malloc_error(), NULL);
i = 0;
in_single = false;
in_double = false;
while (line[i] != '\0')
{
if (line[i] == '$' && !in_single)
expanded = expand_variable(line, &i, minishell);
else
expanded = expand_literal_char(line, &i, &in_single, &in_double);
if (expanded == NULL)
return (free(result), malloc_error(), NULL);
if (!append_text(&result, expanded))
return (free(expanded), malloc_error(), NULL);
free(expanded);
}
return (result);
}

View File

@@ -0,0 +1,77 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* heredoc_file.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 17:50:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 17:50:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "heredoc_internal.h"
#include <errno.h>
#include "variables.h"
static char *build_tmp_path(unsigned int id)
{
char *suffix;
char *path;
suffix = ft_uitoa(id);
if (suffix == NULL)
return (errno = ENOMEM, NULL);
path = ft_strnjoin(2, "/tmp/minishell_heredoc_", suffix);
free(suffix);
if (path == NULL)
return (errno = ENOMEM, NULL);
return (path);
}
int executor_heredoc_open_tmp(char **path_out)
{
static unsigned int counter;
char *path;
unsigned int attempts;
int fd;
attempts = 0;
while (attempts++ < 10000)
{
path = build_tmp_path(counter++);
if (path == NULL)
return (-1);
fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600);
if (fd != -1)
return (*path_out = path, fd);
free(path);
if (errno != EEXIST)
return (-1);
}
errno = EEXIST;
return (-1);
}
void executor_heredoc_discard_tmp(int fd, char *path)
{
if (fd >= 0)
close(fd);
if (path != NULL)
{
unlink(path);
free(path);
}
}
bool executor_heredoc_finalize_tmp(t_redirection *rd, int fd, char *path,
uint8_t *exit_status)
{
if (close(fd) == -1)
return (executor_heredoc_discard_tmp(-1, path), perror("close"),
*exit_status = EXIT_FAILURE, false);
free(rd->target);
rd->target = path;
rd->heredoc_ready = true;
return (true);
}

View File

@@ -0,0 +1,80 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* heredoc_input.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 17:50:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 17:50:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "heredoc_internal.h"
#include "errors.h"
static bool write_all(int fd, const char *buffer, size_t len)
{
size_t total;
ssize_t written;
total = 0;
while (total < len)
{
written = write(fd, buffer + total, len - total);
if (written <= 0)
return (false);
total += written;
}
return (true);
}
static bool write_heredoc_line(int fd, const char *line)
{
if (!write_all(fd, line, ft_strlen(line)))
return (false);
return (write_all(fd, "\n", 1));
}
static bool handle_heredoc_line(int fd, char *line, t_redirection *rd,
t_minishell *minishell)
{
char *expanded;
expanded = line;
if (rd->heredoc_expand)
{
expanded = executor_expand_heredoc_line(line, minishell);
free(line);
if (expanded == NULL)
return (false);
}
if (!write_heredoc_line(fd, expanded))
return (free(expanded), perror("write"), false);
free(expanded);
return (true);
}
bool executor_heredoc_fill_tmp(int fd, t_redirection *rd,
t_minishell *minishell, uint8_t *exit_status)
{
char *line;
while (1)
{
line = executor_heredoc_read_line(minishell);
if (minishell_consume_sigint())
return (free(line), *exit_status = 130, false);
if (line == NULL)
{
if (!isatty(STDIN_FILENO))
minishell->exit = true;
return (ft_eprintf("minishell: warning: here-document delimited by "
"end-of-file (wanted `%s')\n", rd->target), true);
}
if (ft_strcmp(line, rd->target) == 0)
return (free(line), true);
if (!handle_heredoc_line(fd, line, rd, minishell))
return (*exit_status = EXIT_FAILURE, false);
}
}

View File

@@ -0,0 +1,26 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* heredoc_internal.h :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 17:50:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 17:50:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#ifndef HEREDOC_INTERNAL_H
# define HEREDOC_INTERNAL_H
# include "executor.h"
char *executor_heredoc_read_line(t_minishell *minishell);
int executor_heredoc_open_tmp(char **path_out);
void executor_heredoc_discard_tmp(int fd, char *path);
bool executor_heredoc_finalize_tmp(t_redirection *rd, int fd, char *path,
uint8_t *exit_status);
bool executor_heredoc_fill_tmp(int fd, t_redirection *rd,
t_minishell *minishell, uint8_t *exit_status);
#endif

View File

@@ -0,0 +1,89 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* heredoc_readline.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/15 00:20:00 by sede-san #+# #+# */
/* Updated: 2026/02/15 00:20:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "heredoc_internal.h"
#include "variables.h"
#include <errno.h>
static bool append_char(char **line, size_t *len, char c)
{
char *new_line;
size_t i;
new_line = (char *)malloc(*len + 2);
if (new_line == NULL)
return (free(*line), *line = NULL, false);
i = 0;
while (i < *len)
{
new_line[i] = (*line)[i];
i++;
}
new_line[*len] = c;
new_line[*len + 1] = '\0';
free(*line);
*line = new_line;
(*len)++;
return (true);
}
static char *read_line_stdin(void)
{
char *line;
char c;
size_t len;
ssize_t nread;
line = ft_strdup("");
if (line == NULL)
return (NULL);
len = 0;
errno = 0;
nread = read(STDIN_FILENO, &c, 1);
while (nread > 0)
{
if (c == '\n')
return (line);
if (!append_char(&line, &len, c))
return (NULL);
nread = read(STDIN_FILENO, &c, 1);
}
if (nread == -1 && errno == EINTR)
return (line);
if (len > 0)
return (line);
return (free(line), NULL);
}
char *executor_heredoc_read_line(t_minishell *minishell)
{
char *prompt;
char *line;
size_t len;
if (!isatty(STDIN_FILENO))
{
line = get_next_line(STDIN_FILENO);
if (line == NULL)
return (NULL);
len = ft_strlen(line);
if (len > 0 && line[len - 1] == '\n')
line[len - 1] = '\0';
return (line);
}
prompt = get_var("PS2", minishell);
if (prompt == NULL)
prompt = DEFAULT_PS2;
if (write(STDOUT_FILENO, prompt, ft_strlen(prompt)) == -1)
return (NULL);
return (read_line_stdin());
}

View File

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

View File

@@ -0,0 +1,73 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* pipeline_helpers.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/11 00:00:00 by sede-san #+# #+# */
/* Updated: 2026/02/11 00:00:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "executor.h"
#include "builtins.h"
int executor_create_pipe_if_needed(
t_list *current_command,
t_pipeline *pipeline
)
{
if (!current_command->next)
return (0);
if (pipe(pipeline->pipefd) == PIPE_ERROR)
return (perror("pipe"), PIPE_ERROR);
return (0);
}
bool executor_is_builtin_command(
const t_command *command,
t_minishell *minishell
)
{
if (command == NULL || command->argv == NULL || command->argv[0] == NULL)
return (false);
return (is_builtin(command->argv[0], minishell));
}
bool executor_is_fork_required(
t_list *current_command,
const t_pipeline *pipeline,
t_minishell *minishell
)
{
const t_command *command;
command = current_command->content;
return (pipeline->prev_read_fd != -1 || current_command->next != NULL
|| !executor_is_builtin_command(command, minishell));
}
void executor_setup_child_input(
t_pipeline *pipeline
)
{
if (pipeline->prev_read_fd != -1)
{
dup2(pipeline->prev_read_fd, STDIN_FILENO);
close(pipeline->prev_read_fd);
}
}
void executor_setup_child_output(
t_list *current_command,
t_pipeline *pipeline
)
{
if (current_command->next)
{
dup2(pipeline->pipefd[WRITE_PIPE], STDOUT_FILENO);
close(pipeline->pipefd[READ_PIPE]);
close(pipeline->pipefd[WRITE_PIPE]);
}
}

View File

@@ -0,0 +1,95 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* process_helpers.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/11 00:00:00 by sede-san #+# #+# */
/* Updated: 2026/02/11 00:00:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "executor.h"
static void set_signal_exit_status(
int status,
uint8_t *exit_status
)
{
if (WTERMSIG(status) == SIGINT)
write(STDOUT_FILENO, "\n", 1);
else if (WTERMSIG(status) == SIGQUIT)
{
if (WCOREDUMP(status))
write(STDERR_FILENO, "Quit (core dumped)\n", 19);
else
write(STDERR_FILENO, "Quit\n", 5);
}
*exit_status = 128 + WTERMSIG(status);
}
static void set_last_child_status(
int status,
uint8_t *exit_status
)
{
if (WIFEXITED(status))
*exit_status = WEXITSTATUS(status);
else if (WIFSIGNALED(status))
set_signal_exit_status(status, exit_status);
}
void executor_child_process(
t_list *current_command,
t_pipeline *pipeline,
t_minishell *minishell
)
{
uint8_t exit_status;
t_command *command;
command = current_command->content;
minishell_set_child_signals();
executor_setup_child_input(pipeline);
executor_setup_child_output(current_command, pipeline);
if (!executor_apply_redirections(command, NULL, NULL, NULL))
exit(EXIT_FAILURE);
exit_status = executor_execute_command(command, minishell);
exit(exit_status);
}
void executor_parent_cleanup(
t_list *current_command,
t_pipeline *pipeline
)
{
if (pipeline->prev_read_fd != -1)
close(pipeline->prev_read_fd);
if (current_command->next)
{
close(pipeline->pipefd[WRITE_PIPE]);
pipeline->prev_read_fd = pipeline->pipefd[READ_PIPE];
}
else
pipeline->prev_read_fd = -1;
}
uint8_t executor_wait_for_children(
pid_t last_child_pid
)
{
uint8_t exit_status;
int status;
pid_t pid;
exit_status = EXIT_SUCCESS;
pid = wait(&status);
while (last_child_pid > 0 && pid > 0)
{
if (pid == last_child_pid)
set_last_child_status(status, &exit_status);
pid = wait(&status);
}
return (exit_status);
}

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

@@ -0,0 +1,120 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* redirections.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/12 00:00:00 by sede-san #+# #+# */
/* Updated: 2026/02/12 00:00:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "executor.h"
#include <errno.h>
static int open_redirection_target(
const t_redirection *redirection
)
{
if (redirection->type == TOKEN_REDIRECT_IN)
return (open(redirection->target, O_RDONLY));
if (redirection->type == TOKEN_HEREDOC)
return (open(redirection->target, O_RDONLY));
if (redirection->type == TOKEN_REDIRECT_OUT)
return (open(redirection->target, O_WRONLY | O_CREAT | O_TRUNC, 0644));
if (redirection->type == TOKEN_APPEND)
return (open(redirection->target, O_WRONLY | O_CREAT | O_APPEND, 0644));
errno = EINVAL;
return (-1);
}
static bool backup_fd_if_needed(
int io_number,
int *saved_fd
)
{
if (saved_fd == NULL || *saved_fd != -1)
return (true);
*saved_fd = dup(io_number);
if (*saved_fd == -1)
return (perror("dup"), false);
return (true);
}
static bool apply_redirection(
const t_redirection *redirection,
int *saved_stdin,
int *saved_stdout,
int *saved_stderr
)
{
int fd;
if (redirection == NULL || redirection->target == NULL)
return (false);
if ((redirection->io_number == STDIN_FILENO
&& !backup_fd_if_needed(STDIN_FILENO, saved_stdin))
|| (redirection->io_number == STDOUT_FILENO
&& !backup_fd_if_needed(STDOUT_FILENO, saved_stdout))
|| (redirection->io_number == STDERR_FILENO
&& !backup_fd_if_needed(STDERR_FILENO, saved_stderr)))
return (false);
fd = open_redirection_target(redirection);
if (fd == -1)
return (perror(redirection->target), false);
if (dup2(fd, redirection->io_number) == -1)
return (close(fd), perror("dup2"), false);
return (close(fd), true);
}
bool executor_apply_redirections(
const t_command *command,
int *saved_stdin,
int *saved_stdout,
int *saved_stderr
)
{
t_list *node;
if (saved_stdin != NULL)
*saved_stdin = -1;
if (saved_stdout != NULL)
*saved_stdout = -1;
if (saved_stderr != NULL)
*saved_stderr = -1;
if (command == NULL)
return (true);
node = command->redirections;
while (node != NULL
&& apply_redirection((t_redirection *)node->content,
saved_stdin, saved_stdout, saved_stderr))
node = node->next;
return (node == NULL);
}
void executor_restore_redirections(
int saved_stdin,
int saved_stdout,
int saved_stderr
)
{
if (saved_stdin != -1)
{
if (dup2(saved_stdin, STDIN_FILENO) == -1)
perror("dup2");
close(saved_stdin);
}
if (saved_stdout != -1)
{
if (dup2(saved_stdout, STDOUT_FILENO) == -1)
perror("dup2");
close(saved_stdout);
}
if (saved_stderr != -1)
{
if (dup2(saved_stderr, STDERR_FILENO) == -1)
perror("dup2");
close(saved_stderr);
}
}

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/20 20:51:33 by sede-san #+# #+# */
/* Updated: 2026/02/09 18:48:34 by sede-san ### ########.fr */
/* Updated: 2026/02/14 12:57:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -20,6 +20,7 @@ void minishell_init(
char **envp
){
ft_bzero(minishell, sizeof(t_minishell));
set_intp(minishell);
set_envp(envp, minishell);
set_builtins(minishell);
if (minishell->variables.environment == NULL || minishell->builtins == NULL)
@@ -28,28 +29,30 @@ void minishell_init(
void minishell_run(
t_minishell *minishell
){
)
{
char *line;
t_list *commands;
if (minishell == NULL)
{
minishell->exit_status = EXIT_FAILURE;
return ;
}
minishell_set_interactive_signals();
while (!minishell->exit)
{
line = readline(DEFAULT_PS1);
if (line != NULL)
if (isatty(STDIN_FILENO))
line = readline(get_var("PS1", minishell));
else
line = get_next_line(STDIN_FILENO);
handle_sigint_status(minishell);
if (handle_eof(line, minishell))
continue ;
if (*line != '\0')
{
if (*line != '\0')
{
add_history(line);
commands = parse(line, minishell);
execute(commands, minishell);
}
free(line);
add_history(line);
commands = parse(line, minishell);
minishell->exit_status = execute(commands, minishell);
}
free(line);
}
}
@@ -57,6 +60,8 @@ void minishell_clear(
t_minishell *minishell
){
rl_clear_history();
if (minishell->variables.internal != NULL)
ft_hashmap_clear(&minishell->variables.internal, free);
if (minishell->variables.environment != NULL)
ft_hashmap_clear(&minishell->variables.environment, free);
if (minishell->builtins != NULL)

37
src/minishell_helpers.c Normal file
View File

@@ -0,0 +1,37 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* minishell_helpers.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/13 21:09:23 by sede-san #+# #+# */
/* Updated: 2026/02/14 13:03:15 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "minishell.h"
#include "core.h"
void handle_sigint_status(
t_minishell *minishell
)
{
if (!minishell_consume_sigint())
return ;
minishell->exit_status = 130;
}
bool handle_eof(
char *line,
t_minishell *minishell
)
{
if (line != NULL)
return (false);
if (isatty(STDIN_FILENO))
ft_putendl("exit");
free(line);
minishell->exit = true;
return (true);
}

View File

@@ -13,26 +13,20 @@
#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);
static t_token *token_new(t_token_type type, char *text);
void token_clear(t_token *token);
static t_token *read_token(t_token_type type, const char *line, size_t *i);
static t_token *read_word(const char *line, size_t *i);
static inline bool is_meta(char c);
static t_token *tokenize(const char *line, size_t *start);
/**
* @brief Converts a command line string into a list of tokens.
*
* @return A list of tokens or NULL on error.
*/
t_list *lex(
t_list *lex(
const char *line
)
{
t_list *tokens;
t_token *token;
size_t i;
t_list *tokens;
t_token *token;
size_t i;
tokens = NULL;
i = 0;
@@ -56,13 +50,13 @@ t_list *lex(
/**
* @return A new token or NULL on error.
*/
static t_token *tokenize(
static t_token *tokenize(
const char *line,
size_t *start
)
{
t_token *token;
t_token_type type;
t_token *token;
t_token_type type;
if (line == NULL || line[*start] == '\0')
return (NULL);
@@ -73,106 +67,3 @@ static t_token *tokenize(
token = read_word(line, start);
return (token);
}
static t_token_type get_token_type(
const char *str
)
{
if (str == NULL || str[0] == '\0')
return (TOKEN_WORD);
if (str[0] == '|')
return (TOKEN_PIPE);
if (str[0] == '<')
{
if (str[1] == '<')
return (TOKEN_HEREDOC);
return (TOKEN_REDIRECT_IN);
}
if (str[0] == '>')
{
if (str[1] == '>')
return (TOKEN_APPEND);
return (TOKEN_REDIRECT_OUT);
}
return (TOKEN_WORD);
}
static t_token *token_new(
t_token_type type,
char *text
)
{
t_token *token;
token = (t_token *)malloc(sizeof(t_token));
if (token == NULL)
return (NULL);
token->type = type;
token->value = text;
if (token->type == TOKEN_WORD && token->value == NULL)
{
free(token);
return (NULL);
}
return (token);
}
void token_clear(
t_token *token
)
{
if (token != NULL)
{
free(token->value);
free(token);
}
}
static t_token *read_token(
t_token_type type,
const char *line,
size_t *i
)
{
const size_t start = *i;
size_t end;
while (is_meta(line[*i]))
(*i)++;
end = *i;
while (ft_isspace(line[*i]))
(*i)++;
return (token_new(type, ft_substr(line, start, end - start)));
}
static t_token *read_word(
const char *line,
size_t *i
)
{
const size_t start = *i;
bool in_single_quote;
bool in_double_quote;
in_single_quote = false;
in_double_quote = false;
while (line[*i] != '\0')
{
if (line[*i] == '\'' && !in_double_quote)
in_single_quote = !in_single_quote;
else if (line[*i] == '"' && !in_single_quote)
in_double_quote = !in_double_quote;
else if (!in_single_quote && !in_double_quote
&& (isspace(line[*i]) || is_meta(line[*i])))
break;
(*i)++;
}
return (token_new(TOKEN_WORD, ft_substr(line, start, *i - start)));
}
static inline bool is_meta(
char c
)
{
return (c == '|' || c == '<' || c == '>');
}

64
src/parser/lexer_reader.c Normal file
View File

@@ -0,0 +1,64 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* lexer_reader.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/13 21:13:23 by sede-san #+# #+# */
/* Updated: 2026/02/13 21:13:23 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser.h"
static inline bool is_meta(char c);
t_token *read_token(
t_token_type type,
const char *line,
size_t *i
)
{
const size_t start = *i;
size_t end;
while (is_meta(line[*i]))
(*i)++;
end = *i;
while (ft_isspace(line[*i]))
(*i)++;
return (token_new(type, ft_substr(line, start, end - start)));
}
t_token *read_word(
const char *line,
size_t *i
)
{
const size_t start = *i;
bool in_single_quote;
bool in_double_quote;
in_single_quote = false;
in_double_quote = false;
while (line[*i] != '\0')
{
if (line[*i] == '\'' && !in_double_quote)
in_single_quote = !in_single_quote;
else if (line[*i] == '"' && !in_single_quote)
in_double_quote = !in_double_quote;
else if (!in_single_quote && !in_double_quote
&& (isspace(line[*i]) || is_meta(line[*i])))
break ;
(*i)++;
}
return (token_new(TOKEN_WORD, ft_substr(line, start, *i - start)));
}
static inline bool is_meta(
char c
)
{
return (c == '|' || c == '<' || c == '>');
}

67
src/parser/lexer_token.c Normal file
View File

@@ -0,0 +1,67 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* lexer_token.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/13 21:12:49 by sede-san #+# #+# */
/* Updated: 2026/02/13 21:12:49 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser.h"
t_token_type get_token_type(
const char *str
)
{
if (str == NULL || str[0] == '\0')
return (TOKEN_WORD);
if (str[0] == '|')
return (TOKEN_PIPE);
if (str[0] == '<')
{
if (str[1] == '<')
return (TOKEN_HEREDOC);
return (TOKEN_REDIRECT_IN);
}
if (str[0] == '>')
{
if (str[1] == '>')
return (TOKEN_APPEND);
return (TOKEN_REDIRECT_OUT);
}
return (TOKEN_WORD);
}
t_token *token_new(
t_token_type type,
char *text
)
{
t_token *token;
token = (t_token *)malloc(sizeof(t_token));
if (token == NULL)
return (NULL);
token->type = type;
token->value = text;
if (token->type == TOKEN_WORD && token->value == NULL)
{
free(token);
return (NULL);
}
return (token);
}
void token_clear(
t_token *token
)
{
if (token != NULL)
{
free(token->value);
free(token);
}
}

View File

@@ -6,33 +6,16 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/22 18:37:38 by sede-san #+# #+# */
/* Updated: 2026/02/12 20:55:49 by sede-san ### ########.fr */
/* Updated: 2026/02/13 21:22:06 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser.h"
#include "errors.h"
static t_list *parse_tokens(
t_list *tokens
);
static t_list *ft_lstfind(
t_list *lst,
bool (*pre)(void *)
);
static void expand_variable(
char **argument,
int *i,
t_minishell *minishell
);
static void expand(
t_list **commands,
t_minishell *minishell
);
static t_list *parse_tokens(t_list *tokens);
static bool parse_add_command(t_list **commands, t_list **current_token);
static bool parse_advance_token(t_list **commands, t_list **current_token);
/**
* @brief Converts a command line string into a list of commands.
@@ -42,13 +25,13 @@ static void expand(
*
* @return A list of commands or `NULL` on error.
*/
t_list *parse(
t_list *parse(
char *line,
t_minishell *minishell
)
{
t_list *commands;
t_list *tokens;
t_list *commands;
t_list *tokens;
if (line == NULL)
return (NULL);
@@ -63,482 +46,66 @@ t_list *parse(
* @brief Converts a list of tokens into a list of commands.
*
* @param tokens The list of tokens to parse.
* @param minishell The minishell instance.
*
* @return A list of commands or `NULL` on error.
*/
static t_list *parse_tokens(
static t_list *parse_tokens(
t_list *tokens
)
{
t_list *commands;
t_command *command;
t_list *current_token;
t_list *new_command;
t_list *commands;
t_list *current_token;
if (tokens == NULL)
return (NULL);
commands = NULL;
current_token = tokens;
while (current_token != NULL)
{
command = command_new(&current_token);
if (command == NULL)
{
ft_lstclear(&commands, (void (*)(void *))command_clear);
return (NULL);
}
new_command = ft_lstnew(command);
if (new_command == NULL)
{
command_clear(command);
ft_lstclear(&commands, (void (*)(void *))command_clear);
return (malloc_error(), NULL);
}
ft_lstadd_back(&commands, new_command);
if (current_token != NULL)
{
if (current_token->next == NULL)
syntax_error_unexpected_token((t_token *)current_token->content);
current_token = current_token->next;
}
}
if (((t_token *)current_token->content)->type == TOKEN_PIPE)
return (syntax_error_unexpected_token(
(t_token *)current_token->content), NULL);
while (current_token != NULL && parse_add_command(&commands, &current_token)
&& parse_advance_token(&commands, &current_token))
continue ;
if (current_token != NULL)
return (NULL);
return (commands);
}
static void expand_variable(
char **argument,
int *i,
t_minishell *minishell
)
{
char *expanded;
char *variable;
char *variable_name;
const int start = *i + 1;
int end;
end = start;
while (ft_isalnum((*argument)[end]))
end++;
variable_name = ft_substr(*argument, start, end - start);
if (variable_name == NULL)
return (minishell->exit = true, malloc_error());
variable = get_env(variable_name, minishell);
free(variable_name);
if (variable == NULL)
variable = "";
expanded = ft_strnjoin(3,
ft_substr(*argument, 0, *i),
variable,
ft_substr(*argument,end, ft_strlen(*argument) - end));
if (expanded == NULL)
return (minishell->exit = true, malloc_error());
*i += ft_strlen(variable) - 1;
free(*argument);
*argument = expanded;
}
static void expand_argument(
char **argument,
t_minishell *minishell
)
{
bool in_single_quote;
bool in_double_quote;
int i;
in_single_quote = false;
in_double_quote = false;
i = 0;
while ((*argument)[i] != '\0')
{
if ((*argument)[i] == '$' && !in_single_quote)
{
expand_variable(argument, &i, minishell);
if (*argument == NULL)
return (minishell->exit = true, malloc_error());
}
else
{
if ((*argument)[i] == '\'' && !in_double_quote)
in_single_quote = !in_single_quote;
else if ((*argument)[i] == '"' && !in_single_quote)
in_double_quote = !in_double_quote;
i++;
}
}
}
static void expand(
static bool parse_add_command(
t_list **commands,
t_minishell *minishell
t_list **current_token
)
{
t_list *current_command;
t_command *command;
int i;
current_command = *commands;
while (current_command != NULL)
{
command = (t_command *)current_command->content;
i = 0;
while (i < command->argc)
{
expand_argument(&command->argv[i], minishell);
if (command->argv[i] == NULL)
ft_lstclear(commands, (void (*)(void *))command_clear);
i++;
}
if (command == NULL)
return (ft_lstclear(commands, (void (*)(void *))command_clear));
current_command = current_command->next;
}
}
t_list *new_command;
/**
* @brief Creates a new command from a list of tokens.
*
* @param tokens The list of tokens to create the command from.
*
* @return A new command or NULL on error.
*
* @note The `tokens` pointer is moved to the next command's tokens.
*/
t_command *command_new(
t_list **tokens
)
{
t_command *command;
t_list *current_token;
t_list *delimiter_token;
command = (t_command *)ft_calloc(1, sizeof(t_command));
command = command_new(current_token);
if (command == NULL)
return (NULL);
current_token = *tokens;
delimiter_token = ft_lstfind(current_token, (bool (*)(void *))is_pipe);
while (command != NULL && current_token != delimiter_token)
return (ft_lstclear(commands, (void (*)(void *))command_clear), false);
new_command = ft_lstnew(command);
if (new_command == NULL)
{
command_add_tokens(&command, &current_token);
command_clear(command);
return (ft_lstclear(commands, (void (*)(void *))command_clear),
malloc_error(), false);
}
*tokens = current_token;
return (command);
ft_lstadd_back(commands, new_command);
return (true);
}
/**
* @brief Creates a new redirection from a list of tokens.
*
* @param tokens The list of tokens to create the redirection from.
*
* @return A new redirection or `NULL` on error.
*/
t_redirection *redirection_new(
t_list **tokens
static bool parse_advance_token(
t_list **commands,
t_list **current_token
)
{
t_redirection *redirection;
t_token *token;
redirection = (t_redirection *)malloc(sizeof(t_redirection));
if (redirection == NULL)
return (malloc_error(), NULL);
token = (t_token *)(*tokens)->content;
redirection->type = token->type;
*tokens = (*tokens)->next;
if (*tokens == NULL)
if (*current_token == NULL)
return (true);
if ((*current_token)->next == NULL)
{
free(redirection);
return (syntax_error_unexpected_token(NULL), NULL);
ft_lstclear(commands, (void (*)(void *))command_clear);
return (syntax_error_unexpected_token(
(t_token *)(*current_token)->content), false);
}
token = (t_token *)(*tokens)->content;
if (token->type != TOKEN_WORD)
{
free(redirection);
while (*tokens != NULL)
*tokens = (*tokens)->next;
return (syntax_error_unexpected_token(token), NULL);
}
redirection->target = ft_strdup(token->value);
if (redirection->target == NULL)
{
free(redirection);
return (malloc_error(), NULL);
}
*tokens = (*tokens)->next;
return (redirection);
*current_token = (*current_token)->next;
return (true);
}
void redirection_clear(
t_redirection *redirection
)
{
if (redirection != NULL)
{
free(redirection->target);
free(redirection);
}
}
/**
* @brief Adds a token to a command, updating the command's arguments and
* redirections as necessary.
*
* @param command The command to add the token to.
* @param tokens The list of tokens to add to the command.
*
* @note The `command` pointer can be free'd if there is an error while adding
* the token.
*/
void command_add_tokens(
t_command **command,
t_list **tokens
)
{
t_token *token;
token = (t_token *)(*tokens)->content;
if (is_redirection(token))
redirection_add(tokens, token, command);
else
words_add(tokens, command);
}
/**
* @brief Converts a list of arguments into an array.
*
* @param args The list of arguments to convert.
* @param argc The number of arguments in the list.
*
* @return An array of arguments or `NULL` on error.
*
* @note The `args` list is cleared after the conversion and set to NULL.
*/
char **args_to_array(
t_list **args,
size_t argc
)
{
char **argv;
t_list *arg;
size_t i;
argv = (char **)malloc(sizeof(char *) * (argc + 1));
if (argv == NULL)
return (NULL);
i = 0;
arg = *args;
while (arg != NULL)
{
argv[i] = (char *)arg->content;
arg = arg->next;
i++;
}
argv[i] = NULL;
ft_lstclear_nodes(args);
return (argv);
}
/**
* @brief Adds all consecutive word tokens to a command's argv and updates its
* argc accordingly.
*
* @param command The command to add the word tokens to.
* @param tokens The list of tokens to add to the command.
*/
void words_add(
t_list **tokens,
t_command **command
)
{
t_list *args;
t_list *arg;
t_token *token;
args = NULL;
arg = *tokens;
token = (t_token *)arg->content;
while (arg != NULL && token->type == TOKEN_WORD)
{
ft_lstadd_back(&args, ft_lstnew(ft_strdup(token->value)));
(*command)->argc++;
arg = arg->next;
if (arg != NULL)
token = (t_token *)arg->content;
}
*tokens = arg;
(*command)->argv = args_to_array(&args, (*command)->argc);
ft_lstclear_nodes(&args);
}
void redirection_add(
t_list **tokens,
t_token *token,
t_command **command
)
{
t_redirection *redirection;
t_list *redirection_tokens;
redirection = redirection_new(tokens);
if (redirection == NULL)
{
command_clear(*command);
*command = NULL;
return ;
}
redirection_tokens = ft_lstnew(redirection);
if (redirection_tokens == NULL)
{
free(redirection);
return (malloc_error());
}
if (token->type == TOKEN_HEREDOC)
ft_lstadd_back(&(*command)->heredocs, redirection_tokens);
else
ft_lstadd_back(&(*command)->redirections, redirection_tokens);
}
/**
* @brief Checks if a token is a redirection token.
*
* @param token The token to check.
*
* @return `true` if the token is a redirection token, `false` otherwise.
*/
bool is_redirection(
t_token *token
)
{
return (token->type == TOKEN_REDIRECT_IN
|| token->type == TOKEN_REDIRECT_OUT
|| token->type == TOKEN_APPEND
|| token->type == TOKEN_HEREDOC);
}
void command_clear_argv(
t_command *command
)
{
int i;
if (command->argv != NULL)
{
i = 0;
while (i < command->argc)
{
free(command->argv[i]);
i++;
}
free(command->argv);
command->argv = NULL;
}
}
/**
* @brief Clears a command, freeing all associated memory.
*
* @param command The command to clear.
*/
void command_clear(
t_command *command
)
{
if (command != NULL)
{
command_clear_argv(command);
ft_lstclear(&command->redirections, (void (*)(void *))redirection_clear);
ft_lstclear(&command->heredocs, (void (*)(void *))redirection_clear);
free(command);
}
}
/**
* @brief Checks if a token is a pipe token.
*
* @param token The token to check.
*
* @return `true` if the token is a pipe token, `false` otherwise.
*/
bool is_pipe(
t_token *token)
{
return (token->type == TOKEN_PIPE);
}
/**
* @brief Finds a node in a linked list that satisfies a given predicate.
*
* @param lst The linked list to search through.
* @param pre The predicate function to apply to each node's content.
*
* @returns The first node that satisfies the predicate or `NULL` if no such
* node exists or if the list is `NULL`.
*/
t_list *ft_lstfind(
t_list *lst,
bool (*pre)(void *))
{
while (lst != NULL)
{
if (pre(lst->content))
return (lst);
lst = lst->next;
}
return (NULL);
}
// void print_command_info(
// t_command *command
// )
// {
// printf("Command:\n");
// printf(" argc: %d\n", command->argc);
// printf(" argv: [");
// for (int i = 0; i < command->argc; i++)
// {
// printf("%s", command->argv[i]);
// if (i < command->argc - 1)
// printf(", ");
// }
// printf("]\n");
// printf(" path: %s\n", command->path);
// printf(" redirections:\n");
// t_list *redirection_node = command->redirections;
// while (redirection_node != NULL)
// {
// t_redirection *redirection = (t_redirection *)redirection_node->content;
// printf(" type: %d, target: %s\n", redirection->type, redirection->target);
// redirection_node = redirection_node->next;
// }
// printf(" heredocs:\n");
// t_list *heredoc_node = command->heredocs;
// while (heredoc_node != NULL)
// {
// t_redirection *heredoc = (t_redirection *)heredoc_node->content;
// printf(" type: %d, target: %s\n", heredoc->type, heredoc->target);
// heredoc_node = heredoc_node->next;
// }
// }
// int main(int argc, char const *argv[], char **envp)
// {
// t_minishell minishell;
// t_list *commands;
// char *line;
// if (argc != 2)
// return (EXIT_FAILURE);
// minishell_init(&minishell, envp);
// line = ft_strdup(argv[1]);
// commands = parse(line, NULL);
// ft_lstiter(commands, (void (*)(void *))print_command_info);
// if (line != NULL)
// free(line);
// if (commands != NULL)
// ft_lstclear(&commands, (void (*)(void *))command_clear);
// return 0;
// }

46
src/parser/parser_clear.c Normal file
View File

@@ -0,0 +1,46 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_clear.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 16:10:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 16:10:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser.h"
static void command_clear_argv(
t_command *command
)
{
int i;
if (command->argv != NULL)
{
i = 0;
while (i < command->argc)
{
free(command->argv[i]);
i++;
}
free(command->argv);
command->argv = NULL;
}
}
void command_clear(
t_command *command
)
{
if (command != NULL)
{
command_clear_argv(command);
ft_lstclear(&command->redirections,
(void (*)(void *))redirection_clear);
ft_lstclear(&command->heredocs, (void (*)(void *))redirection_clear);
free(command);
}
}

128
src/parser/parser_command.c Normal file
View File

@@ -0,0 +1,128 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_command.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/13 21:26:42 by sede-san #+# #+# */
/* Updated: 2026/02/13 21:26:42 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser.h"
static t_list *ft_lstfind(t_list *lst, bool (*pre)(void *));
/**
* @brief Creates a new command from a list of tokens.
*
* @param tokens The list of tokens to create the command from.
*
* @return A new command or NULL on error.
*
* @note The `tokens` pointer is moved to the next command's tokens.
*/
t_command *command_new(
t_list **tokens
)
{
t_command *command;
t_list *current_token;
t_list *delimiter_token;
command = (t_command *)ft_calloc(1, sizeof(t_command));
if (command == NULL)
return (NULL);
current_token = *tokens;
delimiter_token = ft_lstfind(current_token, (bool (*)(void *))is_pipe);
while (command != NULL && current_token != delimiter_token)
command_add_tokens(&command, &current_token);
*tokens = current_token;
return (command);
}
/**
* @brief Adds a token to a command, updating the command's arguments and
* redirections as necessary.
*
* @param command The command to add the token to.
* @param tokens The list of tokens to add to the command.
*
* @note The `command` pointer can be free'd if there is an error while adding
* the token.
*/
void command_add_tokens(
t_command **command,
t_list **tokens
)
{
t_token *token;
int io_number;
token = (t_token *)(*tokens)->content;
if (parser_token_is_fd_prefix(*tokens, &io_number))
{
*tokens = (*tokens)->next;
token = (t_token *)(*tokens)->content;
redirection_add_with_fd(tokens, token, command, io_number);
return ;
}
if (is_redirection(token))
redirection_add_with_fd(tokens, token, command, -1);
else
words_add(tokens, command);
}
/**
* @brief Checks if a token is a redirection token.
*
* @param token The token to check.
*
* @return `true` if the token is a redirection token, `false` otherwise.
*/
bool is_redirection(
t_token *token
)
{
return (token->type == TOKEN_REDIRECT_IN
|| token->type == TOKEN_REDIRECT_OUT
|| token->type == TOKEN_APPEND
|| token->type == TOKEN_HEREDOC);
}
/**
* @brief Checks if a token is a pipe token.
*
* @param token The token to check.
*
* @return `true` if the token is a pipe token, `false` otherwise.
*/
bool is_pipe(
t_token *token
)
{
return (token->type == TOKEN_PIPE);
}
/**
* @brief Finds a node in a linked list that satisfies a given predicate.
*
* @param lst The linked list to search through.
* @param pre The predicate function to apply to each node's content.
*
* @returns The first node that satisfies the predicate or `NULL` if no such
* node exists or if the list is `NULL`.
*/
static t_list *ft_lstfind(
t_list *lst,
bool (*pre)(void *))
{
while (lst != NULL)
{
if (pre(lst->content))
return (lst);
lst = lst->next;
}
return (NULL);
}

View File

@@ -0,0 +1,54 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_command_fd.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 16:40:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 16:40:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser.h"
#include <limits.h>
static bool parse_io_number(const char *value, int *io_number)
{
long number;
size_t i;
if (value == NULL || value[0] == '\0')
return (false);
number = 0;
i = 0;
while (value[i] != '\0')
{
if (!ft_isdigit(value[i]))
return (false);
if (number > (INT_MAX - (value[i] - '0')) / 10)
return (false);
number = (number * 10) + (value[i] - '0');
i++;
}
*io_number = (int)number;
return (true);
}
bool parser_token_is_fd_prefix(
t_list *token_node,
int *io_number
)
{
t_token *token;
t_token *next_token;
if (token_node == NULL || token_node->next == NULL)
return (false);
token = (t_token *)token_node->content;
next_token = (t_token *)token_node->next->content;
if (token == NULL || next_token == NULL || token->type != TOKEN_WORD
|| !is_redirection(next_token))
return (false);
return (parse_io_number(token->value, io_number));
}

138
src/parser/parser_expand.c Normal file
View File

@@ -0,0 +1,138 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_expand.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/13 21:24:45 by sede-san #+# #+# */
/* Updated: 2026/02/14 06:56:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser_expand_internal.h"
static void command_clear_argv_expand(
t_command *command
)
{
int i;
i = 0;
while (i < command->argc)
{
free(command->argv[i]);
i++;
}
free(command->argv);
}
static bool command_set_expanded_argv(
t_command *command,
t_list *expanded_args,
t_minishell *minishell
)
{
char **argv;
t_list *current;
int argc;
int i;
argc = ft_lstsize(expanded_args);
argv = (char **)malloc(sizeof(char *) * (argc + 1));
if (argv == NULL)
return (ft_lstclear(&expanded_args, free),
parser_expand_malloc_error(minishell), false);
i = 0;
current = expanded_args;
while (current != NULL)
{
argv[i++] = (char *)current->content;
current = current->next;
}
argv[i] = NULL;
ft_lstclear_nodes(&expanded_args);
command_clear_argv_expand(command);
command->argc = argc;
command->argv = argv;
return (true);
}
static bool expand_argv(
t_command *command,
t_minishell *minishell
)
{
int i;
t_list *expanded_args;
t_list *fields;
t_list *last;
i = 0;
expanded_args = NULL;
while (i < command->argc)
{
fields = NULL;
if (!parser_expand_word_fields(command->argv[i], minishell,
true, &fields))
return (ft_lstclear(&expanded_args, free), false);
if (expanded_args == NULL)
expanded_args = fields;
else
{
last = ft_lstlast(expanded_args);
if (last != NULL)
last->next = fields;
}
i++;
}
return (command_set_expanded_argv(command, expanded_args, minishell));
}
static bool expand_redirections(
t_list *redirections,
t_minishell *minishell
)
{
t_redirection *redirection;
char *expanded;
while (redirections != NULL)
{
redirection = (t_redirection *)redirections->content;
expanded = parser_expand_word(redirection->target, minishell,
true);
if (expanded == NULL)
return (false);
free(redirection->target);
redirection->target = expanded;
redirections = redirections->next;
}
return (true);
}
void expand(
t_list **commands,
t_minishell *minishell
)
{
t_list *current;
t_command *command;
if (commands == NULL || *commands == NULL)
return ;
current = *commands;
while (current != NULL)
{
command = (t_command *)current->content;
if (!expand_argv(command, minishell)
|| !expand_redirections(command->redirections, minishell)
|| !expand_redirections(command->heredocs, minishell))
{
ft_lstclear(commands, (void (*)(void *))command_clear);
*commands = NULL;
return ;
}
current = current->next;
}
}

View File

@@ -0,0 +1,106 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_expand_fields.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 13:15:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 13:15:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser_expand_internal.h"
static t_fields_ctx init_fields_ctx(
t_list **fields,
char **current,
t_field_state *state
)
{
t_fields_ctx ctx;
ctx.fields = fields;
ctx.current = current;
ctx.touched = &state->touched;
ctx.in_single_quote = &state->in_single_quote;
ctx.in_double_quote = &state->in_double_quote;
return (ctx);
}
static bool finish_word_fields(
t_fields_ctx ctx
)
{
if (*ctx.in_single_quote || *ctx.in_double_quote)
{
ft_lstclear(ctx.fields, free);
free(*ctx.current);
syntax_error_unexpected_token(NULL);
ctx.minishell->exit_status = 2;
return (false);
}
if ((*ctx.touched || (*ctx.current)[0] != '\0')
&& !parser_fields_push_field(ctx))
return (false);
free(*ctx.current);
return (true);
}
static bool process_word_fields(
const char *word,
t_fields_ctx ctx
)
{
size_t i;
i = 0;
while (word[i] != '\0')
{
if (!parser_fields_step(word, &i, ctx))
return (false);
}
return (true);
}
static bool set_empty_word_field(
t_list **fields,
t_minishell *minishell
)
{
t_list *node;
node = ft_lstnew(ft_strdup(""));
if (node == NULL || node->content == NULL)
return (free(node), parser_expand_malloc_error(minishell), false);
*fields = node;
return (true);
}
bool parser_expand_word_fields(
const char *word,
t_minishell *minishell,
bool expand_vars,
t_list **fields
)
{
char *current;
t_field_state state;
t_fields_ctx ctx;
*fields = NULL;
if (word[0] == '\0')
return (set_empty_word_field(fields, minishell));
current = ft_strdup("");
if (current == NULL)
return (parser_expand_malloc_error(minishell), false);
state.touched = false;
state.in_single_quote = false;
state.in_double_quote = false;
ctx = init_fields_ctx(fields, &current, &state);
ctx.minishell = minishell;
ctx.expand_vars = expand_vars;
if (!process_word_fields(word, ctx))
return (ft_lstclear(fields, free), free(current), false);
return (finish_word_fields(ctx));
}

View File

@@ -0,0 +1,85 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_expand_fields_escape.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 16:40:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 16:40:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser_expand_internal.h"
static bool is_backslash_escaped_in_double_quote(
char c
)
{
return (c == '$' || c == '\"' || c == '\\');
}
static char get_escaped_char(
const char *word,
size_t *i
)
{
char value;
if (word[*i + 1] == '\0')
value = word[(*i)++];
else
{
value = word[*i + 1];
*i += 2;
}
return (value);
}
bool parser_fields_handle_backslash(
const char *word,
size_t *i,
t_fields_ctx ctx,
bool *handled
)
{
char value[2];
*handled = false;
if (word[*i] != '\\' || *ctx.in_single_quote)
return (true);
if (*ctx.in_double_quote
&& !is_backslash_escaped_in_double_quote(word[*i + 1]))
return (true);
*handled = true;
value[0] = get_escaped_char(word, i);
value[1] = '\0';
if (!parser_fields_append_text(ctx.current, value, ctx.minishell))
return (false);
*ctx.touched = true;
return (true);
}
bool parser_fields_expand_tilde(
const char *word,
size_t *i,
t_fields_ctx ctx,
bool *handled
)
{
char *home;
*handled = false;
if (word[*i] != '~' || *ctx.in_single_quote || *ctx.in_double_quote
|| *i != 0 || (word[*i + 1] != '\0' && word[*i + 1] != '/'))
return (true);
home = get_var("HOME", ctx.minishell);
if (home == NULL)
return (true);
*handled = true;
(*i)++;
if (!parser_fields_append_text(ctx.current, home, ctx.minishell))
return (false);
*ctx.touched = true;
return (true);
}

View File

@@ -0,0 +1,113 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_expand_fields_step.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 14:12:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 14:12:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser_expand_internal.h"
static bool handle_quote_char(
const char *word,
size_t *i,
t_fields_ctx ctx
)
{
if (word[*i] == '\'' && !*ctx.in_double_quote)
{
*ctx.in_single_quote = !*ctx.in_single_quote;
*ctx.touched = true;
(*i)++;
return (true);
}
if (word[*i] == '\"' && !*ctx.in_single_quote)
{
*ctx.in_double_quote = !*ctx.in_double_quote;
*ctx.touched = true;
(*i)++;
return (true);
}
return (false);
}
static bool skip_dollar_quote_prefix(
const char *word,
size_t *i,
t_fields_ctx ctx
)
{
if (word[*i] == '$' && !*ctx.in_single_quote && !*ctx.in_double_quote
&& word[*i + 1] != '\0'
&& (word[*i + 1] == '\'' || word[*i + 1] == '\"'))
{
(*i)++;
return (true);
}
return (false);
}
static bool expand_dollar_token(
const char *word,
size_t *i,
t_fields_ctx ctx,
bool *handled
)
{
char *expanded;
*handled = false;
if (word[*i] != '$' || *ctx.in_single_quote || !ctx.expand_vars)
return (true);
*handled = true;
expanded = parser_expand_variable(word, i, ctx.minishell);
if (expanded == NULL)
return (false);
if (!*ctx.in_double_quote
&& !parser_fields_expand_unquoted_value(ctx, expanded))
return (free(expanded), false);
if (*ctx.in_double_quote && !parser_fields_append_text(ctx.current,
expanded, ctx.minishell))
return (free(expanded), false);
if (*ctx.in_double_quote && expanded[0] != '\0')
*ctx.touched = true;
free(expanded);
return (true);
}
bool parser_fields_step(
const char *word,
size_t *i,
t_fields_ctx ctx
)
{
char value[2];
bool handled;
if (handle_quote_char(word, i, ctx))
return (true);
if (!parser_fields_handle_backslash(word, i, ctx, &handled))
return (false);
if (handled)
return (true);
if (!parser_fields_expand_tilde(word, i, ctx, &handled))
return (false);
if (handled)
return (true);
if (skip_dollar_quote_prefix(word, i, ctx))
return (true);
if (!expand_dollar_token(word, i, ctx, &handled))
return (false);
if (handled)
return (true);
value[0] = word[(*i)++];
value[1] = '\0';
if (!parser_fields_append_text(ctx.current, value, ctx.minishell))
return (false);
*ctx.touched = true;
return (true);
}

View File

@@ -0,0 +1,100 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_expand_fields_utils.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 14:12:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 14:12:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser_expand_internal.h"
bool parser_fields_append_text(
char **current,
const char *value,
t_minishell *minishell
)
{
char *joined;
joined = ft_strnjoin(2, *current, (char *)value);
if (joined == NULL)
{
free(*current);
*current = NULL;
return (parser_expand_malloc_error(minishell), false);
}
free(*current);
*current = joined;
return (true);
}
static bool is_ifs_char(
char c
)
{
return (c == ' ' || c == '\t' || c == '\n');
}
bool parser_fields_push_field(
t_fields_ctx ctx
)
{
t_list *node;
node = ft_lstnew(*ctx.current);
if (node == NULL)
{
free(*ctx.current);
*ctx.current = NULL;
return (parser_expand_malloc_error(ctx.minishell), false);
}
ft_lstadd_back(ctx.fields, node);
*ctx.current = ft_strdup("");
if (*ctx.current == NULL)
return (ft_lstclear(ctx.fields, free),
parser_expand_malloc_error(ctx.minishell), false);
*ctx.touched = false;
return (true);
}
static void skip_ifs(
const char *expanded,
size_t *i
)
{
while (expanded[*i] != '\0' && is_ifs_char(expanded[*i]))
(*i)++;
}
bool parser_fields_expand_unquoted_value(
t_fields_ctx ctx,
const char *expanded
)
{
size_t i;
char value[2];
i = 0;
while (expanded[i] != '\0')
{
if (is_ifs_char(expanded[i]))
{
if ((*ctx.touched || (*ctx.current)[0] != '\0')
&& !parser_fields_push_field(ctx))
return (false);
skip_ifs(expanded, &i);
continue ;
}
value[0] = expanded[i];
value[1] = '\0';
if (!parser_fields_append_text(ctx.current, value, ctx.minishell))
return (false);
*ctx.touched = true;
i++;
}
return (true);
}

View File

@@ -0,0 +1,67 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_expand_internal.h :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 06:56:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 06:56:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#ifndef PARSER_EXPAND_INTERNAL_H
# define PARSER_EXPAND_INTERNAL_H
# include "parser.h"
# include "errors.h"
# include "variables.h"
typedef struct s_fields_ctx t_fields_ctx;
void parser_expand_malloc_error(t_minishell *minishell);
char *parser_expand_variable(const char *word, size_t *i,
t_minishell *minishell);
char *parser_expand_word(const char *word, t_minishell *minishell,
bool expand_vars);
bool parser_expand_word_fields(const char *word, t_minishell *minishell,
bool expand_vars, t_list **fields);
bool parser_fields_append_text(char **current, const char *value,
t_minishell *minishell);
bool parser_fields_push_field(t_fields_ctx ctx);
bool parser_fields_expand_unquoted_value(t_fields_ctx ctx,
const char *expanded);
bool parser_fields_step(const char *word, size_t *i, t_fields_ctx ctx);
bool parser_fields_handle_backslash(const char *word, size_t *i,
t_fields_ctx ctx, bool *handled);
bool parser_fields_expand_tilde(const char *word, size_t *i,
t_fields_ctx ctx, bool *handled);
typedef struct s_word_ctx
{
char **result;
bool *in_single_quote;
bool *in_double_quote;
t_minishell *minishell;
bool expand_vars;
} t_word_ctx;
typedef struct s_fields_ctx
{
t_list **fields;
char **current;
bool *touched;
bool *in_single_quote;
bool *in_double_quote;
t_minishell *minishell;
bool expand_vars;
} t_fields_ctx;
typedef struct s_field_state
{
bool touched;
bool in_single_quote;
bool in_double_quote;
} t_field_state;
#endif

View File

@@ -0,0 +1,82 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_expand_variable.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 06:56:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 06:56:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser_expand_internal.h"
void parser_expand_malloc_error(
t_minishell *minishell
)
{
minishell->exit = true;
malloc_error();
}
static bool is_var_char(
char c
)
{
return (ft_isalnum(c) || c == '_');
}
static char *dup_or_error(
const char *value,
t_minishell *minishell
)
{
char *dup;
dup = ft_strdup(value);
if (dup == NULL)
return (parser_expand_malloc_error(minishell), NULL);
return (dup);
}
static char *expand_named_variable(
const char *word,
size_t *i,
t_minishell *minishell
)
{
char *name;
char *value;
size_t start;
start = *i;
while (word[*i] != '\0' && is_var_char(word[*i]))
(*i)++;
name = ft_substr(word, start, *i - start);
if (name == NULL)
return (parser_expand_malloc_error(minishell), NULL);
value = get_var(name, minishell);
free(name);
if (value == NULL)
value = "";
return (dup_or_error(value, minishell));
}
char *parser_expand_variable(const char *word, size_t *i,
t_minishell *minishell)
{
char *expanded;
(*i)++;
if (word[*i] == '?')
{
expanded = ft_itoa(minishell->exit_status);
if (expanded == NULL)
return (parser_expand_malloc_error(minishell), NULL);
return ((*i)++, expanded);
}
if (word[*i] == '\0' || !is_var_char(word[*i]))
return (dup_or_error("$", minishell));
return (expand_named_variable(word, i, minishell));
}

View File

@@ -0,0 +1,104 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_expand_word.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 06:56:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 06:56:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser_expand_internal.h"
static bool word_toggle_quotes(
char c,
bool *in_single_quote,
bool *in_double_quote
)
{
if (c == '\'' && !*in_double_quote)
return (*in_single_quote = !*in_single_quote, true);
if (c == '\"' && !*in_single_quote)
return (*in_double_quote = !*in_double_quote, true);
return (false);
}
static bool word_append(
char **result,
const char *value,
t_minishell *minishell
)
{
char *joined;
joined = ft_strnjoin(2, *result, (char *)value);
if (joined == NULL)
{
free(*result);
*result = NULL;
return (parser_expand_malloc_error(minishell), false);
}
free(*result);
*result = joined;
return (true);
}
static bool word_step(
const char *word,
size_t *i,
t_word_ctx ctx
)
{
char *expanded;
char value[2];
if (word_toggle_quotes(word[*i], ctx.in_single_quote, ctx.in_double_quote))
return ((*i)++, true);
if (word[*i] == '$' && word[*i + 1] != '\0'
&& !*ctx.in_single_quote && !*ctx.in_double_quote
&& (word[*i + 1] == '\'' || word[*i + 1] == '\"'))
return ((*i)++, true);
if (word[*i] == '$' && !*ctx.in_single_quote && ctx.expand_vars)
{
expanded = parser_expand_variable(word, i, ctx.minishell);
if (expanded == NULL || !word_append(ctx.result, expanded,
ctx.minishell))
return (free(expanded), false);
return (free(expanded), true);
}
value[0] = word[*i];
value[1] = '\0';
(*i)++;
return (word_append(ctx.result, value, ctx.minishell));
}
char *parser_expand_word(
const char *word,
t_minishell *minishell,
bool expand_vars
)
{
char *result;
size_t i;
bool in_single_quote;
bool in_double_quote;
result = ft_strdup("");
if (result == NULL)
return (parser_expand_malloc_error(minishell), NULL);
i = 0;
in_single_quote = false;
in_double_quote = false;
while (word[i] != '\0')
{
if (!word_step(word, &i, (t_word_ctx){&result, &in_single_quote,
&in_double_quote, minishell, expand_vars}))
return (NULL);
}
if (in_single_quote || in_double_quote)
return (free(result), syntax_error_unexpected_token(NULL),
minishell->exit_status = 2, NULL);
return (result);
}

View File

@@ -0,0 +1,134 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_redirection.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/13 21:28:35 by sede-san #+# #+# */
/* Updated: 2026/02/13 21:28:35 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser.h"
#include "errors.h"
static t_redirection *redirection_new(t_list **tokens, int io_number);
static bool redirection_read_target(t_redirection *rd, t_list **tk);
static bool has_single_quote(const char *value);
/**
* @brief Creates a new redirection from a list of tokens.
*
* @param tokens The list of tokens to create the redirection from.
*
* @return A new redirection or `NULL` on error.
*/
static t_redirection *redirection_new(
t_list **tokens,
int io_number
)
{
t_redirection *redirection;
t_token *token;
redirection = (t_redirection *)malloc(sizeof(t_redirection));
if (redirection == NULL)
return (malloc_error(), NULL);
token = (t_token *)(*tokens)->content;
redirection->type = token->type;
redirection->heredoc_expand = true;
redirection->heredoc_ready = false;
if (io_number < 0)
redirection->io_number = STDOUT_FILENO;
else
redirection->io_number = io_number;
if (io_number < 0 && (token->type == TOKEN_REDIRECT_IN
|| token->type == TOKEN_HEREDOC))
redirection->io_number = STDIN_FILENO;
if (!redirection_read_target(redirection, tokens))
return (free(redirection), NULL);
return (redirection);
}
static bool redirection_read_target(
t_redirection *rd,
t_list **tk
)
{
t_token *token;
*tk = (*tk)->next;
if (*tk == NULL)
return (syntax_error_unexpected_token(NULL), false);
token = (t_token *)(*tk)->content;
if (token->type != TOKEN_WORD)
{
while (*tk != NULL)
*tk = (*tk)->next;
return (syntax_error_unexpected_token(token), false);
}
if (rd->type == TOKEN_HEREDOC && has_single_quote(token->value))
rd->heredoc_expand = false;
rd->target = ft_strdup(token->value);
if (rd->target == NULL)
return (malloc_error(), false);
*tk = (*tk)->next;
return (true);
}
static bool has_single_quote(
const char *value
)
{
size_t i;
if (value == NULL)
return (false);
i = 0;
while (value[i] != '\0')
{
if (value[i] == '\'')
return (true);
i++;
}
return (false);
}
void redirection_clear(
t_redirection *redirection
)
{
if (redirection != NULL)
{
free(redirection->target);
free(redirection);
}
}
void redirection_add_with_fd(
t_list **tokens,
t_token *token,
t_command **command,
int io_number
)
{
t_redirection *redirection;
t_list *redirection_tokens;
redirection = redirection_new(tokens, io_number);
if (redirection == NULL)
{
command_clear(*command);
*command = NULL;
return ;
}
redirection_tokens = ft_lstnew(redirection);
if (redirection_tokens == NULL)
{
free(redirection);
return (malloc_error());
}
(void)token;
ft_lstadd_back(&(*command)->redirections, redirection_tokens);
}

112
src/parser/parser_words.c Normal file
View File

@@ -0,0 +1,112 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_words.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/13 21:29:44 by sede-san #+# #+# */
/* Updated: 2026/02/14 03:34:38 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser.h"
static bool args_add_word(
t_list **args,
const char *value
)
{
t_list *node;
char *dup;
dup = ft_strdup(value);
if (dup == NULL)
return (false);
node = ft_lstnew(dup);
if (node == NULL)
return (free(dup), false);
ft_lstadd_back(args, node);
return (true);
}
static bool words_collect(
t_list *arg,
t_list **args,
int *new_argc,
t_list **end
)
{
t_token *token;
int io_number;
while (arg != NULL)
{
token = (t_token *)arg->content;
if (token->type != TOKEN_WORD || parser_token_is_fd_prefix(arg,
&io_number))
break ;
if (!args_add_word(args, token->value))
return (false);
(*new_argc)++;
arg = arg->next;
}
*end = arg;
return (true);
}
static bool command_append_words(
t_command *command,
t_list **args,
int new_argc
)
{
char **argv;
t_list *current;
int i;
argv = (char **)malloc(sizeof(char *) * (command->argc + new_argc + 1));
if (argv == NULL)
return (false);
i = 0;
while (i < command->argc)
{
argv[i] = command->argv[i];
i++;
}
current = *args;
while (current != NULL)
{
argv[i++] = (char *)current->content;
current = current->next;
}
argv[i] = NULL;
free(command->argv);
ft_lstclear_nodes(args);
command->argv = argv;
command->argc += new_argc;
return (true);
}
/**
* @brief Adds all consecutive word tokens to a command's argv and updates its
* argc accordingly.
*
* @param command The command to add the word tokens to.
* @param tokens The list of tokens to add to the command.
*/
void words_add(
t_list **tokens,
t_command **command
)
{
t_list *args;
int new_argc;
args = NULL;
new_argc = 0;
if (!words_collect(*tokens, &args, &new_argc, tokens)
|| !command_append_words(*command, &args, new_argc))
return (ft_lstclear(&args, free), command_clear(*command),
*command = NULL, (void)0);
}

View File

@@ -1,179 +0,0 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* environment.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/12/01 09:12:39 by sede-san #+# #+# */
/* 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
*
* This function iterates through the environment variables array (envp) and
* splits each variable string on the '=' delimiter to separate the variable
* name from its value. Each name-value pair is then stored in the minishell's
* environment hashmap for later retrieval.
*
* @param envp Array of environment variable strings in "NAME=value" format
* @param msh Pointer to the minishell structure containing the environment
* hashmap
*
* @note The function assumes envp strings are in the standard format
* "NAME=value"
*/
void set_envp(
char **envp,
t_minishell *msh
) {
char **splitted_env;
msh->variables.environment
= ft_hashmap_new(32, ft_hashmap_hashstr, ft_hashmap_strcmp);
if (msh == NULL)
{
ft_hashmap_clear(&msh->variables.environment, free);
return ;
}
while (*envp != NULL)
{
splitted_env = ft_split(*envp, '=');
if (splitted_env == NULL)
{
ft_hashmap_clear(&msh->variables.environment, free);
return ;
}
ft_hashmap_put(msh->variables.environment,
ft_strdup(splitted_env[0]), ft_strdup(splitted_env[1]));
ft_free_split(splitted_env);
envp++;
}
}
/**
* @brief Sets an environment variable in the minishell's environment hashmap.
*
* This function adds a new environment variable or updates an existing one
* in the minishell's environment hashmap. If the variable already exists,
* the old value is freed to prevent memory leaks. If the variable is new,
* a duplicate of the key name is created for storage.
*
* @param env_name The name of the environment variable to set
* @param env_value The value to assign to the environment variable
* @param msh Pointer to the minishell structure containing the
* environment hashmap
*/
void set_env(
const char *env_name,
char *env_value,
t_minishell *msh
) {
t_hashmap *environment;
const char *key;
char *old_value;
environment = msh->variables.environment;
key = env_name;
if (!ft_hashmap_contains_key(environment, key))
key = ft_strdup(env_name);
old_value = ft_hashmap_put(environment, key, ft_strdup(env_value));
if (old_value != NULL)
free(old_value);
}
/**
* @brief Converts the environment variables hashmap to an envp array format.
*
* This function extracts all environment variables from the minishell's
* environment hashmap and converts them into a NULL-terminated array of
* strings in the format "KEY=VALUE".
*
* @param msh Pointer to the minishell structure containing the environment
* variables hashmap.
*
* @return A dynamically allocated array of strings representing environment
* variables in "KEY=VALUE" format, terminated by NULL. Returns NULL
* if memory allocation fails. The caller is responsible for freeing
* the returned array and its individual string elements using
* the `free_envp()` function.
*
* @note The function allocates memory for both the array and individual
* strings using malloc and ft_strnjoin respectively.
* @note The returned array size is environment->size + 1 to accommodate
* the NULL terminator.
*/
char **get_envp(
t_minishell *msh
) {
char **envp;
t_list *env_list;
t_list *env;
t_map_entry *entry;
size_t i;
env_list = ft_hashmap_entries(msh->variables.environment);
envp = (char **)malloc((msh->variables.environment->size + 1) * sizeof(char *));
if (envp != NULL)
{
i = 0;
env = env_list;
while (env != NULL)
{
entry = env->content;
envp[i++] = ft_strnjoin(3, entry->key, "=", entry->value);
env = env->next;
}
envp[i] = NULL;
}
ft_lstclear_nodes(&env_list);
return (envp);
}
/**
* @brief Frees a dynamically allocated environment variables array
*
* This function deallocates memory for an array of strings that was previously
* allocated by `get_envp()`. It iterates through each string in the array,
* frees the memory for individual strings, and then frees the array itself.
*
* @param envp Pointer to the array of environment variable strings to be freed.
* Each string in the array should be dynamically allocated.
* The array must be NULL-terminated.
*/
void free_envp(
char **envp
) {
size_t i;
i = -1;
while (envp[++i] != NULL)
free(envp[i]);
free(envp);
}
/**
* @brief Retrieves the value of an environment variable from the shell's
* environment hashmap.
*
* This function searches for the specified environment variable name in the
* minishell's environment variable hashmap and returns its associated value.
*
* @param env_name The name of the environment variable to retrieve (e.g.,
* "PATH", "HOME")
* @param msh Pointer to the minishell structure containing the environment
* variables hashmap
*
* @return The value of the environment variable if found, NULL if not found
*/
char *get_env(
const char *env_name,
t_minishell *msh
) {
return (ft_hashmap_get(msh->variables.environment, env_name));
}

View File

@@ -0,0 +1,149 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* environment.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/12/01 09:12:39 by sede-san #+# #+# */
/* Updated: 2026/02/14 13:30:34 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "minishell.h"
#include "core.h"
#include "variables.h"
#include "errors.h"
static char *resolve_key(
const char *name,
t_hashmap *environment,
t_minishell *minishell,
bool *owns_key
);
static char *resolve_value(
char *value,
t_minishell *minishell
);
/**
* @brief Retrieves the value of an environment variable from the shell's
* environment hashmap.
*
* This function searches for the specified environment variable name in the
* minishell's environment variable hashmap and returns its associated value.
*
* @param name The name of the environment variable to retrieve.
* @param minishell Pointer to the minishell object.
*
* @return The value of the environment variable if found, NULL if not found
*/
char *get_env(
const char *name,
t_minishell *minishell
)
{
return (ft_hashmap_get(minishell->variables.environment, name));
}
/**
* @brief Sets an environment variable in the minishell's environment hashmap.
*
* This function adds a new environment variable or updates an existing one
* in the minishell's environment hashmap. If the variable already exists,
* the old value is freed to prevent memory leaks. If the variable is new,
* a duplicate of the key name is created for storage.
*
* @param name The name of the environment variable to set
* @param value The value to assign to the environment variable
* @param minishell Pointer to the minishell object.
*/
void set_env(
const char *name,
char *value,
t_minishell *minishell
)
{
t_hashmap *environment;
char *key;
char *val;
char *old_value;
bool owns_key;
environment = minishell->variables.environment;
key = resolve_key(name, environment, minishell, &owns_key);
if (key == NULL)
return ;
val = resolve_value(value, minishell);
if (val == NULL)
{
if (owns_key)
free(key);
return ;
}
old_value = ft_hashmap_put(environment, key, val);
if (old_value != NULL)
free(old_value);
}
/**
* @brief Removes an environment variable by name.
*
* @param name The name of the environment variable to remove.
* @param minishell Pointer to the minishell structure.
*
* @note If the environment variable exists, it will be removed from the hashmap
* and its associated value will be freed.
*/
void unset_env(
const char *name,
t_minishell *minishell
)
{
t_hashmap *environment;
char *val;
environment = minishell->variables.environment;
val = ft_hashmap_remove(environment, (void *)name);
if (val != NULL)
free(val);
}
static char *resolve_key(
const char *name,
t_hashmap *environment,
t_minishell *minishell,
bool *owns_key
)
{
char *key;
key = (char *)name;
*owns_key = false;
if (name == NULL)
return (NULL);
if (!ft_hashmap_contains_key(environment, name))
{
key = ft_strdup(name);
if (key == NULL)
return (minishell->exit = true, malloc_error(), NULL);
*owns_key = true;
}
return (key);
}
static char *resolve_value(
char *value,
t_minishell *minishell
)
{
char *val;
if (value == NULL)
val = ft_strdup("");
else
val = ft_strdup(value);
if (val == NULL)
return (minishell->exit = true, malloc_error(), NULL);
return (val);
}

View File

@@ -0,0 +1,121 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* environment_envp.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 16:10:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 16:10:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "minishell.h"
#include "core.h"
#include "variables.h"
#include "errors.h"
static void fill_envp(
char **envp,
t_list *env_list
)
{
t_list *env;
t_map_entry *entry;
size_t i;
i = 0;
env = env_list;
while (env != NULL)
{
entry = env->content;
envp[i++] = ft_strnjoin(3, entry->key, "=", entry->value);
env = env->next;
}
envp[i] = NULL;
}
char **get_envp(
t_minishell *minishell
)
{
char **envp;
t_list *env_list;
env_list = ft_hashmap_entries(minishell->variables.environment);
envp = (char **)malloc(
(minishell->variables.environment->size + 1) * sizeof(char *)
);
if (envp != NULL)
fill_envp(envp, env_list);
ft_lstclear_nodes(&env_list);
return (envp);
}
static bool import_env_entry(
char *entry_text,
t_minishell *minishell
)
{
char *equal;
char *key;
char *value;
size_t key_len;
equal = ft_strchr(entry_text, '=');
if (equal == NULL)
return (true);
key_len = (size_t)(equal - entry_text);
key = ft_substr(entry_text, 0, key_len);
value = ft_strdup(equal + 1);
if (key == NULL || value == NULL)
return (free(key), free(value), minishell->exit = true,
malloc_error(), false);
set_env(key, value, minishell);
free(key);
free(value);
return (!minishell->exit);
}
static void update_shlvl(
t_minishell *minishell
)
{
char *value;
char *new_shlvl;
int shlvl;
value = get_env("SHLVL", minishell);
shlvl = 0;
if (value != NULL)
shlvl = ft_atoi(value);
if (shlvl < 0)
shlvl = 0;
new_shlvl = ft_itoa(shlvl + 1);
if (new_shlvl == NULL)
return ((void)(minishell->exit = true), malloc_error());
set_env("SHLVL", new_shlvl, minishell);
free(new_shlvl);
}
void set_envp(
char **envp,
t_minishell *minishell
)
{
t_hashmap *environment;
if (minishell == NULL || envp == NULL)
return ;
environment = ft_hashmap_new(32, ft_hashmap_hashstr, ft_hashmap_strcmp);
if (environment == NULL)
return ;
minishell->variables.environment = environment;
while (*envp != NULL)
{
if (!import_env_entry(*envp, minishell))
return ;
envp++;
}
update_shlvl(minishell);
}

View File

@@ -0,0 +1,123 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* internal.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/13 21:29:43 by sede-san #+# #+# */
/* Updated: 2026/02/14 15:17:17 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "minishell.h"
#include "core.h"
#include "errors.h"
/**
* @brief Retrieves the value of an internal variable by name.
*
* @param name The name of the internal variable to retrieve.
* @param minishell Pointer to the minishell structure.
*
* @return The value of the internal variable if found, or NULL if not found.
*/
char *get_int(
const char *name,
t_minishell *minishell
)
{
return (ft_hashmap_get(minishell->variables.internal, name));
}
/**
* @brief Stores a variable as internal.
*
* @param name The name of the internal variable to set.
* @param value The value to associate with the internal variable.
* @param minishell Pointer to the minishell structure.
*
* @note If the internal variable already exists, its value will be updated and
* the old value will be freed. If it does not exist, a new internal
* variable will be created.
*/
void set_int(
const char *name,
char *value,
t_minishell *minishell
)
{
t_hashmap *internal;
char *key;
char *val;
char *old_val;
internal = minishell->variables.internal;
key = (char *)name;
if (key != NULL && !ft_hashmap_contains_key(internal, key))
{
key = ft_strdup(name);
if (key == NULL)
return (minishell->exit = true, malloc_error());
}
val = value;
if (val != NULL)
val = ft_strdup(value);
if (val == NULL)
{
if (key != name)
free(key);
return (minishell->exit = true, malloc_error());
}
old_val = ft_hashmap_put(internal, key, val);
if (old_val != NULL)
free(old_val);
}
/**
* @brief Removes an internal variable by name.
*
* @param name The name of the internal variable to remove.
* @param minishell Pointer to the minishell structure.
*
* @note If the internal variable exists, it will be removed from the hashmap
* and its associated value will be freed.
*/
void unset_int(
const char *name,
t_minishell *minishell
)
{
char *value;
value = ft_hashmap_remove(minishell->variables.internal, (void *)name);
if (value != NULL)
free(value);
}
/**
* @brief Initializes the internal variables hashmap with default values.
*
* @param minishell Pointer to the minishell structure to initialize.
*
* @warning This function must be called only once during the initialization of
* the minishell.
*/
void set_intp(
t_minishell *minishell
)
{
if (minishell == NULL)
return ;
minishell->variables.internal
= ft_hashmap_new(16, ft_hashmap_hashstr, ft_hashmap_strcmp);
if (minishell->variables.internal == NULL)
return ;
set_int("?", "0", minishell);
set_int("_", "minishell", minishell);
set_int("PS0", "", minishell);
set_int("PS1", DEFAULT_PS1, minishell);
set_int("PS2", DEFAULT_PS2, minishell);
set_int("MINISHELL", "minishell", minishell);
set_int("MINISHELL_VERSION", MINISHELL_VERSION, minishell);
}

50
src/variables/variables.c Normal file
View File

@@ -0,0 +1,50 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* variables.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/13 21:41:44 by sede-san #+# #+# */
/* Updated: 2026/02/14 00:52:12 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "variables.h"
/**
* @brief Retrieves the value of a variable by name.
*
* @param name The name of the variable to retrieve.
* @param minishell Pointer to the minishell structure.
*
* @return The value of the variable if found, or NULL if not found.
*/
char *get_var(
const char *name,
t_minishell *minishell
)
{
char *value;
value = get_int(name, minishell);
if (value == NULL)
value = get_env(name, minishell);
return (value);
}
void set_var(const char *name, char *value, t_minishell *minishell)
{
if (ft_hashmap_contains_key(minishell->variables.internal, name))
set_int(name, value, minishell);
set_env(name, value, minishell);
}
void unset_var(
const char *name,
t_minishell *minishell
)
{
unset_int(name, minishell);
unset_env(name, minishell);
}

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