/* 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 "redir.h" #include "command.h" #include "lex.h" #include "safe_malloc.h" #include "util.h" #include "error.h" #include #include #include #include #include #include #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); } }