#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;
int close_on_exec;
struct undo_redirect_struct *next;
};
static int open_noclobber(const char *filename, int flags, int mode)
{
int fd;
int errnum;
struct stat statbuf;
if ((fd = open(filename, flags | O_EXCL, mode)) != -1) {
return fd;
if (errno != EEXIST)
return -1;
}
if ((fd = open(filename, flags & ~O_CREAT, mode)) == -1)
return -1;
if (fstat(fd, &statbuf) == -1) {
errnum = errno;
goto error_close_fd;
}
if (S_ISREG(statbuf.st_mode)) {
errnum = EEXIST;
goto error_close_fd;
}
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;
}
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;
}
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;
}
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);
}
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);
if (undo_redirect_stack)
if (saved_fd != -1)
add_undo_redirect(orig_fd, saved_fd, close_on_exec, undo_redirect_stack);
return 0;
}
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);
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);
}
}