View of xos/usr/xsh/redir.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 "redir.h"
 
#include "command.h"
#include "lex.h"
#include "safe_malloc.h"
#include "util.h"
#include "error.h"
 
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
 
#define SHELL_FD_BASE 10
 
struct undo_redirect_struct {
  int orig_fd;
  int saved_fd;                 /* -1 pour fermer le descripteur de fichier d'origine */
  int close_on_exec;            /* si saved_fd != -1 */
  struct undo_redirect_struct *next;
};
 
static int open_noclobber(const char *filename, int flags, int mode)
{
  int fd;
  int errnum;
  struct stat statbuf;
 
  /* On essaie de creer le fichier. */
  if ((fd = open(filename, flags | O_EXCL, mode)) != -1) {
    return fd;
    if (errno != EEXIST)
      return -1;
  }
  /* On n'a pas pu creer le fichier car il existe deja. On essaie d'ouvrir le
     fichier sans le creer. */
  if ((fd = open(filename, flags & ~O_CREAT, mode)) == -1)
    return -1;
  /* Le fichier a pu etre cree entre temps. Si le fichier ouvert est un fichier
     regulier, on le ferme et on echoue. */
  if (fstat(fd, &statbuf) == -1) {
    errnum = errno;
    goto error_close_fd;
  }
  if (S_ISREG(statbuf.st_mode)) {
    errnum = EEXIST;
    goto error_close_fd;
  }
  /* Le fichier existe et n'est pas un fichier regulier. */
  return fd;
 
 error_close_fd:
  close(fd);
  errno = errnum;
  return -1;
}
 
static int get_number(const char *s, int *num)
{
  if (!*s)
    return 0;
  *num = 0;
  do {
    if (*s < '0' || *s > '9')
      return 0;
    *num = *num * 10 + (*s - '0');
  }
  while (*++s);
  return 1;
}
 
/* Pile des redirections courantes */
 
static void undo_redirect_stack_init(struct undo_redirect_stack_struct *undo_redirect_stack)
{
  undo_redirect_stack->top = NULL;
}
 
static void undo_redirect_stack_push(struct undo_redirect_stack_struct *undo_redirect_stack, struct undo_redirect_struct *undo_redirect)
{
  undo_redirect->next = undo_redirect_stack->top;
  undo_redirect_stack->top = undo_redirect;
}
 
static struct undo_redirect_struct *undo_redirect_stack_pop(struct undo_redirect_stack_struct *undo_redirect_stack)
{
  struct undo_redirect_struct *undo_redirect;
 
  if (!(undo_redirect = undo_redirect_stack->top))
    return NULL;
  undo_redirect_stack->top = undo_redirect_stack->top->next;
  return undo_redirect;
}
 
/* Fonctions sur les descripteurs de fichiers */
 
static int is_valid_fd(int fd)
{
  return fcntl(fd, F_GETFD) != -1;
}
 
static int save_fd(int fd, int *close_on_exec)
{
  int saved_fd;
  int flags;
 
  if ((saved_fd = fcntl(fd, F_DUPFD, SHELL_FD_BASE)) == -1) {
    sys_error(errno, "redirection error: cannot duplicate fd");
    goto error;
  }
  if ((flags = fcntl(fd, F_GETFD)) == -1) {
    sys_error(errno, "redirection error: cannot duplicate fd");
    goto error_close_saved_fd;
  }
  *close_on_exec = flags & FD_CLOEXEC;
  if (fcntl(saved_fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
    sys_error(errno, "redirection error: cannot duplicate fd");
    goto error_close_saved_fd;
  }
  return saved_fd;
 
 error_close_saved_fd:
  close(saved_fd);
 error:
  return -1;
}
 
static int restore_fd(int orig_fd, int saved_fd, int close_on_exec)
{
  int flags;
 
  if (dup2(saved_fd, orig_fd) == -1)
    return -1;
  if ((flags = fcntl(saved_fd, F_GETFD)) == -1)
    return -1;
  if (close_on_exec)
    if (fcntl(orig_fd, F_SETFD, flags | FD_CLOEXEC) == -1)
      return -1;
  return 0;
}
 
/* Sauvegarde des operations sur les descripteurs de fichiers */
 
static int save_fd_state(int orig_fd, int *saved_fd, int *close_on_exec)
{
  if (is_valid_fd(orig_fd)) {
    if ((*saved_fd = save_fd(orig_fd, close_on_exec)) == -1)
      return -1;
  }
  else
    *saved_fd = -1;
  return 0;
}
 
static void restore_fd_state(int orig_fd, int saved_fd, int close_on_exec)
{
  if (saved_fd != -1) {
    if (restore_fd(orig_fd, saved_fd, close_on_exec) == -1)
      return;
    close(saved_fd);
  }
  else
    close(orig_fd);
}
 
static void add_undo_redirect(int orig_fd, int saved_fd, int close_on_exec, struct undo_redirect_stack_struct *undo_redirect_stack)
{
  struct undo_redirect_struct *undo_redirect;
 
  undo_redirect = safe_malloc(sizeof (struct undo_redirect_struct));
  undo_redirect->orig_fd = orig_fd;
  undo_redirect->saved_fd = saved_fd;
  if (undo_redirect->saved_fd != -1)
    undo_redirect->close_on_exec = close_on_exec;
  undo_redirect_stack_push(undo_redirect_stack, undo_redirect);
}
 
/* Redirections */
 
void init_undo_redirect_stack(struct undo_redirect_stack_struct *undo_redirect_stack)
{
  undo_redirect_stack_init(undo_redirect_stack);
}
 
static int redirect_duplicate(int orig_fd, int dest_fd, struct undo_redirect_stack_struct *undo_redirect_stack, const char *filename, int close_dest_fd)
{
  int retval;
  int close_on_exec;
  int saved_fd;
 
  if (orig_fd == dest_fd) {
    if (close_dest_fd) {
      if (undo_redirect_stack)
        add_undo_redirect(orig_fd, -1, 0, undo_redirect_stack);
      close_dest_fd = 0;
    }
    retval = 0;
    goto ret_close_dest_fd;
  }
  saved_fd = -1;
  if (undo_redirect_stack)
    if (save_fd_state(orig_fd, &saved_fd, &close_on_exec) == -1) {
      retval = -1;
      goto ret_close_dest_fd;
    }
  if (dup2(dest_fd, orig_fd) == -1) {
    file_error(errno, filename);
    retval = -1;
    goto ret_close_saved_fd;
  }
  if (undo_redirect_stack)
    add_undo_redirect(orig_fd, saved_fd, close_on_exec, undo_redirect_stack);
  retval = 0;
  goto ret_close_dest_fd;
 
 ret_close_saved_fd:
  if (undo_redirect_stack)
    if (saved_fd != -1)
      close(saved_fd);
 ret_close_dest_fd:
  if (close_dest_fd)
    close(dest_fd);
  return retval;
}
 
static int redirect_close(int orig_fd, struct undo_redirect_stack_struct *undo_redirect_stack)
{
  int close_on_exec;
  int saved_fd;
 
  if (undo_redirect_stack)
    if (save_fd_state(orig_fd, &saved_fd, &close_on_exec) == -1)
      return -1;
  close(orig_fd); /* echoue silencieusement si orig_fd n'est pas un descripteur ouvert */
  if (undo_redirect_stack)
    if (saved_fd != -1)
      add_undo_redirect(orig_fd, saved_fd, close_on_exec, undo_redirect_stack);
  return 0;
}
 
/* Si undo_redirect_stack est nul, la redirection ne sera pas defaisable. */
int do_redirection(const struct redirect_struct *redirect, struct undo_redirect_stack_struct *undo_redirect_stack)
{
  static const int omode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
 
  char *filename;
  int retval;
  int oflags;
  int dest_fd;
 
  filename = remove_quotes(redirect->filename);
 
  /* selection du type de redirection */
  switch (redirect->type) {
  case RT_REDIRECT_INPUT:
    oflags = O_RDONLY;
    goto redirect;
  case RT_REDIRECT_OUTPUT:
    oflags = O_WRONLY | O_CREAT;
    goto redirect_noclobber;
  case RT_REDIRECT_OUTPUT_FORCE:
    oflags = O_WRONLY | O_CREAT | O_TRUNC;
    goto redirect;
  case RT_REDIRECT_OUTPUT_APPEND:
    oflags = O_WRONLY | O_CREAT | O_APPEND;
    goto redirect;
  case RT_DUPLICATE_INPUT:
  case RT_DUPLICATE_OUTPUT:
    if (!strcmp(filename, "-"))
      goto close;
    else if (get_number(filename, &dest_fd))
      goto duplicate;
    else {
      generic_error("%s: ambiguous redirect", filename);
      retval = -1;
      goto ret_free_filename;
    }
  case RT_READ_WRITE:
    oflags = O_RDWR | O_CREAT;
    goto redirect;
  default:
    unreachable();
  }
 
 redirect:
  if ((dest_fd = open(filename, oflags, omode)) == -1) {
    file_error(errno, filename);
    retval = -1;
    goto ret_free_filename;
  }
  retval = redirect_duplicate(redirect->fd, dest_fd, undo_redirect_stack, filename, 1);
  goto ret_free_filename;
 
 redirect_noclobber:
  if ((dest_fd = open_noclobber(filename, oflags, omode)) == -1) {
    file_error(errno, filename);
    retval = -1;
    goto ret_free_filename;
  }
  retval = redirect_duplicate(redirect->fd, dest_fd, undo_redirect_stack, filename, 1);
  goto ret_free_filename;
 
 duplicate:
  retval = redirect_duplicate(redirect->fd, dest_fd, undo_redirect_stack, filename, 0);
  goto ret_free_filename;
 
 close:
  retval = redirect_close(redirect->fd, undo_redirect_stack);
  goto ret_free_filename;
 
 ret_free_filename:
  free(filename);
  return retval;
}
 
static void undo_redirection(const struct undo_redirect_struct *undo_redirect)
{
  restore_fd_state(undo_redirect->orig_fd, undo_redirect->saved_fd, undo_redirect->close_on_exec);
}
 
void undo_redirections(struct undo_redirect_stack_struct *undo_redirect_stack)
{
  struct undo_redirect_struct *undo_redirect;
 
  while ((undo_redirect = undo_redirect_stack_pop(undo_redirect_stack))) {
    undo_redirection(undo_redirect);
    free(undo_redirect);
  }
}