diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..b07a7fa --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,35 @@ +# Repository Guidelines + +## Project Structure & Module Organization +- `src/` holds the shell implementation, with submodules for `builtins/`, `executor/`, `parser/`, and `variables/`. +- `include/` contains public headers used across modules. +- `lib/` is populated at build time with third-party 42 libraries (libft, get_next_line, ft_printf, ft_args). +- `docs/` stores project references and manual test notes (see `docs/tests.md`). +- `build/` is the object output directory generated by `make`. + +## Build, Test, and Development Commands +- `make` or `make all`: build `minishell` (auto-clones required libs into `lib/`). +- `make clean`: remove objects in `build/`. +- `make fclean`: remove objects and the `minishell` binary (also cleans libs). +- `make re`: full rebuild. +- `./minishell`: run locally after a build. +- `make DEBUG=lldb` or `make DEBUG=valgrind` or `make DEBUG=address`: rebuild with debug/ASan-friendly flags. + +## Coding Style & Naming Conventions +- The codebase follows 42 **Norminette v4** rules. Run `norminette *.c *.h` (or on specific files) before submitting changes. +- Keep file names lowercase with underscores (e.g., `src/builtins/echo/echo.c`). +- Keep headers in `include/` and expose only what modules need. + +## Testing Guidelines +- There is no automated test runner. Use manual checks in `docs/tests.md` and basic shell behavior checks (pipes, redirects, builtins). +- When debugging memory issues, run under valgrind and use the suppression file in `valgrind/readline.supp`. + +## Commit & Pull Request Guidelines +- Commit messages in this repo use a simple `type: summary` format (examples: `update: ...`, `fix: ...`). Keep summaries short and specific. +- For PRs, include: + 1. What changed and why. + 2. How to test (commands or manual steps). + 3. Notes on any parser/executor/builtin behavior changes. + +## Configuration Tips +- The project depends on `readline`; ensure your system has `libreadline-dev` or equivalent before building. diff --git a/Makefile b/Makefile index 6873f10..bb21283 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ # By: sede-san , >>, <<. +- Se resuelven antes de ejecutar el comando. +- Heredoc: lectura hasta delimitador, sin historial. + +### Pipes +- Conecto stdout del comando i con stdin del comando i+1. +- Uso pipe() y dup2() en el child. +- El padre cierra fds que no usa. + +### Builtins +- echo -n, cd, pwd, export, unset, env, exit. +- Si no hay pipeline, se ejecutan en el padre para afectar el estado. +- En pipeline, se ejecutan en el child. + +### Variables +- $VAR: entorno. +- $?: ultimo exit status. + +### Senales +- Una sola variable global para la senal. +- ctrl-C: nueva linea y prompt. +- ctrl-D: salir del shell. +- ctrl-\: no hace nada. + +## 4. Ejemplos rapidos para demostrar + +1. Pipes +- `ls | wc -l` + +2. Redirecciones +- `echo hola > out.txt` +- `cat < out.txt` + +3. Heredoc +- `cat << EOF` +- escribir lineas +- `EOF` + +4. Expansion +- `echo $HOME` +- `echo $?` + +## 5. Errores y robustez +- No ejecuto si hay errores de parseo. +- Mensajes con perror cuando fallan syscalls. +- exit_status siempre actualizado. + +## 6. Mensajes finales recomendados +- "He separado parsing y ejecucion para que el codigo sea mantenible y + la defensa mas clara". +- "Sigo el comportamiento de bash como referencia". + diff --git a/docs/guia_obligatoria_es.md b/docs/guia_obligatoria_es.md new file mode 100644 index 0000000..7a3cf67 --- /dev/null +++ b/docs/guia_obligatoria_es.md @@ -0,0 +1,292 @@ +# Minishell - Guia de estructura y pasos (parte obligatoria) + +Objetivo de este documento +- Proponer una estructura de archivos y datos. +- Describir el proceso paso a paso como tutorial. +- Servir de guion para defensa (explicaciones claras y ordenadas). + +Este documento no implementa nada. Solo define el plan y el por que. + +--- + +## 1. Vision general del flujo + +El shell se puede explicar como una tuberia de fases: +1. Lectura interactiva (prompt + historial). +2. Tokenizacion (lexing) con comillas y metacaracteres. +3. Parseo (construccion de comandos, redirecciones, pipes). +4. Expansion de variables ($VAR y $?). +5. Preparacion de ejecucion (resolucion de rutas, heredoc). +6. Ejecucion (builtins o execve) con pipes y redirecciones. +7. Gestion de exit status y señales. + +Esta separacion permite explicar en defensa cada pieza por separado y justificar +las decisiones tecnicas. + +--- + +## 2. Propuesta de estructuras de datos + +Estas estructuras son solo guia. Adapta nombres a tu estilo y Norminette. + +### 2.1 Token (lexer) +Representa una unidad del input (palabra, pipe, redireccion, etc.). + +- enum e_tokentype + - TOK_WORD + - TOK_PIPE + - TOK_REDIR_IN (<) + - TOK_REDIR_OUT (>) + - TOK_REDIR_APPEND (>>) + - TOK_HEREDOC (<<) + +- struct s_token + - char *text + - t_tokentype type + - struct s_token *next + +Uso en defensa: el lexer separa el input en unidades, respetando comillas. + +### 2.2 Redireccion +Guarda los datos de redireccion por comando. + +- enum e_redirtype + - REDIR_IN + - REDIR_OUT + - REDIR_APPEND + - REDIR_HEREDOC + +- struct s_redir + - t_redirtype type + - char *target + - int fd + - struct s_redir *next + +Notas: +- target es el filename o delimitador de heredoc. +- fd se resuelve en fase de preparacion (open o pipe temporal). + +### 2.3 Comando + +- struct s_command + - char **argv + - int argc + - char *path + - t_redir *redirs + +### 2.4 Pipeline +Una lista de comandos en orden, unidos por pipes. + +- struct s_pipeline + - t_command **cmds + - size_t count + + +--- + +## 3. Lexer: reglas y pasos + +Reglas clave del subject: +- No interpretar comillas sin cerrar. +- Comilla simple: no se expanden variables ni metacaracteres. +- Comilla doble: se expanden variables, pero se respetan caracteres normales. +- Metacaracteres: |, <, >, <<, >> separan tokens. + +Pasos recomendados: +1. Recorrer la linea caracter a caracter. +2. Mantener estado: in_single, in_double. +3. Cuando no estas en comillas, detectar metacaracteres y cortar tokens. +4. Construir TOK_WORD con el texto exacto (sin eliminar comillas aun). +5. Si llegas a fin de linea con in_single o in_double activo, error de parseo. + +Explicacion para defensa: +- El lexer no sabe de ejecucion, solo separa en tokens validos. +- El manejo de comillas se hace aqui para respetar la sintaxis del shell. + +--- + +## 4. Parser: construccion de comandos + +Objetivo: transformar tokens en una estructura ejecutable. + +Pasos: +1. Recorrer lista de tokens. +2. Cada TOK_PIPE cierra un comando actual y abre el siguiente. +3. TOK_WORD se agrega a argv. +4. TOK_REDIR_* consume el siguiente token (debe ser TOK_WORD) como target. +5. Construir lista de redirecciones para cada comando. +6. Validar errores: pipe inicial/final, redireccion sin target, etc. + +Explicacion para defensa: +- El parser aplica reglas de orden y construye una estructura clara. +- Separar argv y redirecciones evita mezclar logica en executor. + +--- + +## 5. Expansion de variables + +Reglas: +- $VAR se sustituye por getenv/tabla interna. +- $? se sustituye por el exit_status anterior. +- En comilla simple no se expande. +- En comilla doble si se expande. + +Proceso recomendado: +1. Durante tokenizacion, guardar el texto con sus comillas o bien + marcar segmentos con estado de comillas. +2. En expansion, recorrer cada palabra y reemplazar $... +3. Si variable no existe, reemplazar por string vacio. +4. Eliminar comillas despues de la expansion. + +Explicacion para defensa: +- La expansion es una fase separada para no complicar el parser. +- $?, variable especial, refleja el estado de la ultima ejecucion. + +--- + +## 6. Redirecciones y heredoc + +Redirecciones basicas: +- <: open(file, O_RDONLY) +- >: open(file, O_WRONLY | O_CREAT | O_TRUNC) +- >>: open(file, O_WRONLY | O_CREAT | O_APPEND) + +Heredoc (<<): +1. Leer lineas hasta delimitador exacto. +2. Guardar el contenido en un pipe o fichero temporal. +3. Usar el extremo de lectura como STDIN del comando. +4. No guardar el contenido en historial. + +Explicacion para defensa: +- Las redirecciones se resuelven antes de ejecutar el proceso. +- Heredoc es una fuente especial de entrada. + +--- + +## 7. Resolucion de comandos y PATH + +Reglas: +- Si argv[0] es una ruta absoluta o relativa (/, ./, ../), usarla tal cual. +- Si no, buscar en PATH separando por ':'. +- Si es builtin, no se necesita path real. + +Proceso: +1. Detectar builtin. +2. Si no builtin y no es ruta, recorrer PATH y usar access(). +3. Guardar path en t_command->path. + +--- + +## 8. Ejecucion + +Caso 1: comando unico builtin +- Ejecutar en el proceso padre para que pueda modificar estado del shell + (ej: cd, export, unset, exit). + +Caso 2: pipeline o comando externo +- Usar fork + execve. +- Crear pipes entre comandos. +- Aplicar redirecciones antes de ejecutar. + +Proceso para pipeline: +1. Para cada comando, crear pipe si hay siguiente. +2. fork. +3. En child: dup2 para redirecciones y pipes, luego ejecutar. +4. En parent: cerrar FDs innecesarios y seguir. +5. Esperar procesos, guardar exit status del ultimo comando. + +Explicacion para defensa: +- Las pipes conectan stdout del comando i con stdin del comando i+1. +- Los builtins dentro de pipeline se ejecutan en child. + +--- + +## 9. Builtins obligatorios + +- echo con -n +- cd (ruta relativa o absoluta) +- pwd +- export (sin opciones) +- unset (sin opciones) +- env (sin opciones o argumentos) +- exit + +Notas de defensa: +- export/unset trabajan sobre la tabla de variables del shell. +- env imprime variables de entorno. +- exit debe actualizar exit_status y terminar el loop principal. + +--- + +## 10. Senales + +Requisitos interactivos: +- ctrl-C: imprime nueva linea y muestra prompt. +- ctrl-D: termina el shell. +- ctrl-\: no hace nada. + +Regla del subject: +- Solo una variable global para indicar la senal recibida. + +Proceso: +1. Definir una variable global int g_signal. +2. Configurar handlers con sigaction. +3. En handler: actualizar g_signal y escribir un '\n' si procede. +4. En el loop principal: si g_signal indica SIGINT, resetear lineas de readline. + +--- + +## 11. Manejo de errores y salida + +- Mostrar errores con perror o mensajes consistentes. +- Si parseo falla, no ejecutar nada. +- Mantener exit_status actualizado. + +Explicacion en defensa: +- Un shell robusto evita ejecutar comandos parcialmente parseados. +- exit_status es clave para $?. + +--- + +## 12. Checklist para defensa (guion rapido) + +1. Explico el flujo completo: lectura -> lexer -> parser -> expansion -> exec. +2. Explico como manejo comillas y metacaracteres. +3. Explico como construyo argv y redirecciones. +4. Explico expansion de $VAR y $?. +5. Explico pipes y redirecciones con dup2. +6. Explico por que los builtins se ejecutan en parent o child. +7. Explico manejo de senales y la variable global unica. +8. Explico exit_status y comportamiento de $?. + +--- + +## 13. Sugerencia de estructura de archivos + +- include/ + - minishell.h + - core.h (estructuras globales y estado) + - parser.h (tokens, parser) + - executor.h + - builtins.h + +- src/ + - core/ (init, signals, util) + - parser/ (lexer.c, parser.c, expand.c) + - executor/ (executor.c, redirs.c) + - builtins/ (echo, cd, pwd, exit, env, export, unset) + - variables/ (environment.c) + - minishell.c (loop principal) + - main.c + +Esto es solo una guia; no es obligatorio seguirla al pie de la letra. + +--- + +## 14. Consejos para la defensa + +- Usa bash como referencia de comportamiento. +- Demuestra un par de ejemplos: pipe, redireccion y expansion. +- Si algo falla, explica que el parser previene ejecucion parcial. +- Recalca el manejo correcto de ctrl-C y ctrl-\. + diff --git a/executor_new_previous.c b/executor_new_previous.c new file mode 100644 index 0000000..86cfb2f --- /dev/null +++ b/executor_new_previous.c @@ -0,0 +1,341 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* executor_new.c :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: sede-san +#include + +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"); +// } \ No newline at end of file diff --git a/executor_old.c b/executor_old.c new file mode 100644 index 0000000..8121b33 --- /dev/null +++ b/executor_old.c @@ -0,0 +1,256 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* executor.c :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: sede-san + +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"); +// } \ No newline at end of file diff --git a/include/builtins.h b/include/builtins.h index 0bce9a3..c8dac83 100644 --- a/include/builtins.h +++ b/include/builtins.h @@ -6,19 +6,18 @@ /* By: sede-san " + +/** + * @brief Default secondary prompt string for multiline input + */ +# define DEFAULT_PS2 "> " + /******************************************************************************/ /* Functions */ /******************************************************************************/ @@ -79,7 +90,7 @@ typedef struct s_command extern int minishell_init(t_minishell *minishell, char **envp); -extern u_int8_t minishell_run(t_minishell *minishell); +extern uint8_t minishell_run(t_minishell *minishell); extern void minishell_clear(t_minishell *minishell); diff --git a/include/executor.h b/include/executor.h index b65aafa..f2a49f4 100644 --- a/include/executor.h +++ b/include/executor.h @@ -6,7 +6,7 @@ /* By: sede-san + +# define READ_PIPE 0 +# define WRITE_PIPE 1 + +typedef struct s_pipeline +{ + int prev_read_fd; + int pipefd[2]; +} t_pipeline; + /******************************************************************************/ /* Functions */ @@ -21,6 +33,9 @@ // executor.c -extern u_int8_t execute(t_command command, t_minishell *minishell); +# define PIPE_ERROR -1 +# define FORK_ERROR -1 + +extern uint8_t execute(t_list *command, t_minishell *minishell); #endif /* EXECUTOR_H */ diff --git a/include/minishell.h b/include/minishell.h index 20140b8..10252d5 100644 --- a/include/minishell.h +++ b/include/minishell.h @@ -6,7 +6,7 @@ /* By: sede-san +# include # include // readline(3), rl_clear_history(), // rl_on_new_line(), rl_replace_line(), // rl_redisplay() diff --git a/include/parser.h b/include/parser.h index e2b8972..3e820a6 100644 --- a/include/parser.h +++ b/include/parser.h @@ -6,7 +6,7 @@ /* By: sede-san " +# define APPEND_STR ">>" +# define HEREDOC_STR "<<" + +# define TOKENS_COUNT 5 + +typedef enum e_token_type +{ + TOKEN_WORD, + TOKEN_PIPE, + TOKEN_REDIRECT_IN, + TOKEN_REDIRECT_OUT, + TOKEN_APPEND, + TOKEN_HEREDOC +} t_token_type; + +typedef struct s_token +{ + t_token_type type; + char *value; +} t_token; + +typedef enum e_redirection_type +{ + REDIRECT_IN, + REDIRECT_OUT, + APPEND, + HEREDOC +} t_redirection_type; + +typedef struct s_redirection +{ + t_redirection_type type; + char *target; +} t_redirection; /******************************************************************************/ /* Functions */ @@ -21,6 +61,6 @@ // parser.c -extern t_command parse(char *line, t_minishell *minishell); +extern t_list *parse(char *line, t_minishell *minishell); #endif /* PARSER_H */ diff --git a/minishell-codex/Makefile b/minishell-codex/Makefile new file mode 100644 index 0000000..7d0fb9a --- /dev/null +++ b/minishell-codex/Makefile @@ -0,0 +1,27 @@ +NAME := minishell +CC := cc +CFLAGS := -Wall -Wextra -Werror -g +INCLUDES := -Iinclude +READLINE_LIBS := -lreadline -lncurses + +SRCS := $(shell find src -name '*.c') +OBJS := $(SRCS:src/%.c=build/%.o) + +all: $(NAME) + +$(NAME): $(OBJS) + $(CC) $(CFLAGS) $(OBJS) $(READLINE_LIBS) -o $(NAME) + +build/%.o: src/%.c + @mkdir -p $(dir $@) + $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ + +clean: + rm -rf build + +fclean: clean + rm -f $(NAME) + +re: fclean all + +.PHONY: all clean fclean re diff --git a/minishell-codex/docs/defensa.md b/minishell-codex/docs/defensa.md new file mode 100644 index 0000000..a5a051a --- /dev/null +++ b/minishell-codex/docs/defensa.md @@ -0,0 +1,72 @@ +# Minishell - Guion de defensa (version codex) + +Este guion esta alineado con la estructura real en `minishell-codex/`. + +## 1. Explicacion corta del proyecto +- Minishell es un interprete de comandos interactivo. +- Implementa pipes, redirecciones, variables y builtins basicos. +- Se basa en el flujo: lectura -> lexer -> parser -> expansion -> ejecucion. + +## 2. Flujo completo (paso a paso) +1. `readline()` muestra el prompt y devuelve la linea. +2. `lex_line()` divide la linea en tokens (`TOK_WORD`, `TOK_PIPE`, redirecciones). +3. `parse_tokens()` construye la pipeline con comandos y redirecciones. +4. `expand_pipeline()` aplica expansion de `$VAR` y `$?` respetando comillas. +5. `execute_pipeline()` resuelve `PATH`, prepara heredocs y ejecuta. + +## 3. Estructuras clave +- `t_token`: tipo y texto de tokens (`minishell-codex/include/minishell.h`). +- `t_command`: argv, redirecciones, path. +- `t_pipeline`: lista de comandos. +- `t_redir`: tipo, target y fd. +- `t_shell`: estado global (env, exit_status, flags). + +## 4. Lexer (por que esta separado) +- Maneja comillas y metacaracteres sin mezclar con ejecucion. +- Detecta errores de comillas sin cerrar. +- Facilita el parseo posterior. + +## 5. Parser +- Convierte tokens en comandos reales. +- Cada `TOK_PIPE` crea un nuevo comando. +- Redirecciones se guardan en lista separada (`t_redir`). +- Valida errores (pipe sin comando, redireccion sin destino). + +## 6. Expansion +- `expand_pipeline()` recorre argv y targets de redireccion. +- Reglas: + - En comilla simple no se expande. + - En comilla doble si se expande. + - `$?` es el exit status anterior. + +## 7. Redirecciones y heredoc +- `apply_redirections()` abre y hace `dup2()`. +- `prepare_heredocs()` genera un pipe con el contenido. +- Heredoc no se mete en el historial. + +## 8. Ejecucion y pipes +- Si hay un solo builtin, se ejecuta en el padre. +- Si hay pipeline, todos se forkean. +- Se conectan con `pipe()` y `dup2()`. +- Se espera a todos, y el exit status es el del ultimo comando. + +## 9. Builtins +- Implementados en `src/builtins/builtins.c`. +- `echo`, `cd`, `pwd`, `env`, `export`, `unset`, `exit`. +- `export` valida identificadores y permite `KEY=VALUE`. + +## 10. Señales +- Una sola global: `g_signal`. +- `ctrl-C`: limpia linea y muestra prompt. +- `ctrl-\`: se ignora en interactivo. +- En child se restauran señales por defecto. + +## 11. Ejemplos rapidos para demostrar +- Pipes: `ls | wc -l` +- Redireccion: `echo hola > out.txt` +- Heredoc: `cat << EOF` -> texto -> `EOF` +- Expansion: `echo $HOME`, `echo $?` + +## 12. Mensaje final recomendado +"Separar lexer, parser, expansion y ejecucion me permitio mantener el codigo claro + y replicar el comportamiento de bash para el minimo requerido por el subject." diff --git a/minishell-codex/docs/tests_manual.md b/minishell-codex/docs/tests_manual.md new file mode 100644 index 0000000..895abe9 --- /dev/null +++ b/minishell-codex/docs/tests_manual.md @@ -0,0 +1,59 @@ +# Minishell - Checklist de pruebas manuales + +Ejecuta en `minishell-codex/`: +- `make` +- `./minishell` + +## 1. Prompt y salida +- Iniciar y salir con `ctrl-D`. +- `exit` debe cerrar el shell con el ultimo status. + +## 2. Comandos simples +- `ls` +- `pwd` +- `echo hola` + +## 3. Builtins +- `echo -n hola` (sin salto de linea) +- `cd /` luego `pwd` +- `export TEST=42` luego `env | grep TEST` +- `unset TEST` luego `env | grep TEST` (no debe aparecer) +- `env` sin argumentos +- `exit 2` + +## 4. Expansion +- `echo $HOME` +- `echo $?` despues de un comando que falle (ej: `ls noexiste`) +- `echo '$HOME'` (no expande) +- `echo "$HOME"` (si expande) + +## 5. Pipes +- `ls | wc -l` +- `echo hola | cat` +- `cat /etc/passwd | grep root | wc -l` + +## 6. Redirecciones +- `echo hola > out.txt` y luego `cat out.txt` +- `echo 1 >> out.txt` y luego `cat out.txt` +- `cat < out.txt` + +## 7. Heredoc +- `cat << EOF` + - escribir varias lineas + - `EOF` +- Ver que se imprime todo lo escrito. + +## 8. Comillas +- `echo "a b c"` (una sola palabra) +- `echo 'a b c'` (una sola palabra) +- `echo "a 'b' c"` + +## 9. Errores de parseo +- `| ls` (no debe ejecutar) +- `echo hola >` (error) +- `echo "hola` (comillas sin cerrar) + +## 10. Senales +- `ctrl-C` en prompt: debe limpiar linea y mostrar prompt nuevo. +- `sleep 5` y `ctrl-C`: debe interrumpir el proceso. +- `ctrl-\` no debe imprimir nada en prompt interactivo. diff --git a/minishell-codex/include/minishell.h b/minishell-codex/include/minishell.h new file mode 100644 index 0000000..fbc0e61 --- /dev/null +++ b/minishell-codex/include/minishell.h @@ -0,0 +1,136 @@ +#ifndef MINISHELL_H +#define MINISHELL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 diff --git a/minishell-codex/src/builtins/builtins.c b/minishell-codex/src/builtins/builtins.c new file mode 100644 index 0000000..372bc05 --- /dev/null +++ b/minishell-codex/src/builtins/builtins.c @@ -0,0 +1,221 @@ +#include "minishell.h" + +static int builtin_echo(t_command *cmd, t_shell *sh); +static int builtin_cd(t_command *cmd, t_shell *sh); +static int builtin_pwd(t_command *cmd, t_shell *sh); +static int builtin_env(t_command *cmd, t_shell *sh); +static int builtin_export(t_command *cmd, t_shell *sh); +static int builtin_unset(t_command *cmd, t_shell *sh); +static int builtin_exit(t_command *cmd, t_shell *sh); + +int is_builtin(const char *name) +{ + if (!name) + return 0; + return (strcmp(name, "echo") == 0 || strcmp(name, "cd") == 0 + || strcmp(name, "pwd") == 0 || strcmp(name, "env") == 0 + || strcmp(name, "export") == 0 || strcmp(name, "unset") == 0 + || strcmp(name, "exit") == 0); +} + +int exec_builtin(t_command *cmd, t_shell *sh) +{ + if (strcmp(cmd->argv[0], "echo") == 0) + return builtin_echo(cmd, sh); + if (strcmp(cmd->argv[0], "cd") == 0) + return builtin_cd(cmd, sh); + if (strcmp(cmd->argv[0], "pwd") == 0) + return builtin_pwd(cmd, sh); + if (strcmp(cmd->argv[0], "env") == 0) + return builtin_env(cmd, sh); + if (strcmp(cmd->argv[0], "export") == 0) + return builtin_export(cmd, sh); + if (strcmp(cmd->argv[0], "unset") == 0) + return builtin_unset(cmd, sh); + if (strcmp(cmd->argv[0], "exit") == 0) + return builtin_exit(cmd, sh); + return 1; +} + +static int builtin_echo(t_command *cmd, t_shell *sh) +{ + int i = 1; + int newline = 1; + + (void)sh; + while (cmd->argv[i] && cmd->argv[i][0] == '-' && cmd->argv[i][1] == 'n') + { + int j = 2; + while (cmd->argv[i][j] == 'n') + j++; + if (cmd->argv[i][j] != '\0') + break; + newline = 0; + i++; + } + while (cmd->argv[i]) + { + printf("%s", cmd->argv[i]); + if (cmd->argv[i + 1]) + printf(" "); + i++; + } + if (newline) + printf("\n"); + return 0; +} + +static int builtin_pwd(t_command *cmd, t_shell *sh) +{ + char buf[4096]; + (void)cmd; + (void)sh; + if (getcwd(buf, sizeof(buf))) + printf("%s\n", buf); + return 0; +} + +static int builtin_cd(t_command *cmd, t_shell *sh) +{ + char *path; + char cwd[4096]; + + if (cmd->argc > 2) + { + fprintf(stderr, "minishell: cd: too many arguments\n"); + return 1; + } + if (cmd->argc == 1) + path = env_get(sh, "HOME"); + else + path = cmd->argv[1]; + if (!path) + { + fprintf(stderr, "minishell: cd: HOME not set\n"); + return 1; + } + if (getcwd(cwd, sizeof(cwd))) + env_set(sh, "OLDPWD", cwd); + if (chdir(path) != 0) + { + perror("minishell: cd"); + return 1; + } + if (getcwd(cwd, sizeof(cwd))) + env_set(sh, "PWD", cwd); + return 0; +} + +static int builtin_env(t_command *cmd, t_shell *sh) +{ + if (cmd->argc > 1) + { + fprintf(stderr, "minishell: env: too many arguments\n"); + return 1; + } + env_print(sh); + return 0; +} + +static int valid_identifier(const char *s) +{ + int i = 0; + if (!s || !ms_is_alpha((unsigned char)s[0])) + return 0; + while (s[i]) + { + if (!ms_is_alnum((unsigned char)s[i])) + return 0; + i++; + } + return 1; +} + +static int builtin_export(t_command *cmd, t_shell *sh) +{ + int i = 1; + if (cmd->argc == 1) + { + env_print(sh); + return 0; + } + while (cmd->argv[i]) + { + char *eq = strchr(cmd->argv[i], '='); + if (eq) + { + char *key = ms_strndup(cmd->argv[i], (size_t)(eq - cmd->argv[i])); + char *val = ms_strdup(eq + 1); + if (!valid_identifier(key)) + fprintf(stderr, "minishell: export: `%s': not a valid identifier\n", cmd->argv[i]); + else + env_set(sh, key, val); + free(key); + free(val); + } + else + { + if (!valid_identifier(cmd->argv[i])) + fprintf(stderr, "minishell: export: `%s': not a valid identifier\n", cmd->argv[i]); + else + env_set(sh, cmd->argv[i], ""); + } + i++; + } + return 0; +} + +static int builtin_unset(t_command *cmd, t_shell *sh) +{ + int i = 1; + while (cmd->argv[i]) + { + env_unset(sh, cmd->argv[i]); + i++; + } + return 0; +} + +static int builtin_exit(t_command *cmd, t_shell *sh) +{ + long code = sh->exit_status; + int i = 0; + + if (sh->interactive) + printf("exit\n"); + if (cmd->argc == 1) + { + sh->exit_requested = 1; + sh->exit_status = (int)(code & 0xFF); + return sh->exit_status; + } + if (cmd->argc > 2) + { + fprintf(stderr, "minishell: exit: too many arguments\n"); + return 1; + } + if (cmd->argv[1][i] == '+' || cmd->argv[1][i] == '-') + i++; + if (cmd->argv[1][i] == '\0') + { + fprintf(stderr, "minishell: exit: %s: numeric argument required\n", cmd->argv[1]); + sh->exit_requested = 1; + sh->exit_status = 2; + return 2; + } + while (cmd->argv[1][i]) + { + if (!ms_is_digit((unsigned char)cmd->argv[1][i])) + { + fprintf(stderr, "minishell: exit: %s: numeric argument required\n", cmd->argv[1]); + sh->exit_requested = 1; + sh->exit_status = 2; + return 2; + } + i++; + } + code = strtol(cmd->argv[1], NULL, 10); + sh->exit_requested = 1; + sh->exit_status = (int)(code & 0xFF); + return sh->exit_status; +} diff --git a/minishell-codex/src/core/init.c b/minishell-codex/src/core/init.c new file mode 100644 index 0000000..3f5a7d1 --- /dev/null +++ b/minishell-codex/src/core/init.c @@ -0,0 +1,18 @@ +#include "minishell.h" + +void ms_init(t_shell *sh, char **envp) +{ + memset(sh, 0, sizeof(*sh)); + sh->interactive = isatty(STDIN_FILENO); + sh->exit_status = 0; + sh->last_status = 0; + sh->exit_requested = 0; + env_init(sh, envp); + ms_setup_signals(sh); +} + +void ms_cleanup(t_shell *sh) +{ + env_clear(sh); + rl_clear_history(); +} diff --git a/minishell-codex/src/core/loop.c b/minishell-codex/src/core/loop.c new file mode 100644 index 0000000..d20a7c3 --- /dev/null +++ b/minishell-codex/src/core/loop.c @@ -0,0 +1,46 @@ +#include "minishell.h" + +static void handle_line(t_shell *sh, char *line) +{ + int error = 0; + t_token *toks = NULL; + t_pipeline *p = NULL; + + toks = lex_line(line, &error); + if (error) + { + free_tokens(toks); + return; + } + p = parse_tokens(toks, &error); + free_tokens(toks); + if (error || !p) + { + free_pipeline(p); + return; + } + if (expand_pipeline(p, sh) != 0) + { + free_pipeline(p); + return; + } + sh->exit_status = execute_pipeline(p, sh); + sh->last_status = sh->exit_status; + free_pipeline(p); +} + +void ms_loop(t_shell *sh) +{ + char *line; + + while (!sh->exit_requested) + { + line = readline(MS_PROMPT); + if (!line) + break; + if (line[0] != '\0') + add_history(line); + handle_line(sh, line); + free(line); + } +} diff --git a/minishell-codex/src/core/signals.c b/minishell-codex/src/core/signals.c new file mode 100644 index 0000000..cd2af9c --- /dev/null +++ b/minishell-codex/src/core/signals.c @@ -0,0 +1,46 @@ +#include "minishell.h" + +int g_signal = 0; + +static void sigint_handler(int sig) +{ + g_signal = sig; + write(STDOUT_FILENO, "\n", 1); + rl_on_new_line(); + rl_replace_line("", 0); + rl_redisplay(); +} + +static void sigquit_handler(int sig) +{ + g_signal = sig; + (void)sig; +} + +void ms_setup_signals(t_shell *sh) +{ + struct sigaction sa_int; + struct sigaction sa_quit; + + (void)sh; + memset(&sa_int, 0, sizeof(sa_int)); + memset(&sa_quit, 0, sizeof(sa_quit)); + sa_int.sa_handler = sigint_handler; + sa_quit.sa_handler = sigquit_handler; + sigemptyset(&sa_int.sa_mask); + sigemptyset(&sa_quit.sa_mask); + sigaction(SIGINT, &sa_int, NULL); + sigaction(SIGQUIT, &sa_quit, NULL); +} + +void ms_set_child_signals(void) +{ + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); +} + +void ms_set_heredoc_signals(void) +{ + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_IGN); +} diff --git a/minishell-codex/src/env/env.c b/minishell-codex/src/env/env.c new file mode 100644 index 0000000..6002e9b --- /dev/null +++ b/minishell-codex/src/env/env.c @@ -0,0 +1,171 @@ +#include "minishell.h" + +static t_env *env_new(const char *key, const char *value) +{ + t_env *n = (t_env *)calloc(1, sizeof(t_env)); + if (!n) + return NULL; + n->key = ms_strdup(key); + n->value = value ? ms_strdup(value) : ms_strdup(""); + if (!n->key || !n->value) + { + free(n->key); + free(n->value); + free(n); + return NULL; + } + return n; +} + +void env_init(t_shell *sh, char **envp) +{ + int i; + char *eq; + + sh->env = NULL; + if (!envp) + return; + i = 0; + while (envp[i]) + { + eq = strchr(envp[i], '='); + if (eq) + { + char *key = ms_strndup(envp[i], (size_t)(eq - envp[i])); + char *val = ms_strdup(eq + 1); + env_set(sh, key, val); + free(key); + free(val); + } + i++; + } +} + +void env_clear(t_shell *sh) +{ + t_env *cur = sh->env; + t_env *next; + + while (cur) + { + next = cur->next; + free(cur->key); + free(cur->value); + free(cur); + cur = next; + } + sh->env = NULL; +} + +char *env_get(t_shell *sh, const char *key) +{ + t_env *cur = sh->env; + + while (cur) + { + if (strcmp(cur->key, key) == 0) + return cur->value; + cur = cur->next; + } + return NULL; +} + +int env_set(t_shell *sh, const char *key, const char *value) +{ + t_env *cur = sh->env; + t_env *prev = NULL; + + while (cur) + { + if (strcmp(cur->key, key) == 0) + { + char *dup = ms_strdup(value ? value : ""); + if (!dup) + return 1; + free(cur->value); + cur->value = dup; + return 0; + } + prev = cur; + cur = cur->next; + } + cur = env_new(key, value); + if (!cur) + return 1; + if (prev) + prev->next = cur; + else + sh->env = cur; + return 0; +} + +int env_unset(t_shell *sh, const char *key) +{ + t_env *cur = sh->env; + t_env *prev = NULL; + + while (cur) + { + if (strcmp(cur->key, key) == 0) + { + if (prev) + prev->next = cur->next; + else + sh->env = cur->next; + free(cur->key); + free(cur->value); + free(cur); + return 0; + } + prev = cur; + cur = cur->next; + } + return 0; +} + +char **env_to_envp(t_shell *sh) +{ + char **envp; + int count = 0; + t_env *cur = sh->env; + int i = 0; + + while (cur) + { + count++; + cur = cur->next; + } + envp = (char **)calloc((size_t)count + 1, sizeof(char *)); + if (!envp) + return NULL; + cur = sh->env; + while (cur) + { + char *kv = ms_strjoin3(cur->key, "=", cur->value); + envp[i++] = kv; + cur = cur->next; + } + envp[i] = NULL; + return envp; +} + +void env_free_envp(char **envp) +{ + int i = 0; + if (!envp) + return; + while (envp[i]) + free(envp[i++]); + free(envp); +} + +void env_print(t_shell *sh) +{ + t_env *cur = sh->env; + while (cur) + { + if (cur->value) + printf("%s=%s\n", cur->key, cur->value); + cur = cur->next; + } +} diff --git a/minishell-codex/src/executor/exec.c b/minishell-codex/src/executor/exec.c new file mode 100644 index 0000000..489d36c --- /dev/null +++ b/minishell-codex/src/executor/exec.c @@ -0,0 +1,134 @@ +#include "minishell.h" + +static int exec_external(t_command *cmd, t_shell *sh) +{ + char **envp = env_to_envp(sh); + if (!envp) + return 1; + execve(cmd->path, cmd->argv, envp); + perror(cmd->path); + env_free_envp(envp); + return 126; +} + +static int run_command_child(t_command *cmd, t_shell *sh) +{ + int saved_in, saved_out; + + if (apply_redirections(cmd, &saved_in, &saved_out) != 0) + return 1; + if (is_builtin(cmd->argv[0])) + return exec_builtin(cmd, sh); + return exec_external(cmd, sh); +} + +static int run_command_parent_builtin(t_command *cmd, t_shell *sh) +{ + int saved_in, saved_out; + int status; + + if (apply_redirections(cmd, &saved_in, &saved_out) != 0) + return 1; + status = exec_builtin(cmd, sh); + restore_redirections(saved_in, saved_out); + return status; +} + +static int setup_pipes(int idx, int count, int pipefd[2]) +{ + if (idx + 1 >= count) + return 0; + if (pipe(pipefd) == -1) + return 1; + return 0; +} + +static void setup_child_fds(int idx, int count, int prev_read, int pipefd[2]) +{ + if (prev_read != -1) + { + dup2(prev_read, STDIN_FILENO); + close(prev_read); + } + if (idx + 1 < count) + { + dup2(pipefd[1], STDOUT_FILENO); + close(pipefd[0]); + close(pipefd[1]); + } +} + +static void close_parent_fds(int idx, int count, int *prev_read, int pipefd[2]) +{ + if (*prev_read != -1) + close(*prev_read); + if (idx + 1 < count) + { + close(pipefd[1]); + *prev_read = pipefd[0]; + } + else + *prev_read = -1; +} + +int execute_pipeline(t_pipeline *p, t_shell *sh) +{ + int i; + int prev_read = -1; + pid_t *pids; + int status = 0; + + if (!p || p->count == 0) + return 0; + if (prepare_heredocs(p, sh) != 0) + return sh->exit_status; + for (size_t k = 0; k < p->count; k++) + { + if (!p->cmds[k]->argv || !p->cmds[k]->argv[0]) + return 1; + free(p->cmds[k]->path); + p->cmds[k]->path = resolve_path(p->cmds[k]->argv[0], sh); + } + if (p->count == 1 && is_builtin(p->cmds[0]->argv[0])) + return run_command_parent_builtin(p->cmds[0], sh); + pids = (pid_t *)calloc(p->count, sizeof(pid_t)); + if (!pids) + return 1; + for (i = 0; i < (int)p->count; i++) + { + int pipefd[2] = {-1, -1}; + if (setup_pipes(i, (int)p->count, pipefd)) + break; + pids[i] = fork(); + if (pids[i] == 0) + { + ms_set_child_signals(); + setup_child_fds(i, (int)p->count, prev_read, pipefd); + if (!p->cmds[i]->path) + { + fprintf(stderr, "minishell: %s: command not found\n", p->cmds[i]->argv[0]); + exit(127); + } + status = run_command_child(p->cmds[i], sh); + exit(status); + } + close_parent_fds(i, (int)p->count, &prev_read, pipefd); + } + for (i = 0; i < (int)p->count; i++) + { + int wstatus = 0; + if (pids[i] > 0) + { + waitpid(pids[i], &wstatus, 0); + if (i == (int)p->count - 1) + { + if (WIFEXITED(wstatus)) + status = WEXITSTATUS(wstatus); + else if (WIFSIGNALED(wstatus)) + status = 128 + WTERMSIG(wstatus); + } + } + } + free(pids); + return status; +} diff --git a/minishell-codex/src/executor/path.c b/minishell-codex/src/executor/path.c new file mode 100644 index 0000000..c454edb --- /dev/null +++ b/minishell-codex/src/executor/path.c @@ -0,0 +1,41 @@ +#include "minishell.h" + +static int has_slash(const char *s) +{ + return (s && strchr(s, '/')); +} + +char *resolve_path(const char *cmd, t_shell *sh) +{ + char *path_env; + char **parts; + char *candidate; + int i; + + if (!cmd) + return NULL; + if (is_builtin(cmd)) + return ms_strdup(cmd); + if (has_slash(cmd)) + return ms_strdup(cmd); + path_env = env_get(sh, "PATH"); + if (!path_env) + return NULL; + parts = ms_split(path_env, ':'); + if (!parts) + return NULL; + i = 0; + while (parts[i]) + { + candidate = ms_strjoin3(parts[i], "/", cmd); + if (candidate && access(candidate, X_OK) == 0) + { + ms_free_split(parts); + return candidate; + } + free(candidate); + i++; + } + ms_free_split(parts); + return NULL; +} diff --git a/minishell-codex/src/executor/redir.c b/minishell-codex/src/executor/redir.c new file mode 100644 index 0000000..54eacd4 --- /dev/null +++ b/minishell-codex/src/executor/redir.c @@ -0,0 +1,108 @@ +#include "minishell.h" + +static int open_redir(t_redir *r) +{ + if (r->type == REDIR_IN) + return open(r->target, O_RDONLY); + if (r->type == REDIR_OUT) + return open(r->target, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (r->type == REDIR_APPEND) + return open(r->target, O_WRONLY | O_CREAT | O_APPEND, 0644); + return -1; +} + +int prepare_heredocs(t_pipeline *p, t_shell *sh) +{ + size_t i; + + g_signal = 0; + for (i = 0; i < p->count; i++) + { + t_redir *r = p->cmds[i]->redirs; + while (r) + { + if (r->type == REDIR_HEREDOC) + { + int fds[2]; + char *line; + ms_set_heredoc_signals(); + if (pipe(fds) == -1) + return 1; + while (1) + { + line = readline("> "); + if (!line) + break; + if (strcmp(line, r->target) == 0) + { + free(line); + break; + } + write(fds[1], line, strlen(line)); + write(fds[1], "\n", 1); + free(line); + } + close(fds[1]); + r->fd = fds[0]; + ms_setup_signals(sh); + if (g_signal == SIGINT) + { + sh->exit_status = 130; + return 1; + } + } + r = r->next; + } + } + return 0; +} + +int apply_redirections(t_command *cmd, int *saved_stdin, int *saved_stdout) +{ + t_redir *r = cmd->redirs; + *saved_stdin = -1; + *saved_stdout = -1; + while (r) + { + int fd = -1; + if (r->type == REDIR_HEREDOC) + fd = r->fd; + else + fd = open_redir(r); + if (fd == -1) + { + perror(r->target); + return 1; + } + if (r->type == REDIR_IN || r->type == REDIR_HEREDOC) + { + if (*saved_stdin == -1) + *saved_stdin = dup(STDIN_FILENO); + dup2(fd, STDIN_FILENO); + } + else + { + if (*saved_stdout == -1) + *saved_stdout = dup(STDOUT_FILENO); + dup2(fd, STDOUT_FILENO); + } + if (r->type != REDIR_HEREDOC) + close(fd); + r = r->next; + } + return 0; +} + +void restore_redirections(int saved_stdin, int saved_stdout) +{ + if (saved_stdin != -1) + { + dup2(saved_stdin, STDIN_FILENO); + close(saved_stdin); + } + if (saved_stdout != -1) + { + dup2(saved_stdout, STDOUT_FILENO); + close(saved_stdout); + } +} diff --git a/minishell-codex/src/main.c b/minishell-codex/src/main.c new file mode 100644 index 0000000..328eb3c --- /dev/null +++ b/minishell-codex/src/main.c @@ -0,0 +1,17 @@ +#include "minishell.h" + +int main(int argc, char **argv, char **envp) +{ + t_shell sh; + + (void)argv; + if (argc != 1) + { + fprintf(stderr, "Usage: ./minishell\n"); + return 1; + } + ms_init(&sh, envp); + ms_loop(&sh); + ms_cleanup(&sh); + return sh.exit_status; +} diff --git a/minishell-codex/src/parser/expand.c b/minishell-codex/src/parser/expand.c new file mode 100644 index 0000000..d028372 --- /dev/null +++ b/minishell-codex/src/parser/expand.c @@ -0,0 +1,113 @@ +#include "minishell.h" + +static void buf_append(char **buf, size_t *len, size_t *cap, const char *s) +{ + size_t slen; + char *newbuf; + + if (!s) + return; + slen = strlen(s); + if (*len + slen + 1 > *cap) + { + *cap = (*len + slen + 1) * 2; + newbuf = (char *)realloc(*buf, *cap); + if (!newbuf) + return; + *buf = newbuf; + } + memcpy(*buf + *len, s, slen); + *len += slen; + (*buf)[*len] = '\0'; +} + +static void buf_append_char(char **buf, size_t *len, size_t *cap, char c) +{ + char tmp[2]; + tmp[0] = c; + tmp[1] = '\0'; + buf_append(buf, len, cap, tmp); +} + +static char *expand_word(const char *word, t_shell *sh) +{ + int in_single = 0; + int in_double = 0; + size_t i = 0; + char *out = NULL; + size_t len = 0, cap = 0; + + while (word && word[i]) + { + if (word[i] == '\'' && !in_double) + { + in_single = !in_single; + i++; + continue; + } + if (word[i] == '"' && !in_single) + { + in_double = !in_double; + i++; + continue; + } + if (word[i] == '$' && !in_single) + { + if (word[i + 1] == '?') + { + char *v = ms_itoa(sh->last_status); + buf_append(&out, &len, &cap, v); + free(v); + i += 2; + continue; + } + if (ms_is_alpha((unsigned char)word[i + 1])) + { + size_t j = i + 1; + while (word[j] && ms_is_alnum((unsigned char)word[j])) + j++; + char *name = ms_strndup(word + i + 1, j - (i + 1)); + char *val = env_get(sh, name); + buf_append(&out, &len, &cap, val ? val : ""); + free(name); + i = j; + continue; + } + } + buf_append_char(&out, &len, &cap, word[i]); + i++; + } + if (!out) + out = ms_strdup(""); + return out; +} + +int expand_pipeline(t_pipeline *p, t_shell *sh) +{ + size_t i; + int j; + for (i = 0; i < p->count; i++) + { + for (j = 0; p->cmds[i]->argv && p->cmds[i]->argv[j]; j++) + { + char *neww = expand_word(p->cmds[i]->argv[j], sh); + free(p->cmds[i]->argv[j]); + p->cmds[i]->argv[j] = neww; + } + if (p->cmds[i]->redirs) + { + t_redir *r = p->cmds[i]->redirs; + while (r) + { + if (r->target) + { + char *nt = expand_word(r->target, sh); + free(r->target); + r->target = nt; + } + r = r->next; + } + } + } + return 0; +} diff --git a/minishell-codex/src/parser/lexer.c b/minishell-codex/src/parser/lexer.c new file mode 100644 index 0000000..db12f18 --- /dev/null +++ b/minishell-codex/src/parser/lexer.c @@ -0,0 +1,131 @@ +#include "minishell.h" + +static t_token *token_new(t_tokentype type, const char *text, size_t len) +{ + t_token *t = (t_token *)calloc(1, sizeof(t_token)); + if (!t) + return NULL; + t->type = type; + if (text) + t->text = ms_strndup(text, len); + return t; +} + +static void token_add(t_token **head, t_token *new) +{ + t_token *cur; + if (!new) + return; + if (!*head) + { + *head = new; + return; + } + cur = *head; + while (cur->next) + cur = cur->next; + cur->next = new; +} + +void free_tokens(t_token *toks) +{ + t_token *n; + while (toks) + { + n = toks->next; + free(toks->text); + free(toks); + toks = n; + } +} + +static int is_meta(char c) +{ + return (c == '|' || c == '<' || c == '>'); +} + +static int read_word(const char *line, size_t *i, t_token **out) +{ + size_t start = *i; + int in_single = 0; + int in_double = 0; + + while (line[*i]) + { + if (line[*i] == '\'' && !in_double) + in_single = !in_single; + else if (line[*i] == '"' && !in_single) + in_double = !in_double; + else if (!in_single && !in_double) + { + if (ms_is_space(line[*i]) || is_meta(line[*i])) + break; + } + (*i)++; + } + if (in_single || in_double) + return 1; + *out = token_new(TOK_WORD, line + start, *i - start); + return 0; +} + +t_token *lex_line(const char *line, int *error) +{ + t_token *toks = NULL; + size_t i = 0; + + *error = 0; + while (line[i]) + { + if (ms_is_space(line[i])) + { + i++; + continue; + } + if (line[i] == '|') + { + token_add(&toks, token_new(TOK_PIPE, "|", 1)); + i++; + continue; + } + if (line[i] == '<') + { + if (line[i + 1] == '<') + { + token_add(&toks, token_new(TOK_HEREDOC, "<<", 2)); + i += 2; + } + else + { + token_add(&toks, token_new(TOK_REDIR_IN, "<", 1)); + i++; + } + continue; + } + if (line[i] == '>') + { + if (line[i + 1] == '>') + { + token_add(&toks, token_new(TOK_REDIR_APPEND, ">>", 2)); + i += 2; + } + else + { + token_add(&toks, token_new(TOK_REDIR_OUT, ">", 1)); + i++; + } + continue; + } + { + t_token *w = NULL; + if (read_word(line, &i, &w)) + { + *error = 1; + free_tokens(toks); + return NULL; + } + token_add(&toks, w); + } + } + return toks; +} diff --git a/minishell-codex/src/parser/parser.c b/minishell-codex/src/parser/parser.c new file mode 100644 index 0000000..586b4dd --- /dev/null +++ b/minishell-codex/src/parser/parser.c @@ -0,0 +1,175 @@ +#include "minishell.h" + +static t_command *command_new(void) +{ + t_command *cmd = (t_command *)calloc(1, sizeof(t_command)); + return cmd; +} + +static void command_add_arg(t_command *cmd, const char *text) +{ + char **new_argv; + int i; + + if (!cmd || !text) + return; + i = cmd->argc; + new_argv = (char **)calloc((size_t)i + 2, sizeof(char *)); + if (!new_argv) + return; + for (int j = 0; j < i; j++) + new_argv[j] = cmd->argv[j]; + new_argv[i] = ms_strdup(text); + new_argv[i + 1] = NULL; + free(cmd->argv); + cmd->argv = new_argv; + cmd->argc += 1; +} + +static void command_add_redir(t_command *cmd, t_redirtype type, const char *target) +{ + t_redir *r = (t_redir *)calloc(1, sizeof(t_redir)); + t_redir *cur; + if (!r) + return; + r->type = type; + r->target = ms_strdup(target); + r->fd = -1; + r->next = NULL; + if (!cmd->redirs) + { + cmd->redirs = r; + return; + } + cur = cmd->redirs; + while (cur->next) + cur = cur->next; + cur->next = r; +} + +static void free_redirs(t_redir *r) +{ + t_redir *n; + while (r) + { + n = r->next; + if (r->fd != -1) + close(r->fd); + free(r->target); + free(r); + r = n; + } +} + +void free_pipeline(t_pipeline *p) +{ + size_t i; + if (!p) + return; + for (i = 0; i < p->count; i++) + { + if (p->cmds[i]) + { + for (int j = 0; p->cmds[i]->argv && p->cmds[i]->argv[j]; j++) + free(p->cmds[i]->argv[j]); + free(p->cmds[i]->argv); + free(p->cmds[i]->path); + free_redirs(p->cmds[i]->redirs); + free(p->cmds[i]); + } + } + free(p->cmds); + free(p); +} + +static int pipeline_add_cmd(t_pipeline *p, t_command *cmd) +{ + t_command **new_cmds; + size_t i; + + new_cmds = (t_command **)calloc(p->count + 1, sizeof(t_command *)); + if (!new_cmds) + return 1; + for (i = 0; i < p->count; i++) + new_cmds[i] = p->cmds[i]; + new_cmds[p->count] = cmd; + free(p->cmds); + p->cmds = new_cmds; + p->count += 1; + return 0; +} + +t_pipeline *parse_tokens(t_token *toks, int *error) +{ + t_pipeline *p; + t_command *cmd; + t_token *cur; + + *error = 0; + p = (t_pipeline *)calloc(1, sizeof(t_pipeline)); + if (!p) + return NULL; + cmd = command_new(); + if (!cmd) + { + free(p); + return NULL; + } + cur = toks; + while (cur) + { + if (cur->type == TOK_PIPE) + { + if (cmd->argc == 0) + { + *error = 1; + free_pipeline(p); + free(cmd); + return NULL; + } + pipeline_add_cmd(p, cmd); + cmd = command_new(); + if (!cmd) + { + *error = 1; + free_pipeline(p); + return NULL; + } + cur = cur->next; + continue; + } + if (cur->type == TOK_REDIR_IN || cur->type == TOK_REDIR_OUT + || cur->type == TOK_REDIR_APPEND || cur->type == TOK_HEREDOC) + { + if (!cur->next || cur->next->type != TOK_WORD) + { + *error = 1; + free_pipeline(p); + free(cmd); + return NULL; + } + if (cur->type == TOK_REDIR_IN) + command_add_redir(cmd, REDIR_IN, cur->next->text); + else if (cur->type == TOK_REDIR_OUT) + command_add_redir(cmd, REDIR_OUT, cur->next->text); + else if (cur->type == TOK_REDIR_APPEND) + command_add_redir(cmd, REDIR_APPEND, cur->next->text); + else if (cur->type == TOK_HEREDOC) + command_add_redir(cmd, REDIR_HEREDOC, cur->next->text); + cur = cur->next->next; + continue; + } + if (cur->type == TOK_WORD) + command_add_arg(cmd, cur->text); + cur = cur->next; + } + if (cmd->argc == 0) + { + *error = 1; + free_pipeline(p); + free(cmd); + return NULL; + } + pipeline_add_cmd(p, cmd); + return p; +} diff --git a/minishell-codex/src/utils/char_utils.c b/minishell-codex/src/utils/char_utils.c new file mode 100644 index 0000000..cc5d97d --- /dev/null +++ b/minishell-codex/src/utils/char_utils.c @@ -0,0 +1,6 @@ +#include "minishell.h" + +int ms_is_space(int c) { return (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f'); } +int ms_is_alpha(int c) { return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_'); } +int ms_is_digit(int c) { return (c >= '0' && c <= '9'); } +int ms_is_alnum(int c) { return (ms_is_alpha(c) || ms_is_digit(c)); } diff --git a/minishell-codex/src/utils/split_utils.c b/minishell-codex/src/utils/split_utils.c new file mode 100644 index 0000000..6ffb33d --- /dev/null +++ b/minishell-codex/src/utils/split_utils.c @@ -0,0 +1,67 @@ +#include "minishell.h" + +static size_t count_parts(const char *s, char delim) +{ + size_t count = 0; + int in = 0; + while (*s) + { + if (*s == delim) + in = 0; + else if (!in) + { + in = 1; + count++; + } + s++; + } + return count; +} + +char **ms_split(const char *s, char delim) +{ + char **out; + size_t parts; + size_t i = 0; + size_t start = 0; + size_t len = 0; + int in = 0; + + if (!s) + return NULL; + parts = count_parts(s, delim); + out = (char **)calloc(parts + 1, sizeof(char *)); + if (!out) + return NULL; + while (s[i]) + { + if (s[i] == delim) + { + if (in) + { + out[len++] = ms_strndup(s + start, i - start); + in = 0; + } + } + else if (!in) + { + in = 1; + start = i; + } + i++; + } + if (in) + out[len++] = ms_strndup(s + start, i - start); + out[len] = NULL; + return out; +} + +void ms_free_split(char **sp) +{ + size_t i = 0; + if (!sp) + return; + while (sp[i]) + free(sp[i++]); + free(sp); +} diff --git a/minishell-codex/src/utils/str_utils.c b/minishell-codex/src/utils/str_utils.c new file mode 100644 index 0000000..7401f0e --- /dev/null +++ b/minishell-codex/src/utils/str_utils.c @@ -0,0 +1,100 @@ +#include "minishell.h" + +char *ms_strdup(const char *s) +{ + char *out; + size_t len; + + if (!s) + return NULL; + len = strlen(s); + out = (char *)malloc(len + 1); + if (!out) + return NULL; + memcpy(out, s, len); + out[len] = '\0'; + return out; +} + +char *ms_strndup(const char *s, size_t n) +{ + char *out; + if (!s) + return NULL; + out = (char *)malloc(n + 1); + if (!out) + return NULL; + memcpy(out, s, n); + out[n] = '\0'; + return out; +} + +char *ms_substr(const char *s, size_t start, size_t len) +{ + size_t slen; + if (!s) + return NULL; + slen = strlen(s); + if (start >= slen) + return ms_strdup(""); + if (start + len > slen) + len = slen - start; + return ms_strndup(s + start, len); +} + +char *ms_strjoin(const char *a, const char *b) +{ + size_t la; + size_t lb; + char *out; + + if (!a || !b) + return NULL; + la = strlen(a); + lb = strlen(b); + out = (char *)malloc(la + lb + 1); + if (!out) + return NULL; + memcpy(out, a, la); + memcpy(out + la, b, lb); + out[la + lb] = '\0'; + return out; +} + +char *ms_strjoin3(const char *a, const char *b, const char *c) +{ + char *ab; + char *abc; + + ab = ms_strjoin(a, b); + if (!ab) + return NULL; + abc = ms_strjoin(ab, c); + free(ab); + return abc; +} + +char *ms_itoa(int n) +{ + char buf[32]; + int i; + int neg; + long nb; + + nb = n; + neg = (nb < 0); + if (neg) + nb = -nb; + i = 30; + buf[31] = '\0'; + if (nb == 0) + buf[i--] = '0'; + while (nb > 0) + { + buf[i--] = (char)('0' + (nb % 10)); + nb /= 10; + } + if (neg) + buf[i--] = '-'; + return ms_strdup(&buf[i + 1]); +} diff --git a/src/builtins/builtins.c b/src/builtins/builtins.c index 236d721..0d8c89b 100644 --- a/src/builtins/builtins.c +++ b/src/builtins/builtins.c @@ -6,28 +6,29 @@ /* By: sede-san builtins = ft_hashmap_new(4, ft_hashmap_hashstr, ft_hashmap_strcmp); - if (msh->builtins == NULL) + minishell->builtins + = ft_hashmap_new(4, ft_hashmap_hashstr, ft_hashmap_strcmp); + if (minishell->builtins == NULL) return (0); - ft_hashmap_put(msh->builtins, ft_strdup("cd"), builtin_cd); - ft_hashmap_put(msh->builtins, ft_strdup("echo"), builtin_echo); - ft_hashmap_put(msh->builtins, ft_strdup("exit"), builtin_exit); - ft_hashmap_put(msh->builtins, ft_strdup("pwd"), builtin_pwd); + ft_hashmap_put(minishell->builtins, ft_strdup("cd"), builtin_cd); + ft_hashmap_put(minishell->builtins, ft_strdup("echo"), builtin_echo); + ft_hashmap_put(minishell->builtins, ft_strdup("exit"), builtin_exit); + ft_hashmap_put(minishell->builtins, ft_strdup("pwd"), builtin_pwd); return (1); } u_int8_t is_builtin( const char *command_name, - t_minishell *msh + t_minishell *minishell ) { - return (ft_hashmap_contains_key(msh->builtins, command_name)); + return (ft_hashmap_contains_key(minishell->builtins, command_name)); } diff --git a/src/builtins/exit/exit.c b/src/builtins/exit/exit.c index 6813bf3..9d32e0d 100644 --- a/src/builtins/exit/exit.c +++ b/src/builtins/exit/exit.c @@ -6,28 +6,28 @@ /* By: sede-san -u_int8_t builtin_exit( +uint8_t builtin_exit( t_command cmd, t_minishell *msh -){ +) +{ + uint8_t exit_status; + ft_eputendl("exit"); if (cmd.argc == 1) - { - msh->exit = 1; - // return the last exit_status, if none 0 is returned - return (msh->exit_status); - } + exit_status = msh->exit_status; else if (!ft_strisnum(cmd.argv[1])) { - ft_eputstr("exit: "); - ft_eputendl(cmd.argv[1]); - ft_eputendl(": numeric argument required"); + ft_eprintf( + "minishell: exit: %s: numeric argument required\n", + cmd.argv[1]); return (2); } else if (cmd.argc > 2) @@ -36,9 +36,11 @@ u_int8_t builtin_exit( return (2); } else - { - msh->exit = 1; - // cast to u_int8_t causes to return a value between 0 and 255 - return ((u_int8_t)ft_atol(cmd.argv[1])); - } + 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); } diff --git a/src/core/command/command.c b/src/core/command/command.c new file mode 100644 index 0000000..0a9250f --- /dev/null +++ b/src/core/command/command.c @@ -0,0 +1,12 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* command.c :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: sede-san builtins, command->path); -u_int8_t execute( - t_command cmd, - t_minishell *msh -) { - pid_t child_pid; - - cmd.path = solve_path(cmd.argv[0], msh); - if (!cmd.path) - { - ft_eprintf("minishell: %s: command not found\n", cmd.argv[0]); - return (msh->exit_status = 127, msh->exit_status); - } - if (!is_builtin(cmd.path, msh) && access(cmd.path, X_OK) != EXIT_SUCCESS) - { - ft_eputstr("minishell: "); - perror(cmd.path); - return (msh->exit_status = 126, msh->exit_status); - } - child_pid = 0; - if (!is_builtin(cmd.path, msh)) - child_pid = fork(); - if (child_pid == -1) - perror("minishell"); - else if (child_pid == 0) - handle_child(&cmd, msh); - else - handle_parent(child_pid, &cmd, msh); - return (msh->exit_status); + return (builtin(*command, minishell)); } -static char *solve_path( - char *cmd_name, - t_minishell *msh -){ - char *cmd_path; - char **path; +static void handle_execve_error( + const t_command *command, + char **envp +) +{ + free_envp(envp); + perror(command->path); + exit(EXIT_FAILURE); +} + +static void execute_external_command( + const t_command *command, + t_minishell *minishell +) +{ + char **envp; + + envp = get_envp(minishell); + if (envp == NULL) + { + perror("get_envp"); + exit(EXIT_FAILURE); + } + execve(command->path, command->argv, envp); + handle_execve_error(command, envp); +} + +static uint8_t execute_command( + const t_command *command, + t_minishell *minishell +) +{ + if (is_builtin(command->path, minishell)) + return (execute_builtin(command, minishell)); + else + { + execute_external_command(command, minishell); + return (EXIT_FAILURE); //! should never reach here + } +} + +static void cmdfree_argv( + char **argv +) +{ size_t i; - if (path_is_solved(cmd_name, msh)) - // return a copy to avoid double free on parent - return (ft_strdup(cmd_name)); - path = ft_split(get_env("PATH", msh), COLON); - if (!path) - return (NULL); - cmd_path = NULL; - i = -1; - while (!cmd_path && path[++i]) + if (argv == NULL) + return ; + i = 0; + while (argv[i] != NULL) { - cmd_path = ft_strnjoin(3, path[i], "/", cmd_name); - if (!cmd_path) - return (NULL); - /** - * If a command exists but user has no execution permission - * the command is shown as non existant instead of showing the - * last ocurrence found - * - * TLDR: bash shows 'Permission denied' - * and minishell 'command not found' - * - * TEST: execute an existing command without permission to do so - */ - if (access(cmd_path, X_OK) != EXIT_SUCCESS) - ft_free((void **)&cmd_path); + free(argv[i]); + i++; } - ft_free_split((char **)path); - if (!cmd_path) - return (NULL); - return (cmd_path); + free(argv); } -static u_int8_t path_is_solved( - char *cmd_name, - t_minishell *msh -){ - return (ft_strncmp(cmd_name, "/", 1) == 0 - || (cmd_name[1] && ft_strncmp(cmd_name, "./", 2) == 0) - || (cmd_name[2] && ft_strncmp(cmd_name, "../", 3) == 0) - || is_builtin(cmd_name, msh) - ); +static void cmdfree( + t_command *command +) +{ + if (command == NULL) + return ; + cmdfree_argv(command->argv); + free(command->path); + free(command); } -static void handle_child( - t_command *cmd, - t_minishell *msh -){ - char **envp; - t_builtin_func builtin; +static int create_pipe_if_needed( + t_list *current_command, + t_pipeline *pipeline +) +{ + if (!current_command->next) + return (0); + if (pipe(pipeline->pipefd) == PIPE_ERROR) + return (perror("pipe"), PIPE_ERROR); + return (0); +} - if (is_builtin(cmd->argv[0], msh)) +static bool is_fork_required( + t_list *current_command, + t_minishell *minishell +) { + const t_command *command = current_command->content; + + return (current_command->next != NULL + || !is_builtin(command->path, minishell)); +} + +static pid_t fork_process( + t_list *current_command, + t_minishell *minishell +) +{ + pid_t pid; + + pid = 0; + if (is_fork_required(current_command, minishell)) + pid = fork(); + if (pid == FORK_ERROR) + perror("fork"); + return (pid); +} + +static void setup_child_input( + t_pipeline *pipeline +) +{ + if (pipeline->prev_read_fd != -1) { - builtin = ft_hashmap_get(msh->builtins, cmd->argv[0]); - builtin(*cmd, msh); + dup2(pipeline->prev_read_fd, STDIN_FILENO); + close(pipeline->prev_read_fd); + } +} + +static void setup_child_output( + t_list *current_command, + t_pipeline *pipeline +) +{ + if (current_command->next) + { + dup2(pipeline->pipefd[WRITE_PIPE], STDOUT_FILENO); + close(pipeline->pipefd[READ_PIPE]); + close(pipeline->pipefd[WRITE_PIPE]); + } +} + +static void child_process( + t_list *current_command, + t_pipeline *pipeline, + t_minishell *minishell +) +{ + uint8_t exit_status; + const t_command *command = current_command->content; + + setup_child_input(pipeline); + setup_child_output(current_command, pipeline); + exit_status = execute_command(command, minishell); + if (is_fork_required(current_command, minishell)) + exit(exit_status); +} + +static void parent_cleanup( + t_list *current_command, + t_pipeline *pipeline +) +{ + if (pipeline->prev_read_fd != -1) + close(pipeline->prev_read_fd); + if (current_command->next) + { + close(pipeline->pipefd[WRITE_PIPE]); + pipeline->prev_read_fd = pipeline->pipefd[READ_PIPE]; } else - { - envp = get_envp(msh); - execve(cmd->path, cmd->argv, envp); - free_envp(envp); - } + pipeline->prev_read_fd = -1; } -static void handle_parent( - pid_t child_pid, - t_command *cmd, - t_minishell *msh -){ - if (waitpid(child_pid, (int *)&msh->exit_status, 0) == EXIT_SUCCESS) +static uint8_t wait_for_children(void) +{ + uint8_t exit_status; + int status; + + exit_status = EXIT_SUCCESS; + while (wait(&status) > 0) { - // handle success + if (WIFEXITED(status)) + exit_status = WEXITSTATUS(status); } - else - { - // handle error - } - ft_free((void **)&cmd->path); - ft_free_split(cmd->argv); + return (exit_status); +} + +uint8_t execute( + t_list *command_list, + t_minishell *minishell +) +{ + uint8_t exit_status; + t_pipeline pipeline; + t_list *current_command; + pid_t pid; + + pipeline.prev_read_fd = -1; + current_command = command_list; + while (current_command) + { + if (create_pipe_if_needed(current_command, &pipeline) == PIPE_ERROR) + break ; + pid = fork_process(current_command, minishell); + if (pid == FORK_ERROR) + break ; + if (pid == 0) + child_process(current_command, &pipeline, minishell); + parent_cleanup(current_command, &pipeline); + current_command = current_command->next; + } + exit_status = wait_for_children(); + ft_lstclear(&command_list, (void (*)(void *))cmdfree); + return (exit_status); } diff --git a/src/main.c b/src/main.c index f5c19a3..2bdf86d 100644 --- a/src/main.c +++ b/src/main.c @@ -3,14 +3,15 @@ /* ::: :::::::: */ /* main.c :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: padan-pe +#+ +:+ +#+ */ +/* By: sede-san variables.environment == NULL || minishell->builtins == NULL) - { minishell_clear(minishell); - return (0); - } - return (1); } -u_int8_t minishell_run( +void minishell_run( t_minishell *minishell ){ - char *line; - t_command command; + char *line; + t_list *commands; - line = NULL; - while (minishell->exit == 0) + if (minishell == NULL) { - line = readline("minishell > "); - if (*line) - { - add_history(line); - command = parse(line, minishell); - execute(command, minishell); - } - ft_free((void **)&line); + minishell->exit_status = EXIT_FAILURE; + return ; + } + while (!minishell->exit) + { + line = readline(DEFAULT_PS1); + if (line != NULL) + { + if (*line != '\0') + { + add_history(line); + commands = parse(line, minishell); + execute(commands, minishell); + } + free(line); + } } - return (minishell->exit_status); } void minishell_clear( @@ -56,5 +61,4 @@ void minishell_clear( ft_hashmap_clear(&minishell->variables.environment, free); if (minishell->builtins != NULL) ft_hashmap_clear_keys(&minishell->builtins); - ft_bzero(minishell, sizeof(t_minishell)); } diff --git a/src/parser/lexer.c b/src/parser/lexer.c new file mode 100644 index 0000000..bca5866 --- /dev/null +++ b/src/parser/lexer.c @@ -0,0 +1,79 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* lexer.c :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: sede-san value); + return (token); +} + +static t_token_type get_token_type(const char *str) +{ + size_t i; + static const t_map_entry tokens[TOKENS_COUNT] = { + {PIPE_STR, (void *)TOKEN_PIPE}, + {REDIRECT_IN_STR, (void *)TOKEN_REDIRECT_IN}, + {REDIRECT_OUT_STR, (void *)TOKEN_REDIRECT_OUT}, + {APPEND_STR, (void *)TOKEN_APPEND}, + {HEREDOC_STR, (void *)TOKEN_HEREDOC} + }; + + i = 0; + while (i < TOKENS_COUNT) + { + if (ft_strcmp(str, tokens[i].key) == 0) + return ((t_token_type)tokens[i].value); + i++; + } + return (TOKEN_WORD); +} diff --git a/src/parser/parser.c b/src/parser/parser.c index 38a2b4c..f220fb1 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -6,73 +6,295 @@ /* By: sede-san content; +// if (previous_command != NULL) +// command->piped_from = (t_command *)previous_command->content; +// next_command = current_command->next; +// if (next_command != NULL) +// command->piped_to = (t_command *)next_command->content; +// previous_command = current_command; +// current_command = current_command->next; +// } +// } + +static char *extract_next_command( + char *line, + size_t *index +) { + char *command_str; + size_t start; + size_t end; + + start = *index; + find_boundary(line, index, '|'); + end = *index; + command_str = trim_whitespaces(line, &start, &end); + while (line[*index] == '|' || ft_isspace(line[*index])) + (*index)++; + return (command_str); +} + +static void find_boundary( + char *line, + size_t *index, + char bound_char +) { + bool in_single_quote; + bool in_double_quote; + + in_single_quote = false; + in_double_quote = false; + while (line[*index] != '\0') + { + if (line[*index] == '\'' && !in_double_quote) + in_single_quote = !in_single_quote; + else if (line[*index] == '"' && !in_single_quote) + in_double_quote = !in_double_quote; + if (line[*index] == bound_char && !in_single_quote && !in_double_quote) + break ; + (*index)++; + } +} + +static char *trim_whitespaces( + char *line, + size_t *start, + size_t *end +) { + while (*start < *end && ft_isspace(line[*start])) + (*start)++; + while (*end > *start && ft_isspace(line[*end - 1])) + (*end)--; + if (*end > *start) + return (ft_substr(line, *start, *end - *start)); return (NULL); } -static char **expand_envs( - char **argv -){ - int i; - char *env; +static t_command *cmdnew( + char *line, + t_minishell *minishell +) { + t_command *command; - if (!argv) + command = (t_command *)ft_calloc(1, sizeof(t_command)); + if (!command) return (NULL); - else if (!*argv) // check if ft_split returned and empty matrix + // resolve_heredoc + set_argv(command, line, minishell); + if (!command->argv) { - ft_free_split(argv); + free(command); return (NULL); } - i = -1; - while (argv[++i]) + set_argc(command); + set_infile(command); + set_outfile(command); + set_path(command, minishell); + return (command); +} + +static void set_argv( + t_command *command, + char *line, + t_minishell *minishell +) { + t_list *argv_list; + char *arg; + size_t i; + size_t start; + size_t end; + + if (line == NULL) + return ; + i = 0; + argv_list = NULL; + while (line[i] != '\0') { - if (!ft_strchr(argv[i], DOLLAR) - || (ft_strchr(argv[i], DOLLAR) && ft_strchr(argv[i], SINGLE_QUOTE) && ft_strchr(argv[i] + (ft_strchr(argv[i], SINGLE_QUOTE) + 1 - argv[i]), SINGLE_QUOTE))) // env is surrounded by single quote - continue ; - env = getenv(ft_strchr(argv[i], DOLLAR) + 1); - free(argv[i]); - if (env) - argv[i] = ft_strdup(env); - else - argv[i] = ft_strdup(""); + start = i; + find_boundary(line, &i, ' '); + end = i; + arg = trim_whitespaces(line, &start, &end); + expand_envs(arg, minishell); + if (arg != NULL) + ft_lstadd_back(&argv_list, ft_lstnew(arg)); + while (ft_isspace(line[i])) + i++; + } + command->argv = lst_to_argv(argv_list); + ft_lstclear(&argv_list, free); +} + +static void expand_envs( + char *arg, + t_minishell *minishell +) { + // TODO + (void)arg; + (void)minishell; +} + +static char **lst_to_argv( + t_list *argv_list +) { + char **argv; + t_list *current_arg; + size_t i; + + argv = (char **)ft_calloc(ft_lstsize(argv_list) + 1, sizeof(char *)); + if (!argv) + return (NULL); + i = 0; + current_arg = argv_list; + while (current_arg != NULL) + { + argv[i] = ft_strdup((char *)current_arg->content); + i++; + current_arg = current_arg->next; } return (argv); } -static int count_argv( - char **argv -){ - int i; +static void set_argc( + t_command *command +) { + int argc; - i = 0; - while (argv[i]) - i++; - return (i); + argc = 0; + while (command->argv[argc] != NULL) + argc++; + command->argc = argc; +} + +static void set_infile( + t_command *command +) { + // test_infile + command->infile = -1; +} + +static void set_outfile( + t_command *command +) { + // test_outfile + command->outfile = STDOUT_FILENO; +} + +static void set_path( + t_command *command, + t_minishell *minishell +) { + char *command_path; + char *command_name; + + command_name = command->argv[0]; + if (!path_is_solved(command_name, minishell)) + command_path = solve_path(command_name, minishell); + else + command_path = ft_strdup(command_name); + command->path = command_path; +} + +static char *solve_path( + char *command_name, + t_minishell *minishell +){ + char *command_path; + char **path_env; + size_t i; + + path_env = ft_split(get_env("PATH", minishell), ':'); + if (!path_env) + return (NULL); + command_path = NULL; + i = -1; + while (!command_path && path_env[++i]) + { + command_path = ft_strnjoin(3, path_env[i], "/", command_name); + if (command_path != NULL && access(command_path, F_OK) != EXIT_SUCCESS) + { + free(command_path); + command_path = NULL; + } + } + ft_free_split(path_env); + return (command_path); +} + +static u_int8_t path_is_solved( + char *command_name, + t_minishell *minishell +){ + return (ft_strncmp(command_name, "/", 1) == 0 + || (command_name[1] && ft_strncmp(command_name, "./", 2) == 0) + || (command_name[2] && ft_strncmp(command_name, "../", 3) == 0) + || is_builtin(command_name, minishell) + ); } diff --git a/src/variables/environment.c b/src/variables/environment.c index 9969929..747db01 100644 --- a/src/variables/environment.c +++ b/src/variables/environment.c @@ -6,11 +6,12 @@ /* By: sede-san variables.environment); - envp = (char **)malloc( - (msh->variables.environment->size + 1) * sizeof(char *)); + envp = (char **)malloc((msh->variables.environment->size + 1) * sizeof(char *)); if (envp != NULL) { i = 0;