View of xos/usr/xsh/execute_cmd.c


XOS | Parent Directory | View | Download

/* 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 <http://www.gnu.org/licenses/>. */
 
#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 <getopt.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
 
/* 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 = &not_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);
}