#include "config.h"
#if !defined (__GNUC__) && !defined (HAVE_ALLOCA_H) && defined (_AIX)
#pragma alloca
#endif
#include <stdio.h>
#include "bashtypes.h"
#ifndef _MINIX
# include <sys/file.h>
#endif
#include "filecntl.h"
#include "posixstat.h"
#if defined (HAVE_UNISTD_H)
# include <unistd.h>
#endif
#include <errno.h>
#if !defined (errno)
extern int errno;
#endif
#include "bashansi.h"
#include "memalloc.h"
#include "shell.h"
#include "flags.h"
#include "execute_cmd.h"
#include "redir.h"
#if defined (BUFFERED_INPUT)
# include "input.h"
#endif
extern int posixly_correct;
extern REDIRECT *redirection_undo_list;
extern REDIRECT *exec_redirection_undo_list;
static void add_undo_close_redirect __P((int));
static void add_exec_redirect __P((REDIRECT *));
static int add_undo_redirect __P((int));
static int expandable_redirection_filename __P((REDIRECT *));
static int stdin_redirection __P((enum r_instruction, int));
static int do_redirection_internal __P((REDIRECT *, int, int, int));
static int write_here_document __P((int, WORD_DESC *));
static int write_here_string __P((int, WORD_DESC *));
static int here_document_to_fd __P((WORD_DESC *, enum r_instruction));
static int redir_special_open __P((int, char *, int, int, enum r_instruction));
static int noclobber_open __P((char *, int, int, enum r_instruction));
static int redir_open __P((char *, int, int, enum r_instruction));
static REDIRECTEE rd;
static int heredoc_errno;
void
redirection_error (temp, error)
REDIRECT *temp;
int error;
{
char *filename, *allocname;
int oflags;
allocname = 0;
if (temp->redirector < 0)
filename = "file descriptor out of range";
#ifdef EBADF
else if (temp->redirector >= 0 && errno == EBADF)
{
switch (temp->instruction)
{
case r_duplicating_input:
case r_duplicating_output:
case r_move_input:
case r_move_output:
filename = allocname = itos (temp->redirectee.dest);
break;
default:
filename = allocname = itos (temp->redirector);
break;
}
}
#endif
else if (expandable_redirection_filename (temp))
{
if (posixly_correct && interactive_shell == 0)
{
oflags = temp->redirectee.filename->flags;
temp->redirectee.filename->flags |= W_NOGLOB;
}
filename = allocname = redirection_expand (temp->redirectee.filename);
if (posixly_correct && interactive_shell == 0)
temp->redirectee.filename->flags = oflags;
if (filename == 0)
filename = temp->redirectee.filename->word;
}
else if (temp->redirectee.dest < 0)
filename = "file descriptor out of range";
else
filename = allocname = itos (temp->redirectee.dest);
switch (error)
{
case AMBIGUOUS_REDIRECT:
internal_error ("%s: ambiguous redirect", filename);
break;
case NOCLOBBER_REDIRECT:
internal_error ("%s: cannot overwrite existing file", filename);
break;
#if defined (RESTRICTED_SHELL)
case RESTRICTED_REDIRECT:
internal_error ("%s: restricted: cannot redirect output", filename);
break;
#endif
case HEREDOC_REDIRECT:
internal_error ("cannot create temp file for here document: %s", strerror (heredoc_errno));
break;
default:
internal_error ("%s: %s", filename, strerror (error));
break;
}
FREE (allocname);
}
int
do_redirections (list, for_real, internal, set_clexec)
REDIRECT *list;
int for_real, internal, set_clexec;
{
int error;
REDIRECT *temp;
if (internal)
{
if (redirection_undo_list)
{
dispose_redirects (redirection_undo_list);
redirection_undo_list = (REDIRECT *)NULL;
}
if (exec_redirection_undo_list)
dispose_exec_redirects ();
}
for (temp = list; temp; temp = temp->next)
{
error = do_redirection_internal (temp, for_real, internal, set_clexec);
if (error)
{
redirection_error (temp, error);
return (error);
}
}
return (0);
}
static int
expandable_redirection_filename (redirect)
REDIRECT *redirect;
{
switch (redirect->instruction)
{
case r_output_direction:
case r_appending_to:
case r_input_direction:
case r_inputa_direction:
case r_err_and_out:
case r_input_output:
case r_output_force:
case r_duplicating_input_word:
case r_duplicating_output_word:
case r_move_input_word:
case r_move_output_word:
return 1;
default:
return 0;
}
}
char *
redirection_expand (word)
WORD_DESC *word;
{
char *result;
WORD_LIST *tlist1, *tlist2;
WORD_DESC *w;
w = copy_word (word);
if (posixly_correct)
w->flags |= W_NOSPLIT;
tlist1 = make_word_list (w, (WORD_LIST *)NULL);
tlist2 = expand_words_no_vars (tlist1);
dispose_words (tlist1);
if (!tlist2 || tlist2->next)
{
if (tlist2)
dispose_words (tlist2);
return ((char *)NULL);
}
result = string_list (tlist2);
dispose_words (tlist2);
return (result);
}
static int
write_here_string (fd, redirectee)
int fd;
WORD_DESC *redirectee;
{
char *herestr;
int herelen, n, e;
herestr = expand_string_to_string (redirectee->word, 0);
herelen = strlen (herestr);
n = write (fd, herestr, herelen);
if (n == herelen)
{
n = write (fd, "\n", 1);
herelen = 1;
}
e = errno;
free (herestr);
if (n != herelen)
{
if (e == 0)
e = ENOSPC;
return e;
}
return 0;
}
static int
write_here_document (fd, redirectee)
int fd;
WORD_DESC *redirectee;
{
char *document;
int document_len, fd2;
FILE *fp;
register WORD_LIST *t, *tlist;
if (redirectee->flags & W_QUOTED)
{
document = redirectee->word;
document_len = strlen (document);
if (write (fd, document, document_len) < document_len)
{
if (errno == 0)
errno = ENOSPC;
return (errno);
}
else
return 0;
}
tlist = expand_string (redirectee->word, Q_HERE_DOCUMENT);
if (tlist)
{
if ((fd2 = dup (fd)) < 0 || (fp = fdopen (fd2, "w")) == NULL)
{
if (fd2 >= 0)
close (fd2);
return (errno);
}
errno = 0;
for (t = tlist; t; t = t->next)
{
document = t->word->word;
document_len = strlen (document);
if (t != tlist)
putc (' ', fp);
fwrite (document, document_len, 1, fp);
if (ferror (fp))
{
if (errno == 0)
errno = ENOSPC;
fd2 = errno;
fclose(fp);
dispose_words (tlist);
return (fd2);
}
}
dispose_words (tlist);
if (fclose (fp) != 0)
{
if (errno == 0)
errno = ENOSPC;
return (errno);
}
}
return 0;
}
static int
here_document_to_fd (redirectee, ri)
WORD_DESC *redirectee;
enum r_instruction ri;
{
char *filename;
int r, fd, fd2;
fd = sh_mktmpfd ("sh-thd", MT_USERANDOM, &filename);
if (fd < 0)
{
FREE (filename);
return (fd);
}
errno = r = 0;
if (redirectee->word)
r = (ri != r_reading_string) ? write_here_document (fd, redirectee)
: write_here_string (fd, redirectee);
if (r)
{
close (fd);
unlink (filename);
free (filename);
errno = r;
return (-1);
}
fd2 = open (filename, O_RDONLY, 0600);
if (fd2 < 0)
{
r = errno;
unlink (filename);
free (filename);
close (fd);
errno = r;
return -1;
}
close (fd);
if (unlink (filename) < 0)
{
r = errno;
#if defined (__CYGWIN__)
if (r == EACCES)
return (fd2);
#endif
close (fd2);
free (filename);
errno = r;
return (-1);
}
free (filename);
return (fd2);
}
#define RF_DEVFD 1
#define RF_DEVSTDERR 2
#define RF_DEVSTDIN 3
#define RF_DEVSTDOUT 4
#define RF_DEVTCP 5
#define RF_DEVUDP 6
static STRING_INT_ALIST _redir_special_filenames[] = {
#if !defined (HAVE_DEV_FD)
{ "/dev/fd/[0-9]*", RF_DEVFD },
#endif
#if !defined (HAVE_DEV_STDIN)
{ "/dev/stderr", RF_DEVSTDERR },
{ "/dev/stdin", RF_DEVSTDIN },
{ "/dev/stdout", RF_DEVSTDOUT },
#endif
#if defined (NETWORK_REDIRECTIONS)
{ "/dev/tcp/*/*", RF_DEVTCP },
{ "/dev/udp/*/*", RF_DEVUDP },
#endif
{ (char *)NULL, -1 }
};
static int
redir_special_open (spec, filename, flags, mode, ri)
int spec;
char *filename;
int flags, mode;
enum r_instruction ri;
{
int fd;
#if !defined (HAVE_DEV_FD)
intmax_t lfd;
#endif
fd = -1;
switch (spec)
{
#if !defined (HAVE_DEV_FD)
case RF_DEVFD:
if (all_digits (filename+8) && legal_number (filename+8, &lfd) && lfd == (int)lfd)
{
fd = lfd;
fd = fcntl (fd, F_DUPFD, 10);
}
else
fd = AMBIGUOUS_REDIRECT;
break;
#endif
#if !defined (HAVE_DEV_STDIN)
case RF_DEVSTDIN:
fd = fcntl (0, F_DUPFD, 10);
break;
case RF_DEVSTDOUT:
fd = fcntl (1, F_DUPFD, 10);
break;
case RF_DEVSTDERR:
fd = fcntl (2, F_DUPFD, 10);
break;
#endif
#if defined (NETWORK_REDIRECTIONS)
case RF_DEVTCP:
case RF_DEVUDP:
#if defined (HAVE_NETWORK)
fd = netopen (filename);
#else
internal_warning ("/dev/(tcp|udp)/host/port not supported without networking");
fd = open (filename, flags, mode);
#endif
break;
#endif
}
return fd;
}
static int
noclobber_open (filename, flags, mode, ri)
char *filename;
int flags, mode;
enum r_instruction ri;
{
int r, fd;
struct stat finfo, finfo2;
r = stat (filename, &finfo);
if (r == 0 && (S_ISREG (finfo.st_mode)))
return (NOCLOBBER_REDIRECT);
flags &= ~O_TRUNC;
if (r != 0)
{
fd = open (filename, flags|O_EXCL, mode);
return ((fd < 0 && errno == EEXIST) ? NOCLOBBER_REDIRECT : fd);
}
fd = open (filename, flags, mode);
if (fd < 0)
return (errno == EEXIST ? NOCLOBBER_REDIRECT : fd);
if ((fstat (fd, &finfo2) == 0) && (S_ISREG (finfo2.st_mode) == 0) &&
r == 0 && (S_ISREG (finfo.st_mode) == 0) &&
same_file (filename, filename, &finfo, &finfo2))
return fd;
close (fd);
errno = EEXIST;
return (NOCLOBBER_REDIRECT);
}
static int
redir_open (filename, flags, mode, ri)
char *filename;
int flags, mode;
enum r_instruction ri;
{
int fd, r;
r = find_string_in_alist (filename, _redir_special_filenames, 1);
if (r >= 0)
return (redir_special_open (r, filename, flags, mode, ri));
if (noclobber && CLOBBERING_REDIRECT (ri))
{
fd = noclobber_open (filename, flags, mode, ri);
if (fd == NOCLOBBER_REDIRECT)
return (NOCLOBBER_REDIRECT);
}
else
{
fd = open (filename, flags, mode);
#if defined (AFS)
if ((fd < 0) && (errno == EACCES))
fd = open (filename, flags & ~O_CREAT, mode);
#endif
}
return fd;
}
static int
do_redirection_internal (redirect, for_real, remembering, set_clexec)
REDIRECT *redirect;
int for_real, remembering, set_clexec;
{
WORD_DESC *redirectee;
int redir_fd, fd, redirector, r, oflags;
intmax_t lfd;
char *redirectee_word;
enum r_instruction ri;
REDIRECT *new_redirect;
redirectee = redirect->redirectee.filename;
redir_fd = redirect->redirectee.dest;
redirector = redirect->redirector;
ri = redirect->instruction;
if (TRANSLATE_REDIRECT (ri))
{
redirectee_word = redirection_expand (redirectee);
if (redirectee_word == 0)
return (AMBIGUOUS_REDIRECT);
else if (redirectee_word[0] == '-' && redirectee_word[1] == '\0')
{
rd.dest = 0;
new_redirect = make_redirection (redirector, r_close_this, rd);
}
else if (all_digits (redirectee_word))
{
if (legal_number (redirectee_word, &lfd) && (int)lfd == lfd)
rd.dest = lfd;
else
rd.dest = -1;
switch (ri)
{
case r_duplicating_input_word:
new_redirect = make_redirection (redirector, r_duplicating_input, rd);
break;
case r_duplicating_output_word:
new_redirect = make_redirection (redirector, r_duplicating_output, rd);
break;
case r_move_input_word:
new_redirect = make_redirection (redirector, r_move_input, rd);
break;
case r_move_output_word:
new_redirect = make_redirection (redirector, r_move_output, rd);
break;
}
}
else if (ri == r_duplicating_output_word && redirector == 1)
{
rd.filename = make_bare_word (redirectee_word);
new_redirect = make_redirection (1, r_err_and_out, rd);
}
else
{
free (redirectee_word);
return (AMBIGUOUS_REDIRECT);
}
free (redirectee_word);
if (new_redirect->instruction == r_err_and_out)
{
char *alloca_hack;
redirectee = (WORD_DESC *)alloca (sizeof (WORD_DESC));
xbcopy ((char *)new_redirect->redirectee.filename,
(char *)redirectee, sizeof (WORD_DESC));
alloca_hack = (char *)
alloca (1 + strlen (new_redirect->redirectee.filename->word));
redirectee->word = alloca_hack;
strcpy (redirectee->word, new_redirect->redirectee.filename->word);
}
else
redirectee = new_redirect->redirectee.filename;
redir_fd = new_redirect->redirectee.dest;
redirector = new_redirect->redirector;
ri = new_redirect->instruction;
redirect->flags = new_redirect->flags;
dispose_redirects (new_redirect);
}
switch (ri)
{
case r_output_direction:
case r_appending_to:
case r_input_direction:
case r_inputa_direction:
case r_err_and_out:
case r_input_output:
case r_output_force:
if (posixly_correct && interactive_shell == 0)
{
oflags = redirectee->flags;
redirectee->flags |= W_NOGLOB;
}
redirectee_word = redirection_expand (redirectee);
if (posixly_correct && interactive_shell == 0)
redirectee->flags = oflags;
if (redirectee_word == 0)
return (AMBIGUOUS_REDIRECT);
#if defined (RESTRICTED_SHELL)
if (restricted && (WRITE_REDIRECT (ri)))
{
free (redirectee_word);
return (RESTRICTED_REDIRECT);
}
#endif
fd = redir_open (redirectee_word, redirect->flags, 0666, ri);
free (redirectee_word);
if (fd == NOCLOBBER_REDIRECT)
return (fd);
if (fd < 0)
return (errno);
if (for_real)
{
if (remembering)
{
if ((fd != redirector) && (fcntl (redirector, F_GETFD, 0) != -1))
add_undo_redirect (redirector);
else
add_undo_close_redirect (redirector);
}
#if defined (BUFFERED_INPUT)
check_bash_input (redirector);
#endif
if ((fd != redirector) && (dup2 (fd, redirector) < 0))
return (errno);
#if defined (BUFFERED_INPUT)
if (ri == r_input_direction || ri == r_input_output)
duplicate_buffered_stream (fd, redirector);
#endif
if (set_clexec && (redirector > 2))
SET_CLOSE_ON_EXEC (redirector);
}
if (fd != redirector)
{
#if defined (BUFFERED_INPUT)
if (INPUT_REDIRECT (ri))
close_buffered_fd (fd);
else
#endif
close (fd);
}
if (ri == r_err_and_out)
{
if (for_real)
{
if (remembering)
add_undo_redirect (2);
if (dup2 (1, 2) < 0)
return (errno);
}
}
break;
case r_reading_until:
case r_deblank_reading_until:
case r_reading_string:
if (redirectee)
{
fd = here_document_to_fd (redirectee, ri);
if (fd < 0)
{
heredoc_errno = errno;
return (HEREDOC_REDIRECT);
}
if (for_real)
{
if (remembering)
{
if ((fd != redirector) && (fcntl (redirector, F_GETFD, 0) != -1))
add_undo_redirect (redirector);
else
add_undo_close_redirect (redirector);
}
#if defined (BUFFERED_INPUT)
check_bash_input (redirector);
#endif
if (fd != redirector && dup2 (fd, redirector) < 0)
{
r = errno;
close (fd);
return (r);
}
#if defined (BUFFERED_INPUT)
duplicate_buffered_stream (fd, redirector);
#endif
if (set_clexec && (redirector > 2))
SET_CLOSE_ON_EXEC (redirector);
}
if (fd != redirector)
#if defined (BUFFERED_INPUT)
close_buffered_fd (fd);
#else
close (fd);
#endif
}
break;
case r_duplicating_input:
case r_duplicating_output:
case r_move_input:
case r_move_output:
if (for_real && (redir_fd != redirector))
{
if (remembering)
{
if (fcntl (redirector, F_GETFD, 0) != -1)
add_undo_redirect (redirector);
else
add_undo_close_redirect (redirector);
}
#if defined (BUFFERED_INPUT)
check_bash_input (redirector);
#endif
if (dup2 (redir_fd, redirector) < 0)
return (errno);
#if defined (BUFFERED_INPUT)
if (ri == r_duplicating_input || ri == r_move_input)
duplicate_buffered_stream (redir_fd, redirector);
#endif
if (((fcntl (redir_fd, F_GETFD, 0) == 1) || set_clexec) &&
(redirector > 2))
SET_CLOSE_ON_EXEC (redirector);
if (ri == r_move_input || ri == r_move_output)
close (redir_fd);
}
break;
case r_close_this:
if (for_real)
{
if (remembering && (fcntl (redirector, F_GETFD, 0) != -1))
add_undo_redirect (redirector);
#if defined (BUFFERED_INPUT)
check_bash_input (redirector);
close_buffered_fd (redirector);
#else
close (redirector);
#endif
}
break;
case r_duplicating_input_word:
case r_duplicating_output_word:
break;
}
return (0);
}
#define SHELL_FD_BASE 10
static int
add_undo_redirect (fd)
int fd;
{
int new_fd, clexec_flag;
REDIRECT *new_redirect, *closer, *dummy_redirect;
new_fd = fcntl (fd, F_DUPFD, SHELL_FD_BASE);
if (new_fd < 0)
{
sys_error ("redirection error: cannot duplicate fd");
return (-1);
}
clexec_flag = fcntl (fd, F_GETFD, 0);
rd.dest = 0;
closer = make_redirection (new_fd, r_close_this, rd);
dummy_redirect = copy_redirects (closer);
rd.dest = new_fd;
if (fd == 0)
new_redirect = make_redirection (fd, r_duplicating_input, rd);
else
new_redirect = make_redirection (fd, r_duplicating_output, rd);
new_redirect->next = closer;
closer->next = redirection_undo_list;
redirection_undo_list = new_redirect;
add_exec_redirect (dummy_redirect);
if (clexec_flag || fd < 3)
SET_CLOSE_ON_EXEC (new_fd);
return (0);
}
static void
add_undo_close_redirect (fd)
int fd;
{
REDIRECT *closer;
rd.dest = 0;
closer = make_redirection (fd, r_close_this, rd);
closer->next = redirection_undo_list;
redirection_undo_list = closer;
}
static void
add_exec_redirect (dummy_redirect)
REDIRECT *dummy_redirect;
{
dummy_redirect->next = exec_redirection_undo_list;
exec_redirection_undo_list = dummy_redirect;
}
static int
stdin_redirection (ri, redirector)
enum r_instruction ri;
int redirector;
{
switch (ri)
{
case r_input_direction:
case r_inputa_direction:
case r_input_output:
case r_reading_until:
case r_deblank_reading_until:
case r_reading_string:
return (1);
case r_duplicating_input:
case r_duplicating_input_word:
case r_close_this:
return (redirector == 0);
case r_output_direction:
case r_appending_to:
case r_duplicating_output:
case r_err_and_out:
case r_output_force:
case r_duplicating_output_word:
return (0);
}
return (0);
}
int
stdin_redirects (redirs)
REDIRECT *redirs;
{
REDIRECT *rp;
int n;
for (n = 0, rp = redirs; rp; rp = rp->next)
n += stdin_redirection (rp->instruction, rp->redirector);
return n;
}