diff --git a/.gitignore b/.gitignore index 2f49f62..8979ded 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,6 @@ *.i*86 *.x86_64 *.hex -./minishell # Debug files *.dSYM/ diff --git a/dev/TODO b/dev/TODO new file mode 100644 index 0000000..26b8dd7 --- /dev/null +++ b/dev/TODO @@ -0,0 +1,21 @@ +- working history (rl_* functions?) + - '<<' with history? +- finding commands + - built-in commands + - relative/absolute paths +- signals + - ^C, ^D, ^\ +- variables + - normal variables + - environment variables + - $? +- pipes + - no calls (like `$ < input_file > output_file`) +- builtins: + - echo (with option -n) + - cd [path] + - pwd (with no options) + - export (with no options) + - unset (with no options) + - env (with no options or arguments) + - exit (with no options) diff --git a/dev/lolcat.sh b/dev/lolcat.sh new file mode 100755 index 0000000..c12be90 --- /dev/null +++ b/dev/lolcat.sh @@ -0,0 +1 @@ +COLORTERM= lolcat -p 1 -F 0.3 -S 0 -f header.nocolor > header diff --git a/header b/header new file mode 100644 index 0000000..d3ec095 --- /dev/null +++ b/header @@ -0,0 +1,21 @@ + Welcome to__________________________________ +||  ___        ___                           || +|| |   \      /   |                          || +|| | |\ \    / /| |   _    __     _    _     || +|| | | \ \  / / | |  |_|  |  \   | |  |_|    || +|| | |  \ \/ /  | |   _   |   \  | |   _     || +|| | |   \  /   | |  | |  | |\ \ | |  | |    || +|| | |    \/    | |  | |  | | \ \| |  | |    || +|| | |          | |  | |  | |  \   |  | |    || +|| |_|          |_|  |_|  |_|   \__|  |_|    || +||                                           || +||       =====     By: Mylan COLONNA         || +||      //            And: Joris SCHAFT      || +||     //                                    || +||     \\                                    || +||      =====  ||  ||  ==== ||    ||         || +||          \\ ||  || ||    ||    ||         || +||          // |====| |===  ||    ||         || +||         //  ||  || ||    ||    ||         || +||     =====   ||  ||  ====  ====  ====      || +||___________________________________________|| diff --git a/header.nocolor b/header.nocolor new file mode 100644 index 0000000..bf43ad5 --- /dev/null +++ b/header.nocolor @@ -0,0 +1,21 @@ + Welcome to__________________________________ +|| ___ ___ || +|| | \ / | || +|| | |\ \ / /| | _ __ _ _ || +|| | | \ \ / / | | |_| | \ | | |_| || +|| | | \ \/ / | | _ | \ | | _ || +|| | | \ / | | | | | |\ \ | | | | || +|| | | \/ | | | | | | \ \| | | | || +|| | | | | | | | | \ | | | || +|| |_| |_| |_| |_| \__| |_| || +|| || +|| ===== By: Mylan COLONNA || +|| // And: Joris SCHAFT || +|| // || +|| \\ || +|| ===== || || ==== || || || +|| \\ || || || || || || +|| // |====| |=== || || || +|| // || || || || || || +|| ===== || || ==== ==== ==== || +||___________________________________________|| diff --git a/include/include.h b/include/include.h index 2785fbf..1a015d2 100644 --- a/include/include.h +++ b/include/include.h @@ -6,7 +6,7 @@ /* By: jschaft +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/04/23 14:15:12 by mcolonna #+# #+# */ -/* Updated: 2024/05/16 17:34:49 by mcolonna ### ########.fr */ +/* Updated: 2024/05/16 18:18:16 by mcolonna ### ########.fr */ /* */ /* ************************************************************************** */ @@ -23,6 +23,7 @@ # include # include # include +# include # include "libft.h" # include "libtf.h" @@ -46,7 +47,8 @@ typedef struct s_call // Represents a command given by the user. typedef struct s_command { - bool error; // true if an error occured in interpret_command(). + int error; // 0 if parse_command() succeded, error status if not + bool empty; // true if there isn't anything to do const t_call *calls; // all calls to programs (ended by .program == NULL) int input_fd; // fd to use with '<' redirection (0 by default) int output_fd; // fd to use with '>' redirection (1 by default) @@ -71,21 +73,29 @@ int execute_command(t_memclass mc, t_command command, ///// ERROR ///// // Call to show an error. -// If msg == "errno", use strerror(errno) +// If msg == "errno", use perror() void minishell_error(const char *msg); -// Call to write the error and exit the program. +// Call perror() and exit the program. void fatal_error(const char *msg); +// Call strerror() and exit the program. +void fatal_error_msg(const char *msg); + ///// PATH ///// // Get the PATH values. // Return a list of strings ended by NULL. -char **get_path(char *const envp[]); +const char **get_path(const t_memclass mc, char *const envp[]); -// Search the program in PATH. +// Search the program in $PATH. // Returns the path of the program to use, or NULL if there is none. +// If there is several possibilities, it returns the one from the first path +// given in $PATH. const char *search_path( const t_memclass mc, const char **path, const char *prog); +///// MAIN ///// +extern t_memclass g_mc; + #endif diff --git a/src/ask_command.c b/src/ask_command.c index 66c1cd2..5b7719a 100644 --- a/src/ask_command.c +++ b/src/ask_command.c @@ -6,7 +6,7 @@ /* By: mcolonna +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/04/23 15:03:23 by mcolonna #+# #+# */ -/* Updated: 2024/04/24 13:19:42 by mcolonna ### ########.fr */ +/* Updated: 2024/05/01 12:46:34 by mcolonna ### ########.fr */ /* */ /* ************************************************************************** */ @@ -16,8 +16,8 @@ const char *ask_command(const t_memclass mc) { const char *r; - print_str(minishell_error, 1, "( ^.^)> "); - r = read_line(minishell_error, mc, 0); + print_str(fatal_error, 1, "\e[1m\e[38;5;45m( ^.^)> \e[0m"); + r = read_line(fatal_error, mc, 0); if (r) return (r); else diff --git a/src/error.c b/src/error.c index 978c545..023bba0 100644 --- a/src/error.c +++ b/src/error.c @@ -6,7 +6,7 @@ /* By: jschaft +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/04/23 15:51:56 by mcolonna #+# #+# */ -/* Updated: 2024/05/16 17:29:43 by mcolonna ### ########.fr */ +/* Updated: 2024/05/16 18:19:05 by mcolonna ### ########.fr */ /* */ /* ************************************************************************** */ @@ -15,16 +15,26 @@ void minishell_error(const char *msg) { if (str_eq(msg, "errno")) - msg = strerror(errno); - print_str(err_remember, 2, "minishell: "); - print_str(err_remember, 2, msg); - err_get(); + perror("minishell"); + else + { + print_str(err_remember, 2, "minishell: "); + print_line(err_remember, 2, msg); + err_get(); + } } -// TODO adapt for msg == errno void fatal_error(const char *msg) { - print_str(err_remember, 2, "Fatal error: "); - print_line(err_remember, 2, msg); - exit(1); + (void)msg; + minishell_error("errno"); + mem_freeall(g_mc); + exit(errno); +} + +void fatal_error_msg(const char *msg) +{ + minishell_error(msg); + mem_freeall(g_mc); + exit(errno); } diff --git a/src/exec_command.c b/src/exec_command.c index c2b4d35..4edbf1d 100644 --- a/src/exec_command.c +++ b/src/exec_command.c @@ -6,22 +6,27 @@ /* By: jschaft +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/04/24 13:48:00 by jschaft #+# #+# */ -/* Updated: 2024/05/16 18:10:48 by mcolonna ### ########.fr */ +/* Updated: 2024/05/16 18:22:42 by mcolonna ### ########.fr */ /* */ /* ************************************************************************** */ #include "include.h" -// Execute the call with stdin = in and stdout = out. -// Find the program in $PATH -static int execute_call(t_memclass mc, t_call call, int inout[2], - char *const envp[]) +// Execute a program with specific stdin and stdout: +// - inout[0] is stdin +// - inout[0] is stdout +// If call.program doesn't start with "./" or "/", find the program in $PATH. +// (TODO) +// If the program wasn't to found in $PATH, or the fork didn't work, +// write the error (TODO) and return the error status. +static int execute_call(t_memclass mc, t_call call, const int inout[2], + char *const envp[]) { - char **const path = get_path(envp); - pid_t pid; - const char *program_path; + const char **path = get_path(mc, envp); + pid_t pid; + const char *program_path; - program_path = search_path(mc, (const char **)path, call.program); + program_path = search_path(mc, path, call.program); if (!program_path) return (minishell_error(str_join(fatal_error, mc, "command not found: ", str_join(fatal_error, mc, diff --git a/src/main.c b/src/main.c index a4dd0cc..432dd22 100644 --- a/src/main.c +++ b/src/main.c @@ -6,67 +6,65 @@ /* By: jschaft +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/04/23 14:33:45 by mcolonna #+# #+# */ -/* Updated: 2024/04/26 14:06:48 by jschaft ### ########.fr */ +/* Updated: 2024/05/16 18:19:37 by mcolonna ### ########.fr */ /* */ /* ************************************************************************** */ #include "include.h" -// Clean the screen (TODO) -static void execclear(void) +t_memclass g_mc; + +// Close the input and output fds of the command. +static void close_fds(const t_command *command) { + if (command->input_fd != 0) + if (close(command->input_fd) == -1) + minishell_error("errno"); + if (command->output_fd != 1) + if (close(command->output_fd) == -1) + minishell_error("errno"); } -// Print hi :D -static void print_hi(void) +// Execute a command from a string. +static int do_command(char *const envp[], const char *str) { - printf("Welcome to____________________________________\n"); - printf("|| ___ ___ ||\n"); - printf("|| | \\ / | ||\n"); - printf("|| | |\\ \\ / /| | __ __ __ __ ||\n"); - printf("|| | | \\ \\ / / | | |__| ||\\ || |__| ||\n"); - printf("|| | | \\ \\/ / | | __ || \\ || __ ||\n"); - printf("|| | | \\ / | | || || \\ || || ||\n"); - printf("|| | | \\/ | | || || \\ || || ||\n"); - printf("|| | | | | || || \\ || || ||\n"); - printf("|| |_| |_| || || \\|| || ||\n"); - printf("|| _____ ||\n"); - printf("|| / ___| By: Mylan COLONNA ||\n"); - printf("|| \\ \\ And: Joris SCHAFT ||\n"); - printf("|| \\ \\ ||\n"); - printf("|| \\ \\ __ __ __ __ __ ||\n"); - printf("|| \\ \\ || || ||=== || || ||\n"); - printf("|| \\ \\ || || || || || ||\n"); - printf("|| / / ||===|| ||=== || || ||\n"); - printf("|| ___/ / || || || || || ||\n"); - printf("|| |____/ || || ||=== ||=== ||=== ||\n"); - printf("|_____________________________________________|\n\n"); + const t_memclass mc = mem_subclass(fatal_error, g_mc); + t_command command; + int r; + + command = parse_command(mc, str); + if (command.error || command.empty) + return (command.error); + r = execute_command(mc, command, envp); + mem_freeall(mc); + close_fds(&command); + return (r); +} + +static void start(char *const envp[]) +{ + do_command(envp, "clear"); + do_command(envp, "cat ./header"); + do_command(envp, "echo"); } int main(const int argc, const char *argv[], char *const envp[]) { - char **const path = get_path(envp); t_memclass mc; const char *command_str; - t_command command; int errorstatus; (void)argc; (void)argv; - (void)path; - (void)command; - execclear(); - print_hi(); + g_mc = NULL; + g_mc = mem_newclass(fatal_error); + start(envp); errorstatus = 0; while (true) { - mc = mem_newclass(minishell_error); + mc = mem_subclass(fatal_error, g_mc); command_str = ask_command(mc); - command = parse_command(mc, command_str); - if (command.error) - continue ; - errorstatus = execute_command(mc, command, envp); - mem_freeall(mc); + do_command(envp, command_str); } return (errorstatus); } diff --git a/src/parse_command.c b/src/parse_command.c index 0370d36..2d6dd3b 100644 --- a/src/parse_command.c +++ b/src/parse_command.c @@ -6,17 +6,53 @@ /* By: mcolonna +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/04/24 13:47:40 by mcolonna #+# #+# */ -/* Updated: 2024/04/25 17:58:54 by mcolonna ### ########.fr */ +/* Updated: 2024/05/07 13:59:30 by mcolonna ### ########.fr */ /* */ /* ************************************************************************** */ #include "include.h" -// To call when a parse error occurs (TODO) -static void parse_error(const char *msg) +// Ask the user for data and set readfd to the fd which will receive this +// data. +// Returns 'errno' on error. +static int heredoc(t_memclass mc, int *readfd, const char *eof) { - (void)msg; - fatal_error("parse error"); + const t_memclass mc_in = mem_subclass(fatal_error, mc); + int outpipe[2]; + t_const_string line; + const t_const_string eof_line = str_join(fatal_error, mc_in, eof, "\n"); + + if (pipe(outpipe) == -1) + minishell_error("errno"); + *readfd = outpipe[0]; + while (true) + { + print_str(fatal_error, 1, "\e[38;5;33m( 'o')> \e[0m"); + line = NULL; + while (!line) + line = read_line(fatal_error, mc_in, 0); + if (str_eq(line, eof) || str_eq(line, eof_line)) + break ; + print_str(err_remember, outpipe[1], line); + mem_free((void *)line); + if (err_get()) + return (minishell_error("errno"), errno); + } + mem_freeall(mc_in); + if (close(outpipe[1]) == -1) + return (minishell_error("errno"), errno); + return (0); +} + +// To call when a parse error occurs. +// Always returns 1 (parse error's status). +static int parse_error(const char *msg) +{ + const t_memclass mc = mem_subclass(fatal_error, g_mc); + + minishell_error(str_join(fatal_error, mc, "parse error: ", msg)); + mem_freeall(mc); + return (1); } // Global variables for all the parsing functions @@ -27,6 +63,7 @@ typedef struct s_parsing_args t_stream stream; t_list calls; bool got_first_call; // got at least the first program call? + const char *heredoc; } t_parsing_args; // Skip blank characters @@ -35,98 +72,216 @@ static void skip_blank(t_stream *stream) stream_skip(stream, " \r\n\t"); } +// Add 'c' at the end of 'str' and return the result. +// Also mem_free() 'str'. +static char *str_addchar(t_err err, t_memclass mc, const char *str, char c) +{ + char *s; + char *r; + + s = str_dup(fatal_error, mc, "-"); + s[0] = c; + r = str_join(err, mc, str, s); + mem_free(s); + mem_free((void *)str); + return (r); +} + // Read the string, stop if the char is in stop_charset -// TODO quotes +// TODO variables if using " static const char *get_string(t_parsing_args *args, const char *stop_charset) { + char quote; const t_memclass mc = mem_subclass(fatal_error, args->mc); - const char *stop_charset_2 - = str_join(fatal_error, mc, stop_charset, " \n"); + const char *stop_charset_2; char *str; - char str2[2]; - char *str3; + quote = '\0'; + if (char_isin(stream_read(&args->stream), "\"'")) + quote = stream_pop(&args->stream); + if (!quote) + stop_charset_2 = str_join(fatal_error, mc, stop_charset, " \n"); + else + stop_charset_2 = str_addchar(fatal_error, mc, + str_dup(fatal_error, mc, ""), quote); str = str_dup(fatal_error, mc, ""); - str2[1] = '\0'; while (stream_read(&args->stream) && !char_isin(stream_read(&args->stream), stop_charset_2)) - { - str2[0] = stream_pop(&args->stream); - str3 = str; - str = str_join(fatal_error, mc, str, str2); - mem_free(str3); - } + str = str_addchar(fatal_error, mc, str, stream_pop(&args->stream)); + if (quote) + if (!stream_pop(&args->stream)) + return (NULL); str = str_dup(fatal_error, args->mc, str); mem_freeall(mc); return (str); } // Get a program call (program names & its arguments) until stop_charset. -static void get_call(t_parsing_args *args, const char *stop_charset) +static int get_call(t_parsing_args *args, const char *stop_charset) { - t_call *r; - t_list arguments; + t_call *r; + t_list arguments; + const char *str; arguments = list_createempty(args->mc); while (stream_read(&args->stream) && !char_isin(stream_read(&args->stream), stop_charset)) { + str = get_string(args, stop_charset); + if (!str) + return (parse_error("EOF unexpected")); list_add(fatal_error, &arguments, - (char *)get_string(args, stop_charset)); + (char *)str); skip_blank(&args->stream); } r = mem_alloc(fatal_error, args->mc, sizeof(t_call)); - r->program = (char *)list_get(parse_error, &arguments, 0); + r->program = (char *)list_get(err_remember, &arguments, 0); + if (err_get()) + return (parse_error("program name expected")); r->argc = list_getsize(&arguments); r->argv = (char *const *)list_convert( fatal_error, args->mc, &arguments); list_add(fatal_error, &args->calls, (t_call *)r); args->got_first_call = true; + return (0); +} + +static int get_inputfile(t_parsing_args *args, const char *stop_charset) +{ + const char *str; + bool heredoc; + + if (args->r.input_fd != 0) + return (parse_error("several input files")); + if (!stream_read(&args->stream)) + return (parse_error("EOF unexpected")); + heredoc = stream_read(&args->stream) == '<'; + if (heredoc) + stream_pop(&args->stream); + skip_blank(&args->stream); + str = get_string(args, stop_charset); + if (!str) + return (parse_error("EOF unexpected")); + if (heredoc) + args->heredoc = str; + else + { + args->r.input_fd = open(str, O_RDONLY); + if (args->r.input_fd == -1) + return (perror(str), errno); + } + return (0); +} + +static int get_outputfile(t_parsing_args *args, const char *stop_charset) +{ + const char *str; + int flag; + + if (args->r.output_fd != 1) + return (parse_error("several output files")); + if (!stream_read(&args->stream)) + return (parse_error("EOF unexpected")); + flag = O_TRUNC; + if (stream_read(&args->stream) == '>') + { + stream_pop(&args->stream); + flag = O_APPEND; + } + skip_blank(&args->stream); + str = get_string(args, stop_charset); + if (!str) + return (parse_error("EOF unexpected")); + args->r.output_fd = open( + str, O_WRONLY | O_CREAT | flag, 0666); + if (args->r.output_fd == -1) + return (perror(str), errno); + return (0); +} + +static void get_element2(t_parsing_args *args, int *error, char c) +{ + stream_pop(&args->stream); + skip_blank(&args->stream); + if (c == '|') + { + if (!args->got_first_call) + *error = parse_error("'|', '>' or '<' expected"); + *error = get_call(args, "<>|"); + } + else if (c == '>') + *error = get_outputfile(args, "<>|"); + else if (c == '<') + *error = get_inputfile(args, "<>|"); + else + fatal_error_msg("internal error u.u"); } // Read an element (a call to a program, a '< FILE' or a '> FILE') -// (TODO redirections) -static void get_element(t_parsing_args *args) +// On success, return 0. On error, return the error status. +static int get_element(t_parsing_args *args) { - char c; + char c; + int error; - while (stream_read(&args->stream)) + error = 0; + while (!error && stream_read(&args->stream)) { c = stream_read(&args->stream); - if (c == '|') - { - if (!args->got_first_call) - parse_error(NULL); - stream_pop(&args->stream); - skip_blank(&args->stream); - get_call(args, "<>|"); - } + if (char_isin(c, "|><")) + get_element2(args, &error, c); else if (!args->got_first_call) - get_call(args, "<>|"); + error = get_call(args, "<>|"); else - parse_error(NULL); + return (parse_error("'|', '>' or '<'' expected")); skip_blank(&args->stream); } + return (error); +} + +t_parsing_args init_parsing_args(const t_memclass mc) +{ + const t_parsing_args r = { + .mc = mc, + .r = { + .error = 0, + .empty = false, + .input_fd = 0, + .output_fd = 1, + }, + .calls = list_createempty(mc), + .got_first_call = false, + .heredoc = NULL, + }; + + return (r); } t_command parse_command(const t_memclass mc, const char *command) { t_parsing_args args; + int error; - args.mc = mc; - args.r.error = false; - args.r.input_fd = 0; - args.r.output_fd = 1; - args.calls = list_createempty(mc); - args.got_first_call = false; + error = 0; + args = init_parsing_args(mc); streamstr_init(&args.stream, command); skip_blank(&args.stream); - while (stream_read(&args.stream)) + if (!stream_read(&args.stream)) { - get_element(&args); + args.r.empty = true; + return (args.r); + } + while (!error && stream_read(&args.stream)) + { + error = get_element(&args); skip_blank(&args.stream); } - args.r.calls = (t_call *)list_convert_type( - fatal_error, args.mc, &args.calls, sizeof(t_call)); + if (!error) + args.r.calls = (t_call *)list_convert_type( + fatal_error, args.mc, &args.calls, sizeof(t_call)); + if (error) + args.r.error = error; + if (args.heredoc) + heredoc(mc, &args.r.input_fd, args.heredoc); return (args.r); } diff --git a/src/path.c b/src/path.c index 8283163..78f0660 100644 --- a/src/path.c +++ b/src/path.c @@ -6,28 +6,26 @@ /* By: jschaft +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2023/11/16 12:07:26 by jschaft #+# #+# */ -/* Updated: 2024/04/26 12:24:41 by mcolonna ### ########.fr */ +/* Updated: 2024/05/01 17:53:38 by mcolonna ### ########.fr */ /* */ /* ************************************************************************** */ #include "include.h" -char **get_path(char *const envp[]) +const char **get_path(const t_memclass mc, char *const envp[]) { int i; - char **re; + const char **re; char *path; i = 0; while (ft_strncmp(envp[i], "PATH=", 5) != 0) i++; - path = ft_substr(envp[i], 5, ft_strlen(envp[i]) - 5); - re = ft_split(path, ':'); - free(path); + path = envp[i] + 5; + re = (const char **)str_split(fatal_error, mc, path, ":"); return (re); } -// TODO does it return the good path if there is several possibilities? const char *search_path( const t_memclass mc, const char **path, const char *prog) {