20 Commits

Author SHA1 Message Date
63dd69b01c update: removed const modifier on token value
following steps on the parsing process can change its content
2026-02-10 09:24:18 +01:00
609a644fa9 update: token is now stored as value of the token instead of null
this change is made so that it is easier to print an error message when
invalid syntax
2026-02-10 09:20:46 +01:00
3d97d6506a fix: removed putendl on malloc 2026-02-10 08:54:11 +01:00
1f0f38e42f fix: removed putendl on free 2026-02-10 08:52:24 +01:00
97d8a6838f norminette: fixed line too long 2026-02-10 08:45:31 +01:00
74111e3da5 update: removed unused macros
not used anymore by lexer
2026-02-09 23:12:04 +01:00
6ad68a8752 update: added lexer 2026-02-09 23:11:33 +01:00
280fa51f94 save commit 2026-02-09 20:47:43 +01:00
e983f7fe64 update: builtins hashmap initialized and moved env hashmap initialization to set_envp() 2025-12-02 09:13:59 +01:00
b1cf1d8560 update: integrated hashmaps in builtins, and replaced command calls by builtins 2025-12-02 09:11:01 +01:00
dce51960b1 fix: fixed ignored result compilation errro 2025-12-01 16:31:02 +01:00
e9a07a6bfb fix: fixed compilation error 2025-12-01 14:12:15 +01:00
a9ba8492a2 fix: executor.c/handle_child was not using the real envp 2025-12-01 14:09:32 +01:00
83d006b40d fix: Makefile's mandatory rule was not cloning ft_args 2025-12-01 14:06:33 +01:00
027fd2ac5e Merge branch 'feature/builtins' into develop 2025-12-01 14:01:24 +01:00
30ce96284a fix: get_env call was not using minishell struct 2025-12-01 14:01:08 +01:00
956505c06a Merge branch 'feature/builtins' into develop 2025-12-01 14:00:13 +01:00
212c4e3fee update: updated getenv calls by get_env 2025-12-01 13:59:52 +01:00
50736d533d update: changed getenv calls by get_env 2025-12-01 13:52:28 +01:00
daad208a00 update: added hashmap functionality to environment variables 2025-12-01 13:47:17 +01:00
42 changed files with 3730 additions and 218 deletions

35
AGENTS.md Normal file
View File

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

@@ -6,7 +6,7 @@
# By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ # # By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ #
# +#+#+#+#+#+ +#+ # # +#+#+#+#+#+ +#+ #
# Created: 2025/07/30 20:22:21 by sede-san #+# #+# # # Created: 2025/07/30 20:22:21 by sede-san #+# #+# #
# Updated: 2025/11/01 19:30:37 by sede-san ### ########.fr # # Updated: 2026/02/05 21:06:52 by sede-san ### ########.fr #
# # # #
# **************************************************************************** # # **************************************************************************** #
@@ -21,11 +21,11 @@ CFLAGS = -Wall -Wextra -Werror
HEADERS = -I $(INCLUDE_PATH) $(LIBS_INCLUDE) HEADERS = -I $(INCLUDE_PATH) $(LIBS_INCLUDE)
ifeq ($(DEBUG), lldb) # debug with LLDB ifeq ($(DEBUG), lldb) # debug with LLDB
CFLAGS += -g3 CFLAGS = -g3
else ifeq ($(DEBUG), valgrind) # debug with valgrind else ifeq ($(DEBUG), valgrind) # debug with valgrind
CFLAGS += -g3 CFLAGS = -g3
else ifeq ($(DEBUG), address) # use AdressSanitize else ifeq ($(DEBUG), address) # use AdressSanitize
CFLAGS += -fsanitize=address -g3 CFLAGS = -fsanitize=address -g3
else # apply optimization flags if no debugging is being done else # apply optimization flags if no debugging is being done
CFLAGS += -O3 CFLAGS += -O3
endif endif
@@ -55,7 +55,7 @@ $(OBJS_PATH)/%.o: $(SRC_PATH)/%.c
all: mandatory all: mandatory
.PHONY: all .PHONY: all
mandatory: libft get_next_line ft_printf $(NAME) mandatory: libft get_next_line ft_printf ft_args $(NAME)
.PHONY: mandatory .PHONY: mandatory
$(NAME): $(OBJS) $(NAME): $(OBJS)

View File

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

292
docs/guia_obligatoria_es.md Normal file
View File

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

341
executor_new_previous.c Normal file
View File

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

256
executor_old.c Normal file
View File

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

View File

@@ -6,34 +6,43 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */ /* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */ /* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/29 22:09:51 by sede-san #+# #+# */ /* Created: 2025/10/29 22:09:51 by sede-san #+# #+# */
/* Updated: 2025/11/02 03:13:46 by sede-san ### ########.fr */ /* Updated: 2026/02/08 19:42:50 by sede-san ### ########.fr */
/* */ /* */
/* ************************************************************************** */ /* ************************************************************************** */
#ifndef BUILTINS_H #ifndef BUILTINS_H
# define BUILTINS_H # define BUILTINS_H
# include "minishell.h"
# include "ft_args.h" # include "ft_args.h"
# include "minishell.h"
# include "core.h"
typedef unsigned char (*t_builtin_func)(t_command cmd, t_minishell *minishell);
/******************************************************************************/ /******************************************************************************/
/* Functions */ /* Functions */
/******************************************************************************/ /******************************************************************************/
// cd.c /* builtins.c */
extern u_int8_t builtin_cd(t_command cmd, t_minishell *msh); extern u_int8_t set_builtins(t_minishell *minishell);
// echo.c extern u_int8_t is_builtin(const char *command_name, t_minishell *minishell);
extern u_int8_t builtin_echo(t_command cmd); /* cd.c */
// exit.c extern u_int8_t builtin_cd(t_command cmd, t_minishell *minishell);
extern u_int8_t builtin_exit(t_command cmd, t_minishell *msh); /* echo.c */
// pwd.c extern u_int8_t builtin_echo(t_command cmd, t_minishell *minishell);
extern u_int8_t builtin_pwd(t_command cmd, t_minishell *msh); /* exit.c */
extern u_int8_t builtin_exit(t_command cmd, t_minishell *minishell);
/* pwd.c */
extern u_int8_t builtin_pwd(t_command cmd, t_minishell *minishell);
#endif /* BUILTINS_H */ #endif /* BUILTINS_H */

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */ /* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */ /* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/22 19:10:13 by sede-san #+# #+# */ /* Created: 2025/10/22 19:10:13 by sede-san #+# #+# */
/* Updated: 2025/10/30 16:05:48 by sede-san ### ########.fr */ /* Updated: 2026/02/09 18:45:41 by sede-san ### ########.fr */
/* */ /* */
/* ************************************************************************** */ /* ************************************************************************** */
@@ -34,7 +34,7 @@ typedef struct s_command t_command;
*/ */
typedef struct s_variables typedef struct s_variables
{ {
char **environment; t_hashmap *environment;
// char **internal; // char **internal;
} t_variables; } t_variables;
@@ -49,8 +49,9 @@ typedef struct s_variables
typedef struct s_minishell typedef struct s_minishell
{ {
t_variables variables; t_variables variables;
u_int8_t exit_status; t_hashmap *builtins;
u_int8_t exit; uint8_t exit_status;
bool exit;
} t_minishell; } t_minishell;
/** /**
@@ -68,8 +69,19 @@ typedef struct s_command
int argc; int argc;
char **argv; char **argv;
char *path; char *path;
t_list *redirections;
} t_command; } t_command;
/**
* @brief Default shell prompt string
*/
# define DEFAULT_PS1 "minishell > "
/**
* @brief Default secondary prompt string for multiline input
*/
# define DEFAULT_PS2 "> "
/******************************************************************************/ /******************************************************************************/
/* Functions */ /* Functions */
/******************************************************************************/ /******************************************************************************/
@@ -78,8 +90,21 @@ typedef struct s_command
extern int minishell_init(t_minishell *minishell, char **envp); extern int minishell_init(t_minishell *minishell, char **envp);
extern u_int8_t minishell_run(t_minishell *minishell); extern uint8_t minishell_run(t_minishell *minishell);
extern void minishell_clear(t_minishell *minishell); extern void minishell_clear(t_minishell *minishell);
/* environment.c */
extern void set_envp(char **envp, t_minishell *msh);
extern void set_env(const char *env_name, char *env_value,
t_minishell *msh);
extern char **get_envp(t_minishell *msh);
extern void free_envp(char **envp);
extern char *get_env(const char *env_name, t_minishell *msh);
#endif /* CORE_H */ #endif /* CORE_H */

View File

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

View File

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

View File

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

27
minishell-codex/Makefile Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

34
src/builtins/builtins.c Normal file
View File

@@ -0,0 +1,34 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* builtins.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/12/01 17:04:57 by sede-san #+# #+# */
/* Updated: 2026/02/08 19:51:38 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#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)
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);
return (1);
}
u_int8_t is_builtin(
const char *command_name,
t_minishell *minishell
) {
return (ft_hashmap_contains_key(minishell->builtins, command_name));
}

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */ /* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */ /* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/31 00:05:22 by sede-san #+# #+# */ /* Created: 2025/10/31 00:05:22 by sede-san #+# #+# */
/* Updated: 2025/10/31 01:38:14 by sede-san ### ########.fr */ /* Updated: 2025/12/01 14:11:32 by sede-san ### ########.fr */
/* */ /* */
/* ************************************************************************** */ /* ************************************************************************** */
@@ -26,7 +26,7 @@ u_int8_t builtin_cd(
return (2); return (2);
} }
else if (cmd.argc == 1) else if (cmd.argc == 1)
path = getenv("HOME"); path = get_env("HOME", msh);
else else
path = cmd.argv[1]; path = cmd.argv[1];
if (chdir(path) == -1) if (chdir(path) == -1)
@@ -41,6 +41,8 @@ static u_int8_t handle_error(
){ ){
u_int8_t exit_code; u_int8_t exit_code;
(void)msh;
exit_code = 0;
if (access(path, F_OK) != -1) if (access(path, F_OK) != -1)
// No such file or directory // No such file or directory
exit_code = 1; exit_code = 1;

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */ /* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */ /* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/31 02:41:11 by sede-san #+# #+# */ /* Created: 2025/10/31 02:41:11 by sede-san #+# #+# */
/* Updated: 2025/11/02 03:12:00 by sede-san ### ########.fr */ /* Updated: 2025/12/01 18:05:51 by sede-san ### ########.fr */
/* */ /* */
/* ************************************************************************** */ /* ************************************************************************** */
@@ -14,11 +14,13 @@
#include "echo_def.h" #include "echo_def.h"
u_int8_t builtin_echo( u_int8_t builtin_echo(
t_command cmd t_command cmd,
t_minishell *msh
){ ){
const t_args args = read_args(cmd); const t_args args = read_args(cmd);
size_t i; size_t i;
(void)msh;
i = -1; i = -1;
while (args.strings.data.sp[++i]) while (args.strings.data.sp[++i])
{ {

View File

@@ -6,28 +6,28 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */ /* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */ /* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/30 01:20:48 by sede-san #+# #+# */ /* Created: 2025/10/30 01:20:48 by sede-san #+# #+# */
/* Updated: 2025/10/30 09:08:02 by sede-san ### ########.fr */ /* Updated: 2026/02/08 21:16:18 by sede-san ### ########.fr */
/* */ /* */
/* ************************************************************************** */ /* ************************************************************************** */
#include "builtins.h" #include "builtins.h"
#include <stdint.h>
u_int8_t builtin_exit( uint8_t builtin_exit(
t_command cmd, t_command cmd,
t_minishell *msh t_minishell *msh
){ )
{
uint8_t exit_status;
ft_eputendl("exit"); ft_eputendl("exit");
if (cmd.argc == 1) if (cmd.argc == 1)
{ exit_status = msh->exit_status;
// msh.exit = 1;
// return the last exit_status, if none 0 is returned
return (msh->exit_status);
}
else if (!ft_strisnum(cmd.argv[1])) else if (!ft_strisnum(cmd.argv[1]))
{ {
ft_eputstr("exit: "); ft_eprintf(
ft_eputendl(cmd.argv[1]); "minishell: exit: %s: numeric argument required\n",
ft_eputendl(": numeric argument required"); cmd.argv[1]);
return (2); return (2);
} }
else if (cmd.argc > 2) else if (cmd.argc > 2)
@@ -36,9 +36,11 @@ u_int8_t builtin_exit(
return (2); return (2);
} }
else else
{ exit_status = (uint8_t)ft_atol(cmd.argv[1]);
// msh.exit = 1;
// cast to u_int8_t causes to return a value between 0 and 255 printf("builtin_exit: exit_status=%d\n", exit_status); // Debug print
return ((u_int8_t)ft_atol(cmd.argv[1]));
} msh->exit = 1;
msh->exit_status = exit_status;
return (exit_status);
} }

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */ /* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */ /* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/29 22:08:55 by sede-san #+# #+# */ /* Created: 2025/10/29 22:08:55 by sede-san #+# #+# */
/* Updated: 2025/10/30 01:14:09 by sede-san ### ########.fr */ /* Updated: 2025/12/01 16:27:08 by sede-san ### ########.fr */
/* */ /* */
/* ************************************************************************** */ /* ************************************************************************** */
@@ -20,7 +20,7 @@ u_int8_t builtin_pwd(
(void)cmd; (void)cmd;
(void)msh; (void)msh;
getcwd(cwd, PATH_MAX); if (getcwd(cwd, PATH_MAX) != NULL)
printf("%s\n", cwd); ft_putendl(cwd);
return (EXIT_SUCCESS); return (EXIT_SUCCESS);
} }

View File

@@ -0,0 +1,12 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* command.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/09 18:40:03 by sede-san #+# #+# */
/* Updated: 2026/02/09 18:40:04 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */

View File

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

View File

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

View File

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

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

@@ -0,0 +1,172 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* lexer.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/09 18:56:41 by sede-san #+# #+# */
/* Updated: 2026/02/10 09:11:23 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "core.h"
#include "parser.h"
static t_token *tokenize(const char *line, size_t *start);
static t_token_type get_token_type(const char *str);
static t_token *token_new(t_token_type type, char *text);
static 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);
/**
* @brief Converts a command line string into a list of tokens.
*
* @return A list of tokens or NULL on error.
*/
t_list *lex(
const char *line)
{
t_list *tokens;
t_token *token;
size_t i;
tokens = NULL;
i = 0;
while (line[i] != '\0')
{
while (ft_isspace(line[i]))
i++;
if (line[i] == '\0')
break;
token = tokenize(line, &i);
ft_lstadd_back(&tokens, ft_lstnew(token));
if (token == NULL)
{
ft_lstclear(&tokens, (void (*)(void *))token_clear);
return (NULL);
}
}
return (tokens);
}
/**
* @return A new token or NULL on error.
*/
static t_token *tokenize(
const char *line,
size_t *start)
{
t_token *token;
t_token_type type;
if (line == NULL || line[*start] == '\0')
return (NULL);
type = get_token_type(line + *start);
if (type != TOKEN_WORD)
token = read_token(type, line, start);
else
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);
}
static 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')
{
char c = line[*i];
(void)c;
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 == '>');
}

View File

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

179
src/variables/environment.c Normal file
View File

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