/* Copyright (C) 2008 Emmanuel Varoquaux This file is part of XOS. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "execute_cmd.h" #include "redir.h" #include "print_cmd.h" #include "command.h" #include "lex.h" #include "builtins/builtins.h" #include "jobs.h" #include "safe_malloc.h" #include "error.h" #include "vars.h" #include #include #include #include #include #include #include #include /* codes de retour standards */ #define ES_NOT_FOUND 127 #define ES_NOT_EXEC 126 struct executable_cmd_struct; struct executable_cmd_operations_struct { unsigned subshell : 1; /* executer dans un sous-interpreteur par defaut */ int (*execute)(struct executable_cmd_struct *, char *[]); void (*destroy)(struct executable_cmd_struct *); }; struct executable_cmd_struct { const struct executable_cmd_operations_struct *operations; union { const struct builtin_struct *builtin; /* built-in */ char *path; /* program */ }; }; /* environnement d'execution courant */ struct exec_env_struct { unsigned subshell : 1; /* non-nul si l'execution a lieu dans un sous-interpreteur */ int saved_line_number; /* numero de ligne a restaurer */ unsigned builtin : 1; /* environnement pour une commande interne */ struct undo_redirect_stack_struct undo_redirect_stack; /* pile des redirections */ }; static inline int file_exists(const char *path) { struct stat statbuf; return stat(path, &statbuf) == 0; } /* Liste de redirections */ void redirect_list_list_init(struct redirect_list_list_struct *redirect_list_list) { redirect_list_list->first = NULL; redirect_list_list->last = NULL; } void redirect_list_list_append(struct redirect_list_list_struct *redirect_list_list, struct redirect_list_struct *redirect_list) { redirect_list->next = NULL; if (redirect_list_list->last) redirect_list_list->last->next = redirect_list; else redirect_list_list->first = redirect_list; redirect_list_list->last = redirect_list; } /* Arguments */ static char **make_argv(const char *command_name, const struct argument_struct *first_argument) { int argc; const struct argument_struct *argument; char **argv, **arg; if (command_name) { argc = 1; for (argument = first_argument; argument; argument = argument->next) argc++; } else argc = 0; argv = safe_malloc((argc + 1) * sizeof (char *)); arg = argv; if (command_name) { *arg++ = safe_strdup(command_name); for (argument = first_argument; argument; argument = argument->next) *arg++ = remove_quotes(argument->word); } *arg = NULL; return argv; } static int count_argv(char *const argv[]) { int argc; argc = 0; while (*argv) argc++, argv++; return argc; } static void free_argv(char **argv) { char **arg; for (arg = argv; *arg; arg++) free(*arg); free(argv); } /* Recherche et execution de commandes internes et de programmes */ /* Cherche une commande interne par son nom. */ static const struct builtin_struct *lookup_builtin(const char *command_name) { const struct builtin_struct **builtin; for (builtin = &builtin_table[0]; *builtin; builtin++) if (!strcmp(command_name, (*builtin)->name)) return *builtin; return NULL; } /* Execute une commande interne dans l'environnement d'execution courant. */ static int execute_builtin(const struct builtin_struct *builtin, char **argv) { int status; opterr = 0; optind = 0; current_builtin = builtin; status = builtin->func(count_argv(argv), argv); current_builtin = NULL; return status; } /* Cherche un programme par son nom en utilisant la variable PATH. */ static char *search_program(const char *command_name) { const char *path_prefixes; char *path; const char *p1; char *p2; if (!(path_prefixes = getenv("PATH"))) return NULL; path = safe_malloc(strlen(path_prefixes) + strlen(command_name) + 2); if (*path_prefixes) { p1 = path_prefixes; while (1) { p2 = path; while (*p1 && *p1 != ':') *p2++ = *p1++; /* "A zero-length prefix is a legacy feature that indicates the current working directory. It appears as two adjacent colons ( "::" ), as an initial colon preceding the rest of the list, or as a trailing colon following the rest of the list." */ if (p2 != path) *p2++ = '/'; strcpy(p2, command_name); if (file_exists(path)) return path; if (!*p1) break; if (*p1 == ':') p1++; } } free(path); return NULL; } /* Execute un programme dans l'environnement d'execution courant. */ static int execute_program(const char *path, char *const argv[]) { int errnum; execve(path, argv, environ); errnum = errno; file_error(errnum, path); return errnum == ENOENT ? ES_NOT_FOUND : ES_NOT_EXEC; } /* Fonctions d'execution des differents types de commandes */ static int builtin_execute(struct executable_cmd_struct *executable_cmd, char *argv[]) { return execute_builtin(executable_cmd->builtin, argv); } static void builtin_destroy(struct executable_cmd_struct *executable_cmd) {} static const struct executable_cmd_operations_struct builtin_operations = { .subshell = 0, .execute = builtin_execute, .destroy = builtin_destroy }; static void init_builtin_executable_cmd(struct executable_cmd_struct *executable_cmd, const struct builtin_struct *builtin) { executable_cmd->operations = &builtin_operations; executable_cmd->builtin = builtin; } static int program_execute(struct executable_cmd_struct *executable_cmd, char *argv[]) { return execute_program(executable_cmd->path, argv); } static void program_destroy(struct executable_cmd_struct *executable_cmd) { free(executable_cmd->path); } static const struct executable_cmd_operations_struct program_operations = { .subshell = 1, .execute = program_execute, .destroy = program_destroy }; static void init_program_executable_cmd(struct executable_cmd_struct *executable_cmd, char *path) { executable_cmd->operations = &program_operations; executable_cmd->path = path; } static int not_found_execute(struct executable_cmd_struct *executable_cmd, char *argv[]) { generic_error("%s: command not found", argv[0]); return ES_NOT_FOUND; } static void not_found_destroy(struct executable_cmd_struct *executable_cmd) {} static const struct executable_cmd_operations_struct not_found_operations = { .subshell = 1, .execute = not_found_execute, .destroy = not_found_destroy }; static void init_not_found_executable_cmd(struct executable_cmd_struct *executable_cmd) { executable_cmd->operations = ¬_found_operations; } static int no_command_execute(struct executable_cmd_struct *executable_cmd, char *argv[]) { return EXIT_SUCCESS; } static void no_command_destroy(struct executable_cmd_struct *executable_cmd) {} static const struct executable_cmd_operations_struct no_command_operations = { .subshell = 0, .execute = no_command_execute, .destroy = no_command_destroy }; static void init_no_command_executable_cmd(struct executable_cmd_struct *executable_cmd) { executable_cmd->operations = &no_command_operations; } /* Environnement d'execution courant */ static int reassign_stdin() { int fd; if ((fd = open("/dev/null", O_RDONLY)) == -1) { sys_error(errno, "cannot open /dev/null"); return -1; } if (dup2(fd, 0) == -1) { sys_error(errno, "cannot duplicate /dev/null"); close(fd); return -1; } close(fd); return 0; } static int reassign_fd(int orig_fd, int dest_fd) { if (dest_fd == orig_fd) return 0; if (dup2(dest_fd, orig_fd) == -1) { sys_error(errno, "cannot duplicate fd"); close(dest_fd); return -1; } close(dest_fd); return 0; } static int do_redirections(struct redirect_list_list_struct *redirect_list_list, struct undo_redirect_stack_struct *undo_redirect_stack) { const struct redirect_list_struct *redirect_list; const struct redirect_struct *redirect; for (redirect_list = redirect_list_list->first; redirect_list; redirect_list = redirect_list->next) for (redirect = redirect_list->first; redirect; redirect = redirect->next) if (do_redirection(redirect, undo_redirect_stack) == -1) goto error_undo_redirections; return 0; error_undo_redirections: if (undo_redirect_stack) undo_redirections(undo_redirect_stack); return -1; } static int setup_shell_execution_environment(struct exec_env_desc_struct *exec_env_desc, struct exec_env_struct *exec_env) { exec_env->subshell = 0; exec_env->saved_line_number = line_number; line_number = exec_env_desc->line_number; free(exec_env_desc->command); exec_env->builtin = exec_env_desc->builtin; /* mise en place des redirections */ init_undo_redirect_stack(&exec_env->undo_redirect_stack); if (do_redirections(&exec_env_desc->redirect_list_list, &exec_env->undo_redirect_stack) == -1) { last_command_status = EXIT_FAILURE; goto error; } return 1; error: if (exec_env->builtin) exit_pseudo_job(last_command_status); line_number = exec_env->saved_line_number; return 0; } static int setup_subshell_execution_environment(struct exec_env_desc_struct *exec_env_desc, struct exec_env_struct *exec_env) { pid_t pid; int must_reassign_stdin; /* creation d'un sous-interpreteur */ if ((pid = make_child(exec_env_desc->running_pipeline, exec_env_desc->command)) == -1) { last_command_status = EXIT_FAILURE; return 0; } if (pid) { /* pere */ last_command_status = EXIT_SUCCESS; return 0; } exec_env->subshell = 1; must_reassign_stdin = !interactive && !exec_env_desc->running_pipeline->foreground && exec_env_desc->pipe_in_fd == -1; interactive = 0; line_number = exec_env_desc->line_number; /* redirection de stdin vers /dev/null si asynchrone */ if (must_reassign_stdin) if (reassign_stdin() == -1) { last_command_status = EXIT_FAILURE; goto error_exit; } /* connection a la conduite */ if (exec_env_desc->pipe_in_fd != -1) if (reassign_fd(0, exec_env_desc->pipe_in_fd) == -1) { last_command_status = EXIT_FAILURE; goto error_exit; } if (exec_env_desc->fd_to_close != -1) close(exec_env_desc->fd_to_close); if (exec_env_desc->pipe_out_fd != -1) if (reassign_fd(1, exec_env_desc->pipe_out_fd) == -1) { last_command_status = EXIT_FAILURE; goto error_exit; } /* mise en place des redirections */ if (do_redirections(&exec_env_desc->redirect_list_list, NULL) == -1) { last_command_status = EXIT_FAILURE; goto error_exit; } return 1; error_exit: exit(last_command_status); } /* Configure l'environnement d'execution selon exec_env_desc. * Retourne une valeur non nulle en cas de succes. L'environnement d'execution * devra alors etre quitte avec exit_execution_environment(). * Retourne une valeur nulle si l'environnement n'a pas ete configure. * last_command_status est non-nul si une erreur s'est produite, nul dans le * cas contraire. */ static int setup_execution_environment(struct exec_env_desc_struct *exec_env_desc, struct exec_env_struct *exec_env) { struct running_pipeline_struct running_pipeline; int must_start_job; if (exec_env_desc->subshell) { if ((must_start_job = !exec_env_desc->running_pipeline)) { init_running_pipeline(&running_pipeline, 1); exec_env_desc->running_pipeline = &running_pipeline; exec_env_desc->pipe_in_fd = -1; exec_env_desc->fd_to_close = -1; exec_env_desc->pipe_out_fd = -1; } if (!setup_subshell_execution_environment(exec_env_desc, exec_env)) { if (must_start_job) { if (!last_command_status) last_command_status = start_job(&running_pipeline); else /* une erreur s'est produite */ kill_running_pipeline(&running_pipeline); } return 0; } return 1; } else return setup_shell_execution_environment(exec_env_desc, exec_env); } static void exit_shell_execution_environment(struct exec_env_struct *exec_env) { fflush(stdout); fflush(stderr); if (ferror(stdout)) clearerr(stdout); undo_redirections(&exec_env->undo_redirect_stack); if (!done && exec_env->builtin) exit_pseudo_job(last_command_status); line_number = exec_env->saved_line_number; } static void exit_subshell_execution_environment(struct exec_env_struct *exec_env) { exit(last_command_status); } /* Quitte l'environnement d'execution courant. */ static void exit_execution_environment(struct exec_env_struct *exec_env) { return exec_env->subshell ? exit_subshell_execution_environment(exec_env) : exit_shell_execution_environment(exec_env); } /* Execution des commandes composees */ static void search_command(const char *command_name, struct executable_cmd_struct *executable_cmd) { const struct builtin_struct *builtin; char *path; if (command_name) { if (!strchr(command_name, '/')) { /* le nom de la commande ne contient pas de barre oblique */ if ((builtin = lookup_builtin(command_name))) /* c'est une commande interne */ init_builtin_executable_cmd(executable_cmd, builtin); else if ((path = search_program(command_name))) /* c'est un programme */ init_program_executable_cmd(executable_cmd, path); else /* non trouve */ init_not_found_executable_cmd(executable_cmd); } else /* le nom de la commande contient une barre oblique */ init_program_executable_cmd(executable_cmd, safe_strdup(command_name)); } else /* command_name == NULL */ init_no_command_executable_cmd(executable_cmd); } static void do_execute_command(struct executable_cmd_struct *executable_cmd, const char *command_name, const struct argument_struct *first_argument) { char **argv; argv = make_argv(command_name, first_argument); last_command_status = executable_cmd->operations->execute(executable_cmd, argv); free_argv(argv); } static void execute_simple_command(const struct simple_command_struct *simple_command, struct exec_env_desc_struct *exec_env_desc) { char *command_name; struct executable_cmd_struct executable_cmd; struct redirect_list_struct redirect_list; struct exec_env_struct exec_env; /* recherche de la commande */ command_name = simple_command->command_name ? remove_quotes(simple_command->command_name) : NULL; search_command(command_name, &executable_cmd); /* configuration de l'interpreteur */ if (executable_cmd.operations->subshell) { if (!exec_env_desc->subshell && !exec_env_desc->no_fork) { exec_env_desc->subshell = 1; exec_env_desc->running_pipeline = NULL; } } else if (!exec_env_desc->subshell) exec_env_desc->builtin = 1; /* configuration des redirections */ redirect_list.first = simple_command->first_redirect; redirect_list_list_append(&exec_env_desc->redirect_list_list, &redirect_list); /* execution de la commande */ if (setup_execution_environment(exec_env_desc, &exec_env)) { do_execute_command(&executable_cmd, command_name, simple_command->first_argument); executable_cmd.operations->destroy(&executable_cmd); if (command_name) free(command_name); exit_execution_environment(&exec_env); } else { executable_cmd.operations->destroy(&executable_cmd); if (command_name) free(command_name); } } static void execute_compound_command(const struct compound_command_struct *compound_command, struct exec_env_desc_struct *exec_env_desc) { struct redirect_list_struct redirect_list; switch (compound_command->type) { case CC_SUBSHELL: if (!exec_env_desc->subshell) { exec_env_desc->subshell = 1; exec_env_desc->running_pipeline = NULL; } redirect_list.first = compound_command->first_redirect; redirect_list_list_append(&exec_env_desc->redirect_list_list, &redirect_list); execute_list(compound_command->list, exec_env_desc); break; } } void execute_command(const struct command_struct *command, struct exec_env_desc_struct *exec_env_desc) { switch (command->type) { case CT_SIMPLE: execute_simple_command(&command->simple_command, exec_env_desc); break; case CT_COMPOUND: execute_compound_command(&command->compound_command, exec_env_desc); break; } } static void do_execute_pipeline(const struct pipeline_struct *pipeline, int foreground) { struct running_pipeline_struct running_pipeline; int prev_fd; struct string_buffer_struct string_buffer; const struct command_struct *command; int pipe_fd[2]; struct exec_env_desc_struct exec_env_desc; init_running_pipeline(&running_pipeline, foreground); prev_fd = -1; string_buffer_init(&string_buffer); for (command = pipeline->first_command; command; command = command->next) { if (command->next) if (pipe(pipe_fd) == -1) { sys_error(errno, "pipe error"); last_command_status = EXIT_FAILURE; goto error_close_prev_fd; } print_command(command, &exec_env_desc.line_number, &string_buffer); exec_env_desc.command = string_buffer_release(&string_buffer); exec_env_desc.subshell = 1; exec_env_desc.running_pipeline = &running_pipeline; exec_env_desc.pipe_in_fd = prev_fd; if (command->next) { exec_env_desc.fd_to_close = pipe_fd[0]; exec_env_desc.pipe_out_fd = pipe_fd[1]; } else { exec_env_desc.fd_to_close = -1; exec_env_desc.pipe_out_fd = -1; } redirect_list_list_init(&exec_env_desc.redirect_list_list); execute_command(command, &exec_env_desc); if (last_command_status) /* une erreur s'est produite */ goto error_close_pipe_fd; if (prev_fd != -1) close(prev_fd); if (command->next) { prev_fd = pipe_fd[0]; close(pipe_fd[1]); } } if (prev_fd != -1) close(prev_fd); last_command_status = start_job(&running_pipeline); return; error_close_pipe_fd: close(pipe_fd[0]); close(pipe_fd[1]); error_close_prev_fd: if (prev_fd != -1) close(prev_fd); kill_running_pipeline(&running_pipeline); } /* Le parametre exec_env_desc est ignore si asynchronous est non-nul. */ void execute_pipeline(const struct pipeline_struct *pipeline, int asynchronous, struct exec_env_desc_struct *exec_env_desc) { struct exec_env_struct exec_env; if (asynchronous) do_execute_pipeline(pipeline, 0); else { if (!pipeline->first_command->next) { execute_command(pipeline->first_command, exec_env_desc); return; } if (!setup_execution_environment(exec_env_desc, &exec_env)) return; do_execute_pipeline(pipeline, 1); exit_execution_environment(&exec_env); } } static void do_execute_and_or_list(const struct and_or_list_struct *and_or_list) { int execute_next_command; struct string_buffer_struct string_buffer; const struct pipeline_struct *pipeline; struct exec_env_desc_struct exec_env_desc; execute_next_command = 1; string_buffer_init(&string_buffer); for (pipeline = and_or_list->first_pipeline; pipeline; pipeline = pipeline->next) { if (execute_next_command) { print_pipeline(pipeline, &exec_env_desc.line_number, &string_buffer); exec_env_desc.command = string_buffer_release(&string_buffer); exec_env_desc.subshell = 0; exec_env_desc.no_fork = 0; exec_env_desc.builtin = 0; redirect_list_list_init(&exec_env_desc.redirect_list_list); execute_pipeline(pipeline, 0, &exec_env_desc); if (done) return; } if (pipeline->next) switch (pipeline->operator) { case AO_AND: execute_next_command = !last_command_status; break; case AO_OR: execute_next_command = last_command_status; break; } } } void execute_and_or_list(const struct and_or_list_struct *and_or_list, struct exec_env_desc_struct *exec_env_desc) { struct running_pipeline_struct running_pipeline; struct exec_env_struct exec_env1, exec_env2; struct string_buffer_struct string_buffer; if (!and_or_list->first_pipeline->next) { if (!and_or_list->asynchronous) /* Exemple: (ls) */ execute_pipeline(and_or_list->first_pipeline, 0, exec_env_desc); else { /* Exemple: (ls&) */ if (!setup_execution_environment(exec_env_desc, &exec_env1)) return; execute_pipeline(and_or_list->first_pipeline, 1, NULL); exit_execution_environment(&exec_env1); } return; } if (!setup_execution_environment(exec_env_desc, &exec_env1)) return; if (!and_or_list->asynchronous) /* Exemple: (cd && ls) */ do_execute_and_or_list(and_or_list); else { /* Exemple: (cd && ls &) */ init_running_pipeline(&running_pipeline, 0); string_buffer_init(&string_buffer); print_and_or_list(and_or_list, &exec_env_desc->line_number, &string_buffer); exec_env_desc->command = string_buffer_release(&string_buffer); exec_env_desc->subshell = 1; exec_env_desc->running_pipeline = &running_pipeline; exec_env_desc->pipe_in_fd = -1; exec_env_desc->fd_to_close = -1; exec_env_desc->pipe_out_fd = -1; redirect_list_list_init(&exec_env_desc->redirect_list_list); if (setup_execution_environment(exec_env_desc, &exec_env2)) { do_execute_and_or_list(and_or_list); exit_execution_environment(&exec_env2); } else { if (!last_command_status) last_command_status = start_job(&running_pipeline); else /* une erreur s'est produite */ kill_running_pipeline(&running_pipeline); } } exit_execution_environment(&exec_env1); } static void do_execute_list(const struct list_struct *list) { struct string_buffer_struct string_buffer; const struct and_or_list_struct *and_or_list; struct exec_env_desc_struct exec_env_desc; string_buffer_init(&string_buffer); for (and_or_list = list->first_and_or_list; !done && and_or_list; and_or_list = and_or_list->next) { print_and_or_list(and_or_list, &exec_env_desc.line_number, &string_buffer); exec_env_desc.command = string_buffer_release(&string_buffer); exec_env_desc.subshell = 0; exec_env_desc.no_fork = 0; exec_env_desc.builtin = 0; redirect_list_list_init(&exec_env_desc.redirect_list_list); execute_and_or_list(and_or_list, &exec_env_desc); } } void execute_list(const struct list_struct *list, struct exec_env_desc_struct *exec_env_desc) { struct exec_env_struct exec_env; if (!list->first_and_or_list->next) { execute_and_or_list(list->first_and_or_list, exec_env_desc); return; } if (!setup_execution_environment(exec_env_desc, &exec_env)) return; do_execute_list(list); exit_execution_environment(&exec_env); }