Compare commits
36 Commits
83553a57d5
...
fix/variab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba40670ace | ||
|
|
dd6101edec | ||
| e2b734cf0c | |||
| 32b3bd72b5 | |||
| c95703b42b | |||
| f4cfae1107 | |||
|
|
6453abfda3 | ||
|
|
7862f3e131 | ||
|
|
637391470b | ||
|
|
217505e3b0 | ||
| 5c33afb20a | |||
| 3f17f7789c | |||
| 5df1520224 | |||
|
|
c166d0f77a | ||
| 3e31447073 | |||
| 3ead1ad547 | |||
| c99e0a17a3 | |||
| dc772d10c2 | |||
| e5d7fcf050 | |||
| d248819dfe | |||
| 8f92cf269c | |||
| 8353d3f64f | |||
|
|
4756edb727 | ||
| d06e49274b | |||
|
|
ff5edf543f | ||
|
|
518ec87e60 | ||
|
|
6bc2eab19b | ||
|
|
3c7ee5b161 | ||
|
|
d1aad3ed20 | ||
|
|
df6ed1c5cc | ||
|
|
e02613253a | ||
|
|
ae578867b2 | ||
|
|
328737c557 | ||
|
|
d39eca2c94 | ||
|
|
084fa4759c | ||
|
|
778e0c0481 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -69,3 +69,5 @@ dkms.conf
|
||||
|
||||
# debug information files
|
||||
*.dwo
|
||||
|
||||
minishell-codex/
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "tests/42_minishell_tester"]
|
||||
path = tests/42_minishell_tester
|
||||
url = https://github.com/zstenger93/42_minishell_tester.git
|
||||
37
AGENTS.md
37
AGENTS.md
@@ -2,6 +2,8 @@
|
||||
|
||||
## Project Structure & Module Organization
|
||||
- `src/` holds the shell implementation, with submodules for `builtins/`, `executor/`, `parser/`, and `variables/`.
|
||||
- `src/builtins/` currently includes: `cd`, `echo`, `env`, `exit`, `export`, `pwd`, `unset`.
|
||||
- `src/executor/` includes pipeline/process orchestration plus file redirections in `redirections.c`.
|
||||
- `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`).
|
||||
@@ -19,11 +21,46 @@
|
||||
- The codebase follows 42 **Norminette v4** rules. Run `norminette *.c *.h` (or on specific files) before submitting changes.
|
||||
- Keep file names lowercase with underscores (e.g., `src/builtins/echo/echo.c`).
|
||||
- Keep headers in `include/` and expose only what modules need.
|
||||
- Before adding or changing code, check `allowed.txt` to know which functions are permitted.
|
||||
- Any function not listed in `allowed.txt` is not allowed in this project.
|
||||
|
||||
## Parser & Lexer Functionality (Current `src/parser`)
|
||||
- Runtime entrypoint is `parse(line, minishell)` from `src/minishell.c` (`readline -> parse -> execute`).
|
||||
- `parse` calls `lex(line)` and then converts token lists to `t_command` nodes with `parse_tokens`.
|
||||
- `command_new` builds one command from tokens up to `TOKEN_PIPE`.
|
||||
- `words_add` stores consecutive `TOKEN_WORD` tokens in `command->argv` and increments `argc`.
|
||||
- `expand_envs` is currently a TODO (no `$VAR` expansion is applied in parser stage).
|
||||
- Redirection tokens are converted into `t_redirection` and stored in `t_command.redirections`; heredocs are stored in `t_command.heredocs`.
|
||||
- Path resolution is handled in executor (`executor_resolve_command_path`) before `execve`.
|
||||
- `src/parser/lexer.c` provides a separate lexer (`lex`) that tokenizes into `TOKEN_WORD`, `TOKEN_PIPE`, `TOKEN_REDIRECT_IN`, `TOKEN_REDIRECT_OUT`, `TOKEN_APPEND`, and `TOKEN_HEREDOC`.
|
||||
- The lexer tracks single/double quote context so metacharacters inside quotes remain part of words.
|
||||
- Meta runs are read as contiguous chunks in `read_token` (for example, repeated `|`/`<`/`>` are captured as one token value).
|
||||
- Current parser flow consumes lexer output directly.
|
||||
|
||||
## Executor Redirections (Current `src/executor`)
|
||||
- File redirections are applied in `src/executor/redirections.c` via `open` + `dup2` before command execution.
|
||||
- Supported file redirections: input `<`, output truncate `>`, output append `>>`.
|
||||
- Redirections are applied for both forked commands (child path) and single builtins executed in parent.
|
||||
- Parent-builtin redirections save/restore `STDIN_FILENO` and `STDOUT_FILENO` after builtin execution.
|
||||
|
||||
## Parser & Lexer Known Gaps
|
||||
- Heredoc tokens are parsed and stored, but runtime heredoc execution/input feeding is still pending in executor.
|
||||
- No explicit unmatched-quote syntax error handling is implemented in parser/lexer path.
|
||||
|
||||
## Testing Guidelines
|
||||
- There is no automated test runner. Use manual checks in `docs/tests.md` and basic shell behavior checks (pipes, redirects, builtins).
|
||||
- A local builtin edge-case script exists at `tests/builtins_edge_cases.sh` (expects a compiled `./minishell`).
|
||||
- When debugging memory issues, run under valgrind and use the suppression file in `valgrind/readline.supp`.
|
||||
|
||||
## Builtins Status
|
||||
- `cd`: handles `HOME` fallback, `cd -` via `OLDPWD`, updates `PWD`/`OLDPWD`, and returns failure on invalid usage (`too many arguments`, missing `HOME`/`OLDPWD`).
|
||||
- `echo`: supports repeated `-n` flags (`-n`, `-nnn`) and prints remaining args preserving spaces between tokens.
|
||||
- `env`: prints current environment as `KEY=VALUE`; returns failure when called with extra arguments.
|
||||
- `exit`: validates numeric argument (with overflow checks), returns `1` on `too many arguments` without exiting, and exits with `2` on non-numeric argument.
|
||||
- `export`: supports `NAME=VALUE` and `NAME` (stored as empty value), validates identifiers, and returns failure when any identifier is invalid.
|
||||
- `pwd`: prints working directory using dynamic `getcwd` and returns failure if `getcwd` fails.
|
||||
- `unset`: validates identifiers and removes matching variables from the environment map.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
- Commit messages in this repo use a simple `type: summary` format (examples: `update: ...`, `fix: ...`). Keep summaries short and specific.
|
||||
- For PRs, include:
|
||||
|
||||
6
Makefile
6
Makefile
@@ -3,10 +3,10 @@
|
||||
# ::: :::::::: #
|
||||
# Makefile :+: :+: :+: #
|
||||
# +:+ +:+ +:+ #
|
||||
# By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ #
|
||||
# By: marcnava <marcnava@student.42madrid.com +#+ +:+ +#+ #
|
||||
# +#+#+#+#+#+ +#+ #
|
||||
# Created: 2025/07/30 20:22:21 by sede-san #+# #+# #
|
||||
# Updated: 2026/02/05 21:06:52 by sede-san ### ########.fr #
|
||||
# Updated: 2026/02/09 22:44:34 by marcnava ### ########.fr #
|
||||
# #
|
||||
# **************************************************************************** #
|
||||
|
||||
@@ -17,7 +17,7 @@ NAME = minishell
|
||||
# ************************** Compilation variables *************************** #
|
||||
|
||||
CC = cc
|
||||
CFLAGS = -Wall -Wextra -Werror
|
||||
CFLAGS = -Wall -Wextra #-Werror
|
||||
HEADERS = -I $(INCLUDE_PATH) $(LIBS_INCLUDE)
|
||||
|
||||
ifeq ($(DEBUG), lldb) # debug with LLDB
|
||||
|
||||
177
allowed.txt
Normal file
177
allowed.txt
Normal file
@@ -0,0 +1,177 @@
|
||||
[minishell_allowed]
|
||||
readline
|
||||
rl_clear_history
|
||||
rl_on_new_line
|
||||
rl_replace_line
|
||||
rl_redisplay
|
||||
add_history
|
||||
printf
|
||||
malloc
|
||||
free
|
||||
write
|
||||
access
|
||||
open
|
||||
read
|
||||
close
|
||||
fork
|
||||
wait
|
||||
waitpid
|
||||
wait3
|
||||
wait4
|
||||
signal
|
||||
sigaction
|
||||
sigemptyset
|
||||
sigaddset
|
||||
kill
|
||||
exit
|
||||
getcwd
|
||||
chdir
|
||||
stat
|
||||
lstat
|
||||
fstat
|
||||
unlink
|
||||
execve
|
||||
dup
|
||||
dup2
|
||||
pipe
|
||||
opendir
|
||||
readdir
|
||||
closedir
|
||||
strerror
|
||||
perror
|
||||
isatty
|
||||
ttyname
|
||||
ttyslot
|
||||
ioctl
|
||||
getenv
|
||||
tcsetattr
|
||||
tcgetattr
|
||||
tgetent
|
||||
tgetflag
|
||||
tgetnum
|
||||
tgetstr
|
||||
tgoto
|
||||
tputs
|
||||
|
||||
[libft]
|
||||
ft_atoi
|
||||
ft_atoi_base
|
||||
ft_atol
|
||||
ft_bzero
|
||||
ft_calloc
|
||||
ft_cdlstadd_back
|
||||
ft_cdlstadd_front
|
||||
ft_cdlstclear
|
||||
ft_cdlstdelone
|
||||
ft_cdlstiter
|
||||
ft_cdlstlast
|
||||
ft_cdlstmap
|
||||
ft_cdlstnew
|
||||
ft_cdlstsize
|
||||
ft_clstadd_back
|
||||
ft_clstadd_front
|
||||
ft_clstclear
|
||||
ft_clstdelone
|
||||
ft_clstiter
|
||||
ft_clstlast
|
||||
ft_clstmap
|
||||
ft_clstnew
|
||||
ft_clstsize
|
||||
ft_dlstadd_back
|
||||
ft_dlstadd_front
|
||||
ft_dlstclear
|
||||
ft_dlstdelone
|
||||
ft_dlstiter
|
||||
ft_dlstlast
|
||||
ft_dlstmap
|
||||
ft_dlstnew
|
||||
ft_dlstsize
|
||||
ft_eputchar
|
||||
ft_eputendl
|
||||
ft_eputnbr
|
||||
ft_eputstr
|
||||
ft_free
|
||||
ft_free_split
|
||||
ft_hashmap_clear
|
||||
ft_hashmap_clear_keys
|
||||
ft_hashmap_contains_key
|
||||
ft_hashmap_entries
|
||||
ft_hashmap_get
|
||||
ft_hashmap_hashstr
|
||||
ft_hashmap_new
|
||||
ft_hashmap_put
|
||||
ft_hashmap_remove
|
||||
ft_hashmap_strcmp
|
||||
ft_hashstr
|
||||
ft_iabs
|
||||
ft_imin
|
||||
ft_isalnum
|
||||
ft_isalpha
|
||||
ft_isascii
|
||||
ft_iscntrl
|
||||
ft_isdigit
|
||||
ft_islower
|
||||
ft_isprint
|
||||
ft_isspace
|
||||
ft_isupper
|
||||
ft_itoa
|
||||
ft_itoa_base
|
||||
ft_lstadd_back
|
||||
ft_lstadd_front
|
||||
ft_lstclear
|
||||
ft_lstclear_nodes
|
||||
ft_lstdelone
|
||||
ft_lstiter
|
||||
ft_lstlast
|
||||
ft_lstmap
|
||||
ft_lstnew
|
||||
ft_lstsize
|
||||
ft_ltoa
|
||||
ft_memchr
|
||||
ft_memcmp
|
||||
ft_memcpy
|
||||
ft_memmove
|
||||
ft_memset
|
||||
ft_nsplit
|
||||
ft_pow
|
||||
ft_putchar
|
||||
ft_putchar_fd
|
||||
ft_putendl
|
||||
ft_putendl_fd
|
||||
ft_putnbr
|
||||
ft_putnbr_fd
|
||||
ft_putstr
|
||||
ft_putstr_fd
|
||||
ft_realloc
|
||||
ft_split
|
||||
ft_strchr
|
||||
ft_strcmp
|
||||
ft_strdup
|
||||
ft_strisnum
|
||||
ft_striteri
|
||||
ft_strjoin
|
||||
ft_strlcat
|
||||
ft_strlcpy
|
||||
ft_strlen
|
||||
ft_strmapi
|
||||
ft_strncmp
|
||||
ft_strncpy
|
||||
ft_strnjoin
|
||||
ft_strnstr
|
||||
ft_strrchr
|
||||
ft_strtrim
|
||||
ft_substr
|
||||
ft_swap
|
||||
ft_tolower
|
||||
ft_toupper
|
||||
ft_uitoa
|
||||
ft_uitoa_base
|
||||
ft_ultoa_base
|
||||
|
||||
[get_next_line]
|
||||
get_next_line
|
||||
|
||||
[ft_printf]
|
||||
ft_eprintf
|
||||
ft_fprintf
|
||||
ft_printf
|
||||
151
docs/builtins_fixes.md
Normal file
151
docs/builtins_fixes.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# Correcciones en Builtins
|
||||
|
||||
Este documento resume los fallos detectados en los builtins actuales y la
|
||||
solucion aplicada en codigo.
|
||||
|
||||
## 1) Infraestructura de builtins (`include/builtins.h`, `src/builtins/builtins.c`)
|
||||
|
||||
### Fallo
|
||||
- Uso inconsistente de tipos (`u_int8_t`/`unsigned char` frente a `uint8_t`).
|
||||
- `set_builtins` no comprobaba fallos de registro por builtin (duplicado de
|
||||
clave o insercion en hashmap), pudiendo dejar estado parcial.
|
||||
- `is_builtin` podia dereferenciar punteros nulos.
|
||||
|
||||
### Por que fallaba
|
||||
- `u_int8_t` no es el tipo estandar C99 y depende de plataforma/headers.
|
||||
- Si falla una insercion, la tabla quedaba inicializada parcialmente sin
|
||||
rollback.
|
||||
- En errores de inicializacion, consultar `is_builtin` podia romper.
|
||||
|
||||
### Solucion
|
||||
- Unificacion de firmas a `uint8_t`.
|
||||
- Nuevo helper `register_builtin()` con validacion tras `ft_hashmap_put`.
|
||||
- Si falla cualquier alta: limpieza de `minishell->builtins` y retorno de
|
||||
error.
|
||||
- Guardas nulas en `is_builtin`.
|
||||
|
||||
## 2) `cd` (`src/builtins/cd/cd.c`)
|
||||
|
||||
### Fallo
|
||||
- `cd` con demasiados argumentos devolvia `2` (bash devuelve `1`).
|
||||
- `cd` sin `HOME` acababa llamando a `chdir(NULL)`.
|
||||
- El manejo de error usaba comprobaciones invertidas (`access`) y codigos
|
||||
incorrectos.
|
||||
- No se actualizaban `PWD` y `OLDPWD` tras `chdir` exitoso.
|
||||
- `cd -` (usar `OLDPWD`) no estaba soportado.
|
||||
|
||||
### Por que fallaba
|
||||
- Codigos de salida incompatibles con el comportamiento esperado del shell.
|
||||
- `HOME` no definido no se controlaba antes del `chdir`.
|
||||
- La logica de `access` estaba al reves y mezclaba condiciones.
|
||||
- Variables de entorno del directorio quedaban desincronizadas.
|
||||
- Faltaba resolver el caso especial de `-` hacia `OLDPWD`.
|
||||
|
||||
### Solucion
|
||||
- Refactor en `resolve_cd_path()` para validar argumentos y `HOME`.
|
||||
- Retorno `EXIT_FAILURE` en `too many arguments` y `HOME not set`.
|
||||
- Error de `chdir` simplificado a `perror("minishell: cd")` + retorno `1`.
|
||||
- Actualizacion de `OLDPWD` y `PWD` mediante `getcwd(NULL, 0)` + `set_env()`.
|
||||
- Soporte de `cd -`: usa `OLDPWD`, valida `OLDPWD not set` e imprime el nuevo
|
||||
directorio tras el cambio.
|
||||
|
||||
## 3) `exit` (`src/builtins/exit/exit.c`)
|
||||
|
||||
### Fallo
|
||||
- Habia un `printf` de debug en ejecucion real.
|
||||
- `exit <no_numerico>` devolvia `2` pero no cerraba el shell.
|
||||
- `exit n m` devolvia `2`; en bash es `1` y no sale del shell.
|
||||
- Validacion numerica basada en `ft_strisnum` sin control de overflow.
|
||||
- Se mostraba `exit` incluso en contexto no interactivo.
|
||||
|
||||
### Por que fallaba
|
||||
- Debug residual contamina salida.
|
||||
- Semantica de `exit` incompleta respecto a bash.
|
||||
- Valores fuera de rango podian tratarse como validos por conversion directa.
|
||||
- Mensaje `exit` debe mostrarse solo en shell interactivo.
|
||||
|
||||
### Solucion
|
||||
- Eliminado debug print.
|
||||
- Nuevo flujo `resolve_exit_status()`:
|
||||
- Sin argumentos: usa `msh->exit_status`.
|
||||
- Argumento no numerico o fuera de `long`: mensaje
|
||||
`numeric argument required`, `msh->exit = true`, estado `2`.
|
||||
- Demasiados argumentos: mensaje de error y estado `1`, sin salir.
|
||||
- Parser numerico propio (`get_uint8_from_num` + `has_overflow`) con soporte
|
||||
de signo y control de overflow.
|
||||
- `ft_eputendl("exit")` solo si `isatty(STDIN_FILENO)`.
|
||||
|
||||
## 4) `pwd` (`src/builtins/pwd/pwd.c`)
|
||||
|
||||
### Fallo
|
||||
- Si `getcwd` fallaba, el builtin devolvia `EXIT_SUCCESS`.
|
||||
- Uso de buffer fijo (`PATH_MAX`) menos robusto para rutas largas.
|
||||
|
||||
### Por que fallaba
|
||||
- El shell reportaba exito aunque no pudiera obtener el directorio.
|
||||
- Un buffer fijo puede truncar o fallar en escenarios de rutas profundas.
|
||||
|
||||
### Solucion
|
||||
- Cambio a `getcwd(NULL, 0)` con memoria dinamica.
|
||||
- Si falla, `perror("minishell: pwd")` y retorno `EXIT_FAILURE`.
|
||||
- `free()` del buffer dinamico tras imprimir.
|
||||
|
||||
## 5) `echo` (`src/builtins/echo/echo.c`, `src/builtins/echo/echo_def.c`)
|
||||
|
||||
### Cambio aplicado
|
||||
- Ajuste de tipos de retorno auxiliares a `uint8_t` para mantener consistencia
|
||||
con `builtins.h`.
|
||||
|
||||
### Nota
|
||||
- No se detectaron fallos funcionales criticos adicionales en la logica actual
|
||||
de `echo` durante esta revision.
|
||||
|
||||
## 6) Builtins faltantes: `env`, `export`, `unset`
|
||||
|
||||
### Fallo
|
||||
- Los builtins `env`, `export` y `unset` no estaban implementados ni
|
||||
registrados en `set_builtins`.
|
||||
|
||||
### Por que fallaba
|
||||
- Comandos basicos de shell no existian en la tabla de builtins.
|
||||
- Cualquier prueba/flujo que dependiera de gestion de variables exportadas
|
||||
fallaba (listar, crear y eliminar variables de entorno).
|
||||
|
||||
### Solucion
|
||||
- Nuevos builtins implementados:
|
||||
- `src/builtins/env/env.c`
|
||||
- `src/builtins/export/export.c`
|
||||
- `src/builtins/unset/unset.c`
|
||||
- Registro en `src/builtins/builtins.c` (tabla ampliada de 4 a 7 entradas).
|
||||
- Nuevos prototipos en `include/builtins.h`.
|
||||
- Soporte de borrado real de entorno mediante `unset_env`:
|
||||
- Declaracion en `include/core.h`
|
||||
- Implementacion en `src/variables/environment_unset.c`
|
||||
|
||||
## 7) Comportamiento aplicado en los nuevos builtins
|
||||
|
||||
### `env`
|
||||
- Si recibe argumentos, devuelve error (`minishell: env: too many arguments`)
|
||||
y estado `1`.
|
||||
- Sin argumentos, lista `KEY=VALUE` de las variables de entorno actuales.
|
||||
|
||||
### `export`
|
||||
- `export NAME=VALUE`: crea/actualiza la variable.
|
||||
- `export NAME`: crea/actualiza con valor vacio (`NAME=`) para que aparezca
|
||||
en `env`.
|
||||
- Identificadores invalidos (`1A=2`, etc.) devuelven error
|
||||
`not a valid identifier` y estado `1`.
|
||||
- Sin argumentos, reutiliza el listado de `env`.
|
||||
|
||||
### `unset`
|
||||
- Elimina variables validas del entorno en memoria.
|
||||
- Identificadores invalidos devuelven error
|
||||
`not a valid identifier` y estado `1`.
|
||||
- Si el identificador es valido y no existe, no falla (comportamiento shell).
|
||||
|
||||
## Validacion realizada
|
||||
|
||||
- `norminette` ejecutado sobre los archivos modificados: `OK`.
|
||||
- Build completa no ejecutable en este entorno por falta de acceso de red al
|
||||
clonado de librerias (`github.com`), por lo que no se pudo validar runtime
|
||||
del binario en esta sesion.
|
||||
Binary file not shown.
56
docs/errores_parser_executor_expansion.md
Normal file
56
docs/errores_parser_executor_expansion.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Errores detectados: parser, expansión y ejecutor
|
||||
|
||||
## Críticos
|
||||
|
||||
1. Segmentation fault al mezclar palabras y redirecciones en un mismo comando.
|
||||
- Síntoma: `echo a > /tmp/x b` puede crashear.
|
||||
- Referencias: `src/parser/parser.c:365`, `src/parser/parser.c:371`, `src/parser/parser.c:187`.
|
||||
|
||||
2. Bucle infinito al recibir EOF en modo no interactivo.
|
||||
- Síntoma: con `printf 'echo ok\n' | ./minishell` entra en loop imprimiendo prompt.
|
||||
- Referencias: `src/minishell.c:40`, `src/minishell.c:42`.
|
||||
|
||||
3. Parseo de pipes inválidos permite ejecución parcial o comandos inválidos.
|
||||
- Síntoma: casos como `| echo x` o `echo hi |` no se bloquean correctamente.
|
||||
- Referencias: `src/parser/parser.c:83`, `src/parser/parser.c:101`, `src/parser/parser.c:221`.
|
||||
|
||||
## Altos
|
||||
|
||||
4. Expansión de variables incompleta e incorrecta en casos especiales.
|
||||
- Síntoma: `$FOO_BAR` y `$_X` fallan; `$?` y `$$` no se expanden correctamente.
|
||||
- Referencias: `src/parser/parser.c:122`, `src/parser/parser.c:156`.
|
||||
|
||||
5. No se eliminan comillas tras parseo/expansión.
|
||||
- Síntoma: `echo "$HOME"` imprime comillas literales.
|
||||
- Referencias: `src/parser/lexer.c:161`, `src/parser/parser.c:142`.
|
||||
|
||||
6. No hay expansión en targets de redirección.
|
||||
- Síntoma: `echo hi > $HOME_test` crea archivo literal `$HOME_test`.
|
||||
- Referencias: `src/parser/parser.c:187`, `src/parser/parser.c:263`.
|
||||
|
||||
7. Heredoc parseado pero no ejecutado en runtime.
|
||||
- Síntoma: se almacena en `heredocs` pero no se aplica en ejecución.
|
||||
- Referencias: `src/parser/parser.c:397`, `src/executor/redirections.c:68`, `src/executor/process_helpers.c:110`.
|
||||
|
||||
8. Lexer acepta operadores inválidos (`||`, `>>>`, `><`) sin validación formal.
|
||||
- Síntoma: tokenización por “runs” de metacaracteres produce entradas inválidas no rechazadas de forma robusta.
|
||||
- Referencias: `src/parser/lexer.c:140`, `src/parser/lexer.c:77`.
|
||||
|
||||
9. Errores de sintaxis no siempre fijan `exit_status` correcto (debería ser `2`).
|
||||
- Síntoma: algunos casos acaban con salida `0`.
|
||||
- Referencias: `src/executor/executor.c:20`, `src/parser/parser.c:101`.
|
||||
|
||||
## Medios
|
||||
|
||||
10. Resolución de rutas explícitas reporta mal algunos errores de permisos/ejecución.
|
||||
- Síntoma: ruta existente no ejecutable puede terminar como `command not found`.
|
||||
- Referencias: `src/executor/path_resolver.c:57`, `src/executor/path_resolver.c:59`, `src/executor/command_exec.c:81`.
|
||||
|
||||
11. Check de puntero nulo defectuoso en el loop principal.
|
||||
- Síntoma: `if (minishell == NULL)` desreferencia igualmente el puntero.
|
||||
- Referencia: `src/minishell.c:35`.
|
||||
|
||||
12. Falta manejo explícito de comillas no cerradas.
|
||||
- Síntoma: entrada con comillas abiertas no genera error sintáctico dedicado.
|
||||
- Referencia: `src/parser/lexer.c:159`.
|
||||
|
||||
Binary file not shown.
9656
docs/mstest_m_failed_tests_report.txt
Normal file
9656
docs/mstest_m_failed_tests_report.txt
Normal file
File diff suppressed because it is too large
Load Diff
9178
docs/mstest_m_failed_tests_report_filtered.txt
Normal file
9178
docs/mstest_m_failed_tests_report_filtered.txt
Normal file
File diff suppressed because it is too large
Load Diff
8656
docs/mstest_m_failed_tests_report_mandatory_only.txt
Normal file
8656
docs/mstest_m_failed_tests_report_mandatory_only.txt
Normal file
File diff suppressed because it is too large
Load Diff
26
docs/mstest_m_removed_extra_tests.txt
Normal file
26
docs/mstest_m_removed_extra_tests.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
Tests eliminados del reporte por ser extras (\ y/o ~):
|
||||
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/0_compare_parsing.sh:52 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/0_compare_parsing.sh:54 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/0_compare_parsing.sh:56 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/0_compare_parsing.sh:58 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/0_compare_parsing.sh:60 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/0_compare_parsing.sh:62 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/0_compare_parsing.sh:64 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:7 | motivo: ~
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:59 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:61 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:63 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:65 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:67 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:69 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:71 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:314 | motivo: ~
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:328 | motivo: ~
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:337 | motivo: ~
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:606 | motivo: \, ~
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_redirs.sh:208 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/2_path_check.sh:30 | motivo: ~
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/8_syntax_errors.sh:19 | motivo: ~
|
||||
|
||||
Total casos eliminados (extras): 22
|
||||
59
docs/mstest_m_removed_extra_tests_mandatory_only.txt
Normal file
59
docs/mstest_m_removed_extra_tests_mandatory_only.txt
Normal file
@@ -0,0 +1,59 @@
|
||||
Tests eliminados del reporte por estar fuera del obligatorio:
|
||||
- \ (barra invertida no especificada)
|
||||
- ~ (tilde expansion no obligatoria)
|
||||
- $"..." (extension bash, no requerida)
|
||||
- ; (separador no obligatorio segun subject)
|
||||
- && / || (bonus)
|
||||
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/0_compare_parsing.sh:26 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/0_compare_parsing.sh:28 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/0_compare_parsing.sh:32 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/0_compare_parsing.sh:34 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/0_compare_parsing.sh:52 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/0_compare_parsing.sh:54 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/0_compare_parsing.sh:56 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/0_compare_parsing.sh:58 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/0_compare_parsing.sh:60 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/0_compare_parsing.sh:62 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/0_compare_parsing.sh:64 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/10_parsing_hell.sh:301 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:7 | motivo: ~
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:31 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:33 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:37 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:39 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:59 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:61 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:63 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:65 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:67 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:69 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:71 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:314 | motivo: ~
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:328 | motivo: ~
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:337 | motivo: ~
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:536 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:538 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:552 | motivo: ; (no obligatorio)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_builtins.sh:606 | motivo: \, ~
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_redirs.sh:208 | motivo: \
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_variables.sh:40 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_variables.sh:42 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_variables.sh:44 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_variables.sh:48 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_variables.sh:60 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/1_variables.sh:62 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/2_correction.sh:129 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/2_correction.sh:139 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/2_correction.sh:141 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/2_correction.sh:143 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/2_correction.sh:147 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/2_correction.sh:159 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/2_correction.sh:161 | motivo: $"..." (bash extension)
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/2_path_check.sh:30 | motivo: ~
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/8_syntax_errors.sh:19 | motivo: ~
|
||||
- /home/marcos/programming/minishell_sergio/tests/42_minishell_tester/cmds/mand/8_syntax_errors.sh:68 | motivo: &&/|| (bonus)
|
||||
|
||||
Total casos evaluados en reporte original: 195
|
||||
Total casos eliminados (extras): 48
|
||||
Total casos restantes (mandatory): 147
|
||||
@@ -1,341 +0,0 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* executor_new.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2025/10/28 13:03:44 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/08 19:05:37 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "executor.h"
|
||||
#include "builtins.h"
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
|
||||
static void handle_execve_error();
|
||||
static void handle_parent_process(
|
||||
int *prev_read_fd,
|
||||
t_list *current_command,
|
||||
int pipefd[2]
|
||||
);
|
||||
static int handle_pipeline(
|
||||
t_list *current_command,
|
||||
int pipefd[2],
|
||||
int *exit_status
|
||||
);
|
||||
static inline uint8_t execute_builtin(
|
||||
t_command *command,
|
||||
t_minishell *minishell
|
||||
);
|
||||
static void execute_external_command(
|
||||
t_command *command,
|
||||
t_minishell *minishell
|
||||
);
|
||||
static pid_t handle_fork(
|
||||
t_list *current_command,
|
||||
t_minishell *minishell
|
||||
);
|
||||
static uint8_t execute_command(
|
||||
t_command *command,
|
||||
t_minishell *minishell
|
||||
);
|
||||
|
||||
static inline bool can_execute_command(t_command *command, t_minishell *minishell);
|
||||
static inline bool command_exists(t_command *command, t_minishell *minishell);
|
||||
static void cmdfree(t_command *command);
|
||||
static void cmdfree_argv(char **argv);
|
||||
|
||||
# define WRITE_PIPE 1
|
||||
# define READ_PIPE 0
|
||||
# define CHILD_PID 0
|
||||
|
||||
u_int8_t execute(
|
||||
t_list *command_list,
|
||||
t_minishell *minishell
|
||||
) {
|
||||
t_list *current_command;
|
||||
int prev_read_fd;
|
||||
int exit_status;
|
||||
int pipefd[2];
|
||||
pid_t pid;
|
||||
|
||||
|
||||
prev_read_fd = -1;
|
||||
exit_status = EXIT_SUCCESS;
|
||||
current_command = command_list;
|
||||
while (current_command != NULL)
|
||||
{
|
||||
if (handle_pipeline(current_command, pipefd, &exit_status) == EXIT_FAILURE)
|
||||
break ;
|
||||
pid = handle_fork(current_command, minishell);
|
||||
if (pid == -1)
|
||||
{
|
||||
perror("fork");
|
||||
exit_status = EXIT_FAILURE;
|
||||
break ;
|
||||
}
|
||||
if (pid == CHILD_PID)
|
||||
handle_child_process(prev_read_fd, current_command, pipefd, minishell);
|
||||
handle_parent_process(&prev_read_fd, current_command, pipefd);
|
||||
current_command = current_command->next;
|
||||
}
|
||||
ft_lstclear(&command_list, (void (*)(void *))cmdfree);
|
||||
while (wait(&exit_status) > 0)
|
||||
{
|
||||
if (WIFEXITED(exit_status))
|
||||
exit_status = WEXITSTATUS(exit_status);
|
||||
}
|
||||
return (exit_status);
|
||||
}
|
||||
|
||||
static int handle_pipeline(
|
||||
t_list *current_command,
|
||||
int pipefd[2],
|
||||
int *exit_status
|
||||
) {
|
||||
if (current_command->next) // create pipe if needed
|
||||
{
|
||||
if (pipe(pipefd) == -1)
|
||||
{
|
||||
perror("pipe");
|
||||
*exit_status = EXIT_FAILURE;
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
static void handle_parent_process(
|
||||
int *prev_read_fd,
|
||||
t_list *current_command,
|
||||
int pipefd[2]
|
||||
) {
|
||||
if (*prev_read_fd != -1)
|
||||
close(*prev_read_fd);
|
||||
if (current_command->next)
|
||||
{
|
||||
close(pipefd[WRITE_PIPE]); // parent does not write
|
||||
*prev_read_fd = pipefd[READ_PIPE]; // pass read end forward
|
||||
}
|
||||
else
|
||||
*prev_read_fd = -1;
|
||||
}
|
||||
|
||||
static pid_t handle_fork(
|
||||
t_list *current_command,
|
||||
t_minishell *minishell
|
||||
) {
|
||||
pid_t pid;
|
||||
const t_command *command = (t_command *)current_command->content;
|
||||
|
||||
pid = 0;
|
||||
if (current_command->next != NULL
|
||||
|| !is_builtin(command->path, minishell))
|
||||
pid = fork();
|
||||
return (pid);
|
||||
}
|
||||
|
||||
void handle_child_process(int prev_read_fd, t_list *current_command, int pipefd[2], t_minishell *minishell)
|
||||
{
|
||||
redirect_pipes(prev_read_fd, current_command, pipefd);
|
||||
execute_command((t_command *)current_command->content, minishell);
|
||||
}
|
||||
|
||||
void redirect_pipes(int prev_read_fd, t_list *current_command, int pipefd[2])
|
||||
{
|
||||
redirect_input_pipe(prev_read_fd);
|
||||
redirect_output_pipe(current_command, pipefd);
|
||||
}
|
||||
|
||||
void redirect_output_pipe(t_list * current_command, int pipefd[2])
|
||||
{
|
||||
if (current_command->next)
|
||||
{
|
||||
dup2(pipefd[WRITE_PIPE], STDOUT_FILENO);
|
||||
close(pipefd[READ_PIPE]);
|
||||
close(pipefd[WRITE_PIPE]);
|
||||
}
|
||||
}
|
||||
|
||||
void redirect_input_pipe(int prev_read_fd)
|
||||
{
|
||||
if (prev_read_fd != -1)
|
||||
{
|
||||
dup2(prev_read_fd, STDIN_FILENO);
|
||||
close(prev_read_fd);
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool can_execute_command(
|
||||
t_command *command,
|
||||
t_minishell *minishell
|
||||
) {
|
||||
if (!is_builtin(command->path, minishell)
|
||||
&& access(command->path, X_OK) != EXIT_SUCCESS)
|
||||
return (false);
|
||||
return (true);
|
||||
}
|
||||
|
||||
static inline bool command_exists(
|
||||
t_command *command,
|
||||
t_minishell *minishell
|
||||
) {
|
||||
if (!is_builtin(command->path, minishell)
|
||||
&& access(command->path, F_OK) != EXIT_SUCCESS)
|
||||
return (false);
|
||||
return (true);
|
||||
}
|
||||
|
||||
static uint8_t execute_command(
|
||||
t_command *command,
|
||||
t_minishell *minishell
|
||||
) {
|
||||
if (is_builtin(command->path, minishell))
|
||||
return (execute_builtin(command, minishell));
|
||||
else
|
||||
{
|
||||
execute_external_command(command, minishell);
|
||||
return (EXIT_FAILURE); //! should never reach here
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint8_t execute_builtin(
|
||||
t_command *command,
|
||||
t_minishell *minishell
|
||||
) {
|
||||
const t_builtin_func builtin
|
||||
= ft_hashmap_get(minishell->builtins, command->path);
|
||||
|
||||
return (builtin(*command, minishell));
|
||||
}
|
||||
|
||||
static void execute_external_command(
|
||||
t_command *command,
|
||||
t_minishell *minishell
|
||||
) {
|
||||
char **envp;
|
||||
|
||||
envp = get_envp(minishell);
|
||||
if (envp == NULL)
|
||||
{
|
||||
perror("get_envp");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
execve(command->path, command->argv, envp);
|
||||
handle_execve_error();
|
||||
}
|
||||
|
||||
static void handle_execve_error() {
|
||||
perror("execve");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// static inline bool is_piped(
|
||||
// t_command *command
|
||||
// ) {
|
||||
// return (command->piped_to != NULL || command->piped_from != NULL);
|
||||
// }
|
||||
|
||||
// static u_int8_t handle_child_process(
|
||||
// t_command *command,
|
||||
// t_minishell *minishell
|
||||
// ) {
|
||||
// if (!redirect_pipes(command))
|
||||
// exit(EXIT_FAILURE);
|
||||
// execute_command(command, minishell);
|
||||
// exit(EXIT_FAILURE);
|
||||
// }
|
||||
|
||||
// static int redirect_pipe(
|
||||
// int from,
|
||||
// int to
|
||||
// ) {
|
||||
// if (dup2(from, to) == -1)
|
||||
// return (-1);
|
||||
// close(from);
|
||||
// return (0);
|
||||
// }
|
||||
|
||||
// static int redirect_pipes(
|
||||
// t_command *command
|
||||
// ) {
|
||||
// if (command->piped_from &&
|
||||
// redirect_pipe(command->piped_from->outfile, STDIN_FILENO) == -1)
|
||||
// {
|
||||
// perror("dup2");
|
||||
// return (0);
|
||||
// }
|
||||
// if (command->piped_to &&
|
||||
// redirect_pipe(command->outfile, STDOUT_FILENO) == -1)
|
||||
// {
|
||||
// perror("dup2");
|
||||
// return (0);
|
||||
// }
|
||||
// return (1);
|
||||
// }
|
||||
|
||||
// static void show_error(
|
||||
// t_command *command,
|
||||
// t_minishell *minishell
|
||||
// ) {
|
||||
// if (!command_exists(command, minishell))
|
||||
// {
|
||||
// ft_eprintf("minishell: %s: command not found\n", command->argv[0]);
|
||||
// minishell->exit_status = 127;
|
||||
// }
|
||||
// else if (!can_execute_command(command, minishell))
|
||||
// {
|
||||
// ft_eprintf("minishell: %s: %s\n", command->path, strerror(errno));
|
||||
// minishell->exit_status = errno;
|
||||
// }
|
||||
// }
|
||||
|
||||
static void cmdfree(
|
||||
t_command *command
|
||||
) {
|
||||
if (command == NULL)
|
||||
return ;
|
||||
cmdfree_argv(command->argv);
|
||||
free(command->path);
|
||||
free(command);
|
||||
}
|
||||
|
||||
static void cmdfree_argv(
|
||||
char **argv
|
||||
) {
|
||||
size_t i;
|
||||
|
||||
if (argv == NULL)
|
||||
return ;
|
||||
i = 0;
|
||||
while (argv[i] != NULL)
|
||||
{
|
||||
free(argv[i]);
|
||||
i++;
|
||||
}
|
||||
free(argv);
|
||||
}
|
||||
|
||||
// static void debug_print_command_info(
|
||||
// t_command *command
|
||||
// ) {
|
||||
// size_t i;
|
||||
|
||||
// if (command == NULL)
|
||||
// {
|
||||
// printf("Command is NULL\n");
|
||||
// return ;
|
||||
// }
|
||||
// printf("Command info:\n");
|
||||
// printf(" Path: %s\n", command->path);
|
||||
// printf(" Argc: %d\n", command->argc);
|
||||
// printf(" Argv:\n");
|
||||
// for (i = 0; i < (size_t)command->argc; i++)
|
||||
// printf(" arg[%zu]: %s\n", i, command->argv[i]);
|
||||
// printf(" Infile FD: %d\n", command->infile);
|
||||
// printf(" Outfile FD: %d\n", command->outfile);
|
||||
// printf("--------------------------\n");
|
||||
// }
|
||||
256
executor_old.c
256
executor_old.c
@@ -1,256 +0,0 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* executor.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2025/10/28 13:03:44 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/08 03:42:11 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "executor.h"
|
||||
#include "builtins.h"
|
||||
#include <errno.h>
|
||||
|
||||
static inline bool can_execute_command(t_command *command, t_minishell *minishell);
|
||||
static inline bool command_exists(t_command *command, t_minishell *minishell);
|
||||
static u_int8_t execute_command(t_command *command, t_minishell *minishell);
|
||||
static u_int8_t execute_command(t_command *command, t_minishell *minishell);
|
||||
// static inline bool is_piped(t_command *command);
|
||||
static void cmdfree(t_command *command);
|
||||
static void cmdfree_argv(char **argv);
|
||||
|
||||
# define WRITE_PIPE 1
|
||||
# define READ_PIPE 0
|
||||
# define CHILD_PID 0
|
||||
|
||||
u_int8_t execute(
|
||||
t_list *command_list,
|
||||
t_minishell *minishell
|
||||
) {
|
||||
t_list *current_command;
|
||||
int prev_read_fd;
|
||||
pid_t pid;
|
||||
u_int8_t exit_status;
|
||||
int child_exit_status;
|
||||
|
||||
prev_read_fd = -1;
|
||||
exit_status = EXIT_SUCCESS;
|
||||
current_command = command_list;
|
||||
while (current_command != NULL)
|
||||
{
|
||||
t_command *command = (t_command *)current_command->content;
|
||||
int pipefd[2];
|
||||
|
||||
/* Create pipe ONLY if there is a next command */
|
||||
if (current_command->next)
|
||||
{
|
||||
if (pipe(pipefd) == -1)
|
||||
{
|
||||
perror("pipe");
|
||||
exit_status = EXIT_FAILURE;
|
||||
break ;
|
||||
}
|
||||
}
|
||||
|
||||
// create fork
|
||||
pid = 0;
|
||||
if (current_command->next != NULL
|
||||
|| !is_builtin(command->path, minishell))
|
||||
pid = fork();
|
||||
// handle fork error
|
||||
if (pid == -1)
|
||||
{
|
||||
perror("fork");
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// handle child process
|
||||
if (pid == CHILD_PID)
|
||||
{
|
||||
/* If we have input from previous pipe */
|
||||
if (prev_read_fd != -1)
|
||||
{
|
||||
dup2(prev_read_fd, STDIN_FILENO);
|
||||
close(prev_read_fd);
|
||||
}
|
||||
/* If we pipe output to next command */
|
||||
if (current_command->next)
|
||||
{
|
||||
dup2(pipefd[WRITE_PIPE], STDOUT_FILENO);
|
||||
close(pipefd[READ_PIPE]);
|
||||
close(pipefd[WRITE_PIPE]);
|
||||
}
|
||||
execute_command(command, minishell); // child process exits here
|
||||
}
|
||||
|
||||
// handle parent process
|
||||
waitpid(pid, &child_exit_status, 0); // wait for child to finish
|
||||
if (prev_read_fd != -1)
|
||||
close(prev_read_fd);
|
||||
|
||||
if (current_command->next)
|
||||
{
|
||||
close(pipefd[WRITE_PIPE]); /* parent does not write */
|
||||
prev_read_fd = pipefd[READ_PIPE]; /* pass read end forward */
|
||||
}
|
||||
else
|
||||
prev_read_fd = -1;
|
||||
|
||||
// continue executing
|
||||
current_command = current_command->next;
|
||||
}
|
||||
ft_lstclear(&command_list, (void (*)(void *))cmdfree);
|
||||
exit_status = child_exit_status;
|
||||
return (exit_status);
|
||||
}
|
||||
|
||||
static inline bool can_execute_command(
|
||||
t_command *command,
|
||||
t_minishell *minishell
|
||||
) {
|
||||
if (!is_builtin(command->path, minishell)
|
||||
&& access(command->path, X_OK) != EXIT_SUCCESS)
|
||||
return (false);
|
||||
return (true);
|
||||
}
|
||||
|
||||
static inline bool command_exists(
|
||||
t_command *command,
|
||||
t_minishell *minishell
|
||||
) {
|
||||
if (!is_builtin(command->path, minishell)
|
||||
&& access(command->path, F_OK) != EXIT_SUCCESS)
|
||||
return (false);
|
||||
return (true);
|
||||
}
|
||||
|
||||
static u_int8_t execute_command(
|
||||
t_command *command,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
char **envp;
|
||||
t_builtin_func builtin;
|
||||
|
||||
if (is_builtin(command->path, minishell))
|
||||
{
|
||||
builtin = ft_hashmap_get(minishell->builtins, command->path);
|
||||
return (builtin(*command, minishell));
|
||||
}
|
||||
envp = get_envp(minishell);
|
||||
execve(command->path, command->argv, envp);
|
||||
// handle error if execve fails
|
||||
perror("execve");
|
||||
free_envp(envp);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// static inline bool is_piped(
|
||||
// t_command *command
|
||||
// ) {
|
||||
// return (command->piped_to != NULL || command->piped_from != NULL);
|
||||
// }
|
||||
|
||||
// static u_int8_t handle_child_process(
|
||||
// t_command *command,
|
||||
// t_minishell *minishell
|
||||
// ) {
|
||||
// if (!redirect_pipes(command))
|
||||
// exit(EXIT_FAILURE);
|
||||
// execute_command(command, minishell);
|
||||
// exit(EXIT_FAILURE);
|
||||
// }
|
||||
|
||||
// static int redirect_pipe(
|
||||
// int from,
|
||||
// int to
|
||||
// ) {
|
||||
// if (dup2(from, to) == -1)
|
||||
// return (-1);
|
||||
// close(from);
|
||||
// return (0);
|
||||
// }
|
||||
|
||||
// static int redirect_pipes(
|
||||
// t_command *command
|
||||
// ) {
|
||||
// if (command->piped_from &&
|
||||
// redirect_pipe(command->piped_from->outfile, STDIN_FILENO) == -1)
|
||||
// {
|
||||
// perror("dup2");
|
||||
// return (0);
|
||||
// }
|
||||
// if (command->piped_to &&
|
||||
// redirect_pipe(command->outfile, STDOUT_FILENO) == -1)
|
||||
// {
|
||||
// perror("dup2");
|
||||
// return (0);
|
||||
// }
|
||||
// return (1);
|
||||
// }
|
||||
|
||||
// static void show_error(
|
||||
// t_command *command,
|
||||
// t_minishell *minishell
|
||||
// ) {
|
||||
// if (!command_exists(command, minishell))
|
||||
// {
|
||||
// ft_eprintf("minishell: %s: command not found\n", command->argv[0]);
|
||||
// minishell->exit_status = 127;
|
||||
// }
|
||||
// else if (!can_execute_command(command, minishell))
|
||||
// {
|
||||
// ft_eprintf("minishell: %s: %s\n", command->path, strerror(errno));
|
||||
// minishell->exit_status = errno;
|
||||
// }
|
||||
// }
|
||||
|
||||
static void cmdfree(
|
||||
t_command *command
|
||||
) {
|
||||
if (command == NULL)
|
||||
return ;
|
||||
cmdfree_argv(command->argv);
|
||||
free(command->path);
|
||||
free(command);
|
||||
}
|
||||
|
||||
static void cmdfree_argv(
|
||||
char **argv
|
||||
) {
|
||||
size_t i;
|
||||
|
||||
if (argv == NULL)
|
||||
return ;
|
||||
i = 0;
|
||||
while (argv[i] != NULL)
|
||||
{
|
||||
free(argv[i]);
|
||||
i++;
|
||||
}
|
||||
free(argv);
|
||||
}
|
||||
|
||||
// static void debug_print_command_info(
|
||||
// t_command *command
|
||||
// ) {
|
||||
// size_t i;
|
||||
|
||||
// if (command == NULL)
|
||||
// {
|
||||
// printf("Command is NULL\n");
|
||||
// return ;
|
||||
// }
|
||||
// printf("Command info:\n");
|
||||
// printf(" Path: %s\n", command->path);
|
||||
// printf(" Argc: %d\n", command->argc);
|
||||
// printf(" Argv:\n");
|
||||
// for (i = 0; i < (size_t)command->argc; i++)
|
||||
// printf(" arg[%zu]: %s\n", i, command->argv[i]);
|
||||
// printf(" Infile FD: %d\n", command->infile);
|
||||
// printf(" Outfile FD: %d\n", command->outfile);
|
||||
// printf("--------------------------\n");
|
||||
// }
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2025/10/29 22:09:51 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/08 19:42:50 by sede-san ### ########.fr */
|
||||
/* Updated: 2026/02/14 01:15:34 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
# include "ft_args.h"
|
||||
# include "minishell.h"
|
||||
# include "core.h"
|
||||
# include "variables.h"
|
||||
|
||||
typedef unsigned char (*t_builtin_func)(t_command cmd, t_minishell *minishell);
|
||||
typedef uint8_t (*t_builtin_func)(t_command cmd, t_minishell *minishell);
|
||||
|
||||
/******************************************************************************/
|
||||
/* Functions */
|
||||
@@ -25,24 +26,36 @@ typedef unsigned char (*t_builtin_func)(t_command cmd, t_minishell *minishell)
|
||||
|
||||
/* builtins.c */
|
||||
|
||||
extern u_int8_t set_builtins(t_minishell *minishell);
|
||||
extern uint8_t set_builtins(t_minishell *minishell);
|
||||
|
||||
extern u_int8_t is_builtin(const char *command_name, t_minishell *minishell);
|
||||
extern uint8_t is_builtin(const char *command_name, t_minishell *minishell);
|
||||
|
||||
/* cd.c */
|
||||
|
||||
extern u_int8_t builtin_cd(t_command cmd, t_minishell *minishell);
|
||||
extern uint8_t builtin_cd(t_command cmd, t_minishell *minishell);
|
||||
|
||||
/* echo.c */
|
||||
|
||||
extern u_int8_t builtin_echo(t_command cmd, t_minishell *minishell);
|
||||
extern uint8_t builtin_echo(t_command cmd, t_minishell *minishell);
|
||||
|
||||
/* exit.c */
|
||||
|
||||
extern u_int8_t builtin_exit(t_command cmd, t_minishell *minishell);
|
||||
extern uint8_t builtin_exit(t_command cmd, t_minishell *minishell);
|
||||
|
||||
/* pwd.c */
|
||||
|
||||
extern u_int8_t builtin_pwd(t_command cmd, t_minishell *minishell);
|
||||
extern uint8_t builtin_pwd(t_command cmd, t_minishell *minishell);
|
||||
|
||||
/* env.c */
|
||||
|
||||
extern uint8_t builtin_env(t_command cmd, t_minishell *minishell);
|
||||
|
||||
/* export.c */
|
||||
|
||||
extern uint8_t builtin_export(t_command cmd, t_minishell *minishell);
|
||||
|
||||
/* unset.c */
|
||||
|
||||
extern uint8_t builtin_unset(t_command cmd, t_minishell *minishell);
|
||||
|
||||
#endif /* BUILTINS_H */
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2025/10/22 19:10:13 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/12 20:19:23 by sede-san ### ########.fr */
|
||||
/* Updated: 2026/02/14 01:25:43 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@@ -34,8 +34,8 @@ typedef enum e_token_type
|
||||
typedef struct s_token
|
||||
{
|
||||
t_token_type type;
|
||||
char *value;
|
||||
} t_token;
|
||||
char *value;
|
||||
} t_token;
|
||||
|
||||
typedef enum e_redirection_type
|
||||
{
|
||||
@@ -49,7 +49,7 @@ typedef struct s_redirection
|
||||
{
|
||||
t_token_type type;
|
||||
char *target;
|
||||
} t_redirection;
|
||||
} t_redirection;
|
||||
|
||||
/**
|
||||
* @brief Structure that holds both environment and internal variables
|
||||
@@ -63,7 +63,7 @@ typedef struct s_redirection
|
||||
typedef struct s_variables
|
||||
{
|
||||
t_hashmap *environment;
|
||||
// char **internal;
|
||||
t_hashmap *internal;
|
||||
} t_variables;
|
||||
|
||||
/**
|
||||
@@ -82,12 +82,6 @@ typedef struct s_minishell
|
||||
bool exit;
|
||||
} t_minishell;
|
||||
|
||||
typedef struct s_redirection
|
||||
{
|
||||
t_token_type type;
|
||||
char *target;
|
||||
} t_redirection;
|
||||
|
||||
/**
|
||||
* @brief Structure representing a single command in the shell
|
||||
*
|
||||
@@ -129,6 +123,14 @@ extern void minishell_run(t_minishell *minishell);
|
||||
|
||||
extern void minishell_clear(t_minishell *minishell);
|
||||
|
||||
extern void minishell_set_interactive_signals(void);
|
||||
|
||||
extern void minishell_set_execution_signals(void);
|
||||
|
||||
extern void minishell_set_child_signals(void);
|
||||
|
||||
extern bool minishell_consume_sigint(void);
|
||||
|
||||
/* environment.c */
|
||||
|
||||
extern void set_envp(char **envp, t_minishell *msh);
|
||||
@@ -140,6 +142,8 @@ extern char **get_envp(t_minishell *msh);
|
||||
|
||||
extern void free_envp(char **envp);
|
||||
|
||||
extern char *get_env(const char *env_name, t_minishell *msh);
|
||||
void handle_sigint_status(t_minishell *minishell);
|
||||
bool handle_eof(char *line, t_minishell *minishell);
|
||||
|
||||
|
||||
#endif /* CORE_H */
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/10 22:28:01 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/10 23:24:16 by sede-san ### ########.fr */
|
||||
/* Updated: 2026/02/14 01:11:38 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
# include "core.h"
|
||||
# include "parser.h"
|
||||
|
||||
extern void syntax_error_unexpected_token(t_token *token);
|
||||
extern void syntax_error_unexpected_token(t_token *token);
|
||||
extern void malloc_error(void);
|
||||
extern void command_not_found_error(const char *command);
|
||||
|
||||
#endif /* ERRORS_H */
|
||||
@@ -19,23 +19,42 @@
|
||||
|
||||
# define READ_PIPE 0
|
||||
# define WRITE_PIPE 1
|
||||
|
||||
typedef struct s_pipeline
|
||||
{
|
||||
int prev_read_fd;
|
||||
int pipefd[2];
|
||||
} t_pipeline;
|
||||
|
||||
|
||||
/******************************************************************************/
|
||||
/* Functions */
|
||||
/******************************************************************************/
|
||||
|
||||
// executor.c
|
||||
|
||||
# define PIPE_ERROR -1
|
||||
# define FORK_ERROR -1
|
||||
|
||||
extern uint8_t execute(t_list *command, t_minishell *minishell);
|
||||
typedef struct s_pipeline
|
||||
{
|
||||
int prev_read_fd;
|
||||
int pipefd[2];
|
||||
} t_pipeline;
|
||||
|
||||
typedef struct s_exec_state
|
||||
{
|
||||
uint8_t exit_status;
|
||||
t_pipeline pipeline;
|
||||
t_list *current_command;
|
||||
pid_t last_child_pid;
|
||||
} t_exec_state;
|
||||
|
||||
extern uint8_t execute(t_list *command, t_minishell *minishell);
|
||||
extern uint8_t executor_execute_command(t_command *cmd, t_minishell *msh);
|
||||
extern char *executor_resolve_command_path(const t_command *cmd,
|
||||
t_minishell *msh);
|
||||
extern bool executor_is_builtin_command(const t_command *cmd,
|
||||
t_minishell *msh);
|
||||
extern int executor_create_pipe_if_needed(t_list *node, t_pipeline *pl);
|
||||
extern bool executor_is_fork_required(t_list *node, const t_pipeline *pl,
|
||||
t_minishell *msh);
|
||||
extern void executor_setup_child_input(t_pipeline *pipeline);
|
||||
extern void executor_setup_child_output(t_list *node, t_pipeline *pl);
|
||||
extern void executor_child_process(t_list *node, t_pipeline *pl,
|
||||
t_minishell *msh);
|
||||
extern void executor_parent_cleanup(t_list *node, t_pipeline *pl);
|
||||
extern uint8_t executor_wait_for_children(pid_t last_child_pid);
|
||||
extern void executor_cmdfree(t_command *command);
|
||||
extern bool executor_apply_redirections(const t_command *command,
|
||||
int *saved_stdin, int *saved_stdout);
|
||||
extern void executor_restore_redirections(int saved_stdin,
|
||||
int saved_stdout);
|
||||
|
||||
#endif /* EXECUTOR_H */
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2025/10/20 16:35:10 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/10 22:12:58 by sede-san ### ########.fr */
|
||||
/* Updated: 2026/02/14 01:13:42 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
# include "libft.h"
|
||||
# include "ft_printf.h"
|
||||
# include "get_next_line.h"
|
||||
# include "chardefs.h"
|
||||
# include <stdbool.h>
|
||||
# include <stdint.h>
|
||||
|
||||
@@ -23,19 +23,29 @@
|
||||
|
||||
// parser.c
|
||||
|
||||
extern t_list *parse(char *line, t_minishell *minishell);
|
||||
extern t_list *parse(char *line, t_minishell *minishell);
|
||||
|
||||
// lexer.c
|
||||
|
||||
extern t_list *lex(const char *line);
|
||||
t_token_type get_token_type(const char *str);
|
||||
t_token *token_new(t_token_type type, char *text);
|
||||
t_token *read_token(t_token_type type, const char *line,
|
||||
size_t *i);
|
||||
t_token *read_word(const char *line, size_t *i);
|
||||
|
||||
extern void token_clear(t_token *token);
|
||||
extern t_list *lex(const char *line);
|
||||
|
||||
extern void token_clear(t_token *token);
|
||||
extern t_command *command_new(t_list **tokens);
|
||||
extern void command_clear(t_command *command);
|
||||
extern void command_add_tokens(t_command **command, t_list **tokens);
|
||||
extern bool is_pipe(t_token *token);
|
||||
extern bool is_redirection(t_token *token);
|
||||
void redirection_add(t_list **tokens, t_token *token, t_command **command);
|
||||
void words_add(t_list **tokens, t_command **command);
|
||||
extern bool is_redirection(t_token *token);
|
||||
void redirection_add(t_list **tokens, t_token *token,
|
||||
t_command **command);
|
||||
void words_add(t_list **tokens, t_command **command);
|
||||
|
||||
void expand(t_list **commands, t_minishell *minishell);
|
||||
void redirection_clear(t_redirection *redirection);
|
||||
|
||||
#endif /* PARSER_H */
|
||||
|
||||
43
include/variables.h
Normal file
43
include/variables.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* variables.h :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/13 21:45:35 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/14 01:24:49 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#ifndef VARIABLES_H
|
||||
# define VARIABLES_H
|
||||
|
||||
# include "minishell.h"
|
||||
# include "core.h"
|
||||
|
||||
// variables.c
|
||||
|
||||
extern char *get_var(const char *name, t_minishell *minishell);
|
||||
extern void set_var(const char *name, char *value, t_minishell *minishell);
|
||||
extern void unset_var(const char *name, t_minishell *minishell);
|
||||
|
||||
// environment.c
|
||||
|
||||
extern char *get_env(const char *name, t_minishell *minishell);
|
||||
extern void set_env(const char *name, char *value, t_minishell *minishell);
|
||||
extern void unset_env(const char *name, t_minishell *minishell);
|
||||
|
||||
extern void set_envp(char **envp, t_minishell *minishell);
|
||||
extern char **get_envp(t_minishell *minishell);
|
||||
extern void free_envp(char **envp);
|
||||
|
||||
// internal.c
|
||||
|
||||
extern char *get_int(const char *name, t_minishell *minishell);
|
||||
extern void set_int(const char *name, char *value, t_minishell *minishell);
|
||||
extern void unset_int(const char *name, t_minishell *minishell);
|
||||
|
||||
extern void set_intp(t_minishell *minishell);
|
||||
|
||||
#endif /* VARIABLES_H */
|
||||
@@ -1,27 +0,0 @@
|
||||
NAME := minishell
|
||||
CC := cc
|
||||
CFLAGS := -Wall -Wextra -Werror -g
|
||||
INCLUDES := -Iinclude
|
||||
READLINE_LIBS := -lreadline -lncurses
|
||||
|
||||
SRCS := $(shell find src -name '*.c')
|
||||
OBJS := $(SRCS:src/%.c=build/%.o)
|
||||
|
||||
all: $(NAME)
|
||||
|
||||
$(NAME): $(OBJS)
|
||||
$(CC) $(CFLAGS) $(OBJS) $(READLINE_LIBS) -o $(NAME)
|
||||
|
||||
build/%.o: src/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
|
||||
fclean: clean
|
||||
rm -f $(NAME)
|
||||
|
||||
re: fclean all
|
||||
|
||||
.PHONY: all clean fclean re
|
||||
@@ -1,72 +0,0 @@
|
||||
# Minishell - Guion de defensa (version codex)
|
||||
|
||||
Este guion esta alineado con la estructura real en `minishell-codex/`.
|
||||
|
||||
## 1. Explicacion corta del proyecto
|
||||
- Minishell es un interprete de comandos interactivo.
|
||||
- Implementa pipes, redirecciones, variables y builtins basicos.
|
||||
- Se basa en el flujo: lectura -> lexer -> parser -> expansion -> ejecucion.
|
||||
|
||||
## 2. Flujo completo (paso a paso)
|
||||
1. `readline()` muestra el prompt y devuelve la linea.
|
||||
2. `lex_line()` divide la linea en tokens (`TOK_WORD`, `TOK_PIPE`, redirecciones).
|
||||
3. `parse_tokens()` construye la pipeline con comandos y redirecciones.
|
||||
4. `expand_pipeline()` aplica expansion de `$VAR` y `$?` respetando comillas.
|
||||
5. `execute_pipeline()` resuelve `PATH`, prepara heredocs y ejecuta.
|
||||
|
||||
## 3. Estructuras clave
|
||||
- `t_token`: tipo y texto de tokens (`minishell-codex/include/minishell.h`).
|
||||
- `t_command`: argv, redirecciones, path.
|
||||
- `t_pipeline`: lista de comandos.
|
||||
- `t_redir`: tipo, target y fd.
|
||||
- `t_shell`: estado global (env, exit_status, flags).
|
||||
|
||||
## 4. Lexer (por que esta separado)
|
||||
- Maneja comillas y metacaracteres sin mezclar con ejecucion.
|
||||
- Detecta errores de comillas sin cerrar.
|
||||
- Facilita el parseo posterior.
|
||||
|
||||
## 5. Parser
|
||||
- Convierte tokens en comandos reales.
|
||||
- Cada `TOK_PIPE` crea un nuevo comando.
|
||||
- Redirecciones se guardan en lista separada (`t_redir`).
|
||||
- Valida errores (pipe sin comando, redireccion sin destino).
|
||||
|
||||
## 6. Expansion
|
||||
- `expand_pipeline()` recorre argv y targets de redireccion.
|
||||
- Reglas:
|
||||
- En comilla simple no se expande.
|
||||
- En comilla doble si se expande.
|
||||
- `$?` es el exit status anterior.
|
||||
|
||||
## 7. Redirecciones y heredoc
|
||||
- `apply_redirections()` abre y hace `dup2()`.
|
||||
- `prepare_heredocs()` genera un pipe con el contenido.
|
||||
- Heredoc no se mete en el historial.
|
||||
|
||||
## 8. Ejecucion y pipes
|
||||
- Si hay un solo builtin, se ejecuta en el padre.
|
||||
- Si hay pipeline, todos se forkean.
|
||||
- Se conectan con `pipe()` y `dup2()`.
|
||||
- Se espera a todos, y el exit status es el del ultimo comando.
|
||||
|
||||
## 9. Builtins
|
||||
- Implementados en `src/builtins/builtins.c`.
|
||||
- `echo`, `cd`, `pwd`, `env`, `export`, `unset`, `exit`.
|
||||
- `export` valida identificadores y permite `KEY=VALUE`.
|
||||
|
||||
## 10. Señales
|
||||
- Una sola global: `g_signal`.
|
||||
- `ctrl-C`: limpia linea y muestra prompt.
|
||||
- `ctrl-\`: se ignora en interactivo.
|
||||
- En child se restauran señales por defecto.
|
||||
|
||||
## 11. Ejemplos rapidos para demostrar
|
||||
- Pipes: `ls | wc -l`
|
||||
- Redireccion: `echo hola > out.txt`
|
||||
- Heredoc: `cat << EOF` -> texto -> `EOF`
|
||||
- Expansion: `echo $HOME`, `echo $?`
|
||||
|
||||
## 12. Mensaje final recomendado
|
||||
"Separar lexer, parser, expansion y ejecucion me permitio mantener el codigo claro
|
||||
y replicar el comportamiento de bash para el minimo requerido por el subject."
|
||||
@@ -1,59 +0,0 @@
|
||||
# Minishell - Checklist de pruebas manuales
|
||||
|
||||
Ejecuta en `minishell-codex/`:
|
||||
- `make`
|
||||
- `./minishell`
|
||||
|
||||
## 1. Prompt y salida
|
||||
- Iniciar y salir con `ctrl-D`.
|
||||
- `exit` debe cerrar el shell con el ultimo status.
|
||||
|
||||
## 2. Comandos simples
|
||||
- `ls`
|
||||
- `pwd`
|
||||
- `echo hola`
|
||||
|
||||
## 3. Builtins
|
||||
- `echo -n hola` (sin salto de linea)
|
||||
- `cd /` luego `pwd`
|
||||
- `export TEST=42` luego `env | grep TEST`
|
||||
- `unset TEST` luego `env | grep TEST` (no debe aparecer)
|
||||
- `env` sin argumentos
|
||||
- `exit 2`
|
||||
|
||||
## 4. Expansion
|
||||
- `echo $HOME`
|
||||
- `echo $?` despues de un comando que falle (ej: `ls noexiste`)
|
||||
- `echo '$HOME'` (no expande)
|
||||
- `echo "$HOME"` (si expande)
|
||||
|
||||
## 5. Pipes
|
||||
- `ls | wc -l`
|
||||
- `echo hola | cat`
|
||||
- `cat /etc/passwd | grep root | wc -l`
|
||||
|
||||
## 6. Redirecciones
|
||||
- `echo hola > out.txt` y luego `cat out.txt`
|
||||
- `echo 1 >> out.txt` y luego `cat out.txt`
|
||||
- `cat < out.txt`
|
||||
|
||||
## 7. Heredoc
|
||||
- `cat << EOF`
|
||||
- escribir varias lineas
|
||||
- `EOF`
|
||||
- Ver que se imprime todo lo escrito.
|
||||
|
||||
## 8. Comillas
|
||||
- `echo "a b c"` (una sola palabra)
|
||||
- `echo 'a b c'` (una sola palabra)
|
||||
- `echo "a 'b' c"`
|
||||
|
||||
## 9. Errores de parseo
|
||||
- `| ls` (no debe ejecutar)
|
||||
- `echo hola >` (error)
|
||||
- `echo "hola` (comillas sin cerrar)
|
||||
|
||||
## 10. Senales
|
||||
- `ctrl-C` en prompt: debe limpiar linea y mostrar prompt nuevo.
|
||||
- `sleep 5` y `ctrl-C`: debe interrumpir el proceso.
|
||||
- `ctrl-\` no debe imprimir nada en prompt interactivo.
|
||||
@@ -1,136 +0,0 @@
|
||||
#ifndef MINISHELL_H
|
||||
#define MINISHELL_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/types.h>
|
||||
#include <termios.h>
|
||||
#include <readline/readline.h>
|
||||
#include <readline/history.h>
|
||||
|
||||
#define MS_PROMPT "minishell> "
|
||||
|
||||
extern int g_signal;
|
||||
|
||||
typedef enum e_tokentype
|
||||
{
|
||||
TOK_WORD,
|
||||
TOK_PIPE,
|
||||
TOK_REDIR_IN,
|
||||
TOK_REDIR_OUT,
|
||||
TOK_REDIR_APPEND,
|
||||
TOK_HEREDOC
|
||||
} t_tokentype;
|
||||
|
||||
typedef struct s_token
|
||||
{
|
||||
char *text;
|
||||
t_tokentype type;
|
||||
struct s_token *next;
|
||||
} t_token;
|
||||
|
||||
typedef enum e_redirtype
|
||||
{
|
||||
REDIR_IN,
|
||||
REDIR_OUT,
|
||||
REDIR_APPEND,
|
||||
REDIR_HEREDOC
|
||||
} t_redirtype;
|
||||
|
||||
typedef struct s_redir
|
||||
{
|
||||
t_redirtype type;
|
||||
char *target;
|
||||
int fd;
|
||||
struct s_redir *next;
|
||||
} t_redir;
|
||||
|
||||
typedef struct s_command
|
||||
{
|
||||
char **argv;
|
||||
int argc;
|
||||
char *path;
|
||||
t_redir *redirs;
|
||||
} t_command;
|
||||
|
||||
typedef struct s_pipeline
|
||||
{
|
||||
t_command **cmds;
|
||||
size_t count;
|
||||
} t_pipeline;
|
||||
|
||||
typedef struct s_env
|
||||
{
|
||||
char *key;
|
||||
char *value;
|
||||
struct s_env *next;
|
||||
} t_env;
|
||||
|
||||
typedef struct s_shell
|
||||
{
|
||||
t_env *env;
|
||||
int exit_status;
|
||||
int last_status;
|
||||
int exit_requested;
|
||||
int interactive;
|
||||
} t_shell;
|
||||
|
||||
/* core */
|
||||
void ms_init(t_shell *sh, char **envp);
|
||||
void ms_loop(t_shell *sh);
|
||||
void ms_cleanup(t_shell *sh);
|
||||
void ms_setup_signals(t_shell *sh);
|
||||
void ms_set_child_signals(void);
|
||||
void ms_set_heredoc_signals(void);
|
||||
|
||||
/* parser */
|
||||
t_token *lex_line(const char *line, int *error);
|
||||
void free_tokens(t_token *toks);
|
||||
|
||||
t_pipeline *parse_tokens(t_token *toks, int *error);
|
||||
void free_pipeline(t_pipeline *p);
|
||||
|
||||
int expand_pipeline(t_pipeline *p, t_shell *sh);
|
||||
|
||||
/* executor */
|
||||
int execute_pipeline(t_pipeline *p, t_shell *sh);
|
||||
int prepare_heredocs(t_pipeline *p, t_shell *sh);
|
||||
int apply_redirections(t_command *cmd, int *saved_stdin, int *saved_stdout);
|
||||
void restore_redirections(int saved_stdin, int saved_stdout);
|
||||
char *resolve_path(const char *cmd, t_shell *sh);
|
||||
|
||||
/* env */
|
||||
void env_init(t_shell *sh, char **envp);
|
||||
void env_clear(t_shell *sh);
|
||||
char *env_get(t_shell *sh, const char *key);
|
||||
int env_set(t_shell *sh, const char *key, const char *value);
|
||||
int env_unset(t_shell *sh, const char *key);
|
||||
char **env_to_envp(t_shell *sh);
|
||||
void env_free_envp(char **envp);
|
||||
void env_print(t_shell *sh);
|
||||
|
||||
/* builtins */
|
||||
int is_builtin(const char *name);
|
||||
int exec_builtin(t_command *cmd, t_shell *sh);
|
||||
|
||||
/* utils */
|
||||
int ms_is_space(int c);
|
||||
int ms_is_alpha(int c);
|
||||
int ms_is_alnum(int c);
|
||||
int ms_is_digit(int c);
|
||||
char *ms_strdup(const char *s);
|
||||
char *ms_strndup(const char *s, size_t n);
|
||||
char *ms_strjoin(const char *a, const char *b);
|
||||
char *ms_strjoin3(const char *a, const char *b, const char *c);
|
||||
char *ms_substr(const char *s, size_t start, size_t len);
|
||||
char **ms_split(const char *s, char delim);
|
||||
void ms_free_split(char **sp);
|
||||
char *ms_itoa(int n);
|
||||
|
||||
#endif
|
||||
@@ -1,221 +0,0 @@
|
||||
#include "minishell.h"
|
||||
|
||||
static int builtin_echo(t_command *cmd, t_shell *sh);
|
||||
static int builtin_cd(t_command *cmd, t_shell *sh);
|
||||
static int builtin_pwd(t_command *cmd, t_shell *sh);
|
||||
static int builtin_env(t_command *cmd, t_shell *sh);
|
||||
static int builtin_export(t_command *cmd, t_shell *sh);
|
||||
static int builtin_unset(t_command *cmd, t_shell *sh);
|
||||
static int builtin_exit(t_command *cmd, t_shell *sh);
|
||||
|
||||
int is_builtin(const char *name)
|
||||
{
|
||||
if (!name)
|
||||
return 0;
|
||||
return (strcmp(name, "echo") == 0 || strcmp(name, "cd") == 0
|
||||
|| strcmp(name, "pwd") == 0 || strcmp(name, "env") == 0
|
||||
|| strcmp(name, "export") == 0 || strcmp(name, "unset") == 0
|
||||
|| strcmp(name, "exit") == 0);
|
||||
}
|
||||
|
||||
int exec_builtin(t_command *cmd, t_shell *sh)
|
||||
{
|
||||
if (strcmp(cmd->argv[0], "echo") == 0)
|
||||
return builtin_echo(cmd, sh);
|
||||
if (strcmp(cmd->argv[0], "cd") == 0)
|
||||
return builtin_cd(cmd, sh);
|
||||
if (strcmp(cmd->argv[0], "pwd") == 0)
|
||||
return builtin_pwd(cmd, sh);
|
||||
if (strcmp(cmd->argv[0], "env") == 0)
|
||||
return builtin_env(cmd, sh);
|
||||
if (strcmp(cmd->argv[0], "export") == 0)
|
||||
return builtin_export(cmd, sh);
|
||||
if (strcmp(cmd->argv[0], "unset") == 0)
|
||||
return builtin_unset(cmd, sh);
|
||||
if (strcmp(cmd->argv[0], "exit") == 0)
|
||||
return builtin_exit(cmd, sh);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int builtin_echo(t_command *cmd, t_shell *sh)
|
||||
{
|
||||
int i = 1;
|
||||
int newline = 1;
|
||||
|
||||
(void)sh;
|
||||
while (cmd->argv[i] && cmd->argv[i][0] == '-' && cmd->argv[i][1] == 'n')
|
||||
{
|
||||
int j = 2;
|
||||
while (cmd->argv[i][j] == 'n')
|
||||
j++;
|
||||
if (cmd->argv[i][j] != '\0')
|
||||
break;
|
||||
newline = 0;
|
||||
i++;
|
||||
}
|
||||
while (cmd->argv[i])
|
||||
{
|
||||
printf("%s", cmd->argv[i]);
|
||||
if (cmd->argv[i + 1])
|
||||
printf(" ");
|
||||
i++;
|
||||
}
|
||||
if (newline)
|
||||
printf("\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int builtin_pwd(t_command *cmd, t_shell *sh)
|
||||
{
|
||||
char buf[4096];
|
||||
(void)cmd;
|
||||
(void)sh;
|
||||
if (getcwd(buf, sizeof(buf)))
|
||||
printf("%s\n", buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int builtin_cd(t_command *cmd, t_shell *sh)
|
||||
{
|
||||
char *path;
|
||||
char cwd[4096];
|
||||
|
||||
if (cmd->argc > 2)
|
||||
{
|
||||
fprintf(stderr, "minishell: cd: too many arguments\n");
|
||||
return 1;
|
||||
}
|
||||
if (cmd->argc == 1)
|
||||
path = env_get(sh, "HOME");
|
||||
else
|
||||
path = cmd->argv[1];
|
||||
if (!path)
|
||||
{
|
||||
fprintf(stderr, "minishell: cd: HOME not set\n");
|
||||
return 1;
|
||||
}
|
||||
if (getcwd(cwd, sizeof(cwd)))
|
||||
env_set(sh, "OLDPWD", cwd);
|
||||
if (chdir(path) != 0)
|
||||
{
|
||||
perror("minishell: cd");
|
||||
return 1;
|
||||
}
|
||||
if (getcwd(cwd, sizeof(cwd)))
|
||||
env_set(sh, "PWD", cwd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int builtin_env(t_command *cmd, t_shell *sh)
|
||||
{
|
||||
if (cmd->argc > 1)
|
||||
{
|
||||
fprintf(stderr, "minishell: env: too many arguments\n");
|
||||
return 1;
|
||||
}
|
||||
env_print(sh);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int valid_identifier(const char *s)
|
||||
{
|
||||
int i = 0;
|
||||
if (!s || !ms_is_alpha((unsigned char)s[0]))
|
||||
return 0;
|
||||
while (s[i])
|
||||
{
|
||||
if (!ms_is_alnum((unsigned char)s[i]))
|
||||
return 0;
|
||||
i++;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int builtin_export(t_command *cmd, t_shell *sh)
|
||||
{
|
||||
int i = 1;
|
||||
if (cmd->argc == 1)
|
||||
{
|
||||
env_print(sh);
|
||||
return 0;
|
||||
}
|
||||
while (cmd->argv[i])
|
||||
{
|
||||
char *eq = strchr(cmd->argv[i], '=');
|
||||
if (eq)
|
||||
{
|
||||
char *key = ms_strndup(cmd->argv[i], (size_t)(eq - cmd->argv[i]));
|
||||
char *val = ms_strdup(eq + 1);
|
||||
if (!valid_identifier(key))
|
||||
fprintf(stderr, "minishell: export: `%s': not a valid identifier\n", cmd->argv[i]);
|
||||
else
|
||||
env_set(sh, key, val);
|
||||
free(key);
|
||||
free(val);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!valid_identifier(cmd->argv[i]))
|
||||
fprintf(stderr, "minishell: export: `%s': not a valid identifier\n", cmd->argv[i]);
|
||||
else
|
||||
env_set(sh, cmd->argv[i], "");
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int builtin_unset(t_command *cmd, t_shell *sh)
|
||||
{
|
||||
int i = 1;
|
||||
while (cmd->argv[i])
|
||||
{
|
||||
env_unset(sh, cmd->argv[i]);
|
||||
i++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int builtin_exit(t_command *cmd, t_shell *sh)
|
||||
{
|
||||
long code = sh->exit_status;
|
||||
int i = 0;
|
||||
|
||||
if (sh->interactive)
|
||||
printf("exit\n");
|
||||
if (cmd->argc == 1)
|
||||
{
|
||||
sh->exit_requested = 1;
|
||||
sh->exit_status = (int)(code & 0xFF);
|
||||
return sh->exit_status;
|
||||
}
|
||||
if (cmd->argc > 2)
|
||||
{
|
||||
fprintf(stderr, "minishell: exit: too many arguments\n");
|
||||
return 1;
|
||||
}
|
||||
if (cmd->argv[1][i] == '+' || cmd->argv[1][i] == '-')
|
||||
i++;
|
||||
if (cmd->argv[1][i] == '\0')
|
||||
{
|
||||
fprintf(stderr, "minishell: exit: %s: numeric argument required\n", cmd->argv[1]);
|
||||
sh->exit_requested = 1;
|
||||
sh->exit_status = 2;
|
||||
return 2;
|
||||
}
|
||||
while (cmd->argv[1][i])
|
||||
{
|
||||
if (!ms_is_digit((unsigned char)cmd->argv[1][i]))
|
||||
{
|
||||
fprintf(stderr, "minishell: exit: %s: numeric argument required\n", cmd->argv[1]);
|
||||
sh->exit_requested = 1;
|
||||
sh->exit_status = 2;
|
||||
return 2;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
code = strtol(cmd->argv[1], NULL, 10);
|
||||
sh->exit_requested = 1;
|
||||
sh->exit_status = (int)(code & 0xFF);
|
||||
return sh->exit_status;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
#include "minishell.h"
|
||||
|
||||
void ms_init(t_shell *sh, char **envp)
|
||||
{
|
||||
memset(sh, 0, sizeof(*sh));
|
||||
sh->interactive = isatty(STDIN_FILENO);
|
||||
sh->exit_status = 0;
|
||||
sh->last_status = 0;
|
||||
sh->exit_requested = 0;
|
||||
env_init(sh, envp);
|
||||
ms_setup_signals(sh);
|
||||
}
|
||||
|
||||
void ms_cleanup(t_shell *sh)
|
||||
{
|
||||
env_clear(sh);
|
||||
rl_clear_history();
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
#include "minishell.h"
|
||||
|
||||
static void handle_line(t_shell *sh, char *line)
|
||||
{
|
||||
int error = 0;
|
||||
t_token *toks = NULL;
|
||||
t_pipeline *p = NULL;
|
||||
|
||||
toks = lex_line(line, &error);
|
||||
if (error)
|
||||
{
|
||||
free_tokens(toks);
|
||||
return;
|
||||
}
|
||||
p = parse_tokens(toks, &error);
|
||||
free_tokens(toks);
|
||||
if (error || !p)
|
||||
{
|
||||
free_pipeline(p);
|
||||
return;
|
||||
}
|
||||
if (expand_pipeline(p, sh) != 0)
|
||||
{
|
||||
free_pipeline(p);
|
||||
return;
|
||||
}
|
||||
sh->exit_status = execute_pipeline(p, sh);
|
||||
sh->last_status = sh->exit_status;
|
||||
free_pipeline(p);
|
||||
}
|
||||
|
||||
void ms_loop(t_shell *sh)
|
||||
{
|
||||
char *line;
|
||||
|
||||
while (!sh->exit_requested)
|
||||
{
|
||||
line = readline(MS_PROMPT);
|
||||
if (!line)
|
||||
break;
|
||||
if (line[0] != '\0')
|
||||
add_history(line);
|
||||
handle_line(sh, line);
|
||||
free(line);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
#include "minishell.h"
|
||||
|
||||
int g_signal = 0;
|
||||
|
||||
static void sigint_handler(int sig)
|
||||
{
|
||||
g_signal = sig;
|
||||
write(STDOUT_FILENO, "\n", 1);
|
||||
rl_on_new_line();
|
||||
rl_replace_line("", 0);
|
||||
rl_redisplay();
|
||||
}
|
||||
|
||||
static void sigquit_handler(int sig)
|
||||
{
|
||||
g_signal = sig;
|
||||
(void)sig;
|
||||
}
|
||||
|
||||
void ms_setup_signals(t_shell *sh)
|
||||
{
|
||||
struct sigaction sa_int;
|
||||
struct sigaction sa_quit;
|
||||
|
||||
(void)sh;
|
||||
memset(&sa_int, 0, sizeof(sa_int));
|
||||
memset(&sa_quit, 0, sizeof(sa_quit));
|
||||
sa_int.sa_handler = sigint_handler;
|
||||
sa_quit.sa_handler = sigquit_handler;
|
||||
sigemptyset(&sa_int.sa_mask);
|
||||
sigemptyset(&sa_quit.sa_mask);
|
||||
sigaction(SIGINT, &sa_int, NULL);
|
||||
sigaction(SIGQUIT, &sa_quit, NULL);
|
||||
}
|
||||
|
||||
void ms_set_child_signals(void)
|
||||
{
|
||||
signal(SIGINT, SIG_DFL);
|
||||
signal(SIGQUIT, SIG_DFL);
|
||||
}
|
||||
|
||||
void ms_set_heredoc_signals(void)
|
||||
{
|
||||
signal(SIGINT, SIG_DFL);
|
||||
signal(SIGQUIT, SIG_IGN);
|
||||
}
|
||||
171
minishell-codex/src/env/env.c
vendored
171
minishell-codex/src/env/env.c
vendored
@@ -1,171 +0,0 @@
|
||||
#include "minishell.h"
|
||||
|
||||
static t_env *env_new(const char *key, const char *value)
|
||||
{
|
||||
t_env *n = (t_env *)calloc(1, sizeof(t_env));
|
||||
if (!n)
|
||||
return NULL;
|
||||
n->key = ms_strdup(key);
|
||||
n->value = value ? ms_strdup(value) : ms_strdup("");
|
||||
if (!n->key || !n->value)
|
||||
{
|
||||
free(n->key);
|
||||
free(n->value);
|
||||
free(n);
|
||||
return NULL;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void env_init(t_shell *sh, char **envp)
|
||||
{
|
||||
int i;
|
||||
char *eq;
|
||||
|
||||
sh->env = NULL;
|
||||
if (!envp)
|
||||
return;
|
||||
i = 0;
|
||||
while (envp[i])
|
||||
{
|
||||
eq = strchr(envp[i], '=');
|
||||
if (eq)
|
||||
{
|
||||
char *key = ms_strndup(envp[i], (size_t)(eq - envp[i]));
|
||||
char *val = ms_strdup(eq + 1);
|
||||
env_set(sh, key, val);
|
||||
free(key);
|
||||
free(val);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void env_clear(t_shell *sh)
|
||||
{
|
||||
t_env *cur = sh->env;
|
||||
t_env *next;
|
||||
|
||||
while (cur)
|
||||
{
|
||||
next = cur->next;
|
||||
free(cur->key);
|
||||
free(cur->value);
|
||||
free(cur);
|
||||
cur = next;
|
||||
}
|
||||
sh->env = NULL;
|
||||
}
|
||||
|
||||
char *env_get(t_shell *sh, const char *key)
|
||||
{
|
||||
t_env *cur = sh->env;
|
||||
|
||||
while (cur)
|
||||
{
|
||||
if (strcmp(cur->key, key) == 0)
|
||||
return cur->value;
|
||||
cur = cur->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int env_set(t_shell *sh, const char *key, const char *value)
|
||||
{
|
||||
t_env *cur = sh->env;
|
||||
t_env *prev = NULL;
|
||||
|
||||
while (cur)
|
||||
{
|
||||
if (strcmp(cur->key, key) == 0)
|
||||
{
|
||||
char *dup = ms_strdup(value ? value : "");
|
||||
if (!dup)
|
||||
return 1;
|
||||
free(cur->value);
|
||||
cur->value = dup;
|
||||
return 0;
|
||||
}
|
||||
prev = cur;
|
||||
cur = cur->next;
|
||||
}
|
||||
cur = env_new(key, value);
|
||||
if (!cur)
|
||||
return 1;
|
||||
if (prev)
|
||||
prev->next = cur;
|
||||
else
|
||||
sh->env = cur;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int env_unset(t_shell *sh, const char *key)
|
||||
{
|
||||
t_env *cur = sh->env;
|
||||
t_env *prev = NULL;
|
||||
|
||||
while (cur)
|
||||
{
|
||||
if (strcmp(cur->key, key) == 0)
|
||||
{
|
||||
if (prev)
|
||||
prev->next = cur->next;
|
||||
else
|
||||
sh->env = cur->next;
|
||||
free(cur->key);
|
||||
free(cur->value);
|
||||
free(cur);
|
||||
return 0;
|
||||
}
|
||||
prev = cur;
|
||||
cur = cur->next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char **env_to_envp(t_shell *sh)
|
||||
{
|
||||
char **envp;
|
||||
int count = 0;
|
||||
t_env *cur = sh->env;
|
||||
int i = 0;
|
||||
|
||||
while (cur)
|
||||
{
|
||||
count++;
|
||||
cur = cur->next;
|
||||
}
|
||||
envp = (char **)calloc((size_t)count + 1, sizeof(char *));
|
||||
if (!envp)
|
||||
return NULL;
|
||||
cur = sh->env;
|
||||
while (cur)
|
||||
{
|
||||
char *kv = ms_strjoin3(cur->key, "=", cur->value);
|
||||
envp[i++] = kv;
|
||||
cur = cur->next;
|
||||
}
|
||||
envp[i] = NULL;
|
||||
return envp;
|
||||
}
|
||||
|
||||
void env_free_envp(char **envp)
|
||||
{
|
||||
int i = 0;
|
||||
if (!envp)
|
||||
return;
|
||||
while (envp[i])
|
||||
free(envp[i++]);
|
||||
free(envp);
|
||||
}
|
||||
|
||||
void env_print(t_shell *sh)
|
||||
{
|
||||
t_env *cur = sh->env;
|
||||
while (cur)
|
||||
{
|
||||
if (cur->value)
|
||||
printf("%s=%s\n", cur->key, cur->value);
|
||||
cur = cur->next;
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
#include "minishell.h"
|
||||
|
||||
static int exec_external(t_command *cmd, t_shell *sh)
|
||||
{
|
||||
char **envp = env_to_envp(sh);
|
||||
if (!envp)
|
||||
return 1;
|
||||
execve(cmd->path, cmd->argv, envp);
|
||||
perror(cmd->path);
|
||||
env_free_envp(envp);
|
||||
return 126;
|
||||
}
|
||||
|
||||
static int run_command_child(t_command *cmd, t_shell *sh)
|
||||
{
|
||||
int saved_in, saved_out;
|
||||
|
||||
if (apply_redirections(cmd, &saved_in, &saved_out) != 0)
|
||||
return 1;
|
||||
if (is_builtin(cmd->argv[0]))
|
||||
return exec_builtin(cmd, sh);
|
||||
return exec_external(cmd, sh);
|
||||
}
|
||||
|
||||
static int run_command_parent_builtin(t_command *cmd, t_shell *sh)
|
||||
{
|
||||
int saved_in, saved_out;
|
||||
int status;
|
||||
|
||||
if (apply_redirections(cmd, &saved_in, &saved_out) != 0)
|
||||
return 1;
|
||||
status = exec_builtin(cmd, sh);
|
||||
restore_redirections(saved_in, saved_out);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int setup_pipes(int idx, int count, int pipefd[2])
|
||||
{
|
||||
if (idx + 1 >= count)
|
||||
return 0;
|
||||
if (pipe(pipefd) == -1)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void setup_child_fds(int idx, int count, int prev_read, int pipefd[2])
|
||||
{
|
||||
if (prev_read != -1)
|
||||
{
|
||||
dup2(prev_read, STDIN_FILENO);
|
||||
close(prev_read);
|
||||
}
|
||||
if (idx + 1 < count)
|
||||
{
|
||||
dup2(pipefd[1], STDOUT_FILENO);
|
||||
close(pipefd[0]);
|
||||
close(pipefd[1]);
|
||||
}
|
||||
}
|
||||
|
||||
static void close_parent_fds(int idx, int count, int *prev_read, int pipefd[2])
|
||||
{
|
||||
if (*prev_read != -1)
|
||||
close(*prev_read);
|
||||
if (idx + 1 < count)
|
||||
{
|
||||
close(pipefd[1]);
|
||||
*prev_read = pipefd[0];
|
||||
}
|
||||
else
|
||||
*prev_read = -1;
|
||||
}
|
||||
|
||||
int execute_pipeline(t_pipeline *p, t_shell *sh)
|
||||
{
|
||||
int i;
|
||||
int prev_read = -1;
|
||||
pid_t *pids;
|
||||
int status = 0;
|
||||
|
||||
if (!p || p->count == 0)
|
||||
return 0;
|
||||
if (prepare_heredocs(p, sh) != 0)
|
||||
return sh->exit_status;
|
||||
for (size_t k = 0; k < p->count; k++)
|
||||
{
|
||||
if (!p->cmds[k]->argv || !p->cmds[k]->argv[0])
|
||||
return 1;
|
||||
free(p->cmds[k]->path);
|
||||
p->cmds[k]->path = resolve_path(p->cmds[k]->argv[0], sh);
|
||||
}
|
||||
if (p->count == 1 && is_builtin(p->cmds[0]->argv[0]))
|
||||
return run_command_parent_builtin(p->cmds[0], sh);
|
||||
pids = (pid_t *)calloc(p->count, sizeof(pid_t));
|
||||
if (!pids)
|
||||
return 1;
|
||||
for (i = 0; i < (int)p->count; i++)
|
||||
{
|
||||
int pipefd[2] = {-1, -1};
|
||||
if (setup_pipes(i, (int)p->count, pipefd))
|
||||
break;
|
||||
pids[i] = fork();
|
||||
if (pids[i] == 0)
|
||||
{
|
||||
ms_set_child_signals();
|
||||
setup_child_fds(i, (int)p->count, prev_read, pipefd);
|
||||
if (!p->cmds[i]->path)
|
||||
{
|
||||
fprintf(stderr, "minishell: %s: command not found\n", p->cmds[i]->argv[0]);
|
||||
exit(127);
|
||||
}
|
||||
status = run_command_child(p->cmds[i], sh);
|
||||
exit(status);
|
||||
}
|
||||
close_parent_fds(i, (int)p->count, &prev_read, pipefd);
|
||||
}
|
||||
for (i = 0; i < (int)p->count; i++)
|
||||
{
|
||||
int wstatus = 0;
|
||||
if (pids[i] > 0)
|
||||
{
|
||||
waitpid(pids[i], &wstatus, 0);
|
||||
if (i == (int)p->count - 1)
|
||||
{
|
||||
if (WIFEXITED(wstatus))
|
||||
status = WEXITSTATUS(wstatus);
|
||||
else if (WIFSIGNALED(wstatus))
|
||||
status = 128 + WTERMSIG(wstatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
free(pids);
|
||||
return status;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
#include "minishell.h"
|
||||
|
||||
static int has_slash(const char *s)
|
||||
{
|
||||
return (s && strchr(s, '/'));
|
||||
}
|
||||
|
||||
char *resolve_path(const char *cmd, t_shell *sh)
|
||||
{
|
||||
char *path_env;
|
||||
char **parts;
|
||||
char *candidate;
|
||||
int i;
|
||||
|
||||
if (!cmd)
|
||||
return NULL;
|
||||
if (is_builtin(cmd))
|
||||
return ms_strdup(cmd);
|
||||
if (has_slash(cmd))
|
||||
return ms_strdup(cmd);
|
||||
path_env = env_get(sh, "PATH");
|
||||
if (!path_env)
|
||||
return NULL;
|
||||
parts = ms_split(path_env, ':');
|
||||
if (!parts)
|
||||
return NULL;
|
||||
i = 0;
|
||||
while (parts[i])
|
||||
{
|
||||
candidate = ms_strjoin3(parts[i], "/", cmd);
|
||||
if (candidate && access(candidate, X_OK) == 0)
|
||||
{
|
||||
ms_free_split(parts);
|
||||
return candidate;
|
||||
}
|
||||
free(candidate);
|
||||
i++;
|
||||
}
|
||||
ms_free_split(parts);
|
||||
return NULL;
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
#include "minishell.h"
|
||||
|
||||
static int open_redir(t_redir *r)
|
||||
{
|
||||
if (r->type == REDIR_IN)
|
||||
return open(r->target, O_RDONLY);
|
||||
if (r->type == REDIR_OUT)
|
||||
return open(r->target, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (r->type == REDIR_APPEND)
|
||||
return open(r->target, O_WRONLY | O_CREAT | O_APPEND, 0644);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int prepare_heredocs(t_pipeline *p, t_shell *sh)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
g_signal = 0;
|
||||
for (i = 0; i < p->count; i++)
|
||||
{
|
||||
t_redir *r = p->cmds[i]->redirs;
|
||||
while (r)
|
||||
{
|
||||
if (r->type == REDIR_HEREDOC)
|
||||
{
|
||||
int fds[2];
|
||||
char *line;
|
||||
ms_set_heredoc_signals();
|
||||
if (pipe(fds) == -1)
|
||||
return 1;
|
||||
while (1)
|
||||
{
|
||||
line = readline("> ");
|
||||
if (!line)
|
||||
break;
|
||||
if (strcmp(line, r->target) == 0)
|
||||
{
|
||||
free(line);
|
||||
break;
|
||||
}
|
||||
write(fds[1], line, strlen(line));
|
||||
write(fds[1], "\n", 1);
|
||||
free(line);
|
||||
}
|
||||
close(fds[1]);
|
||||
r->fd = fds[0];
|
||||
ms_setup_signals(sh);
|
||||
if (g_signal == SIGINT)
|
||||
{
|
||||
sh->exit_status = 130;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
r = r->next;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int apply_redirections(t_command *cmd, int *saved_stdin, int *saved_stdout)
|
||||
{
|
||||
t_redir *r = cmd->redirs;
|
||||
*saved_stdin = -1;
|
||||
*saved_stdout = -1;
|
||||
while (r)
|
||||
{
|
||||
int fd = -1;
|
||||
if (r->type == REDIR_HEREDOC)
|
||||
fd = r->fd;
|
||||
else
|
||||
fd = open_redir(r);
|
||||
if (fd == -1)
|
||||
{
|
||||
perror(r->target);
|
||||
return 1;
|
||||
}
|
||||
if (r->type == REDIR_IN || r->type == REDIR_HEREDOC)
|
||||
{
|
||||
if (*saved_stdin == -1)
|
||||
*saved_stdin = dup(STDIN_FILENO);
|
||||
dup2(fd, STDIN_FILENO);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (*saved_stdout == -1)
|
||||
*saved_stdout = dup(STDOUT_FILENO);
|
||||
dup2(fd, STDOUT_FILENO);
|
||||
}
|
||||
if (r->type != REDIR_HEREDOC)
|
||||
close(fd);
|
||||
r = r->next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void restore_redirections(int saved_stdin, int saved_stdout)
|
||||
{
|
||||
if (saved_stdin != -1)
|
||||
{
|
||||
dup2(saved_stdin, STDIN_FILENO);
|
||||
close(saved_stdin);
|
||||
}
|
||||
if (saved_stdout != -1)
|
||||
{
|
||||
dup2(saved_stdout, STDOUT_FILENO);
|
||||
close(saved_stdout);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#include "minishell.h"
|
||||
|
||||
int main(int argc, char **argv, char **envp)
|
||||
{
|
||||
t_shell sh;
|
||||
|
||||
(void)argv;
|
||||
if (argc != 1)
|
||||
{
|
||||
fprintf(stderr, "Usage: ./minishell\n");
|
||||
return 1;
|
||||
}
|
||||
ms_init(&sh, envp);
|
||||
ms_loop(&sh);
|
||||
ms_cleanup(&sh);
|
||||
return sh.exit_status;
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
#include "minishell.h"
|
||||
|
||||
static void buf_append(char **buf, size_t *len, size_t *cap, const char *s)
|
||||
{
|
||||
size_t slen;
|
||||
char *newbuf;
|
||||
|
||||
if (!s)
|
||||
return;
|
||||
slen = strlen(s);
|
||||
if (*len + slen + 1 > *cap)
|
||||
{
|
||||
*cap = (*len + slen + 1) * 2;
|
||||
newbuf = (char *)realloc(*buf, *cap);
|
||||
if (!newbuf)
|
||||
return;
|
||||
*buf = newbuf;
|
||||
}
|
||||
memcpy(*buf + *len, s, slen);
|
||||
*len += slen;
|
||||
(*buf)[*len] = '\0';
|
||||
}
|
||||
|
||||
static void buf_append_char(char **buf, size_t *len, size_t *cap, char c)
|
||||
{
|
||||
char tmp[2];
|
||||
tmp[0] = c;
|
||||
tmp[1] = '\0';
|
||||
buf_append(buf, len, cap, tmp);
|
||||
}
|
||||
|
||||
static char *expand_word(const char *word, t_shell *sh)
|
||||
{
|
||||
int in_single = 0;
|
||||
int in_double = 0;
|
||||
size_t i = 0;
|
||||
char *out = NULL;
|
||||
size_t len = 0, cap = 0;
|
||||
|
||||
while (word && word[i])
|
||||
{
|
||||
if (word[i] == '\'' && !in_double)
|
||||
{
|
||||
in_single = !in_single;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (word[i] == '"' && !in_single)
|
||||
{
|
||||
in_double = !in_double;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (word[i] == '$' && !in_single)
|
||||
{
|
||||
if (word[i + 1] == '?')
|
||||
{
|
||||
char *v = ms_itoa(sh->last_status);
|
||||
buf_append(&out, &len, &cap, v);
|
||||
free(v);
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
if (ms_is_alpha((unsigned char)word[i + 1]))
|
||||
{
|
||||
size_t j = i + 1;
|
||||
while (word[j] && ms_is_alnum((unsigned char)word[j]))
|
||||
j++;
|
||||
char *name = ms_strndup(word + i + 1, j - (i + 1));
|
||||
char *val = env_get(sh, name);
|
||||
buf_append(&out, &len, &cap, val ? val : "");
|
||||
free(name);
|
||||
i = j;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
buf_append_char(&out, &len, &cap, word[i]);
|
||||
i++;
|
||||
}
|
||||
if (!out)
|
||||
out = ms_strdup("");
|
||||
return out;
|
||||
}
|
||||
|
||||
int expand_pipeline(t_pipeline *p, t_shell *sh)
|
||||
{
|
||||
size_t i;
|
||||
int j;
|
||||
for (i = 0; i < p->count; i++)
|
||||
{
|
||||
for (j = 0; p->cmds[i]->argv && p->cmds[i]->argv[j]; j++)
|
||||
{
|
||||
char *neww = expand_word(p->cmds[i]->argv[j], sh);
|
||||
free(p->cmds[i]->argv[j]);
|
||||
p->cmds[i]->argv[j] = neww;
|
||||
}
|
||||
if (p->cmds[i]->redirs)
|
||||
{
|
||||
t_redir *r = p->cmds[i]->redirs;
|
||||
while (r)
|
||||
{
|
||||
if (r->target)
|
||||
{
|
||||
char *nt = expand_word(r->target, sh);
|
||||
free(r->target);
|
||||
r->target = nt;
|
||||
}
|
||||
r = r->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
#include "minishell.h"
|
||||
|
||||
static t_token *token_new(t_tokentype type, const char *text, size_t len)
|
||||
{
|
||||
t_token *t = (t_token *)calloc(1, sizeof(t_token));
|
||||
if (!t)
|
||||
return NULL;
|
||||
t->type = type;
|
||||
if (text)
|
||||
t->text = ms_strndup(text, len);
|
||||
return t;
|
||||
}
|
||||
|
||||
static void token_add(t_token **head, t_token *new)
|
||||
{
|
||||
t_token *cur;
|
||||
if (!new)
|
||||
return;
|
||||
if (!*head)
|
||||
{
|
||||
*head = new;
|
||||
return;
|
||||
}
|
||||
cur = *head;
|
||||
while (cur->next)
|
||||
cur = cur->next;
|
||||
cur->next = new;
|
||||
}
|
||||
|
||||
void free_tokens(t_token *toks)
|
||||
{
|
||||
t_token *n;
|
||||
while (toks)
|
||||
{
|
||||
n = toks->next;
|
||||
free(toks->text);
|
||||
free(toks);
|
||||
toks = n;
|
||||
}
|
||||
}
|
||||
|
||||
static int is_meta(char c)
|
||||
{
|
||||
return (c == '|' || c == '<' || c == '>');
|
||||
}
|
||||
|
||||
static int read_word(const char *line, size_t *i, t_token **out)
|
||||
{
|
||||
size_t start = *i;
|
||||
int in_single = 0;
|
||||
int in_double = 0;
|
||||
|
||||
while (line[*i])
|
||||
{
|
||||
if (line[*i] == '\'' && !in_double)
|
||||
in_single = !in_single;
|
||||
else if (line[*i] == '"' && !in_single)
|
||||
in_double = !in_double;
|
||||
else if (!in_single && !in_double)
|
||||
{
|
||||
if (ms_is_space(line[*i]) || is_meta(line[*i]))
|
||||
break;
|
||||
}
|
||||
(*i)++;
|
||||
}
|
||||
if (in_single || in_double)
|
||||
return 1;
|
||||
*out = token_new(TOK_WORD, line + start, *i - start);
|
||||
return 0;
|
||||
}
|
||||
|
||||
t_token *lex_line(const char *line, int *error)
|
||||
{
|
||||
t_token *toks = NULL;
|
||||
size_t i = 0;
|
||||
|
||||
*error = 0;
|
||||
while (line[i])
|
||||
{
|
||||
if (ms_is_space(line[i]))
|
||||
{
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (line[i] == '|')
|
||||
{
|
||||
token_add(&toks, token_new(TOK_PIPE, "|", 1));
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (line[i] == '<')
|
||||
{
|
||||
if (line[i + 1] == '<')
|
||||
{
|
||||
token_add(&toks, token_new(TOK_HEREDOC, "<<", 2));
|
||||
i += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
token_add(&toks, token_new(TOK_REDIR_IN, "<", 1));
|
||||
i++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (line[i] == '>')
|
||||
{
|
||||
if (line[i + 1] == '>')
|
||||
{
|
||||
token_add(&toks, token_new(TOK_REDIR_APPEND, ">>", 2));
|
||||
i += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
token_add(&toks, token_new(TOK_REDIR_OUT, ">", 1));
|
||||
i++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
{
|
||||
t_token *w = NULL;
|
||||
if (read_word(line, &i, &w))
|
||||
{
|
||||
*error = 1;
|
||||
free_tokens(toks);
|
||||
return NULL;
|
||||
}
|
||||
token_add(&toks, w);
|
||||
}
|
||||
}
|
||||
return toks;
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
#include "minishell.h"
|
||||
|
||||
static t_command *command_new(void)
|
||||
{
|
||||
t_command *cmd = (t_command *)calloc(1, sizeof(t_command));
|
||||
return cmd;
|
||||
}
|
||||
|
||||
static void command_add_arg(t_command *cmd, const char *text)
|
||||
{
|
||||
char **new_argv;
|
||||
int i;
|
||||
|
||||
if (!cmd || !text)
|
||||
return;
|
||||
i = cmd->argc;
|
||||
new_argv = (char **)calloc((size_t)i + 2, sizeof(char *));
|
||||
if (!new_argv)
|
||||
return;
|
||||
for (int j = 0; j < i; j++)
|
||||
new_argv[j] = cmd->argv[j];
|
||||
new_argv[i] = ms_strdup(text);
|
||||
new_argv[i + 1] = NULL;
|
||||
free(cmd->argv);
|
||||
cmd->argv = new_argv;
|
||||
cmd->argc += 1;
|
||||
}
|
||||
|
||||
static void command_add_redir(t_command *cmd, t_redirtype type, const char *target)
|
||||
{
|
||||
t_redir *r = (t_redir *)calloc(1, sizeof(t_redir));
|
||||
t_redir *cur;
|
||||
if (!r)
|
||||
return;
|
||||
r->type = type;
|
||||
r->target = ms_strdup(target);
|
||||
r->fd = -1;
|
||||
r->next = NULL;
|
||||
if (!cmd->redirs)
|
||||
{
|
||||
cmd->redirs = r;
|
||||
return;
|
||||
}
|
||||
cur = cmd->redirs;
|
||||
while (cur->next)
|
||||
cur = cur->next;
|
||||
cur->next = r;
|
||||
}
|
||||
|
||||
static void free_redirs(t_redir *r)
|
||||
{
|
||||
t_redir *n;
|
||||
while (r)
|
||||
{
|
||||
n = r->next;
|
||||
if (r->fd != -1)
|
||||
close(r->fd);
|
||||
free(r->target);
|
||||
free(r);
|
||||
r = n;
|
||||
}
|
||||
}
|
||||
|
||||
void free_pipeline(t_pipeline *p)
|
||||
{
|
||||
size_t i;
|
||||
if (!p)
|
||||
return;
|
||||
for (i = 0; i < p->count; i++)
|
||||
{
|
||||
if (p->cmds[i])
|
||||
{
|
||||
for (int j = 0; p->cmds[i]->argv && p->cmds[i]->argv[j]; j++)
|
||||
free(p->cmds[i]->argv[j]);
|
||||
free(p->cmds[i]->argv);
|
||||
free(p->cmds[i]->path);
|
||||
free_redirs(p->cmds[i]->redirs);
|
||||
free(p->cmds[i]);
|
||||
}
|
||||
}
|
||||
free(p->cmds);
|
||||
free(p);
|
||||
}
|
||||
|
||||
static int pipeline_add_cmd(t_pipeline *p, t_command *cmd)
|
||||
{
|
||||
t_command **new_cmds;
|
||||
size_t i;
|
||||
|
||||
new_cmds = (t_command **)calloc(p->count + 1, sizeof(t_command *));
|
||||
if (!new_cmds)
|
||||
return 1;
|
||||
for (i = 0; i < p->count; i++)
|
||||
new_cmds[i] = p->cmds[i];
|
||||
new_cmds[p->count] = cmd;
|
||||
free(p->cmds);
|
||||
p->cmds = new_cmds;
|
||||
p->count += 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
t_pipeline *parse_tokens(t_token *toks, int *error)
|
||||
{
|
||||
t_pipeline *p;
|
||||
t_command *cmd;
|
||||
t_token *cur;
|
||||
|
||||
*error = 0;
|
||||
p = (t_pipeline *)calloc(1, sizeof(t_pipeline));
|
||||
if (!p)
|
||||
return NULL;
|
||||
cmd = command_new();
|
||||
if (!cmd)
|
||||
{
|
||||
free(p);
|
||||
return NULL;
|
||||
}
|
||||
cur = toks;
|
||||
while (cur)
|
||||
{
|
||||
if (cur->type == TOK_PIPE)
|
||||
{
|
||||
if (cmd->argc == 0)
|
||||
{
|
||||
*error = 1;
|
||||
free_pipeline(p);
|
||||
free(cmd);
|
||||
return NULL;
|
||||
}
|
||||
pipeline_add_cmd(p, cmd);
|
||||
cmd = command_new();
|
||||
if (!cmd)
|
||||
{
|
||||
*error = 1;
|
||||
free_pipeline(p);
|
||||
return NULL;
|
||||
}
|
||||
cur = cur->next;
|
||||
continue;
|
||||
}
|
||||
if (cur->type == TOK_REDIR_IN || cur->type == TOK_REDIR_OUT
|
||||
|| cur->type == TOK_REDIR_APPEND || cur->type == TOK_HEREDOC)
|
||||
{
|
||||
if (!cur->next || cur->next->type != TOK_WORD)
|
||||
{
|
||||
*error = 1;
|
||||
free_pipeline(p);
|
||||
free(cmd);
|
||||
return NULL;
|
||||
}
|
||||
if (cur->type == TOK_REDIR_IN)
|
||||
command_add_redir(cmd, REDIR_IN, cur->next->text);
|
||||
else if (cur->type == TOK_REDIR_OUT)
|
||||
command_add_redir(cmd, REDIR_OUT, cur->next->text);
|
||||
else if (cur->type == TOK_REDIR_APPEND)
|
||||
command_add_redir(cmd, REDIR_APPEND, cur->next->text);
|
||||
else if (cur->type == TOK_HEREDOC)
|
||||
command_add_redir(cmd, REDIR_HEREDOC, cur->next->text);
|
||||
cur = cur->next->next;
|
||||
continue;
|
||||
}
|
||||
if (cur->type == TOK_WORD)
|
||||
command_add_arg(cmd, cur->text);
|
||||
cur = cur->next;
|
||||
}
|
||||
if (cmd->argc == 0)
|
||||
{
|
||||
*error = 1;
|
||||
free_pipeline(p);
|
||||
free(cmd);
|
||||
return NULL;
|
||||
}
|
||||
pipeline_add_cmd(p, cmd);
|
||||
return p;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
#include "minishell.h"
|
||||
|
||||
int ms_is_space(int c) { return (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f'); }
|
||||
int ms_is_alpha(int c) { return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_'); }
|
||||
int ms_is_digit(int c) { return (c >= '0' && c <= '9'); }
|
||||
int ms_is_alnum(int c) { return (ms_is_alpha(c) || ms_is_digit(c)); }
|
||||
@@ -1,67 +0,0 @@
|
||||
#include "minishell.h"
|
||||
|
||||
static size_t count_parts(const char *s, char delim)
|
||||
{
|
||||
size_t count = 0;
|
||||
int in = 0;
|
||||
while (*s)
|
||||
{
|
||||
if (*s == delim)
|
||||
in = 0;
|
||||
else if (!in)
|
||||
{
|
||||
in = 1;
|
||||
count++;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
char **ms_split(const char *s, char delim)
|
||||
{
|
||||
char **out;
|
||||
size_t parts;
|
||||
size_t i = 0;
|
||||
size_t start = 0;
|
||||
size_t len = 0;
|
||||
int in = 0;
|
||||
|
||||
if (!s)
|
||||
return NULL;
|
||||
parts = count_parts(s, delim);
|
||||
out = (char **)calloc(parts + 1, sizeof(char *));
|
||||
if (!out)
|
||||
return NULL;
|
||||
while (s[i])
|
||||
{
|
||||
if (s[i] == delim)
|
||||
{
|
||||
if (in)
|
||||
{
|
||||
out[len++] = ms_strndup(s + start, i - start);
|
||||
in = 0;
|
||||
}
|
||||
}
|
||||
else if (!in)
|
||||
{
|
||||
in = 1;
|
||||
start = i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (in)
|
||||
out[len++] = ms_strndup(s + start, i - start);
|
||||
out[len] = NULL;
|
||||
return out;
|
||||
}
|
||||
|
||||
void ms_free_split(char **sp)
|
||||
{
|
||||
size_t i = 0;
|
||||
if (!sp)
|
||||
return;
|
||||
while (sp[i])
|
||||
free(sp[i++]);
|
||||
free(sp);
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
#include "minishell.h"
|
||||
|
||||
char *ms_strdup(const char *s)
|
||||
{
|
||||
char *out;
|
||||
size_t len;
|
||||
|
||||
if (!s)
|
||||
return NULL;
|
||||
len = strlen(s);
|
||||
out = (char *)malloc(len + 1);
|
||||
if (!out)
|
||||
return NULL;
|
||||
memcpy(out, s, len);
|
||||
out[len] = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
char *ms_strndup(const char *s, size_t n)
|
||||
{
|
||||
char *out;
|
||||
if (!s)
|
||||
return NULL;
|
||||
out = (char *)malloc(n + 1);
|
||||
if (!out)
|
||||
return NULL;
|
||||
memcpy(out, s, n);
|
||||
out[n] = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
char *ms_substr(const char *s, size_t start, size_t len)
|
||||
{
|
||||
size_t slen;
|
||||
if (!s)
|
||||
return NULL;
|
||||
slen = strlen(s);
|
||||
if (start >= slen)
|
||||
return ms_strdup("");
|
||||
if (start + len > slen)
|
||||
len = slen - start;
|
||||
return ms_strndup(s + start, len);
|
||||
}
|
||||
|
||||
char *ms_strjoin(const char *a, const char *b)
|
||||
{
|
||||
size_t la;
|
||||
size_t lb;
|
||||
char *out;
|
||||
|
||||
if (!a || !b)
|
||||
return NULL;
|
||||
la = strlen(a);
|
||||
lb = strlen(b);
|
||||
out = (char *)malloc(la + lb + 1);
|
||||
if (!out)
|
||||
return NULL;
|
||||
memcpy(out, a, la);
|
||||
memcpy(out + la, b, lb);
|
||||
out[la + lb] = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
char *ms_strjoin3(const char *a, const char *b, const char *c)
|
||||
{
|
||||
char *ab;
|
||||
char *abc;
|
||||
|
||||
ab = ms_strjoin(a, b);
|
||||
if (!ab)
|
||||
return NULL;
|
||||
abc = ms_strjoin(ab, c);
|
||||
free(ab);
|
||||
return abc;
|
||||
}
|
||||
|
||||
char *ms_itoa(int n)
|
||||
{
|
||||
char buf[32];
|
||||
int i;
|
||||
int neg;
|
||||
long nb;
|
||||
|
||||
nb = n;
|
||||
neg = (nb < 0);
|
||||
if (neg)
|
||||
nb = -nb;
|
||||
i = 30;
|
||||
buf[31] = '\0';
|
||||
if (nb == 0)
|
||||
buf[i--] = '0';
|
||||
while (nb > 0)
|
||||
{
|
||||
buf[i--] = (char)('0' + (nb % 10));
|
||||
nb /= 10;
|
||||
}
|
||||
if (neg)
|
||||
buf[i--] = '-';
|
||||
return ms_strdup(&buf[i + 1]);
|
||||
}
|
||||
@@ -12,23 +12,53 @@
|
||||
|
||||
#include "builtins.h"
|
||||
|
||||
u_int8_t set_builtins(
|
||||
t_minishell *minishell
|
||||
) {
|
||||
minishell->builtins
|
||||
= ft_hashmap_new(4, ft_hashmap_hashstr, ft_hashmap_strcmp);
|
||||
if (minishell->builtins == NULL)
|
||||
static uint8_t register_builtin(
|
||||
t_minishell *minishell,
|
||||
const char *name,
|
||||
t_builtin_func builtin
|
||||
)
|
||||
{
|
||||
char *key;
|
||||
|
||||
key = ft_strdup(name);
|
||||
if (key == NULL)
|
||||
return (0);
|
||||
ft_hashmap_put(minishell->builtins, ft_strdup("cd"), builtin_cd);
|
||||
ft_hashmap_put(minishell->builtins, ft_strdup("echo"), builtin_echo);
|
||||
ft_hashmap_put(minishell->builtins, ft_strdup("exit"), builtin_exit);
|
||||
ft_hashmap_put(minishell->builtins, ft_strdup("pwd"), builtin_pwd);
|
||||
ft_hashmap_put(minishell->builtins, key, builtin);
|
||||
if (!ft_hashmap_contains_key(minishell->builtins, name))
|
||||
{
|
||||
free(key);
|
||||
return (0);
|
||||
}
|
||||
return (1);
|
||||
}
|
||||
|
||||
u_int8_t is_builtin(
|
||||
uint8_t set_builtins(
|
||||
t_minishell *minishell
|
||||
) {
|
||||
minishell->builtins
|
||||
= ft_hashmap_new(7, ft_hashmap_hashstr, ft_hashmap_strcmp);
|
||||
if (minishell->builtins == NULL)
|
||||
return (0);
|
||||
if (!register_builtin(minishell, "cd", builtin_cd)
|
||||
|| !register_builtin(minishell, "echo", builtin_echo)
|
||||
|| !register_builtin(minishell, "env", builtin_env)
|
||||
|| !register_builtin(minishell, "exit", builtin_exit)
|
||||
|| !register_builtin(minishell, "export", builtin_export)
|
||||
|| !register_builtin(minishell, "pwd", builtin_pwd)
|
||||
|| !register_builtin(minishell, "unset", builtin_unset))
|
||||
{
|
||||
ft_hashmap_clear_keys(&minishell->builtins);
|
||||
return (0);
|
||||
}
|
||||
return (1);
|
||||
}
|
||||
|
||||
uint8_t is_builtin(
|
||||
const char *command_name,
|
||||
t_minishell *minishell
|
||||
) {
|
||||
if (command_name == NULL || minishell == NULL
|
||||
|| minishell->builtins == NULL)
|
||||
return (0);
|
||||
return (ft_hashmap_contains_key(minishell->builtins, command_name));
|
||||
}
|
||||
|
||||
@@ -12,43 +12,104 @@
|
||||
|
||||
#include "builtins.h"
|
||||
|
||||
static u_int8_t handle_error(t_command cmd, t_minishell *msh, char *path);
|
||||
static uint8_t handle_error(void);
|
||||
static void update_pwd_vars(t_minishell *msh, char *oldpwd);
|
||||
static char *get_path_from_env(
|
||||
t_minishell *msh,
|
||||
const char *env_name,
|
||||
const char *error,
|
||||
uint8_t *status
|
||||
);
|
||||
static char *resolve_cd_path(
|
||||
t_command cmd,
|
||||
t_minishell *msh,
|
||||
uint8_t *status
|
||||
);
|
||||
|
||||
u_int8_t builtin_cd(
|
||||
uint8_t builtin_cd(
|
||||
t_command cmd,
|
||||
t_minishell *msh
|
||||
){
|
||||
char *path;
|
||||
char *oldpwd;
|
||||
uint8_t status;
|
||||
bool show_pwd;
|
||||
|
||||
if (cmd.argc > 2)
|
||||
{
|
||||
ft_eputendl("minishell: cd: too many arguments");
|
||||
return (2);
|
||||
}
|
||||
else if (cmd.argc == 1)
|
||||
path = get_env("HOME", msh);
|
||||
else
|
||||
path = cmd.argv[1];
|
||||
path = resolve_cd_path(cmd, msh, &status);
|
||||
if (status != EXIT_SUCCESS)
|
||||
return (status);
|
||||
show_pwd = (cmd.argc == 2 && ft_strcmp(cmd.argv[1], "-") == 0);
|
||||
oldpwd = getcwd(NULL, 0);
|
||||
if (chdir(path) == -1)
|
||||
return (handle_error(cmd, msh, path));
|
||||
{
|
||||
free(oldpwd);
|
||||
return (handle_error());
|
||||
}
|
||||
update_pwd_vars(msh, oldpwd);
|
||||
if (show_pwd && get_env("PWD", msh) != NULL)
|
||||
ft_putendl(get_env("PWD", msh));
|
||||
free(oldpwd);
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
static u_int8_t handle_error(
|
||||
static uint8_t handle_error(void)
|
||||
{
|
||||
perror("minishell: cd");
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static void update_pwd_vars(
|
||||
t_minishell *msh,
|
||||
char *oldpwd
|
||||
){
|
||||
char *newpwd;
|
||||
|
||||
if (oldpwd != NULL)
|
||||
set_env("OLDPWD", oldpwd, msh);
|
||||
newpwd = getcwd(NULL, 0);
|
||||
if (newpwd != NULL)
|
||||
{
|
||||
set_env("PWD", newpwd, msh);
|
||||
free(newpwd);
|
||||
}
|
||||
}
|
||||
|
||||
static char *resolve_cd_path(
|
||||
t_command cmd,
|
||||
t_minishell *msh,
|
||||
char *path
|
||||
uint8_t *status
|
||||
){
|
||||
u_int8_t exit_code;
|
||||
|
||||
(void)msh;
|
||||
exit_code = 0;
|
||||
if (access(path, F_OK) != -1)
|
||||
// No such file or directory
|
||||
exit_code = 1;
|
||||
else if (access(path, X_OK) == -1)
|
||||
// Permission denied
|
||||
exit_code = 2;
|
||||
perror(cmd.argv[0]);
|
||||
return (exit_code);
|
||||
if (cmd.argc > 2)
|
||||
{
|
||||
ft_eputendl("minishell: cd: too many arguments");
|
||||
*status = EXIT_FAILURE;
|
||||
return (NULL);
|
||||
}
|
||||
if (cmd.argc == 2 && ft_strcmp(cmd.argv[1], "-") == 0)
|
||||
return (get_path_from_env(msh, "OLDPWD",
|
||||
"minishell: cd: OLDPWD not set", status));
|
||||
if (cmd.argc == 1)
|
||||
return (get_path_from_env(msh, "HOME",
|
||||
"minishell: cd: HOME not set", status));
|
||||
*status = EXIT_SUCCESS;
|
||||
return (cmd.argv[1]);
|
||||
}
|
||||
|
||||
static char *get_path_from_env(
|
||||
t_minishell *msh,
|
||||
const char *env_name,
|
||||
const char *error,
|
||||
uint8_t *status
|
||||
){
|
||||
char *path;
|
||||
|
||||
path = get_env(env_name, msh);
|
||||
if (path == NULL)
|
||||
{
|
||||
ft_eputendl((char *)error);
|
||||
*status = EXIT_FAILURE;
|
||||
return (NULL);
|
||||
}
|
||||
*status = EXIT_SUCCESS;
|
||||
return (path);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "builtins.h"
|
||||
#include "echo_def.h"
|
||||
|
||||
u_int8_t builtin_echo(
|
||||
uint8_t builtin_echo(
|
||||
t_command cmd,
|
||||
t_minishell *msh
|
||||
){
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "echo_def.h"
|
||||
|
||||
static void assign_flags(t_args *args, t_command cmd, size_t *i);
|
||||
static u_int8_t is_valid_flag(t_args *args, char *flag);
|
||||
static uint8_t is_valid_flag(t_args *args, char *flag);
|
||||
|
||||
static void assign_default_flags(
|
||||
t_args *args
|
||||
@@ -51,7 +51,7 @@ static void assign_flags(
|
||||
}
|
||||
}
|
||||
|
||||
static u_int8_t is_valid_flag(
|
||||
static uint8_t is_valid_flag(
|
||||
t_args *args,
|
||||
char *flag
|
||||
){
|
||||
|
||||
58
src/builtins/env/env.c
vendored
Normal file
58
src/builtins/env/env.c
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* env.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/09 22:05:00 by codex #+# #+# */
|
||||
/* Updated: 2026/02/09 22:05:00 by codex ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "builtins.h"
|
||||
|
||||
static void print_entry(t_map_entry *entry);
|
||||
static void print_env(t_minishell *msh);
|
||||
|
||||
uint8_t builtin_env(
|
||||
t_command cmd,
|
||||
t_minishell *msh
|
||||
)
|
||||
{
|
||||
if (cmd.argc > 1)
|
||||
{
|
||||
ft_eputendl("minishell: env: too many arguments");
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
print_env(msh);
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
static void print_entry(
|
||||
t_map_entry *entry
|
||||
)
|
||||
{
|
||||
ft_putstr((char *)entry->key);
|
||||
ft_putchar('=');
|
||||
if (entry->value != NULL)
|
||||
ft_putstr((char *)entry->value);
|
||||
ft_putchar('\n');
|
||||
}
|
||||
|
||||
static void print_env(
|
||||
t_minishell *msh
|
||||
)
|
||||
{
|
||||
t_list *entries;
|
||||
t_list *current;
|
||||
|
||||
entries = ft_hashmap_entries(msh->variables.environment);
|
||||
current = entries;
|
||||
while (current != NULL)
|
||||
{
|
||||
print_entry((t_map_entry *)current->content);
|
||||
current = current->next;
|
||||
}
|
||||
ft_lstclear_nodes(&entries);
|
||||
}
|
||||
@@ -11,36 +11,45 @@
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "builtins.h"
|
||||
#include <stdint.h>
|
||||
|
||||
static bool exit_arg_is_invalid(const char *arg)
|
||||
{
|
||||
if (arg == NULL)
|
||||
return (true);
|
||||
if ((*arg == '+' || *arg == '-') && arg[1] == '\0')
|
||||
return (true);
|
||||
if (!ft_strisnum(arg))
|
||||
return (true);
|
||||
return (false);
|
||||
}
|
||||
|
||||
uint8_t builtin_exit(
|
||||
t_command cmd,
|
||||
t_minishell *msh
|
||||
)
|
||||
{
|
||||
uint8_t exit_status;
|
||||
|
||||
ft_eputendl("exit");
|
||||
if (isatty(STDIN_FILENO))
|
||||
ft_eputendl("exit");
|
||||
if (cmd.argc == 1)
|
||||
exit_status = msh->exit_status;
|
||||
else if (!ft_strisnum(cmd.argv[1]))
|
||||
{
|
||||
ft_eprintf(
|
||||
"minishell: exit: %s: numeric argument required\n",
|
||||
msh->exit = 1;
|
||||
return (msh->exit_status);
|
||||
}
|
||||
if (exit_arg_is_invalid(cmd.argv[1]))
|
||||
{
|
||||
ft_eprintf("minishell: exit: %s: numeric argument required\n",
|
||||
cmd.argv[1]);
|
||||
return (2);
|
||||
msh->exit = 1;
|
||||
msh->exit_status = 2;
|
||||
return (msh->exit_status);
|
||||
}
|
||||
else if (cmd.argc > 2)
|
||||
if (cmd.argc > 2)
|
||||
{
|
||||
ft_eputendl("exit: too many arguments");
|
||||
return (2);
|
||||
ft_eputendl("minishell: exit: too many arguments");
|
||||
msh->exit_status = EXIT_FAILURE;
|
||||
return (msh->exit_status);
|
||||
}
|
||||
else
|
||||
exit_status = (uint8_t)ft_atol(cmd.argv[1]);
|
||||
|
||||
printf("builtin_exit: exit_status=%d\n", exit_status); // Debug print
|
||||
|
||||
msh->exit = 1;
|
||||
msh->exit_status = exit_status;
|
||||
return (exit_status);
|
||||
msh->exit_status = (uint8_t)ft_atol(cmd.argv[1]);
|
||||
return (msh->exit_status);
|
||||
}
|
||||
|
||||
79
src/builtins/export/export.c
Normal file
79
src/builtins/export/export.c
Normal file
@@ -0,0 +1,79 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* export.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/09 22:05:00 by codex #+# #+# */
|
||||
/* Updated: 2026/02/14 02:02:14 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "builtins.h"
|
||||
|
||||
static uint8_t is_valid_identifier(const char *arg, size_t name_len);
|
||||
static uint8_t export_one(char *arg, t_minishell *msh);
|
||||
|
||||
uint8_t builtin_export(
|
||||
t_command cmd,
|
||||
t_minishell *msh
|
||||
)
|
||||
{
|
||||
uint8_t status;
|
||||
size_t i;
|
||||
|
||||
if (cmd.argc == 1)
|
||||
return (builtin_env(cmd, msh));
|
||||
status = EXIT_SUCCESS;
|
||||
i = 0;
|
||||
while (cmd.argv[++i] != NULL)
|
||||
if (export_one(cmd.argv[i], msh) != EXIT_SUCCESS)
|
||||
status = EXIT_FAILURE;
|
||||
return (status);
|
||||
}
|
||||
|
||||
static uint8_t is_valid_identifier(
|
||||
const char *arg,
|
||||
size_t name_len
|
||||
)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (name_len == 0 || arg == NULL)
|
||||
return (0);
|
||||
if (!ft_isalpha(arg[0]) && arg[0] != '_')
|
||||
return (0);
|
||||
i = 0;
|
||||
while (++i < name_len)
|
||||
if (!ft_isalnum(arg[i]) && arg[i] != '_')
|
||||
return (0);
|
||||
return (1);
|
||||
}
|
||||
|
||||
static uint8_t export_one(
|
||||
char *arg,
|
||||
t_minishell *msh
|
||||
)
|
||||
{
|
||||
char *eq_pos;
|
||||
char *name;
|
||||
size_t name_len;
|
||||
|
||||
eq_pos = ft_strchr(arg, '=');
|
||||
name_len = ft_strlen(arg);
|
||||
if (eq_pos != NULL)
|
||||
name_len = (size_t)(eq_pos - arg);
|
||||
if (!is_valid_identifier(arg, name_len))
|
||||
return (ft_eprintf("minishell: export: `%s': not a valid identifier\n",
|
||||
arg), EXIT_FAILURE);
|
||||
name = ft_substr(arg, 0, name_len);
|
||||
if (name == NULL)
|
||||
return (EXIT_FAILURE);
|
||||
if (eq_pos != NULL)
|
||||
set_var(name, eq_pos + 1, msh);
|
||||
else
|
||||
set_var(name, "", msh);
|
||||
free(name);
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
@@ -12,15 +12,21 @@
|
||||
|
||||
#include "builtins.h"
|
||||
|
||||
u_int8_t builtin_pwd(
|
||||
uint8_t builtin_pwd(
|
||||
t_command cmd,
|
||||
t_minishell *msh
|
||||
){
|
||||
char cwd[PATH_MAX];
|
||||
char *cwd;
|
||||
|
||||
(void)cmd;
|
||||
(void)msh;
|
||||
if (getcwd(cwd, PATH_MAX) != NULL)
|
||||
ft_putendl(cwd);
|
||||
cwd = getcwd(NULL, 0);
|
||||
if (cwd == NULL)
|
||||
{
|
||||
perror("minishell: pwd");
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
ft_putendl(cwd);
|
||||
free(cwd);
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
61
src/builtins/unset/unset.c
Normal file
61
src/builtins/unset/unset.c
Normal file
@@ -0,0 +1,61 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* unset.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/09 22:05:00 by codex #+# #+# */
|
||||
/* Updated: 2026/02/09 22:05:00 by codex ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "builtins.h"
|
||||
|
||||
static uint8_t is_valid_identifier(const char *arg);
|
||||
static uint8_t unset_one(char *arg, t_minishell *msh);
|
||||
|
||||
uint8_t builtin_unset(
|
||||
t_command cmd,
|
||||
t_minishell *msh
|
||||
)
|
||||
{
|
||||
uint8_t status;
|
||||
size_t i;
|
||||
|
||||
status = EXIT_SUCCESS;
|
||||
i = 0;
|
||||
while (cmd.argv[++i] != NULL)
|
||||
if (unset_one(cmd.argv[i], msh) != EXIT_SUCCESS)
|
||||
status = EXIT_FAILURE;
|
||||
return (status);
|
||||
}
|
||||
|
||||
static uint8_t is_valid_identifier(
|
||||
const char *arg
|
||||
)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (arg == NULL || *arg == '\0')
|
||||
return (0);
|
||||
if (!ft_isalpha(arg[0]) && arg[0] != '_')
|
||||
return (0);
|
||||
i = 0;
|
||||
while (arg[++i] != '\0')
|
||||
if (!ft_isalnum(arg[i]) && arg[i] != '_')
|
||||
return (0);
|
||||
return (1);
|
||||
}
|
||||
|
||||
static uint8_t unset_one(
|
||||
char *arg,
|
||||
t_minishell *msh
|
||||
)
|
||||
{
|
||||
if (!is_valid_identifier(arg))
|
||||
return (ft_eprintf("minishell: unset: `%s': not a valid identifier\n",
|
||||
arg), EXIT_FAILURE);
|
||||
unset_env(arg, msh);
|
||||
return (EXIT_SUCCESS);
|
||||
}
|
||||
@@ -9,4 +9,3 @@
|
||||
/* Updated: 2026/02/09 18:40:04 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
|
||||
66
src/core/signals.c
Normal file
66
src/core/signals.c
Normal file
@@ -0,0 +1,66 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* signals.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/13 00:00:00 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/13 00:00:00 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static int g_signal = 0;
|
||||
|
||||
static void sigint_handler(int signal)
|
||||
{
|
||||
g_signal = signal;
|
||||
write(STDOUT_FILENO, "\n", 1);
|
||||
rl_on_new_line();
|
||||
rl_replace_line("", 0);
|
||||
rl_redisplay();
|
||||
}
|
||||
|
||||
void minishell_set_interactive_signals(void)
|
||||
{
|
||||
struct sigaction action;
|
||||
|
||||
ft_bzero(&action, sizeof(action));
|
||||
action.sa_handler = sigint_handler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
sigaction(SIGINT, &action, NULL);
|
||||
action.sa_handler = SIG_IGN;
|
||||
sigaction(SIGQUIT, &action, NULL);
|
||||
}
|
||||
|
||||
void minishell_set_execution_signals(void)
|
||||
{
|
||||
struct sigaction action;
|
||||
|
||||
ft_bzero(&action, sizeof(action));
|
||||
action.sa_handler = SIG_IGN;
|
||||
sigemptyset(&action.sa_mask);
|
||||
sigaction(SIGINT, &action, NULL);
|
||||
sigaction(SIGQUIT, &action, NULL);
|
||||
}
|
||||
|
||||
void minishell_set_child_signals(void)
|
||||
{
|
||||
struct sigaction action;
|
||||
|
||||
ft_bzero(&action, sizeof(action));
|
||||
action.sa_handler = SIG_DFL;
|
||||
sigemptyset(&action.sa_mask);
|
||||
sigaction(SIGINT, &action, NULL);
|
||||
sigaction(SIGQUIT, &action, NULL);
|
||||
}
|
||||
|
||||
bool minishell_consume_sigint(void)
|
||||
{
|
||||
if (g_signal != SIGINT)
|
||||
return (false);
|
||||
g_signal = 0;
|
||||
return (true);
|
||||
}
|
||||
@@ -28,4 +28,4 @@ void syntax_error_unexpected_token(
|
||||
void malloc_error(void)
|
||||
{
|
||||
ft_eprintf("minishell: %s\n", strerror(ENOMEM));
|
||||
}
|
||||
}
|
||||
|
||||
90
src/executor/command_exec.c
Normal file
90
src/executor/command_exec.c
Normal file
@@ -0,0 +1,90 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* command_exec.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/11 00:00:00 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/14 01:34:37 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "executor.h"
|
||||
#include "builtins.h"
|
||||
#include <errno.h>
|
||||
|
||||
static uint8_t resolve_execve_status(void)
|
||||
{
|
||||
if (errno == ENOENT)
|
||||
return (127);
|
||||
if (errno == EACCES || errno == ENOEXEC || errno == EISDIR)
|
||||
return (126);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static uint8_t execute_builtin(
|
||||
const t_command *command,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
const t_builtin_func builtin
|
||||
= ft_hashmap_get(minishell->builtins, command->argv[0]);
|
||||
|
||||
return (builtin(*command, minishell));
|
||||
}
|
||||
|
||||
static void handle_execve_error(
|
||||
const t_command *command,
|
||||
char **envp
|
||||
)
|
||||
{
|
||||
uint8_t exit_status;
|
||||
size_t i;
|
||||
|
||||
exit_status = resolve_execve_status();
|
||||
i = 0;
|
||||
while (envp[i] != NULL)
|
||||
free(envp[i++]);
|
||||
free(envp);
|
||||
perror(command->path);
|
||||
exit(exit_status);
|
||||
}
|
||||
|
||||
static void execute_external_command(
|
||||
const t_command *command,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
char **envp;
|
||||
|
||||
envp = get_envp(minishell);
|
||||
if (envp == NULL)
|
||||
{
|
||||
perror("get_envp");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
execve(command->path, command->argv, envp);
|
||||
handle_execve_error(command, envp);
|
||||
}
|
||||
|
||||
uint8_t executor_execute_command(
|
||||
t_command *command,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
if (command == NULL || command->argv == NULL || command->argv[0] == NULL)
|
||||
return (EXIT_SUCCESS);
|
||||
if (executor_is_builtin_command(command, minishell))
|
||||
return (execute_builtin(command, minishell));
|
||||
if (command->path != NULL)
|
||||
free(command->path);
|
||||
command->path = executor_resolve_command_path(command, minishell);
|
||||
if (command->path == NULL)
|
||||
{
|
||||
ft_eprintf("minishell: %s: command not found\n", command->argv[0]);
|
||||
return (127);
|
||||
}
|
||||
execute_external_command(command, minishell);
|
||||
return (EXIT_FAILURE);
|
||||
}
|
||||
54
src/executor/command_free.c
Normal file
54
src/executor/command_free.c
Normal file
@@ -0,0 +1,54 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* command_free.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/13 00:00:00 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/13 00:00:00 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "executor.h"
|
||||
|
||||
static void cmdfree_argv(
|
||||
char **argv
|
||||
)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (argv == NULL)
|
||||
return ;
|
||||
i = 0;
|
||||
while (argv[i] != NULL)
|
||||
{
|
||||
free(argv[i]);
|
||||
i++;
|
||||
}
|
||||
free(argv);
|
||||
}
|
||||
|
||||
static void cmdfree_redirection(
|
||||
t_redirection *redirection
|
||||
)
|
||||
{
|
||||
if (redirection == NULL)
|
||||
return ;
|
||||
free(redirection->target);
|
||||
free(redirection);
|
||||
}
|
||||
|
||||
void executor_cmdfree(
|
||||
t_command *command
|
||||
)
|
||||
{
|
||||
if (command == NULL)
|
||||
return ;
|
||||
cmdfree_argv(command->argv);
|
||||
free(command->path);
|
||||
ft_lstclear(&command->redirections,
|
||||
(void (*)(void *))cmdfree_redirection);
|
||||
ft_lstclear(&command->heredocs, (void (*)(void *))cmdfree_redirection);
|
||||
free(command);
|
||||
}
|
||||
@@ -6,198 +6,79 @@
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/08 19:10:47 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/08 21:32:03 by sede-san ### ########.fr */
|
||||
/* Updated: 2026/02/11 00:00:00 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "executor.h"
|
||||
#include "builtins.h"
|
||||
|
||||
static inline uint8_t execute_builtin(
|
||||
const t_command *command,
|
||||
static void init_exec_state(
|
||||
t_exec_state *state,
|
||||
t_list *command_list
|
||||
)
|
||||
{
|
||||
state->exit_status = EXIT_SUCCESS;
|
||||
state->pipeline.prev_read_fd = -1;
|
||||
state->current_command = command_list;
|
||||
state->last_child_pid = -1;
|
||||
}
|
||||
|
||||
static bool fork_current_command(
|
||||
t_exec_state *state,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
const t_builtin_func builtin
|
||||
= ft_hashmap_get(minishell->builtins, command->path);
|
||||
pid_t pid;
|
||||
|
||||
return (builtin(*command, minishell));
|
||||
}
|
||||
|
||||
static void handle_execve_error(
|
||||
const t_command *command,
|
||||
char **envp
|
||||
)
|
||||
{
|
||||
free_envp(envp);
|
||||
perror(command->path);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static void execute_external_command(
|
||||
const t_command *command,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
char **envp;
|
||||
|
||||
envp = get_envp(minishell);
|
||||
if (envp == NULL)
|
||||
{
|
||||
perror("get_envp");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
execve(command->path, command->argv, envp);
|
||||
handle_execve_error(command, envp);
|
||||
}
|
||||
|
||||
static uint8_t execute_command(
|
||||
const t_command *command,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
if (is_builtin(command->path, minishell))
|
||||
return (execute_builtin(command, minishell));
|
||||
else
|
||||
{
|
||||
execute_external_command(command, minishell);
|
||||
return (EXIT_FAILURE); //! should never reach here
|
||||
}
|
||||
}
|
||||
|
||||
static void cmdfree_argv(
|
||||
char **argv
|
||||
)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (argv == NULL)
|
||||
return ;
|
||||
i = 0;
|
||||
while (argv[i] != NULL)
|
||||
{
|
||||
free(argv[i]);
|
||||
i++;
|
||||
}
|
||||
free(argv);
|
||||
}
|
||||
|
||||
static void cmdfree(
|
||||
t_command *command
|
||||
)
|
||||
{
|
||||
if (command == NULL)
|
||||
return ;
|
||||
cmdfree_argv(command->argv);
|
||||
free(command->path);
|
||||
free(command);
|
||||
}
|
||||
|
||||
static int create_pipe_if_needed(
|
||||
t_list *current_command,
|
||||
t_pipeline *pipeline
|
||||
)
|
||||
{
|
||||
if (!current_command->next)
|
||||
return (0);
|
||||
if (pipe(pipeline->pipefd) == PIPE_ERROR)
|
||||
return (perror("pipe"), PIPE_ERROR);
|
||||
return (0);
|
||||
}
|
||||
|
||||
static bool is_fork_required(
|
||||
t_list *current_command,
|
||||
t_minishell *minishell
|
||||
) {
|
||||
const t_command *command = current_command->content;
|
||||
|
||||
return (current_command->next != NULL
|
||||
|| !is_builtin(command->path, minishell));
|
||||
}
|
||||
|
||||
static pid_t fork_process(
|
||||
t_list *current_command,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
pid_t pid;
|
||||
|
||||
pid = 0;
|
||||
if (is_fork_required(current_command, minishell))
|
||||
pid = fork();
|
||||
pid = fork();
|
||||
if (pid == FORK_ERROR)
|
||||
perror("fork");
|
||||
return (pid);
|
||||
return (perror("fork"), state->exit_status = EXIT_FAILURE, false);
|
||||
if (pid == 0)
|
||||
executor_child_process(state->current_command, &state->pipeline,
|
||||
minishell);
|
||||
state->last_child_pid = pid;
|
||||
return (true);
|
||||
}
|
||||
|
||||
static void setup_child_input(
|
||||
t_pipeline *pipeline
|
||||
)
|
||||
{
|
||||
if (pipeline->prev_read_fd != -1)
|
||||
{
|
||||
dup2(pipeline->prev_read_fd, STDIN_FILENO);
|
||||
close(pipeline->prev_read_fd);
|
||||
}
|
||||
}
|
||||
|
||||
static void setup_child_output(
|
||||
t_list *current_command,
|
||||
t_pipeline *pipeline
|
||||
)
|
||||
{
|
||||
if (current_command->next)
|
||||
{
|
||||
dup2(pipeline->pipefd[WRITE_PIPE], STDOUT_FILENO);
|
||||
close(pipeline->pipefd[READ_PIPE]);
|
||||
close(pipeline->pipefd[WRITE_PIPE]);
|
||||
}
|
||||
}
|
||||
|
||||
static void child_process(
|
||||
t_list *current_command,
|
||||
t_pipeline *pipeline,
|
||||
static void run_command_in_parent(
|
||||
t_exec_state *state,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
uint8_t exit_status;
|
||||
const t_command *command = current_command->content;
|
||||
t_command *command;
|
||||
int saved_stdin;
|
||||
int saved_stdout;
|
||||
|
||||
setup_child_input(pipeline);
|
||||
setup_child_output(current_command, pipeline);
|
||||
exit_status = execute_command(command, minishell);
|
||||
if (is_fork_required(current_command, minishell))
|
||||
exit(exit_status);
|
||||
command = state->current_command->content;
|
||||
if (!executor_apply_redirections(command, &saved_stdin, &saved_stdout))
|
||||
state->exit_status = EXIT_FAILURE;
|
||||
else
|
||||
state->exit_status = executor_execute_command(command, minishell);
|
||||
executor_restore_redirections(saved_stdin, saved_stdout);
|
||||
}
|
||||
|
||||
static void parent_cleanup(
|
||||
t_list *current_command,
|
||||
t_pipeline *pipeline
|
||||
static bool run_current_command(
|
||||
t_exec_state *state,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
if (pipeline->prev_read_fd != -1)
|
||||
close(pipeline->prev_read_fd);
|
||||
if (current_command->next)
|
||||
bool should_fork;
|
||||
|
||||
if (executor_create_pipe_if_needed(state->current_command,
|
||||
&state->pipeline) == PIPE_ERROR)
|
||||
return (state->exit_status = EXIT_FAILURE, false);
|
||||
should_fork = executor_is_fork_required(state->current_command,
|
||||
&state->pipeline, minishell);
|
||||
if (should_fork)
|
||||
{
|
||||
close(pipeline->pipefd[WRITE_PIPE]);
|
||||
pipeline->prev_read_fd = pipeline->pipefd[READ_PIPE];
|
||||
if (!fork_current_command(state, minishell))
|
||||
return (false);
|
||||
}
|
||||
else
|
||||
pipeline->prev_read_fd = -1;
|
||||
}
|
||||
|
||||
static uint8_t wait_for_children(void)
|
||||
{
|
||||
uint8_t exit_status;
|
||||
int status;
|
||||
|
||||
exit_status = EXIT_SUCCESS;
|
||||
while (wait(&status) > 0)
|
||||
{
|
||||
if (WIFEXITED(status))
|
||||
exit_status = WEXITSTATUS(status);
|
||||
}
|
||||
return (exit_status);
|
||||
run_command_in_parent(state, minishell);
|
||||
executor_parent_cleanup(state->current_command, &state->pipeline);
|
||||
state->current_command = state->current_command->next;
|
||||
return (true);
|
||||
}
|
||||
|
||||
uint8_t execute(
|
||||
@@ -205,26 +86,19 @@ uint8_t execute(
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
uint8_t exit_status;
|
||||
t_pipeline pipeline;
|
||||
t_list *current_command;
|
||||
pid_t pid;
|
||||
t_exec_state state;
|
||||
|
||||
pipeline.prev_read_fd = -1;
|
||||
current_command = command_list;
|
||||
while (current_command)
|
||||
init_exec_state(&state, command_list);
|
||||
minishell_set_execution_signals();
|
||||
while (state.current_command)
|
||||
{
|
||||
if (create_pipe_if_needed(current_command, &pipeline) == PIPE_ERROR)
|
||||
if (!run_current_command(&state, minishell))
|
||||
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);
|
||||
if (state.last_child_pid > 0)
|
||||
state.exit_status = executor_wait_for_children(state.last_child_pid);
|
||||
minishell_set_interactive_signals();
|
||||
minishell->exit_status = state.exit_status;
|
||||
ft_lstclear(&command_list, (void (*)(void *))executor_cmdfree);
|
||||
return (state.exit_status);
|
||||
}
|
||||
|
||||
78
src/executor/path_resolver.c
Normal file
78
src/executor/path_resolver.c
Normal file
@@ -0,0 +1,78 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* path_resolver.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/11 00:00:00 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/14 01:17:01 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "executor.h"
|
||||
#include "variables.h"
|
||||
|
||||
static bool is_path_explicit(
|
||||
const char *command_name
|
||||
)
|
||||
{
|
||||
return (command_name != NULL && ft_strchr(command_name, '/') != NULL);
|
||||
}
|
||||
|
||||
static char *resolve_path_from_env(
|
||||
const char *command_name,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
char *command_path;
|
||||
char **path_env;
|
||||
char *path_value;
|
||||
size_t i;
|
||||
|
||||
path_value = get_env("PATH", minishell);
|
||||
if (path_value == NULL)
|
||||
return (NULL);
|
||||
path_env = ft_split(path_value, ':');
|
||||
if (path_env == NULL)
|
||||
return (NULL);
|
||||
command_path = NULL;
|
||||
i = -1;
|
||||
while (!command_path && path_env[++i] != NULL)
|
||||
{
|
||||
command_path = ft_strnjoin(3, path_env[i], "/", command_name);
|
||||
if (command_path != NULL && access(command_path, X_OK) != EXIT_SUCCESS)
|
||||
{
|
||||
free(command_path);
|
||||
command_path = NULL;
|
||||
}
|
||||
}
|
||||
ft_free_split(path_env);
|
||||
return (command_path);
|
||||
}
|
||||
|
||||
static char *resolve_explicit_path(
|
||||
const char *command_name
|
||||
)
|
||||
{
|
||||
if (access(command_name, F_OK) != EXIT_SUCCESS)
|
||||
return (NULL);
|
||||
if (access(command_name, X_OK) != EXIT_SUCCESS)
|
||||
return (NULL);
|
||||
return (ft_strdup(command_name));
|
||||
}
|
||||
|
||||
char *executor_resolve_command_path(
|
||||
const t_command *command,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
const char *command_name;
|
||||
|
||||
if (command == NULL || command->argv == NULL || command->argv[0] == NULL)
|
||||
return (NULL);
|
||||
command_name = command->argv[0];
|
||||
if (is_path_explicit(command_name))
|
||||
return (resolve_explicit_path(command_name));
|
||||
return (resolve_path_from_env(command_name, minishell));
|
||||
}
|
||||
73
src/executor/pipeline_helpers.c
Normal file
73
src/executor/pipeline_helpers.c
Normal file
@@ -0,0 +1,73 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* pipeline_helpers.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/11 00:00:00 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/11 00:00:00 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "executor.h"
|
||||
#include "builtins.h"
|
||||
|
||||
int executor_create_pipe_if_needed(
|
||||
t_list *current_command,
|
||||
t_pipeline *pipeline
|
||||
)
|
||||
{
|
||||
if (!current_command->next)
|
||||
return (0);
|
||||
if (pipe(pipeline->pipefd) == PIPE_ERROR)
|
||||
return (perror("pipe"), PIPE_ERROR);
|
||||
return (0);
|
||||
}
|
||||
|
||||
bool executor_is_builtin_command(
|
||||
const t_command *command,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
if (command == NULL || command->argv == NULL || command->argv[0] == NULL)
|
||||
return (false);
|
||||
return (is_builtin(command->argv[0], minishell));
|
||||
}
|
||||
|
||||
bool executor_is_fork_required(
|
||||
t_list *current_command,
|
||||
const t_pipeline *pipeline,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
const t_command *command;
|
||||
|
||||
command = current_command->content;
|
||||
return (pipeline->prev_read_fd != -1 || current_command->next != NULL
|
||||
|| !executor_is_builtin_command(command, minishell));
|
||||
}
|
||||
|
||||
void executor_setup_child_input(
|
||||
t_pipeline *pipeline
|
||||
)
|
||||
{
|
||||
if (pipeline->prev_read_fd != -1)
|
||||
{
|
||||
dup2(pipeline->prev_read_fd, STDIN_FILENO);
|
||||
close(pipeline->prev_read_fd);
|
||||
}
|
||||
}
|
||||
|
||||
void executor_setup_child_output(
|
||||
t_list *current_command,
|
||||
t_pipeline *pipeline
|
||||
)
|
||||
{
|
||||
if (current_command->next)
|
||||
{
|
||||
dup2(pipeline->pipefd[WRITE_PIPE], STDOUT_FILENO);
|
||||
close(pipeline->pipefd[READ_PIPE]);
|
||||
close(pipeline->pipefd[WRITE_PIPE]);
|
||||
}
|
||||
}
|
||||
95
src/executor/process_helpers.c
Normal file
95
src/executor/process_helpers.c
Normal file
@@ -0,0 +1,95 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* process_helpers.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/11 00:00:00 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/11 00:00:00 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "executor.h"
|
||||
|
||||
static void set_signal_exit_status(
|
||||
int status,
|
||||
uint8_t *exit_status
|
||||
)
|
||||
{
|
||||
if (WTERMSIG(status) == SIGINT)
|
||||
write(STDOUT_FILENO, "\n", 1);
|
||||
else if (WTERMSIG(status) == SIGQUIT)
|
||||
{
|
||||
if (WCOREDUMP(status))
|
||||
write(STDERR_FILENO, "Quit (core dumped)\n", 19);
|
||||
else
|
||||
write(STDERR_FILENO, "Quit\n", 5);
|
||||
}
|
||||
*exit_status = 128 + WTERMSIG(status);
|
||||
}
|
||||
|
||||
static void set_last_child_status(
|
||||
int status,
|
||||
uint8_t *exit_status
|
||||
)
|
||||
{
|
||||
if (WIFEXITED(status))
|
||||
*exit_status = WEXITSTATUS(status);
|
||||
else if (WIFSIGNALED(status))
|
||||
set_signal_exit_status(status, exit_status);
|
||||
}
|
||||
|
||||
void executor_child_process(
|
||||
t_list *current_command,
|
||||
t_pipeline *pipeline,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
uint8_t exit_status;
|
||||
t_command *command;
|
||||
|
||||
command = current_command->content;
|
||||
minishell_set_child_signals();
|
||||
executor_setup_child_input(pipeline);
|
||||
executor_setup_child_output(current_command, pipeline);
|
||||
if (!executor_apply_redirections(command, NULL, NULL))
|
||||
exit(EXIT_FAILURE);
|
||||
exit_status = executor_execute_command(command, minishell);
|
||||
exit(exit_status);
|
||||
}
|
||||
|
||||
void executor_parent_cleanup(
|
||||
t_list *current_command,
|
||||
t_pipeline *pipeline
|
||||
)
|
||||
{
|
||||
if (pipeline->prev_read_fd != -1)
|
||||
close(pipeline->prev_read_fd);
|
||||
if (current_command->next)
|
||||
{
|
||||
close(pipeline->pipefd[WRITE_PIPE]);
|
||||
pipeline->prev_read_fd = pipeline->pipefd[READ_PIPE];
|
||||
}
|
||||
else
|
||||
pipeline->prev_read_fd = -1;
|
||||
}
|
||||
|
||||
uint8_t executor_wait_for_children(
|
||||
pid_t last_child_pid
|
||||
)
|
||||
{
|
||||
uint8_t exit_status;
|
||||
int status;
|
||||
pid_t pid;
|
||||
|
||||
exit_status = EXIT_SUCCESS;
|
||||
pid = wait(&status);
|
||||
while (last_child_pid > 0 && pid > 0)
|
||||
{
|
||||
if (pid == last_child_pid)
|
||||
set_last_child_status(status, &exit_status);
|
||||
pid = wait(&status);
|
||||
}
|
||||
return (exit_status);
|
||||
}
|
||||
111
src/executor/redirections.c
Normal file
111
src/executor/redirections.c
Normal file
@@ -0,0 +1,111 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* redirections.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/12 00:00:00 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/12 00:00:00 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "executor.h"
|
||||
#include <errno.h>
|
||||
|
||||
static int open_redirection_target(
|
||||
const t_redirection *redirection
|
||||
)
|
||||
{
|
||||
if (redirection->type == TOKEN_REDIRECT_IN)
|
||||
return (open(redirection->target, O_RDONLY));
|
||||
if (redirection->type == TOKEN_REDIRECT_OUT)
|
||||
return (open(redirection->target, O_WRONLY | O_CREAT | O_TRUNC, 0644));
|
||||
if (redirection->type == TOKEN_APPEND)
|
||||
return (open(redirection->target, O_WRONLY | O_CREAT | O_APPEND, 0644));
|
||||
errno = EINVAL;
|
||||
return (-1);
|
||||
}
|
||||
|
||||
static bool backup_stream_if_needed(
|
||||
const t_redirection *redirection,
|
||||
int *saved_stdin,
|
||||
int *saved_stdout
|
||||
)
|
||||
{
|
||||
if (redirection->type == TOKEN_REDIRECT_IN && saved_stdin != NULL
|
||||
&& *saved_stdin == -1)
|
||||
{
|
||||
*saved_stdin = dup(STDIN_FILENO);
|
||||
if (*saved_stdin == -1)
|
||||
return (perror("dup"), false);
|
||||
}
|
||||
if ((redirection->type == TOKEN_REDIRECT_OUT
|
||||
|| redirection->type == TOKEN_APPEND) && saved_stdout != NULL
|
||||
&& *saved_stdout == -1)
|
||||
{
|
||||
*saved_stdout = dup(STDOUT_FILENO);
|
||||
if (*saved_stdout == -1)
|
||||
return (perror("dup"), false);
|
||||
}
|
||||
return (true);
|
||||
}
|
||||
|
||||
bool executor_apply_redirections(
|
||||
const t_command *command,
|
||||
int *saved_stdin,
|
||||
int *saved_stdout
|
||||
)
|
||||
{
|
||||
t_list *node;
|
||||
t_redirection *redirection;
|
||||
int fd;
|
||||
|
||||
if (saved_stdin != NULL)
|
||||
*saved_stdin = -1;
|
||||
if (saved_stdout != NULL)
|
||||
*saved_stdout = -1;
|
||||
if (command == NULL || command->redirections == NULL)
|
||||
return (true);
|
||||
node = command->redirections;
|
||||
while (node != NULL)
|
||||
{
|
||||
redirection = (t_redirection *)node->content;
|
||||
if (redirection == NULL || redirection->target == NULL)
|
||||
return (false);
|
||||
if (!backup_stream_if_needed(redirection, saved_stdin, saved_stdout))
|
||||
return (false);
|
||||
fd = open_redirection_target(redirection);
|
||||
if (fd == -1)
|
||||
return (perror(redirection->target), false);
|
||||
if (redirection->type == TOKEN_REDIRECT_IN
|
||||
&& dup2(fd, STDIN_FILENO) == -1)
|
||||
return (close(fd), perror("dup2"), false);
|
||||
if ((redirection->type == TOKEN_REDIRECT_OUT
|
||||
|| redirection->type == TOKEN_APPEND)
|
||||
&& dup2(fd, STDOUT_FILENO) == -1)
|
||||
return (close(fd), perror("dup2"), false);
|
||||
close(fd);
|
||||
node = node->next;
|
||||
}
|
||||
return (true);
|
||||
}
|
||||
|
||||
void executor_restore_redirections(
|
||||
int saved_stdin,
|
||||
int saved_stdout
|
||||
)
|
||||
{
|
||||
if (saved_stdin != -1)
|
||||
{
|
||||
if (dup2(saved_stdin, STDIN_FILENO) == -1)
|
||||
perror("dup2");
|
||||
close(saved_stdin);
|
||||
}
|
||||
if (saved_stdout != -1)
|
||||
{
|
||||
if (dup2(saved_stdout, STDOUT_FILENO) == -1)
|
||||
perror("dup2");
|
||||
close(saved_stdout);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2025/10/20 20:51:33 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/09 18:48:34 by sede-san ### ########.fr */
|
||||
/* Updated: 2026/02/14 02:23:17 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@@ -20,6 +20,7 @@ void minishell_init(
|
||||
char **envp
|
||||
){
|
||||
ft_bzero(minishell, sizeof(t_minishell));
|
||||
set_intp(minishell);
|
||||
set_envp(envp, minishell);
|
||||
set_builtins(minishell);
|
||||
if (minishell->variables.environment == NULL || minishell->builtins == NULL)
|
||||
@@ -28,28 +29,30 @@ void minishell_init(
|
||||
|
||||
void minishell_run(
|
||||
t_minishell *minishell
|
||||
){
|
||||
)
|
||||
{
|
||||
char *line;
|
||||
t_list *commands;
|
||||
|
||||
if (minishell == NULL)
|
||||
{
|
||||
minishell->exit_status = EXIT_FAILURE;
|
||||
return ;
|
||||
}
|
||||
minishell_set_interactive_signals();
|
||||
while (!minishell->exit)
|
||||
{
|
||||
line = readline(DEFAULT_PS1);
|
||||
if (line != NULL)
|
||||
if (isatty(STDIN_FILENO))
|
||||
line = readline(get_var("PS1", minishell));
|
||||
else
|
||||
line = get_next_line(STDIN_FILENO);
|
||||
handle_sigint_status(minishell);
|
||||
if (handle_eof(line, minishell))
|
||||
continue ;
|
||||
if (*line != '\0')
|
||||
{
|
||||
if (*line != '\0')
|
||||
{
|
||||
add_history(line);
|
||||
commands = parse(line, minishell);
|
||||
execute(commands, minishell);
|
||||
}
|
||||
free(line);
|
||||
add_history(line);
|
||||
commands = parse(line, minishell);
|
||||
minishell->exit_status = execute(commands, minishell);
|
||||
}
|
||||
free(line);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +60,8 @@ void minishell_clear(
|
||||
t_minishell *minishell
|
||||
){
|
||||
rl_clear_history();
|
||||
if (minishell->variables.internal != NULL)
|
||||
ft_hashmap_clear(&minishell->variables.internal, free);
|
||||
if (minishell->variables.environment != NULL)
|
||||
ft_hashmap_clear(&minishell->variables.environment, free);
|
||||
if (minishell->builtins != NULL)
|
||||
|
||||
36
src/minishell_helpers.c
Normal file
36
src/minishell_helpers.c
Normal file
@@ -0,0 +1,36 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* minishell_helpers.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/13 21:09:23 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/13 21:09:23 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "minishell.h"
|
||||
#include "core.h"
|
||||
|
||||
void handle_sigint_status(
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
if (!minishell_consume_sigint())
|
||||
return ;
|
||||
minishell->exit_status = 130;
|
||||
}
|
||||
|
||||
bool handle_eof(
|
||||
char *line,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
if (line != NULL)
|
||||
return (false);
|
||||
if (isatty(STDIN_FILENO))
|
||||
ft_putendl("exit");
|
||||
minishell->exit = true;
|
||||
return (true);
|
||||
}
|
||||
@@ -13,26 +13,20 @@
|
||||
#include "core.h"
|
||||
#include "parser.h"
|
||||
|
||||
static t_token *tokenize(const char *line, size_t *start);
|
||||
static t_token_type get_token_type(const char *str);
|
||||
static t_token *token_new(t_token_type type, char *text);
|
||||
void token_clear(t_token *token);
|
||||
static t_token *read_token(t_token_type type, const char *line, size_t *i);
|
||||
static t_token *read_word(const char *line, size_t *i);
|
||||
static inline bool is_meta(char c);
|
||||
static t_token *tokenize(const char *line, size_t *start);
|
||||
|
||||
/**
|
||||
* @brief Converts a command line string into a list of tokens.
|
||||
*
|
||||
* @return A list of tokens or NULL on error.
|
||||
*/
|
||||
t_list *lex(
|
||||
t_list *lex(
|
||||
const char *line
|
||||
)
|
||||
{
|
||||
t_list *tokens;
|
||||
t_token *token;
|
||||
size_t i;
|
||||
t_list *tokens;
|
||||
t_token *token;
|
||||
size_t i;
|
||||
|
||||
tokens = NULL;
|
||||
i = 0;
|
||||
@@ -56,13 +50,13 @@ t_list *lex(
|
||||
/**
|
||||
* @return A new token or NULL on error.
|
||||
*/
|
||||
static t_token *tokenize(
|
||||
static t_token *tokenize(
|
||||
const char *line,
|
||||
size_t *start
|
||||
)
|
||||
{
|
||||
t_token *token;
|
||||
t_token_type type;
|
||||
t_token *token;
|
||||
t_token_type type;
|
||||
|
||||
if (line == NULL || line[*start] == '\0')
|
||||
return (NULL);
|
||||
@@ -73,106 +67,3 @@ static t_token *tokenize(
|
||||
token = read_word(line, start);
|
||||
return (token);
|
||||
}
|
||||
|
||||
static t_token_type get_token_type(
|
||||
const char *str
|
||||
)
|
||||
{
|
||||
if (str == NULL || str[0] == '\0')
|
||||
return (TOKEN_WORD);
|
||||
if (str[0] == '|')
|
||||
return (TOKEN_PIPE);
|
||||
if (str[0] == '<')
|
||||
{
|
||||
if (str[1] == '<')
|
||||
return (TOKEN_HEREDOC);
|
||||
return (TOKEN_REDIRECT_IN);
|
||||
}
|
||||
if (str[0] == '>')
|
||||
{
|
||||
if (str[1] == '>')
|
||||
return (TOKEN_APPEND);
|
||||
return (TOKEN_REDIRECT_OUT);
|
||||
}
|
||||
return (TOKEN_WORD);
|
||||
}
|
||||
|
||||
static t_token *token_new(
|
||||
t_token_type type,
|
||||
char *text
|
||||
)
|
||||
{
|
||||
t_token *token;
|
||||
|
||||
token = (t_token *)malloc(sizeof(t_token));
|
||||
if (token == NULL)
|
||||
return (NULL);
|
||||
token->type = type;
|
||||
token->value = text;
|
||||
if (token->type == TOKEN_WORD && token->value == NULL)
|
||||
{
|
||||
free(token);
|
||||
return (NULL);
|
||||
}
|
||||
return (token);
|
||||
}
|
||||
|
||||
void token_clear(
|
||||
t_token *token
|
||||
)
|
||||
{
|
||||
if (token != NULL)
|
||||
{
|
||||
free(token->value);
|
||||
free(token);
|
||||
}
|
||||
}
|
||||
|
||||
static t_token *read_token(
|
||||
t_token_type type,
|
||||
const char *line,
|
||||
size_t *i
|
||||
)
|
||||
{
|
||||
const size_t start = *i;
|
||||
size_t end;
|
||||
|
||||
while (is_meta(line[*i]))
|
||||
(*i)++;
|
||||
end = *i;
|
||||
while (ft_isspace(line[*i]))
|
||||
(*i)++;
|
||||
return (token_new(type, ft_substr(line, start, end - start)));
|
||||
}
|
||||
|
||||
static t_token *read_word(
|
||||
const char *line,
|
||||
size_t *i
|
||||
)
|
||||
{
|
||||
const size_t start = *i;
|
||||
bool in_single_quote;
|
||||
bool in_double_quote;
|
||||
|
||||
in_single_quote = false;
|
||||
in_double_quote = false;
|
||||
while (line[*i] != '\0')
|
||||
{
|
||||
if (line[*i] == '\'' && !in_double_quote)
|
||||
in_single_quote = !in_single_quote;
|
||||
else if (line[*i] == '"' && !in_single_quote)
|
||||
in_double_quote = !in_double_quote;
|
||||
else if (!in_single_quote && !in_double_quote
|
||||
&& (isspace(line[*i]) || is_meta(line[*i])))
|
||||
break;
|
||||
(*i)++;
|
||||
}
|
||||
return (token_new(TOKEN_WORD, ft_substr(line, start, *i - start)));
|
||||
}
|
||||
|
||||
static inline bool is_meta(
|
||||
char c
|
||||
)
|
||||
{
|
||||
return (c == '|' || c == '<' || c == '>');
|
||||
}
|
||||
|
||||
64
src/parser/lexer_reader.c
Normal file
64
src/parser/lexer_reader.c
Normal file
@@ -0,0 +1,64 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* lexer_reader.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/13 21:13:23 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/13 21:13:23 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "parser.h"
|
||||
|
||||
static inline bool is_meta(char c);
|
||||
|
||||
t_token *read_token(
|
||||
t_token_type type,
|
||||
const char *line,
|
||||
size_t *i
|
||||
)
|
||||
{
|
||||
const size_t start = *i;
|
||||
size_t end;
|
||||
|
||||
while (is_meta(line[*i]))
|
||||
(*i)++;
|
||||
end = *i;
|
||||
while (ft_isspace(line[*i]))
|
||||
(*i)++;
|
||||
return (token_new(type, ft_substr(line, start, end - start)));
|
||||
}
|
||||
|
||||
t_token *read_word(
|
||||
const char *line,
|
||||
size_t *i
|
||||
)
|
||||
{
|
||||
const size_t start = *i;
|
||||
bool in_single_quote;
|
||||
bool in_double_quote;
|
||||
|
||||
in_single_quote = false;
|
||||
in_double_quote = false;
|
||||
while (line[*i] != '\0')
|
||||
{
|
||||
if (line[*i] == '\'' && !in_double_quote)
|
||||
in_single_quote = !in_single_quote;
|
||||
else if (line[*i] == '"' && !in_single_quote)
|
||||
in_double_quote = !in_double_quote;
|
||||
else if (!in_single_quote && !in_double_quote
|
||||
&& (isspace(line[*i]) || is_meta(line[*i])))
|
||||
break ;
|
||||
(*i)++;
|
||||
}
|
||||
return (token_new(TOKEN_WORD, ft_substr(line, start, *i - start)));
|
||||
}
|
||||
|
||||
static inline bool is_meta(
|
||||
char c
|
||||
)
|
||||
{
|
||||
return (c == '|' || c == '<' || c == '>');
|
||||
}
|
||||
67
src/parser/lexer_token.c
Normal file
67
src/parser/lexer_token.c
Normal file
@@ -0,0 +1,67 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* lexer_token.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/13 21:12:49 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/13 21:12:49 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "parser.h"
|
||||
|
||||
t_token_type get_token_type(
|
||||
const char *str
|
||||
)
|
||||
{
|
||||
if (str == NULL || str[0] == '\0')
|
||||
return (TOKEN_WORD);
|
||||
if (str[0] == '|')
|
||||
return (TOKEN_PIPE);
|
||||
if (str[0] == '<')
|
||||
{
|
||||
if (str[1] == '<')
|
||||
return (TOKEN_HEREDOC);
|
||||
return (TOKEN_REDIRECT_IN);
|
||||
}
|
||||
if (str[0] == '>')
|
||||
{
|
||||
if (str[1] == '>')
|
||||
return (TOKEN_APPEND);
|
||||
return (TOKEN_REDIRECT_OUT);
|
||||
}
|
||||
return (TOKEN_WORD);
|
||||
}
|
||||
|
||||
t_token *token_new(
|
||||
t_token_type type,
|
||||
char *text
|
||||
)
|
||||
{
|
||||
t_token *token;
|
||||
|
||||
token = (t_token *)malloc(sizeof(t_token));
|
||||
if (token == NULL)
|
||||
return (NULL);
|
||||
token->type = type;
|
||||
token->value = text;
|
||||
if (token->type == TOKEN_WORD && token->value == NULL)
|
||||
{
|
||||
free(token);
|
||||
return (NULL);
|
||||
}
|
||||
return (token);
|
||||
}
|
||||
|
||||
void token_clear(
|
||||
t_token *token
|
||||
)
|
||||
{
|
||||
if (token != NULL)
|
||||
{
|
||||
free(token->value);
|
||||
free(token);
|
||||
}
|
||||
}
|
||||
@@ -6,33 +6,14 @@
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2025/10/22 18:37:38 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/12 20:55:49 by sede-san ### ########.fr */
|
||||
/* Updated: 2026/02/13 21:22:06 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "parser.h"
|
||||
#include "errors.h"
|
||||
|
||||
static t_list *parse_tokens(
|
||||
t_list *tokens
|
||||
);
|
||||
|
||||
static t_list *ft_lstfind(
|
||||
t_list *lst,
|
||||
bool (*pre)(void *)
|
||||
);
|
||||
|
||||
static void expand_variable(
|
||||
char **argument,
|
||||
int *i,
|
||||
t_minishell *minishell
|
||||
);
|
||||
|
||||
static void expand(
|
||||
t_list **commands,
|
||||
t_minishell *minishell
|
||||
);
|
||||
|
||||
static t_list *parse_tokens(t_list *tokens);
|
||||
|
||||
/**
|
||||
* @brief Converts a command line string into a list of commands.
|
||||
@@ -42,13 +23,13 @@ static void expand(
|
||||
*
|
||||
* @return A list of commands or `NULL` on error.
|
||||
*/
|
||||
t_list *parse(
|
||||
t_list *parse(
|
||||
char *line,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
t_list *commands;
|
||||
t_list *tokens;
|
||||
t_list *commands;
|
||||
t_list *tokens;
|
||||
|
||||
if (line == NULL)
|
||||
return (NULL);
|
||||
@@ -63,11 +44,10 @@ t_list *parse(
|
||||
* @brief Converts a list of tokens into a list of commands.
|
||||
*
|
||||
* @param tokens The list of tokens to parse.
|
||||
* @param minishell The minishell instance.
|
||||
*
|
||||
* @return A list of commands or `NULL` on error.
|
||||
*/
|
||||
static t_list *parse_tokens(
|
||||
static t_list *parse_tokens(
|
||||
t_list *tokens
|
||||
)
|
||||
{
|
||||
@@ -80,6 +60,9 @@ static t_list *parse_tokens(
|
||||
return (NULL);
|
||||
commands = NULL;
|
||||
current_token = tokens;
|
||||
if (((t_token *)current_token->content)->type == TOKEN_PIPE)
|
||||
return (syntax_error_unexpected_token(
|
||||
(t_token *)current_token->content), NULL);
|
||||
while (current_token != NULL)
|
||||
{
|
||||
command = command_new(¤t_token);
|
||||
@@ -99,446 +82,13 @@ static t_list *parse_tokens(
|
||||
if (current_token != NULL)
|
||||
{
|
||||
if (current_token->next == NULL)
|
||||
syntax_error_unexpected_token((t_token *)current_token->content);
|
||||
{
|
||||
ft_lstclear(&commands, (void (*)(void *))command_clear);
|
||||
return (syntax_error_unexpected_token(
|
||||
(t_token *)current_token->content), NULL);
|
||||
}
|
||||
current_token = current_token->next;
|
||||
}
|
||||
}
|
||||
return (commands);
|
||||
}
|
||||
|
||||
static void expand_variable(
|
||||
char **argument,
|
||||
int *i,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
char *expanded;
|
||||
char *variable;
|
||||
char *variable_name;
|
||||
const int start = *i + 1;
|
||||
int end;
|
||||
|
||||
end = start;
|
||||
while (ft_isalnum((*argument)[end]))
|
||||
end++;
|
||||
variable_name = ft_substr(*argument, start, end - start);
|
||||
if (variable_name == NULL)
|
||||
return (minishell->exit = true, malloc_error());
|
||||
variable = get_env(variable_name, minishell);
|
||||
free(variable_name);
|
||||
if (variable == NULL)
|
||||
variable = "";
|
||||
expanded = ft_strnjoin(3,
|
||||
ft_substr(*argument, 0, *i),
|
||||
variable,
|
||||
ft_substr(*argument,end, ft_strlen(*argument) - end));
|
||||
if (expanded == NULL)
|
||||
return (minishell->exit = true, malloc_error());
|
||||
*i += ft_strlen(variable) - 1;
|
||||
free(*argument);
|
||||
*argument = expanded;
|
||||
}
|
||||
|
||||
static void expand_argument(
|
||||
char **argument,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
bool in_single_quote;
|
||||
bool in_double_quote;
|
||||
int i;
|
||||
|
||||
in_single_quote = false;
|
||||
in_double_quote = false;
|
||||
i = 0;
|
||||
while ((*argument)[i] != '\0')
|
||||
{
|
||||
if ((*argument)[i] == '$' && !in_single_quote)
|
||||
{
|
||||
expand_variable(argument, &i, minishell);
|
||||
if (*argument == NULL)
|
||||
return (minishell->exit = true, malloc_error());
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((*argument)[i] == '\'' && !in_double_quote)
|
||||
in_single_quote = !in_single_quote;
|
||||
else if ((*argument)[i] == '"' && !in_single_quote)
|
||||
in_double_quote = !in_double_quote;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void expand(
|
||||
t_list **commands,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
t_list *current_command;
|
||||
t_command *command;
|
||||
int i;
|
||||
|
||||
current_command = *commands;
|
||||
while (current_command != NULL)
|
||||
{
|
||||
command = (t_command *)current_command->content;
|
||||
i = 0;
|
||||
while (i < command->argc)
|
||||
{
|
||||
expand_argument(&command->argv[i], minishell);
|
||||
if (command->argv[i] == NULL)
|
||||
ft_lstclear(commands, (void (*)(void *))command_clear);
|
||||
i++;
|
||||
}
|
||||
if (command == NULL)
|
||||
return (ft_lstclear(commands, (void (*)(void *))command_clear));
|
||||
current_command = current_command->next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new command from a list of tokens.
|
||||
*
|
||||
* @param tokens The list of tokens to create the command from.
|
||||
*
|
||||
* @return A new command or NULL on error.
|
||||
*
|
||||
* @note The `tokens` pointer is moved to the next command's tokens.
|
||||
*/
|
||||
t_command *command_new(
|
||||
t_list **tokens
|
||||
)
|
||||
{
|
||||
t_command *command;
|
||||
t_list *current_token;
|
||||
t_list *delimiter_token;
|
||||
|
||||
command = (t_command *)ft_calloc(1, sizeof(t_command));
|
||||
if (command == NULL)
|
||||
return (NULL);
|
||||
current_token = *tokens;
|
||||
delimiter_token = ft_lstfind(current_token, (bool (*)(void *))is_pipe);
|
||||
while (command != NULL && current_token != delimiter_token)
|
||||
{
|
||||
command_add_tokens(&command, ¤t_token);
|
||||
}
|
||||
*tokens = current_token;
|
||||
return (command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new redirection from a list of tokens.
|
||||
*
|
||||
* @param tokens The list of tokens to create the redirection from.
|
||||
*
|
||||
* @return A new redirection or `NULL` on error.
|
||||
*/
|
||||
t_redirection *redirection_new(
|
||||
t_list **tokens
|
||||
)
|
||||
{
|
||||
t_redirection *redirection;
|
||||
t_token *token;
|
||||
|
||||
redirection = (t_redirection *)malloc(sizeof(t_redirection));
|
||||
if (redirection == NULL)
|
||||
return (malloc_error(), NULL);
|
||||
token = (t_token *)(*tokens)->content;
|
||||
redirection->type = token->type;
|
||||
*tokens = (*tokens)->next;
|
||||
if (*tokens == NULL)
|
||||
{
|
||||
free(redirection);
|
||||
return (syntax_error_unexpected_token(NULL), NULL);
|
||||
}
|
||||
token = (t_token *)(*tokens)->content;
|
||||
if (token->type != TOKEN_WORD)
|
||||
{
|
||||
free(redirection);
|
||||
while (*tokens != NULL)
|
||||
*tokens = (*tokens)->next;
|
||||
return (syntax_error_unexpected_token(token), NULL);
|
||||
}
|
||||
redirection->target = ft_strdup(token->value);
|
||||
if (redirection->target == NULL)
|
||||
{
|
||||
free(redirection);
|
||||
return (malloc_error(), NULL);
|
||||
}
|
||||
*tokens = (*tokens)->next;
|
||||
return (redirection);
|
||||
}
|
||||
|
||||
void redirection_clear(
|
||||
t_redirection *redirection
|
||||
)
|
||||
{
|
||||
if (redirection != NULL)
|
||||
{
|
||||
free(redirection->target);
|
||||
free(redirection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds a token to a command, updating the command's arguments and
|
||||
* redirections as necessary.
|
||||
*
|
||||
* @param command The command to add the token to.
|
||||
* @param tokens The list of tokens to add to the command.
|
||||
*
|
||||
* @note The `command` pointer can be free'd if there is an error while adding
|
||||
* the token.
|
||||
*/
|
||||
void command_add_tokens(
|
||||
t_command **command,
|
||||
t_list **tokens
|
||||
)
|
||||
{
|
||||
t_token *token;
|
||||
|
||||
token = (t_token *)(*tokens)->content;
|
||||
if (is_redirection(token))
|
||||
redirection_add(tokens, token, command);
|
||||
else
|
||||
words_add(tokens, command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts a list of arguments into an array.
|
||||
*
|
||||
* @param args The list of arguments to convert.
|
||||
* @param argc The number of arguments in the list.
|
||||
*
|
||||
* @return An array of arguments or `NULL` on error.
|
||||
*
|
||||
* @note The `args` list is cleared after the conversion and set to NULL.
|
||||
*/
|
||||
char **args_to_array(
|
||||
t_list **args,
|
||||
size_t argc
|
||||
)
|
||||
{
|
||||
char **argv;
|
||||
t_list *arg;
|
||||
size_t i;
|
||||
|
||||
argv = (char **)malloc(sizeof(char *) * (argc + 1));
|
||||
if (argv == NULL)
|
||||
return (NULL);
|
||||
i = 0;
|
||||
arg = *args;
|
||||
while (arg != NULL)
|
||||
{
|
||||
argv[i] = (char *)arg->content;
|
||||
arg = arg->next;
|
||||
i++;
|
||||
}
|
||||
argv[i] = NULL;
|
||||
ft_lstclear_nodes(args);
|
||||
return (argv);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds all consecutive word tokens to a command's argv and updates its
|
||||
* argc accordingly.
|
||||
*
|
||||
* @param command The command to add the word tokens to.
|
||||
* @param tokens The list of tokens to add to the command.
|
||||
*/
|
||||
void words_add(
|
||||
t_list **tokens,
|
||||
t_command **command
|
||||
)
|
||||
{
|
||||
t_list *args;
|
||||
t_list *arg;
|
||||
t_token *token;
|
||||
|
||||
args = NULL;
|
||||
arg = *tokens;
|
||||
token = (t_token *)arg->content;
|
||||
while (arg != NULL && token->type == TOKEN_WORD)
|
||||
{
|
||||
ft_lstadd_back(&args, ft_lstnew(ft_strdup(token->value)));
|
||||
(*command)->argc++;
|
||||
arg = arg->next;
|
||||
if (arg != NULL)
|
||||
token = (t_token *)arg->content;
|
||||
}
|
||||
*tokens = arg;
|
||||
(*command)->argv = args_to_array(&args, (*command)->argc);
|
||||
ft_lstclear_nodes(&args);
|
||||
}
|
||||
|
||||
void redirection_add(
|
||||
t_list **tokens,
|
||||
t_token *token,
|
||||
t_command **command
|
||||
)
|
||||
{
|
||||
t_redirection *redirection;
|
||||
t_list *redirection_tokens;
|
||||
|
||||
redirection = redirection_new(tokens);
|
||||
if (redirection == NULL)
|
||||
{
|
||||
command_clear(*command);
|
||||
*command = NULL;
|
||||
return ;
|
||||
}
|
||||
redirection_tokens = ft_lstnew(redirection);
|
||||
if (redirection_tokens == NULL)
|
||||
{
|
||||
free(redirection);
|
||||
return (malloc_error());
|
||||
}
|
||||
if (token->type == TOKEN_HEREDOC)
|
||||
ft_lstadd_back(&(*command)->heredocs, redirection_tokens);
|
||||
else
|
||||
ft_lstadd_back(&(*command)->redirections, redirection_tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a token is a redirection token.
|
||||
*
|
||||
* @param token The token to check.
|
||||
*
|
||||
* @return `true` if the token is a redirection token, `false` otherwise.
|
||||
*/
|
||||
bool is_redirection(
|
||||
t_token *token
|
||||
)
|
||||
{
|
||||
return (token->type == TOKEN_REDIRECT_IN
|
||||
|| token->type == TOKEN_REDIRECT_OUT
|
||||
|| token->type == TOKEN_APPEND
|
||||
|| token->type == TOKEN_HEREDOC);
|
||||
}
|
||||
|
||||
void command_clear_argv(
|
||||
t_command *command
|
||||
)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (command->argv != NULL)
|
||||
{
|
||||
i = 0;
|
||||
while (i < command->argc)
|
||||
{
|
||||
free(command->argv[i]);
|
||||
i++;
|
||||
}
|
||||
free(command->argv);
|
||||
command->argv = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clears a command, freeing all associated memory.
|
||||
*
|
||||
* @param command The command to clear.
|
||||
*/
|
||||
void command_clear(
|
||||
t_command *command
|
||||
)
|
||||
{
|
||||
if (command != NULL)
|
||||
{
|
||||
command_clear_argv(command);
|
||||
ft_lstclear(&command->redirections, (void (*)(void *))redirection_clear);
|
||||
ft_lstclear(&command->heredocs, (void (*)(void *))redirection_clear);
|
||||
free(command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a token is a pipe token.
|
||||
*
|
||||
* @param token The token to check.
|
||||
*
|
||||
* @return `true` if the token is a pipe token, `false` otherwise.
|
||||
*/
|
||||
bool is_pipe(
|
||||
t_token *token)
|
||||
{
|
||||
return (token->type == TOKEN_PIPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finds a node in a linked list that satisfies a given predicate.
|
||||
*
|
||||
* @param lst The linked list to search through.
|
||||
* @param pre The predicate function to apply to each node's content.
|
||||
*
|
||||
* @returns The first node that satisfies the predicate or `NULL` if no such
|
||||
* node exists or if the list is `NULL`.
|
||||
*/
|
||||
t_list *ft_lstfind(
|
||||
t_list *lst,
|
||||
bool (*pre)(void *))
|
||||
{
|
||||
while (lst != NULL)
|
||||
{
|
||||
if (pre(lst->content))
|
||||
return (lst);
|
||||
lst = lst->next;
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
// void print_command_info(
|
||||
// t_command *command
|
||||
// )
|
||||
// {
|
||||
// printf("Command:\n");
|
||||
// printf(" argc: %d\n", command->argc);
|
||||
// printf(" argv: [");
|
||||
// for (int i = 0; i < command->argc; i++)
|
||||
// {
|
||||
// printf("%s", command->argv[i]);
|
||||
// if (i < command->argc - 1)
|
||||
// printf(", ");
|
||||
// }
|
||||
// printf("]\n");
|
||||
// printf(" path: %s\n", command->path);
|
||||
// printf(" redirections:\n");
|
||||
// t_list *redirection_node = command->redirections;
|
||||
// while (redirection_node != NULL)
|
||||
// {
|
||||
// t_redirection *redirection = (t_redirection *)redirection_node->content;
|
||||
// printf(" type: %d, target: %s\n", redirection->type, redirection->target);
|
||||
// redirection_node = redirection_node->next;
|
||||
// }
|
||||
// printf(" heredocs:\n");
|
||||
// t_list *heredoc_node = command->heredocs;
|
||||
// while (heredoc_node != NULL)
|
||||
// {
|
||||
// t_redirection *heredoc = (t_redirection *)heredoc_node->content;
|
||||
// printf(" type: %d, target: %s\n", heredoc->type, heredoc->target);
|
||||
// heredoc_node = heredoc_node->next;
|
||||
// }
|
||||
// }
|
||||
|
||||
// int main(int argc, char const *argv[], char **envp)
|
||||
// {
|
||||
// t_minishell minishell;
|
||||
// t_list *commands;
|
||||
// char *line;
|
||||
|
||||
// if (argc != 2)
|
||||
// return (EXIT_FAILURE);
|
||||
|
||||
// minishell_init(&minishell, envp);
|
||||
|
||||
// line = ft_strdup(argv[1]);
|
||||
// commands = parse(line, NULL);
|
||||
// ft_lstiter(commands, (void (*)(void *))print_command_info);
|
||||
// if (line != NULL)
|
||||
// free(line);
|
||||
// if (commands != NULL)
|
||||
// ft_lstclear(&commands, (void (*)(void *))command_clear);
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
120
src/parser/parser_command.c
Normal file
120
src/parser/parser_command.c
Normal file
@@ -0,0 +1,120 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* parser_command.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/13 21:26:42 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/13 21:26:42 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "parser.h"
|
||||
|
||||
static t_list *ft_lstfind(t_list *lst, bool (*pre)(void *));
|
||||
|
||||
/**
|
||||
* @brief Creates a new command from a list of tokens.
|
||||
*
|
||||
* @param tokens The list of tokens to create the command from.
|
||||
*
|
||||
* @return A new command or NULL on error.
|
||||
*
|
||||
* @note The `tokens` pointer is moved to the next command's tokens.
|
||||
*/
|
||||
t_command *command_new(
|
||||
t_list **tokens
|
||||
)
|
||||
{
|
||||
t_command *command;
|
||||
t_list *current_token;
|
||||
t_list *delimiter_token;
|
||||
|
||||
command = (t_command *)ft_calloc(1, sizeof(t_command));
|
||||
if (command == NULL)
|
||||
return (NULL);
|
||||
current_token = *tokens;
|
||||
delimiter_token = ft_lstfind(current_token, (bool (*)(void *))is_pipe);
|
||||
while (command != NULL && current_token != delimiter_token)
|
||||
command_add_tokens(&command, ¤t_token);
|
||||
*tokens = current_token;
|
||||
return (command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds a token to a command, updating the command's arguments and
|
||||
* redirections as necessary.
|
||||
*
|
||||
* @param command The command to add the token to.
|
||||
* @param tokens The list of tokens to add to the command.
|
||||
*
|
||||
* @note The `command` pointer can be free'd if there is an error while adding
|
||||
* the token.
|
||||
*/
|
||||
void command_add_tokens(
|
||||
t_command **command,
|
||||
t_list **tokens
|
||||
)
|
||||
{
|
||||
t_token *token;
|
||||
|
||||
token = (t_token *)(*tokens)->content;
|
||||
if (is_redirection(token))
|
||||
redirection_add(tokens, token, command);
|
||||
else
|
||||
words_add(tokens, command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a token is a redirection token.
|
||||
*
|
||||
* @param token The token to check.
|
||||
*
|
||||
* @return `true` if the token is a redirection token, `false` otherwise.
|
||||
*/
|
||||
bool is_redirection(
|
||||
t_token *token
|
||||
)
|
||||
{
|
||||
return (token->type == TOKEN_REDIRECT_IN
|
||||
|| token->type == TOKEN_REDIRECT_OUT
|
||||
|| token->type == TOKEN_APPEND
|
||||
|| token->type == TOKEN_HEREDOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a token is a pipe token.
|
||||
*
|
||||
* @param token The token to check.
|
||||
*
|
||||
* @return `true` if the token is a pipe token, `false` otherwise.
|
||||
*/
|
||||
bool is_pipe(
|
||||
t_token *token
|
||||
)
|
||||
{
|
||||
return (token->type == TOKEN_PIPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finds a node in a linked list that satisfies a given predicate.
|
||||
*
|
||||
* @param lst The linked list to search through.
|
||||
* @param pre The predicate function to apply to each node's content.
|
||||
*
|
||||
* @returns The first node that satisfies the predicate or `NULL` if no such
|
||||
* node exists or if the list is `NULL`.
|
||||
*/
|
||||
static t_list *ft_lstfind(
|
||||
t_list *lst,
|
||||
bool (*pre)(void *))
|
||||
{
|
||||
while (lst != NULL)
|
||||
{
|
||||
if (pre(lst->content))
|
||||
return (lst);
|
||||
lst = lst->next;
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
139
src/parser/parser_expand.c
Normal file
139
src/parser/parser_expand.c
Normal file
@@ -0,0 +1,139 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* parser_expand.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/13 21:24:45 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/14 06:56:00 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "parser_expand_internal.h"
|
||||
|
||||
static void command_clear_argv_expand(
|
||||
t_command *command
|
||||
)
|
||||
{
|
||||
int i;
|
||||
|
||||
i = 0;
|
||||
while (i < command->argc)
|
||||
{
|
||||
free(command->argv[i]);
|
||||
i++;
|
||||
}
|
||||
free(command->argv);
|
||||
}
|
||||
|
||||
static bool command_set_expanded_argv(
|
||||
t_command *command,
|
||||
t_list *expanded_args,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
char **argv;
|
||||
t_list *current;
|
||||
int argc;
|
||||
int i;
|
||||
|
||||
argc = ft_lstsize(expanded_args);
|
||||
argv = (char **)malloc(sizeof(char *) * (argc + 1));
|
||||
if (argv == NULL)
|
||||
return (ft_lstclear(&expanded_args, free),
|
||||
parser_expand_malloc_error(minishell), false);
|
||||
i = 0;
|
||||
current = expanded_args;
|
||||
while (current != NULL)
|
||||
{
|
||||
argv[i++] = (char *)current->content;
|
||||
current = current->next;
|
||||
}
|
||||
argv[i] = NULL;
|
||||
ft_lstclear_nodes(&expanded_args);
|
||||
command_clear_argv_expand(command);
|
||||
command->argc = argc;
|
||||
command->argv = argv;
|
||||
return (true);
|
||||
}
|
||||
|
||||
static bool expand_argv(
|
||||
t_command *command,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
int i;
|
||||
t_list *expanded_args;
|
||||
t_list *fields;
|
||||
t_list *last;
|
||||
|
||||
i = 0;
|
||||
expanded_args = NULL;
|
||||
while (i < command->argc)
|
||||
{
|
||||
fields = NULL;
|
||||
if (!parser_expand_word_fields(command->argv[i], minishell,
|
||||
true, &fields))
|
||||
return (ft_lstclear(&expanded_args, free), false);
|
||||
if (expanded_args == NULL)
|
||||
expanded_args = fields;
|
||||
else
|
||||
{
|
||||
last = ft_lstlast(expanded_args);
|
||||
if (last != NULL)
|
||||
last->next = fields;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return (command_set_expanded_argv(command, expanded_args, minishell));
|
||||
}
|
||||
|
||||
static bool expand_redirections(
|
||||
t_list *redirections,
|
||||
t_minishell *minishell,
|
||||
bool expand_vars
|
||||
)
|
||||
{
|
||||
t_redirection *redirection;
|
||||
char *expanded;
|
||||
|
||||
while (redirections != NULL)
|
||||
{
|
||||
redirection = (t_redirection *)redirections->content;
|
||||
expanded = parser_expand_word(redirection->target, minishell,
|
||||
expand_vars);
|
||||
if (expanded == NULL)
|
||||
return (false);
|
||||
free(redirection->target);
|
||||
redirection->target = expanded;
|
||||
redirections = redirections->next;
|
||||
}
|
||||
return (true);
|
||||
}
|
||||
|
||||
void expand(
|
||||
t_list **commands,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
t_list *current;
|
||||
t_command *command;
|
||||
|
||||
if (commands == NULL || *commands == NULL)
|
||||
return ;
|
||||
current = *commands;
|
||||
while (current != NULL)
|
||||
{
|
||||
command = (t_command *)current->content;
|
||||
if (!expand_argv(command, minishell)
|
||||
|| !expand_redirections(command->redirections, minishell, true)
|
||||
|| !expand_redirections(command->heredocs, minishell, false))
|
||||
{
|
||||
ft_lstclear(commands, (void (*)(void *))command_clear);
|
||||
*commands = NULL;
|
||||
return ;
|
||||
}
|
||||
current = current->next;
|
||||
}
|
||||
}
|
||||
90
src/parser/parser_expand_fields.c
Normal file
90
src/parser/parser_expand_fields.c
Normal file
@@ -0,0 +1,90 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* parser_expand_fields.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/14 13:15:00 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/14 13:15:00 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "parser_expand_internal.h"
|
||||
|
||||
static t_fields_ctx init_fields_ctx(
|
||||
t_list **fields,
|
||||
char **current,
|
||||
t_field_state *state
|
||||
)
|
||||
{
|
||||
t_fields_ctx ctx;
|
||||
|
||||
ctx.fields = fields;
|
||||
ctx.current = current;
|
||||
ctx.touched = &state->touched;
|
||||
ctx.in_single_quote = &state->in_single_quote;
|
||||
ctx.in_double_quote = &state->in_double_quote;
|
||||
return (ctx);
|
||||
}
|
||||
|
||||
static bool finish_word_fields(
|
||||
t_fields_ctx ctx
|
||||
)
|
||||
{
|
||||
if (*ctx.in_single_quote || *ctx.in_double_quote)
|
||||
{
|
||||
ft_lstclear(ctx.fields, free);
|
||||
free(*ctx.current);
|
||||
syntax_error_unexpected_token(NULL);
|
||||
ctx.minishell->exit_status = 2;
|
||||
return (false);
|
||||
}
|
||||
if ((*ctx.touched || (*ctx.current)[0] != '\0')
|
||||
&& !parser_fields_push_field(ctx))
|
||||
return (false);
|
||||
free(*ctx.current);
|
||||
return (true);
|
||||
}
|
||||
|
||||
static bool process_word_fields(
|
||||
const char *word,
|
||||
t_fields_ctx ctx
|
||||
)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
i = 0;
|
||||
while (word[i] != '\0')
|
||||
{
|
||||
if (!parser_fields_step(word, &i, ctx))
|
||||
return (false);
|
||||
}
|
||||
return (true);
|
||||
}
|
||||
|
||||
bool parser_expand_word_fields(
|
||||
const char *word,
|
||||
t_minishell *minishell,
|
||||
bool expand_vars,
|
||||
t_list **fields
|
||||
)
|
||||
{
|
||||
char *current;
|
||||
t_field_state state;
|
||||
t_fields_ctx ctx;
|
||||
|
||||
*fields = NULL;
|
||||
current = ft_strdup("");
|
||||
if (current == NULL)
|
||||
return (parser_expand_malloc_error(minishell), false);
|
||||
state.touched = false;
|
||||
state.in_single_quote = false;
|
||||
state.in_double_quote = false;
|
||||
ctx = init_fields_ctx(fields, ¤t, &state);
|
||||
ctx.minishell = minishell;
|
||||
ctx.expand_vars = expand_vars;
|
||||
if (!process_word_fields(word, ctx))
|
||||
return (ft_lstclear(fields, free), free(current), false);
|
||||
return (finish_word_fields(ctx));
|
||||
}
|
||||
105
src/parser/parser_expand_fields_step.c
Normal file
105
src/parser/parser_expand_fields_step.c
Normal file
@@ -0,0 +1,105 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* parser_expand_fields_step.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/14 14:12:00 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/14 14:12:00 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "parser_expand_internal.h"
|
||||
|
||||
static bool handle_quote_char(
|
||||
const char *word,
|
||||
size_t *i,
|
||||
t_fields_ctx ctx
|
||||
)
|
||||
{
|
||||
if (word[*i] == '\'' && !*ctx.in_double_quote)
|
||||
{
|
||||
*ctx.in_single_quote = !*ctx.in_single_quote;
|
||||
*ctx.touched = true;
|
||||
(*i)++;
|
||||
return (true);
|
||||
}
|
||||
if (word[*i] == '\"' && !*ctx.in_single_quote)
|
||||
{
|
||||
*ctx.in_double_quote = !*ctx.in_double_quote;
|
||||
*ctx.touched = true;
|
||||
(*i)++;
|
||||
return (true);
|
||||
}
|
||||
return (false);
|
||||
}
|
||||
|
||||
static bool skip_dollar_quote_prefix(
|
||||
const char *word,
|
||||
size_t *i,
|
||||
t_fields_ctx ctx
|
||||
)
|
||||
{
|
||||
if (word[*i] == '$' && !*ctx.in_single_quote && !*ctx.in_double_quote
|
||||
&& word[*i + 1] != '\0'
|
||||
&& (word[*i + 1] == '\'' || word[*i + 1] == '\"'))
|
||||
{
|
||||
(*i)++;
|
||||
return (true);
|
||||
}
|
||||
return (false);
|
||||
}
|
||||
|
||||
static bool expand_dollar_token(
|
||||
const char *word,
|
||||
size_t *i,
|
||||
t_fields_ctx ctx,
|
||||
bool *handled
|
||||
)
|
||||
{
|
||||
char *expanded;
|
||||
|
||||
*handled = false;
|
||||
if (word[*i] != '$' || *ctx.in_single_quote || !ctx.expand_vars)
|
||||
return (true);
|
||||
*handled = true;
|
||||
expanded = parser_expand_variable(word, i, ctx.minishell);
|
||||
if (expanded == NULL)
|
||||
return (false);
|
||||
if (!*ctx.in_double_quote
|
||||
&& !parser_fields_expand_unquoted_value(ctx, expanded))
|
||||
return (free(expanded), false);
|
||||
if (*ctx.in_double_quote && !parser_fields_append_text(ctx.current,
|
||||
expanded, ctx.minishell))
|
||||
return (free(expanded), false);
|
||||
if (*ctx.in_double_quote && expanded[0] != '\0')
|
||||
*ctx.touched = true;
|
||||
free(expanded);
|
||||
return (true);
|
||||
}
|
||||
|
||||
bool parser_fields_step(
|
||||
const char *word,
|
||||
size_t *i,
|
||||
t_fields_ctx ctx
|
||||
)
|
||||
{
|
||||
char value[2];
|
||||
bool handled;
|
||||
|
||||
if (handle_quote_char(word, i, ctx))
|
||||
return (true);
|
||||
if (skip_dollar_quote_prefix(word, i, ctx))
|
||||
return (true);
|
||||
if (!expand_dollar_token(word, i, ctx, &handled))
|
||||
return (false);
|
||||
if (handled)
|
||||
return (true);
|
||||
value[0] = word[(*i)++];
|
||||
value[1] = '\0';
|
||||
if (!parser_fields_append_text(ctx.current, value, ctx.minishell))
|
||||
return (false);
|
||||
*ctx.touched = true;
|
||||
return (true);
|
||||
}
|
||||
100
src/parser/parser_expand_fields_utils.c
Normal file
100
src/parser/parser_expand_fields_utils.c
Normal file
@@ -0,0 +1,100 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* parser_expand_fields_utils.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/14 14:12:00 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/14 14:12:00 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "parser_expand_internal.h"
|
||||
|
||||
bool parser_fields_append_text(
|
||||
char **current,
|
||||
const char *value,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
char *joined;
|
||||
|
||||
joined = ft_strnjoin(2, *current, (char *)value);
|
||||
if (joined == NULL)
|
||||
{
|
||||
free(*current);
|
||||
*current = NULL;
|
||||
return (parser_expand_malloc_error(minishell), false);
|
||||
}
|
||||
free(*current);
|
||||
*current = joined;
|
||||
return (true);
|
||||
}
|
||||
|
||||
static bool is_ifs_char(
|
||||
char c
|
||||
)
|
||||
{
|
||||
return (c == ' ' || c == '\t' || c == '\n');
|
||||
}
|
||||
|
||||
bool parser_fields_push_field(
|
||||
t_fields_ctx ctx
|
||||
)
|
||||
{
|
||||
t_list *node;
|
||||
|
||||
node = ft_lstnew(*ctx.current);
|
||||
if (node == NULL)
|
||||
{
|
||||
free(*ctx.current);
|
||||
*ctx.current = NULL;
|
||||
return (parser_expand_malloc_error(ctx.minishell), false);
|
||||
}
|
||||
ft_lstadd_back(ctx.fields, node);
|
||||
*ctx.current = ft_strdup("");
|
||||
if (*ctx.current == NULL)
|
||||
return (ft_lstclear(ctx.fields, free),
|
||||
parser_expand_malloc_error(ctx.minishell), false);
|
||||
*ctx.touched = false;
|
||||
return (true);
|
||||
}
|
||||
|
||||
static void skip_ifs(
|
||||
const char *expanded,
|
||||
size_t *i
|
||||
)
|
||||
{
|
||||
while (expanded[*i] != '\0' && is_ifs_char(expanded[*i]))
|
||||
(*i)++;
|
||||
}
|
||||
|
||||
bool parser_fields_expand_unquoted_value(
|
||||
t_fields_ctx ctx,
|
||||
const char *expanded
|
||||
)
|
||||
{
|
||||
size_t i;
|
||||
char value[2];
|
||||
|
||||
i = 0;
|
||||
while (expanded[i] != '\0')
|
||||
{
|
||||
if (is_ifs_char(expanded[i]))
|
||||
{
|
||||
if ((*ctx.touched || (*ctx.current)[0] != '\0')
|
||||
&& !parser_fields_push_field(ctx))
|
||||
return (false);
|
||||
skip_ifs(expanded, &i);
|
||||
continue ;
|
||||
}
|
||||
value[0] = expanded[i];
|
||||
value[1] = '\0';
|
||||
if (!parser_fields_append_text(ctx.current, value, ctx.minishell))
|
||||
return (false);
|
||||
*ctx.touched = true;
|
||||
i++;
|
||||
}
|
||||
return (true);
|
||||
}
|
||||
63
src/parser/parser_expand_internal.h
Normal file
63
src/parser/parser_expand_internal.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* parser_expand_internal.h :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/14 06:56:00 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/14 06:56:00 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#ifndef PARSER_EXPAND_INTERNAL_H
|
||||
# define PARSER_EXPAND_INTERNAL_H
|
||||
|
||||
# include "parser.h"
|
||||
# include "errors.h"
|
||||
# include "variables.h"
|
||||
|
||||
typedef struct s_fields_ctx t_fields_ctx;
|
||||
|
||||
void parser_expand_malloc_error(t_minishell *minishell);
|
||||
char *parser_expand_variable(const char *word, size_t *i,
|
||||
t_minishell *minishell);
|
||||
char *parser_expand_word(const char *word, t_minishell *minishell,
|
||||
bool expand_vars);
|
||||
bool parser_expand_word_fields(const char *word, t_minishell *minishell,
|
||||
bool expand_vars, t_list **fields);
|
||||
bool parser_fields_append_text(char **current, const char *value,
|
||||
t_minishell *minishell);
|
||||
bool parser_fields_push_field(t_fields_ctx ctx);
|
||||
bool parser_fields_expand_unquoted_value(t_fields_ctx ctx,
|
||||
const char *expanded);
|
||||
bool parser_fields_step(const char *word, size_t *i, t_fields_ctx ctx);
|
||||
|
||||
typedef struct s_word_ctx
|
||||
{
|
||||
char **result;
|
||||
bool *in_single_quote;
|
||||
bool *in_double_quote;
|
||||
t_minishell *minishell;
|
||||
bool expand_vars;
|
||||
} t_word_ctx;
|
||||
|
||||
typedef struct s_fields_ctx
|
||||
{
|
||||
t_list **fields;
|
||||
char **current;
|
||||
bool *touched;
|
||||
bool *in_single_quote;
|
||||
bool *in_double_quote;
|
||||
t_minishell *minishell;
|
||||
bool expand_vars;
|
||||
} t_fields_ctx;
|
||||
|
||||
typedef struct s_field_state
|
||||
{
|
||||
bool touched;
|
||||
bool in_single_quote;
|
||||
bool in_double_quote;
|
||||
} t_field_state;
|
||||
|
||||
#endif
|
||||
82
src/parser/parser_expand_variable.c
Normal file
82
src/parser/parser_expand_variable.c
Normal file
@@ -0,0 +1,82 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* parser_expand_variable.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/14 06:56:00 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/14 06:56:00 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "parser_expand_internal.h"
|
||||
|
||||
void parser_expand_malloc_error(
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
minishell->exit = true;
|
||||
malloc_error();
|
||||
}
|
||||
|
||||
static bool is_var_char(
|
||||
char c
|
||||
)
|
||||
{
|
||||
return (ft_isalnum(c) || c == '_');
|
||||
}
|
||||
|
||||
static char *dup_or_error(
|
||||
const char *value,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
char *dup;
|
||||
|
||||
dup = ft_strdup(value);
|
||||
if (dup == NULL)
|
||||
return (parser_expand_malloc_error(minishell), NULL);
|
||||
return (dup);
|
||||
}
|
||||
|
||||
static char *expand_named_variable(
|
||||
const char *word,
|
||||
size_t *i,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
char *name;
|
||||
char *value;
|
||||
size_t start;
|
||||
|
||||
start = *i;
|
||||
while (word[*i] != '\0' && is_var_char(word[*i]))
|
||||
(*i)++;
|
||||
name = ft_substr(word, start, *i - start);
|
||||
if (name == NULL)
|
||||
return (parser_expand_malloc_error(minishell), NULL);
|
||||
value = get_var(name, minishell);
|
||||
free(name);
|
||||
if (value == NULL)
|
||||
value = "";
|
||||
return (dup_or_error(value, minishell));
|
||||
}
|
||||
|
||||
char *parser_expand_variable(const char *word, size_t *i,
|
||||
t_minishell *minishell)
|
||||
{
|
||||
char *expanded;
|
||||
|
||||
(*i)++;
|
||||
if (word[*i] == '?')
|
||||
{
|
||||
expanded = ft_itoa(minishell->exit_status);
|
||||
if (expanded == NULL)
|
||||
return (parser_expand_malloc_error(minishell), NULL);
|
||||
return ((*i)++, expanded);
|
||||
}
|
||||
if (word[*i] == '\0' || !is_var_char(word[*i]))
|
||||
return (dup_or_error("$", minishell));
|
||||
return (expand_named_variable(word, i, minishell));
|
||||
}
|
||||
104
src/parser/parser_expand_word.c
Normal file
104
src/parser/parser_expand_word.c
Normal file
@@ -0,0 +1,104 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* parser_expand_word.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/14 06:56:00 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/14 06:56:00 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "parser_expand_internal.h"
|
||||
|
||||
static bool word_toggle_quotes(
|
||||
char c,
|
||||
bool *in_single_quote,
|
||||
bool *in_double_quote
|
||||
)
|
||||
{
|
||||
if (c == '\'' && !*in_double_quote)
|
||||
return (*in_single_quote = !*in_single_quote, true);
|
||||
if (c == '\"' && !*in_single_quote)
|
||||
return (*in_double_quote = !*in_double_quote, true);
|
||||
return (false);
|
||||
}
|
||||
|
||||
static bool word_append(
|
||||
char **result,
|
||||
const char *value,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
char *joined;
|
||||
|
||||
joined = ft_strnjoin(2, *result, (char *)value);
|
||||
if (joined == NULL)
|
||||
{
|
||||
free(*result);
|
||||
*result = NULL;
|
||||
return (parser_expand_malloc_error(minishell), false);
|
||||
}
|
||||
free(*result);
|
||||
*result = joined;
|
||||
return (true);
|
||||
}
|
||||
|
||||
static bool word_step(
|
||||
const char *word,
|
||||
size_t *i,
|
||||
t_word_ctx ctx
|
||||
)
|
||||
{
|
||||
char *expanded;
|
||||
char value[2];
|
||||
|
||||
if (word_toggle_quotes(word[*i], ctx.in_single_quote, ctx.in_double_quote))
|
||||
return ((*i)++, true);
|
||||
if (word[*i] == '$' && word[*i + 1] != '\0'
|
||||
&& !*ctx.in_single_quote && !*ctx.in_double_quote
|
||||
&& (word[*i + 1] == '\'' || word[*i + 1] == '\"'))
|
||||
return ((*i)++, true);
|
||||
if (word[*i] == '$' && !*ctx.in_single_quote && ctx.expand_vars)
|
||||
{
|
||||
expanded = parser_expand_variable(word, i, ctx.minishell);
|
||||
if (expanded == NULL || !word_append(ctx.result, expanded,
|
||||
ctx.minishell))
|
||||
return (free(expanded), false);
|
||||
return (free(expanded), true);
|
||||
}
|
||||
value[0] = word[*i];
|
||||
value[1] = '\0';
|
||||
(*i)++;
|
||||
return (word_append(ctx.result, value, ctx.minishell));
|
||||
}
|
||||
|
||||
char *parser_expand_word(
|
||||
const char *word,
|
||||
t_minishell *minishell,
|
||||
bool expand_vars
|
||||
)
|
||||
{
|
||||
char *result;
|
||||
size_t i;
|
||||
bool in_single_quote;
|
||||
bool in_double_quote;
|
||||
|
||||
result = ft_strdup("");
|
||||
if (result == NULL)
|
||||
return (parser_expand_malloc_error(minishell), NULL);
|
||||
i = 0;
|
||||
in_single_quote = false;
|
||||
in_double_quote = false;
|
||||
while (word[i] != '\0')
|
||||
{
|
||||
if (!word_step(word, &i, (t_word_ctx){&result, &in_single_quote,
|
||||
&in_double_quote, minishell, expand_vars}))
|
||||
return (NULL);
|
||||
}
|
||||
if (in_single_quote || in_double_quote)
|
||||
return (free(result), syntax_error_unexpected_token(NULL),
|
||||
minishell->exit_status = 2, NULL);
|
||||
return (result);
|
||||
}
|
||||
98
src/parser/parser_redirection.c
Normal file
98
src/parser/parser_redirection.c
Normal file
@@ -0,0 +1,98 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* parser_redirection.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/13 21:28:35 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/13 21:28:35 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "parser.h"
|
||||
#include "errors.h"
|
||||
|
||||
static t_redirection *redirection_new(t_list **tokens);
|
||||
|
||||
/**
|
||||
* @brief Creates a new redirection from a list of tokens.
|
||||
*
|
||||
* @param tokens The list of tokens to create the redirection from.
|
||||
*
|
||||
* @return A new redirection or `NULL` on error.
|
||||
*/
|
||||
static t_redirection *redirection_new(
|
||||
t_list **tokens
|
||||
)
|
||||
{
|
||||
t_redirection *redirection;
|
||||
t_token *token;
|
||||
|
||||
redirection = (t_redirection *)malloc(sizeof(t_redirection));
|
||||
if (redirection == NULL)
|
||||
return (malloc_error(), NULL);
|
||||
token = (t_token *)(*tokens)->content;
|
||||
redirection->type = token->type;
|
||||
*tokens = (*tokens)->next;
|
||||
if (*tokens == NULL)
|
||||
{
|
||||
free(redirection);
|
||||
return (syntax_error_unexpected_token(NULL), NULL);
|
||||
}
|
||||
token = (t_token *)(*tokens)->content;
|
||||
if (token->type != TOKEN_WORD)
|
||||
{
|
||||
free(redirection);
|
||||
while (*tokens != NULL)
|
||||
*tokens = (*tokens)->next;
|
||||
return (syntax_error_unexpected_token(token), NULL);
|
||||
}
|
||||
redirection->target = ft_strdup(token->value);
|
||||
if (redirection->target == NULL)
|
||||
{
|
||||
free(redirection);
|
||||
return (malloc_error(), NULL);
|
||||
}
|
||||
*tokens = (*tokens)->next;
|
||||
return (redirection);
|
||||
}
|
||||
|
||||
void redirection_clear(
|
||||
t_redirection *redirection
|
||||
)
|
||||
{
|
||||
if (redirection != NULL)
|
||||
{
|
||||
free(redirection->target);
|
||||
free(redirection);
|
||||
}
|
||||
}
|
||||
|
||||
void redirection_add(
|
||||
t_list **tokens,
|
||||
t_token *token,
|
||||
t_command **command
|
||||
)
|
||||
{
|
||||
t_redirection *redirection;
|
||||
t_list *redirection_tokens;
|
||||
|
||||
redirection = redirection_new(tokens);
|
||||
if (redirection == NULL)
|
||||
{
|
||||
command_clear(*command);
|
||||
*command = NULL;
|
||||
return ;
|
||||
}
|
||||
redirection_tokens = ft_lstnew(redirection);
|
||||
if (redirection_tokens == NULL)
|
||||
{
|
||||
free(redirection);
|
||||
return (malloc_error());
|
||||
}
|
||||
if (token->type == TOKEN_HEREDOC)
|
||||
ft_lstadd_back(&(*command)->heredocs, redirection_tokens);
|
||||
else
|
||||
ft_lstadd_back(&(*command)->redirections, redirection_tokens);
|
||||
}
|
||||
121
src/parser/parser_words.c
Normal file
121
src/parser/parser_words.c
Normal file
@@ -0,0 +1,121 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* parser_words.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/13 21:29:44 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/14 03:34:38 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "parser.h"
|
||||
|
||||
char **args_to_array(t_list **args, size_t argc);
|
||||
void command_clear_argv(t_command *command);
|
||||
|
||||
/**
|
||||
* @brief Converts a list of arguments into an array.
|
||||
*
|
||||
* @param args The list of arguments to convert.
|
||||
* @param argc The number of arguments in the list.
|
||||
*
|
||||
* @return An array of arguments or `NULL` on error.
|
||||
*
|
||||
* @note The `args` list is cleared after the conversion and set to NULL.
|
||||
*/
|
||||
char **args_to_array(
|
||||
t_list **args,
|
||||
size_t argc
|
||||
)
|
||||
{
|
||||
char **argv;
|
||||
t_list *arg;
|
||||
size_t i;
|
||||
|
||||
argv = (char **)malloc(sizeof(char *) * (argc + 1));
|
||||
if (argv == NULL)
|
||||
return (NULL);
|
||||
i = 0;
|
||||
arg = *args;
|
||||
while (arg != NULL)
|
||||
{
|
||||
argv[i] = (char *)arg->content;
|
||||
arg = arg->next;
|
||||
i++;
|
||||
}
|
||||
argv[i] = NULL;
|
||||
ft_lstclear_nodes(args);
|
||||
return (argv);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds all consecutive word tokens to a command's argv and updates its
|
||||
* argc accordingly.
|
||||
*
|
||||
* @param command The command to add the word tokens to.
|
||||
* @param tokens The list of tokens to add to the command.
|
||||
*/
|
||||
void words_add(
|
||||
t_list **tokens,
|
||||
t_command **command
|
||||
)
|
||||
{
|
||||
t_list *args;
|
||||
t_list *arg;
|
||||
t_token *token;
|
||||
|
||||
args = NULL;
|
||||
arg = *tokens;
|
||||
token = (t_token *)arg->content;
|
||||
while (arg != NULL && token->type == TOKEN_WORD)
|
||||
{
|
||||
ft_lstadd_back(&args, ft_lstnew(ft_strdup(token->value)));
|
||||
(*command)->argc++;
|
||||
arg = arg->next;
|
||||
if (arg != NULL)
|
||||
token = (t_token *)arg->content;
|
||||
}
|
||||
*tokens = arg;
|
||||
(*command)->argv = args_to_array(&args, (*command)->argc);
|
||||
ft_lstclear_nodes(&args);
|
||||
}
|
||||
|
||||
void command_clear_argv(
|
||||
t_command *command
|
||||
)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (command->argv != NULL)
|
||||
{
|
||||
i = 0;
|
||||
while (i < command->argc)
|
||||
{
|
||||
free(command->argv[i]);
|
||||
i++;
|
||||
}
|
||||
free(command->argv);
|
||||
command->argv = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clears a command, freeing all associated memory.
|
||||
*
|
||||
* @param command The command to clear.
|
||||
*/
|
||||
void command_clear(
|
||||
t_command *command
|
||||
)
|
||||
{
|
||||
if (command != NULL)
|
||||
{
|
||||
command_clear_argv(command);
|
||||
ft_lstclear(&command->redirections,
|
||||
(void (*)(void *))redirection_clear);
|
||||
ft_lstclear(&command->heredocs, (void (*)(void *))redirection_clear);
|
||||
free(command);
|
||||
}
|
||||
}
|
||||
@@ -6,54 +6,33 @@
|
||||
/* 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 */
|
||||
/* Updated: 2026/02/14 01:32:42 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "minishell.h"
|
||||
#include "core.h"
|
||||
#include "variables.h"
|
||||
#include "errors.h"
|
||||
|
||||
/**
|
||||
* @brief Parses and stores environment variables from envp array into a hashmap
|
||||
* @brief Retrieves the value of an environment variable from the shell's
|
||||
* environment 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.
|
||||
* This function searches for the specified environment variable name in the
|
||||
* minishell's environment variable hashmap and returns its associated value.
|
||||
*
|
||||
* @param envp Array of environment variable strings in "NAME=value" format
|
||||
* @param msh Pointer to the minishell structure containing the environment
|
||||
* hashmap
|
||||
* @param name The name of the environment variable to retrieve.
|
||||
* @param minishell Pointer to the minishell object.
|
||||
*
|
||||
* @note The function assumes envp strings are in the standard format
|
||||
* "NAME=value"
|
||||
* @return The value of the environment variable if found, NULL if not found
|
||||
*/
|
||||
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++;
|
||||
}
|
||||
char *get_env(
|
||||
const char *name,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
return (ft_hashmap_get(minishell->variables.environment, name));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,29 +43,74 @@ void set_envp(
|
||||
* 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
|
||||
* @param name The name of the environment variable to set
|
||||
* @param value The value to assign to the environment variable
|
||||
* @param minishell Pointer to the minishell object.
|
||||
*/
|
||||
void set_env(
|
||||
const char *env_name,
|
||||
char *env_value,
|
||||
t_minishell *msh
|
||||
) {
|
||||
t_hashmap *environment;
|
||||
const char *key;
|
||||
char *old_value;
|
||||
const char *name,
|
||||
char *value,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
t_hashmap *environment;
|
||||
char *key;
|
||||
char *val;
|
||||
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));
|
||||
environment = minishell->variables.environment;
|
||||
key = (char *)name;
|
||||
if (key != NULL && !ft_hashmap_contains_key(environment, key))
|
||||
{
|
||||
key = ft_strdup(name);
|
||||
if (key == NULL)
|
||||
{
|
||||
minishell->exit = true;
|
||||
malloc_error();
|
||||
return ;
|
||||
}
|
||||
}
|
||||
val = value;
|
||||
if (val == NULL)
|
||||
val = ft_strdup("");
|
||||
else
|
||||
val = ft_strdup(value);
|
||||
if (val == NULL)
|
||||
{
|
||||
if (key != name)
|
||||
free(key);
|
||||
minishell->exit = true;
|
||||
malloc_error();
|
||||
return ;
|
||||
}
|
||||
old_value = ft_hashmap_put(environment, key, val);
|
||||
if (old_value != NULL)
|
||||
free(old_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes an environment variable by name.
|
||||
*
|
||||
* @param name The name of the environment variable to remove.
|
||||
* @param minishell Pointer to the minishell structure.
|
||||
*
|
||||
* @note If the environment variable exists, it will be removed from the hashmap
|
||||
* and its associated value will be freed.
|
||||
*/
|
||||
void unset_env(
|
||||
const char *name,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
t_hashmap *environment;
|
||||
char *val;
|
||||
|
||||
environment = minishell->variables.environment;
|
||||
val = ft_hashmap_remove(environment, (void *)name);
|
||||
if (val != NULL)
|
||||
free(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts the environment variables hashmap to an envp array format.
|
||||
*
|
||||
@@ -94,7 +118,7 @@ void set_env(
|
||||
* 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
|
||||
* @param minishell Pointer to the minishell structure containing the environment
|
||||
* variables hashmap.
|
||||
*
|
||||
* @return A dynamically allocated array of strings representing environment
|
||||
@@ -109,16 +133,19 @@ void set_env(
|
||||
* the NULL terminator.
|
||||
*/
|
||||
char **get_envp(
|
||||
t_minishell *msh
|
||||
) {
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
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 *));
|
||||
env_list = ft_hashmap_entries(minishell->variables.environment);
|
||||
envp = (char **)malloc(
|
||||
(minishell->variables.environment->size + 1) * sizeof(char *)
|
||||
);
|
||||
if (envp != NULL)
|
||||
{
|
||||
i = 0;
|
||||
@@ -136,44 +163,39 @@ char **get_envp(
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Frees a dynamically allocated environment variables array
|
||||
* @brief Parses and stores environment variables from envp array into a hashmap
|
||||
*
|
||||
* 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.
|
||||
* 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 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.
|
||||
* @param envp Array of environment variable strings in "NAME=value" format
|
||||
* @param minishell Pointer to the minishell structure containing the environment
|
||||
* hashmap
|
||||
*
|
||||
* @note The function assumes envp strings are in the standard format
|
||||
* "NAME=value"
|
||||
*/
|
||||
void free_envp(
|
||||
char **envp
|
||||
) {
|
||||
size_t i;
|
||||
void set_envp(
|
||||
char **envp,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
t_hashmap **environment;
|
||||
char **key_value;
|
||||
|
||||
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));
|
||||
if (minishell == NULL || envp == NULL)
|
||||
return ;
|
||||
environment = &minishell->variables.environment;
|
||||
*environment = ft_hashmap_new(32, ft_hashmap_hashstr, ft_hashmap_strcmp);
|
||||
if (*environment == NULL)
|
||||
return ;
|
||||
while (*envp != NULL)
|
||||
{
|
||||
key_value = ft_split(*envp, '=');
|
||||
set_env(key_value[0], key_value[1], minishell);
|
||||
ft_free_split(key_value);
|
||||
envp++;
|
||||
}
|
||||
}
|
||||
120
src/variables/internal/internal.c
Normal file
120
src/variables/internal/internal.c
Normal file
@@ -0,0 +1,120 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* internal.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/13 21:29:43 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/14 01:39:36 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "minishell.h"
|
||||
#include "core.h"
|
||||
#include "errors.h"
|
||||
|
||||
/**
|
||||
* @brief Retrieves the value of an internal variable by name.
|
||||
*
|
||||
* @param name The name of the internal variable to retrieve.
|
||||
* @param minishell Pointer to the minishell structure.
|
||||
*
|
||||
* @return The value of the internal variable if found, or NULL if not found.
|
||||
*/
|
||||
char *get_int(
|
||||
const char *name,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
return (ft_hashmap_get(minishell->variables.internal, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stores a variable as internal.
|
||||
*
|
||||
* @param name The name of the internal variable to set.
|
||||
* @param value The value to associate with the internal variable.
|
||||
* @param minishell Pointer to the minishell structure.
|
||||
*
|
||||
* @note If the internal variable already exists, its value will be updated and
|
||||
* the old value will be freed. If it does not exist, a new internal
|
||||
* variable will be created.
|
||||
*/
|
||||
void set_int(
|
||||
const char *name,
|
||||
char *value,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
t_hashmap *internal;
|
||||
char *key;
|
||||
char *val;
|
||||
char *old_val;
|
||||
|
||||
internal = minishell->variables.internal;
|
||||
key = (char *)name;
|
||||
if (key != NULL && !ft_hashmap_contains_key(internal, key))
|
||||
{
|
||||
key = ft_strdup(name);
|
||||
if (key == NULL)
|
||||
return (minishell->exit = true, malloc_error());
|
||||
}
|
||||
val = value;
|
||||
if (val != NULL)
|
||||
val = ft_strdup(value);
|
||||
if (val == NULL)
|
||||
{
|
||||
if (key != name)
|
||||
free(key);
|
||||
return (minishell->exit = true, malloc_error());
|
||||
}
|
||||
old_val = ft_hashmap_put(internal, key, val);
|
||||
if (old_val != NULL)
|
||||
free(old_val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes an internal variable by name.
|
||||
*
|
||||
* @param name The name of the internal variable to remove.
|
||||
* @param minishell Pointer to the minishell structure.
|
||||
*
|
||||
* @note If the internal variable exists, it will be removed from the hashmap
|
||||
* and its associated value will be freed.
|
||||
*/
|
||||
void unset_int(
|
||||
const char *name,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
char *value;
|
||||
|
||||
value = ft_hashmap_remove(minishell->variables.internal, (void *)name);
|
||||
if (value != NULL)
|
||||
free(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes the internal variables hashmap with default values.
|
||||
*
|
||||
* @param minishell Pointer to the minishell structure to initialize.
|
||||
*
|
||||
* @warning This function must be called only once during the initialization of
|
||||
* the minishell.
|
||||
*/
|
||||
void set_intp(
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
if (minishell == NULL)
|
||||
return ;
|
||||
minishell->variables.internal
|
||||
= ft_hashmap_new(16, ft_hashmap_hashstr, ft_hashmap_strcmp);
|
||||
if (minishell->variables.internal == NULL)
|
||||
return ;
|
||||
set_int("?", "0", minishell);
|
||||
set_int("_", "minishell", minishell);
|
||||
set_int("PS1", DEFAULT_PS1, minishell);
|
||||
set_int("PS2", DEFAULT_PS2, minishell);
|
||||
}
|
||||
50
src/variables/variables.c
Normal file
50
src/variables/variables.c
Normal file
@@ -0,0 +1,50 @@
|
||||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* variables.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2026/02/13 21:41:44 by sede-san #+# #+# */
|
||||
/* Updated: 2026/02/14 00:52:12 by sede-san ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "variables.h"
|
||||
|
||||
/**
|
||||
* @brief Retrieves the value of a variable by name.
|
||||
*
|
||||
* @param name The name of the variable to retrieve.
|
||||
* @param minishell Pointer to the minishell structure.
|
||||
*
|
||||
* @return The value of the variable if found, or NULL if not found.
|
||||
*/
|
||||
char *get_var(
|
||||
const char *name,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
char *value;
|
||||
|
||||
value = get_int(name, minishell);
|
||||
if (value == NULL)
|
||||
value = get_env(name, minishell);
|
||||
return (value);
|
||||
}
|
||||
|
||||
void set_var(const char *name, char *value, t_minishell *minishell)
|
||||
{
|
||||
if (ft_hashmap_contains_key(minishell->variables.internal, name))
|
||||
set_int(name, value, minishell);
|
||||
set_env(name, value, minishell);
|
||||
}
|
||||
|
||||
void unset_var(
|
||||
const char *name,
|
||||
t_minishell *minishell
|
||||
)
|
||||
{
|
||||
unset_int(name, minishell);
|
||||
unset_env(name, minishell);
|
||||
}
|
||||
1
tests/42_minishell_tester
Submodule
1
tests/42_minishell_tester
Submodule
Submodule tests/42_minishell_tester added at e391cbc6a1
204
tests/builtins_edge_cases.sh
Executable file
204
tests/builtins_edge_cases.sh
Executable file
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -u
|
||||
|
||||
ROOT_DIR="$(cd -- "$(dirname -- "$0")/.." && pwd)"
|
||||
MSH_BIN="$ROOT_DIR/minishell"
|
||||
TMP_DIR="$(mktemp -d /tmp/minishell-builtins-tests.XXXXXX)"
|
||||
|
||||
PASS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
CASE_OUT=""
|
||||
CASE_ERR=""
|
||||
CASE_EC=0
|
||||
CASE_NAME=""
|
||||
CASE_FAILED=0
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
|
||||
pass() {
|
||||
printf "PASS: %s\n" "$1"
|
||||
PASS_COUNT=$((PASS_COUNT + 1))
|
||||
}
|
||||
|
||||
fail() {
|
||||
printf "FAIL: %s\n" "$1"
|
||||
FAIL_COUNT=$((FAIL_COUNT + 1))
|
||||
CASE_FAILED=1
|
||||
}
|
||||
|
||||
assert_exit_code() {
|
||||
local expected="$1"
|
||||
if [ "$CASE_EC" -ne "$expected" ]; then
|
||||
fail "$CASE_NAME -> exit code esperado $expected, obtenido $CASE_EC"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_err_contains() {
|
||||
local text="$1"
|
||||
if ! grep -Fq -- "$text" "$CASE_ERR"; then
|
||||
fail "$CASE_NAME -> stderr no contiene: $text"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_out_contains() {
|
||||
local text="$1"
|
||||
if ! grep -Fq -- "$text" "$CASE_OUT"; then
|
||||
fail "$CASE_NAME -> stdout no contiene: $text"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_out_regex_count() {
|
||||
local regex="$1"
|
||||
local expected="$2"
|
||||
local count
|
||||
|
||||
count="$(grep -Ec -- "$regex" "$CASE_OUT" || true)"
|
||||
if [ "$count" -ne "$expected" ]; then
|
||||
fail "$CASE_NAME -> regex [$regex] esperado $expected, obtenido $count"
|
||||
fi
|
||||
}
|
||||
|
||||
run_case() {
|
||||
local name="$1"
|
||||
local input="$2"
|
||||
local expected_exit="$3"
|
||||
local clean_env="${4:-0}"
|
||||
|
||||
CASE_NAME="$name"
|
||||
CASE_OUT="$TMP_DIR/$name.out"
|
||||
CASE_ERR="$TMP_DIR/$name.err"
|
||||
CASE_EC=0
|
||||
CASE_FAILED=0
|
||||
|
||||
if [ "$clean_env" -eq 1 ]; then
|
||||
printf "%b" "$input" | env -i PATH="$PATH" "$MSH_BIN" >"$CASE_OUT" 2>"$CASE_ERR"
|
||||
else
|
||||
printf "%b" "$input" | "$MSH_BIN" >"$CASE_OUT" 2>"$CASE_ERR"
|
||||
fi
|
||||
CASE_EC=$?
|
||||
assert_exit_code "$expected_exit"
|
||||
if [ "$CASE_FAILED" -eq 0 ]; then
|
||||
pass "$CASE_NAME"
|
||||
fi
|
||||
}
|
||||
|
||||
run_pwd_deleted_dir_case() {
|
||||
local tmpd
|
||||
|
||||
tmpd="$TMP_DIR/pwd_deleted_dir"
|
||||
mkdir -p "$tmpd"
|
||||
run_case "pwd_deleted_dir" \
|
||||
"cd $tmpd\n/bin/rmdir $tmpd\npwd\nexit\n" \
|
||||
1
|
||||
assert_err_contains "minishell: pwd:"
|
||||
if [ "$CASE_FAILED" -eq 0 ]; then
|
||||
pass "$CASE_NAME assertions"
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
if [ ! -x "$MSH_BIN" ]; then
|
||||
printf "Compilando minishell...\n"
|
||||
if ! make -C "$ROOT_DIR" >/dev/null; then
|
||||
printf "No se pudo compilar minishell\n"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
run_case "exit_non_numeric" "exit abc\n" 2
|
||||
assert_err_contains "numeric argument required"
|
||||
if [ "$CASE_FAILED" -eq 0 ]; then
|
||||
pass "$CASE_NAME assertions"
|
||||
fi
|
||||
|
||||
run_case "exit_sign_only" "exit +\n" 2
|
||||
assert_err_contains "numeric argument required"
|
||||
if [ "$CASE_FAILED" -eq 0 ]; then
|
||||
pass "$CASE_NAME assertions"
|
||||
fi
|
||||
|
||||
run_case "exit_negative_wrap" "exit -1\n" 255
|
||||
|
||||
run_case "exit_too_many_args" "exit 7 8\npwd\nexit\n" 0
|
||||
assert_err_contains "too many arguments"
|
||||
assert_out_contains "$ROOT_DIR"
|
||||
if [ "$CASE_FAILED" -eq 0 ]; then
|
||||
pass "$CASE_NAME assertions"
|
||||
fi
|
||||
|
||||
run_case "cd_home_not_set" "cd\nexit\n" 1 1
|
||||
assert_err_contains "HOME not set"
|
||||
if [ "$CASE_FAILED" -eq 0 ]; then
|
||||
pass "$CASE_NAME assertions"
|
||||
fi
|
||||
|
||||
run_case "cd_invalid_path" "cd /definitely/not/here\nexit\n" 1
|
||||
assert_err_contains "No such file or directory"
|
||||
if [ "$CASE_FAILED" -eq 0 ]; then
|
||||
pass "$CASE_NAME assertions"
|
||||
fi
|
||||
|
||||
run_case "cd_dash_roundtrip" "cd /tmp\ncd -\npwd\nexit\n" 0
|
||||
assert_out_contains "$ROOT_DIR"
|
||||
if [ "$CASE_FAILED" -eq 0 ]; then
|
||||
pass "$CASE_NAME assertions"
|
||||
fi
|
||||
|
||||
run_pwd_deleted_dir_case
|
||||
|
||||
run_case "echo_flags" "echo -nn hi\necho hi-there\nexit\n" 0
|
||||
assert_out_contains "himinishell >"
|
||||
assert_out_contains "hi-there"
|
||||
if [ "$CASE_FAILED" -eq 0 ]; then
|
||||
pass "$CASE_NAME assertions"
|
||||
fi
|
||||
|
||||
run_case "env_builtin_with_arg" "env EXTRA\nexit\n" 1
|
||||
assert_err_contains "too many arguments"
|
||||
if [ "$CASE_FAILED" -eq 0 ]; then
|
||||
pass "$CASE_NAME assertions"
|
||||
fi
|
||||
|
||||
run_case "export_unset_cycle" \
|
||||
"export FOO=bar\n/usr/bin/env\nunset FOO\n/usr/bin/env\nexit\n" \
|
||||
0
|
||||
assert_out_regex_count "^FOO=bar$" 1
|
||||
if [ "$CASE_FAILED" -eq 0 ]; then
|
||||
pass "$CASE_NAME assertions"
|
||||
fi
|
||||
|
||||
run_case "env_builtin_lists_exported" \
|
||||
"export FOO=bar\nenv\nunset FOO\nenv\nexit\n" \
|
||||
0
|
||||
assert_out_regex_count "^FOO=bar$" 1
|
||||
if [ "$CASE_FAILED" -eq 0 ]; then
|
||||
pass "$CASE_NAME assertions"
|
||||
fi
|
||||
|
||||
run_case "export_invalid_identifier" "export 1A=2\nexit\n" 1
|
||||
assert_err_contains "not a valid identifier"
|
||||
if [ "$CASE_FAILED" -eq 0 ]; then
|
||||
pass "$CASE_NAME assertions"
|
||||
fi
|
||||
|
||||
run_case "unset_invalid_identifier" "unset 1A\nexit\n" 1
|
||||
assert_err_contains "not a valid identifier"
|
||||
if [ "$CASE_FAILED" -eq 0 ]; then
|
||||
pass "$CASE_NAME assertions"
|
||||
fi
|
||||
|
||||
run_case "export_without_value" "unset ZZZ\nexport ZZZ\n/usr/bin/env\nexit\n" 0
|
||||
assert_out_regex_count "^ZZZ=$" 1
|
||||
if [ "$CASE_FAILED" -eq 0 ]; then
|
||||
pass "$CASE_NAME assertions"
|
||||
fi
|
||||
|
||||
printf "\nResumen tests builtins: PASS=%d FAIL=%d\n" "$PASS_COUNT" "$FAIL_COUNT"
|
||||
if [ "$FAIL_COUNT" -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
Reference in New Issue
Block a user