1066 lines
23 KiB
C
1066 lines
23 KiB
C
/* $NetBSD: redir.c,v 1.66 2019/03/01 06:15:01 kre Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 1991, 1993
|
|
* The Regents of the University of California. All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to Berkeley by
|
|
* Kenneth Almquist.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
#ifndef lint
|
|
#if 0
|
|
static char sccsid[] = "@(#)redir.c 8.2 (Berkeley) 5/4/95";
|
|
#else
|
|
__RCSID("$NetBSD: redir.c,v 1.66 2019/03/01 06:15:01 kre Exp $");
|
|
#endif
|
|
#endif /* not lint */
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h> /* PIPE_BUF */
|
|
#include <sys/stat.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
|
|
/*
|
|
* Code for dealing with input/output redirection.
|
|
*/
|
|
|
|
#include "main.h"
|
|
#include "builtins.h"
|
|
#include "shell.h"
|
|
#include "nodes.h"
|
|
#include "jobs.h"
|
|
#include "options.h"
|
|
#include "expand.h"
|
|
#include "redir.h"
|
|
#include "output.h"
|
|
#include "memalloc.h"
|
|
#include "mystring.h"
|
|
#include "error.h"
|
|
#include "show.h"
|
|
|
|
|
|
#define EMPTY -2 /* marks an unused slot in redirtab */
|
|
#define CLOSED -1 /* fd was not open before redir */
|
|
#ifndef PIPE_BUF
|
|
# define PIPESIZE 4096 /* amount of buffering in a pipe */
|
|
#else
|
|
# define PIPESIZE PIPE_BUF
|
|
#endif
|
|
|
|
#ifndef FD_CLOEXEC
|
|
# define FD_CLOEXEC 1 /* well known from before there was a name */
|
|
#endif
|
|
|
|
#ifndef F_DUPFD_CLOEXEC
|
|
#define F_DUPFD_CLOEXEC F_DUPFD
|
|
#define CLOEXEC(fd) (fcntl((fd), F_SETFD, fcntl((fd),F_GETFD) | FD_CLOEXEC))
|
|
#else
|
|
#define CLOEXEC(fd)
|
|
#endif
|
|
|
|
|
|
MKINIT
|
|
struct renamelist {
|
|
struct renamelist *next;
|
|
int orig;
|
|
int into;
|
|
};
|
|
|
|
MKINIT
|
|
struct redirtab {
|
|
struct redirtab *next;
|
|
struct renamelist *renamed;
|
|
};
|
|
|
|
|
|
MKINIT struct redirtab *redirlist;
|
|
|
|
/*
|
|
* We keep track of whether or not fd0 has been redirected. This is for
|
|
* background commands, where we want to redirect fd0 to /dev/null only
|
|
* if it hasn't already been redirected.
|
|
*/
|
|
STATIC int fd0_redirected = 0;
|
|
|
|
/*
|
|
* And also where to put internal use fds that should be out of the
|
|
* way of user defined fds (normally)
|
|
*/
|
|
STATIC int big_sh_fd = 0;
|
|
|
|
STATIC const struct renamelist *is_renamed(const struct renamelist *, int);
|
|
STATIC void fd_rename(struct redirtab *, int, int);
|
|
STATIC void free_rl(struct redirtab *, int);
|
|
STATIC void openredirect(union node *, char[10], int);
|
|
STATIC int openhere(const union node *);
|
|
STATIC int copyfd(int, int, int);
|
|
STATIC void find_big_fd(void);
|
|
|
|
|
|
struct shell_fds { /* keep track of internal shell fds */
|
|
struct shell_fds *nxt;
|
|
void (*cb)(int, int);
|
|
int fd;
|
|
};
|
|
|
|
STATIC struct shell_fds *sh_fd_list;
|
|
|
|
STATIC void renumber_sh_fd(struct shell_fds *);
|
|
STATIC struct shell_fds *sh_fd(int);
|
|
|
|
STATIC const struct renamelist *
|
|
is_renamed(const struct renamelist *rl, int fd)
|
|
{
|
|
while (rl != NULL) {
|
|
if (rl->orig == fd)
|
|
return rl;
|
|
rl = rl->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
STATIC void
|
|
free_rl(struct redirtab *rt, int reset)
|
|
{
|
|
struct renamelist *rl, *rn = rt->renamed;
|
|
|
|
while ((rl = rn) != NULL) {
|
|
rn = rl->next;
|
|
if (rl->orig == 0)
|
|
fd0_redirected--;
|
|
VTRACE(DBG_REDIR, ("popredir %d%s: %s",
|
|
rl->orig, rl->orig==0 ? " (STDIN)" : "",
|
|
reset ? "" : "no reset\n"));
|
|
if (reset) {
|
|
if (rl->into < 0) {
|
|
VTRACE(DBG_REDIR, ("closed\n"));
|
|
close(rl->orig);
|
|
} else {
|
|
VTRACE(DBG_REDIR, ("from %d\n", rl->into));
|
|
movefd(rl->into, rl->orig);
|
|
}
|
|
}
|
|
ckfree(rl);
|
|
}
|
|
rt->renamed = NULL;
|
|
}
|
|
|
|
STATIC void
|
|
fd_rename(struct redirtab *rt, int from, int to)
|
|
{
|
|
/* XXX someday keep a short list (8..10) of freed renamelists XXX */
|
|
struct renamelist *rl = ckmalloc(sizeof(struct renamelist));
|
|
|
|
rl->next = rt->renamed;
|
|
rt->renamed = rl;
|
|
|
|
rl->orig = from;
|
|
rl->into = to;
|
|
}
|
|
|
|
/*
|
|
* Process a list of redirection commands. If the REDIR_PUSH flag is set,
|
|
* old file descriptors are stashed away so that the redirection can be
|
|
* undone by calling popredir. If the REDIR_BACKQ flag is set, then the
|
|
* standard output, and the standard error if it becomes a duplicate of
|
|
* stdout, is saved in memory.
|
|
*/
|
|
|
|
void
|
|
redirect(union node *redir, int flags)
|
|
{
|
|
union node *n;
|
|
struct redirtab *sv = NULL;
|
|
int i;
|
|
int fd;
|
|
char memory[10]; /* file descriptors to write to memory */
|
|
|
|
CTRACE(DBG_REDIR, ("redirect(F=0x%x):%s\n", flags, redir?"":" NONE"));
|
|
for (i = 10 ; --i >= 0 ; )
|
|
memory[i] = 0;
|
|
memory[1] = flags & REDIR_BACKQ;
|
|
if (flags & REDIR_PUSH) {
|
|
/*
|
|
* We don't have to worry about REDIR_VFORK here, as
|
|
* flags & REDIR_PUSH is never true if REDIR_VFORK is set.
|
|
*/
|
|
sv = ckmalloc(sizeof (struct redirtab));
|
|
sv->renamed = NULL;
|
|
sv->next = redirlist;
|
|
redirlist = sv;
|
|
}
|
|
for (n = redir ; n ; n = n->nfile.next) {
|
|
fd = n->nfile.fd;
|
|
VTRACE(DBG_REDIR, ("redir %d (max=%d) ", fd, max_user_fd));
|
|
if (fd > max_user_fd)
|
|
max_user_fd = fd;
|
|
renumber_sh_fd(sh_fd(fd));
|
|
if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) &&
|
|
n->ndup.dupfd == fd) {
|
|
/* redirect from/to same file descriptor */
|
|
/* make sure it stays open */
|
|
if (fcntl(fd, F_SETFD, 0) < 0)
|
|
error("fd %d: %s", fd, strerror(errno));
|
|
VTRACE(DBG_REDIR, ("!cloexec\n"));
|
|
continue;
|
|
}
|
|
|
|
if ((flags & REDIR_PUSH) && !is_renamed(sv->renamed, fd)) {
|
|
INTOFF;
|
|
if (big_sh_fd < 10)
|
|
find_big_fd();
|
|
if ((i = fcntl(fd, F_DUPFD, big_sh_fd)) == -1) {
|
|
switch (errno) {
|
|
case EBADF:
|
|
i = CLOSED;
|
|
break;
|
|
case EMFILE:
|
|
case EINVAL:
|
|
find_big_fd();
|
|
i = fcntl(fd, F_DUPFD, big_sh_fd);
|
|
if (i >= 0)
|
|
break;
|
|
/* FALLTHRU */
|
|
default:
|
|
i = errno;
|
|
error("%d: %s", fd, strerror(i));
|
|
/* NOTREACHED */
|
|
}
|
|
}
|
|
if (i >= 0)
|
|
(void)fcntl(i, F_SETFD, FD_CLOEXEC);
|
|
fd_rename(sv, fd, i);
|
|
VTRACE(DBG_REDIR, ("saved as %d ", i));
|
|
INTON;
|
|
}
|
|
VTRACE(DBG_REDIR, ("%s\n", fd == 0 ? "STDIN" : ""));
|
|
if (fd == 0)
|
|
fd0_redirected++;
|
|
openredirect(n, memory, flags);
|
|
}
|
|
if (memory[1])
|
|
out1 = &memout;
|
|
if (memory[2])
|
|
out2 = &memout;
|
|
}
|
|
|
|
|
|
STATIC void
|
|
openredirect(union node *redir, char memory[10], int flags)
|
|
{
|
|
struct stat sb;
|
|
int fd = redir->nfile.fd;
|
|
char *fname;
|
|
int f;
|
|
int eflags, cloexec;
|
|
|
|
/*
|
|
* We suppress interrupts so that we won't leave open file
|
|
* descriptors around. This may not be such a good idea because
|
|
* an open of a device or a fifo can block indefinitely.
|
|
*/
|
|
INTOFF;
|
|
if (fd < 10)
|
|
memory[fd] = 0;
|
|
switch (redir->nfile.type) {
|
|
case NFROM:
|
|
fname = redir->nfile.expfname;
|
|
if (flags & REDIR_VFORK)
|
|
eflags = O_NONBLOCK;
|
|
else
|
|
eflags = 0;
|
|
if ((f = open(fname, O_RDONLY|eflags)) < 0)
|
|
goto eopen;
|
|
VTRACE(DBG_REDIR, ("openredirect(< '%s') -> %d [%#x]",
|
|
fname, f, eflags));
|
|
if (eflags)
|
|
(void)fcntl(f, F_SETFL, fcntl(f, F_GETFL, 0) & ~eflags);
|
|
break;
|
|
case NFROMTO:
|
|
fname = redir->nfile.expfname;
|
|
if ((f = open(fname, O_RDWR|O_CREAT, 0666)) < 0)
|
|
goto ecreate;
|
|
VTRACE(DBG_REDIR, ("openredirect(<> '%s') -> %d", fname, f));
|
|
break;
|
|
case NTO:
|
|
if (Cflag) {
|
|
fname = redir->nfile.expfname;
|
|
if ((f = open(fname, O_WRONLY)) == -1) {
|
|
if ((f = open(fname, O_WRONLY|O_CREAT|O_EXCL,
|
|
0666)) < 0)
|
|
goto ecreate;
|
|
} else if (fstat(f, &sb) == -1) {
|
|
int serrno = errno;
|
|
close(f);
|
|
errno = serrno;
|
|
goto ecreate;
|
|
} else if (S_ISREG(sb.st_mode)) {
|
|
close(f);
|
|
errno = EEXIST;
|
|
goto ecreate;
|
|
}
|
|
VTRACE(DBG_REDIR, ("openredirect(>| '%s') -> %d",
|
|
fname, f));
|
|
break;
|
|
}
|
|
/* FALLTHROUGH */
|
|
case NCLOBBER:
|
|
fname = redir->nfile.expfname;
|
|
if ((f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0)
|
|
goto ecreate;
|
|
VTRACE(DBG_REDIR, ("openredirect(> '%s') -> %d", fname, f));
|
|
break;
|
|
case NAPPEND:
|
|
fname = redir->nfile.expfname;
|
|
if ((f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666)) < 0)
|
|
goto ecreate;
|
|
VTRACE(DBG_REDIR, ("openredirect(>> '%s') -> %d", fname, f));
|
|
break;
|
|
case NTOFD:
|
|
case NFROMFD:
|
|
if (redir->ndup.dupfd >= 0) { /* if not ">&-" */
|
|
if (fd < 10 && redir->ndup.dupfd < 10 &&
|
|
memory[redir->ndup.dupfd])
|
|
memory[fd] = 1;
|
|
else if (copyfd(redir->ndup.dupfd, fd,
|
|
(flags & REDIR_KEEP) == 0) < 0)
|
|
error("Redirect (from %d to %d) failed: %s",
|
|
redir->ndup.dupfd, fd, strerror(errno));
|
|
VTRACE(DBG_REDIR, ("openredirect: %d%c&%d\n", fd,
|
|
"<>"[redir->nfile.type==NTOFD], redir->ndup.dupfd));
|
|
} else {
|
|
(void) close(fd);
|
|
VTRACE(DBG_REDIR, ("openredirect: %d%c&-\n", fd,
|
|
"<>"[redir->nfile.type==NTOFD]));
|
|
}
|
|
INTON;
|
|
return;
|
|
case NHERE:
|
|
case NXHERE:
|
|
VTRACE(DBG_REDIR, ("openredirect: %d<<...", fd));
|
|
f = openhere(redir);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
|
|
cloexec = fd > 2 && (flags & REDIR_KEEP) == 0 && !posix;
|
|
if (f != fd) {
|
|
VTRACE(DBG_REDIR, (" -> %d", fd));
|
|
if (copyfd(f, fd, cloexec) < 0) {
|
|
int e = errno;
|
|
|
|
close(f);
|
|
error("redirect reassignment (fd %d) failed: %s", fd,
|
|
strerror(e));
|
|
}
|
|
close(f);
|
|
} else if (cloexec)
|
|
(void)fcntl(f, F_SETFD, FD_CLOEXEC);
|
|
VTRACE(DBG_REDIR, ("%s\n", cloexec ? " cloexec" : ""));
|
|
|
|
INTON;
|
|
return;
|
|
ecreate:
|
|
exerrno = 1;
|
|
error("cannot create %s: %s", fname, errmsg(errno, E_CREAT));
|
|
eopen:
|
|
exerrno = 1;
|
|
error("cannot open %s: %s", fname, errmsg(errno, E_OPEN));
|
|
}
|
|
|
|
|
|
/*
|
|
* Handle here documents. Normally we fork off a process to write the
|
|
* data to a pipe. If the document is short, we can stuff the data in
|
|
* the pipe without forking.
|
|
*/
|
|
|
|
STATIC int
|
|
openhere(const union node *redir)
|
|
{
|
|
int pip[2];
|
|
int len = 0;
|
|
|
|
if (pipe(pip) < 0)
|
|
error("Pipe call failed");
|
|
if (redir->type == NHERE) {
|
|
len = strlen(redir->nhere.doc->narg.text);
|
|
if (len <= PIPESIZE) {
|
|
xwrite(pip[1], redir->nhere.doc->narg.text, len);
|
|
goto out;
|
|
}
|
|
}
|
|
VTRACE(DBG_REDIR, (" forking [%d,%d]\n", pip[0], pip[1]));
|
|
if (forkshell(NULL, NULL, FORK_NOJOB) == 0) {
|
|
close(pip[0]);
|
|
signal(SIGINT, SIG_IGN);
|
|
signal(SIGQUIT, SIG_IGN);
|
|
signal(SIGHUP, SIG_IGN);
|
|
#ifdef SIGTSTP
|
|
signal(SIGTSTP, SIG_IGN);
|
|
#endif
|
|
signal(SIGPIPE, SIG_DFL);
|
|
if (redir->type == NHERE)
|
|
xwrite(pip[1], redir->nhere.doc->narg.text, len);
|
|
else
|
|
expandhere(redir->nhere.doc, pip[1]);
|
|
VTRACE(DBG_PROCS|DBG_REDIR, ("wrote here doc. exiting\n"));
|
|
_exit(0);
|
|
}
|
|
VTRACE(DBG_REDIR, ("openhere (closing %d)", pip[1]));
|
|
out:
|
|
close(pip[1]);
|
|
VTRACE(DBG_REDIR, (" (pipe fd=%d)", pip[0]));
|
|
return pip[0];
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Undo the effects of the last redirection.
|
|
*/
|
|
|
|
void
|
|
popredir(void)
|
|
{
|
|
struct redirtab *rp = redirlist;
|
|
|
|
INTOFF;
|
|
free_rl(rp, 1);
|
|
redirlist = rp->next;
|
|
ckfree(rp);
|
|
INTON;
|
|
}
|
|
|
|
/*
|
|
* Undo all redirections. Called on error or interrupt.
|
|
*/
|
|
|
|
#ifdef mkinit
|
|
|
|
INCLUDE "redir.h"
|
|
|
|
RESET {
|
|
while (redirlist)
|
|
popredir();
|
|
}
|
|
|
|
SHELLPROC {
|
|
clearredir(0);
|
|
}
|
|
|
|
#endif
|
|
|
|
/* Return true if fd 0 has already been redirected at least once. */
|
|
int
|
|
fd0_redirected_p(void)
|
|
{
|
|
return fd0_redirected != 0;
|
|
}
|
|
|
|
/*
|
|
* Discard all saved file descriptors.
|
|
*/
|
|
|
|
void
|
|
clearredir(int vforked)
|
|
{
|
|
struct redirtab *rp;
|
|
struct renamelist *rl;
|
|
|
|
for (rp = redirlist ; rp ; rp = rp->next) {
|
|
if (!vforked)
|
|
free_rl(rp, 0);
|
|
else for (rl = rp->renamed; rl; rl = rl->next)
|
|
if (rl->into >= 0)
|
|
close(rl->into);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Copy a file descriptor to be == to.
|
|
* cloexec indicates if we want close-on-exec or not.
|
|
* Returns -1 if any error occurs.
|
|
*/
|
|
|
|
STATIC int
|
|
copyfd(int from, int to, int cloexec)
|
|
{
|
|
int newfd;
|
|
|
|
if (cloexec && to > 2) {
|
|
#ifdef O_CLOEXEC
|
|
newfd = dup3(from, to, O_CLOEXEC);
|
|
#else
|
|
newfd = dup2(from, to);
|
|
fcntl(newfd, F_SETFD, fcntl(newfd,F_GETFD) | FD_CLOEXEC);
|
|
#endif
|
|
} else
|
|
newfd = dup2(from, to);
|
|
|
|
return newfd;
|
|
}
|
|
|
|
/*
|
|
* rename fd from to be fd to (closing from).
|
|
* close-on-exec is never set on 'to' (unless
|
|
* from==to and it was set on from) - ie: a no-op
|
|
* returns to (or errors() if an error occurs).
|
|
*
|
|
* This is mostly used for rearranging the
|
|
* results from pipe().
|
|
*/
|
|
int
|
|
movefd(int from, int to)
|
|
{
|
|
if (from == to)
|
|
return to;
|
|
|
|
(void) close(to);
|
|
if (copyfd(from, to, 0) != to) {
|
|
int e = errno;
|
|
|
|
(void) close(from);
|
|
error("Unable to make fd %d: %s", to, strerror(e));
|
|
}
|
|
(void) close(from);
|
|
|
|
return to;
|
|
}
|
|
|
|
STATIC void
|
|
find_big_fd(void)
|
|
{
|
|
int i, fd;
|
|
static int last_start = 3; /* aim to keep sh fd's under 20 */
|
|
|
|
if (last_start < 10)
|
|
last_start++;
|
|
|
|
for (i = (1 << last_start); i >= 10; i >>= 1) {
|
|
if ((fd = fcntl(0, F_DUPFD, i - 1)) >= 0) {
|
|
close(fd);
|
|
break;
|
|
}
|
|
}
|
|
|
|
fd = (i / 5) * 4;
|
|
if (fd < 10)
|
|
fd = 10;
|
|
|
|
big_sh_fd = fd;
|
|
}
|
|
|
|
/*
|
|
* If possible, move file descriptor fd out of the way
|
|
* of expected user fd values. Returns the new fd
|
|
* (which may be the input fd if things do not go well.)
|
|
* Always set close-on-exec on the result, and close
|
|
* the input fd unless it is to be our result.
|
|
*/
|
|
int
|
|
to_upper_fd(int fd)
|
|
{
|
|
int i;
|
|
|
|
VTRACE(DBG_REDIR|DBG_OUTPUT, ("to_upper_fd(%d)", fd));
|
|
if (big_sh_fd < 10)
|
|
find_big_fd();
|
|
do {
|
|
i = fcntl(fd, F_DUPFD_CLOEXEC, big_sh_fd);
|
|
if (i >= 0) {
|
|
if (fd != i)
|
|
close(fd);
|
|
VTRACE(DBG_REDIR|DBG_OUTPUT, ("-> %d\n", i));
|
|
return i;
|
|
}
|
|
if (errno != EMFILE && errno != EINVAL)
|
|
break;
|
|
find_big_fd();
|
|
} while (big_sh_fd > 10);
|
|
|
|
/*
|
|
* If we wanted to move this fd to some random high number
|
|
* we certainly do not intend to pass it through exec, even
|
|
* if the reassignment failed.
|
|
*/
|
|
(void)fcntl(fd, F_SETFD, FD_CLOEXEC);
|
|
VTRACE(DBG_REDIR|DBG_OUTPUT, (" fails ->%d\n", fd));
|
|
return fd;
|
|
}
|
|
|
|
void
|
|
register_sh_fd(int fd, void (*cb)(int, int))
|
|
{
|
|
struct shell_fds *fp;
|
|
|
|
fp = ckmalloc(sizeof (struct shell_fds));
|
|
if (fp != NULL) {
|
|
fp->nxt = sh_fd_list;
|
|
sh_fd_list = fp;
|
|
|
|
fp->fd = fd;
|
|
fp->cb = cb;
|
|
}
|
|
}
|
|
|
|
void
|
|
sh_close(int fd)
|
|
{
|
|
struct shell_fds **fpp, *fp;
|
|
|
|
fpp = &sh_fd_list;
|
|
while ((fp = *fpp) != NULL) {
|
|
if (fp->fd == fd) {
|
|
*fpp = fp->nxt;
|
|
ckfree(fp);
|
|
break;
|
|
}
|
|
fpp = &fp->nxt;
|
|
}
|
|
(void)close(fd);
|
|
}
|
|
|
|
STATIC struct shell_fds *
|
|
sh_fd(int fd)
|
|
{
|
|
struct shell_fds *fp;
|
|
|
|
for (fp = sh_fd_list; fp != NULL; fp = fp->nxt)
|
|
if (fp->fd == fd)
|
|
return fp;
|
|
return NULL;
|
|
}
|
|
|
|
STATIC void
|
|
renumber_sh_fd(struct shell_fds *fp)
|
|
{
|
|
int to;
|
|
|
|
if (fp == NULL)
|
|
return;
|
|
|
|
/*
|
|
* if we have had a collision, and the sh fd was a "big" one
|
|
* try moving the sh fd base to a higher number (if possible)
|
|
* so future sh fds are less likely to be in the user's sights
|
|
* (incl this one when moved)
|
|
*/
|
|
if (fp->fd >= big_sh_fd)
|
|
find_big_fd();
|
|
|
|
to = fcntl(fp->fd, F_DUPFD_CLOEXEC, big_sh_fd);
|
|
if (to == -1)
|
|
to = fcntl(fp->fd, F_DUPFD_CLOEXEC, big_sh_fd/2);
|
|
if (to == -1)
|
|
to = fcntl(fp->fd, F_DUPFD_CLOEXEC, fp->fd + 1);
|
|
if (to == -1)
|
|
to = fcntl(fp->fd, F_DUPFD_CLOEXEC, 10);
|
|
if (to == -1)
|
|
to = fcntl(fp->fd, F_DUPFD_CLOEXEC, 3);
|
|
if (to == -1)
|
|
error("insufficient file descriptors available");
|
|
CLOEXEC(to);
|
|
|
|
if (fp->fd == to) /* impossible? */
|
|
return;
|
|
|
|
(*fp->cb)(fp->fd, to);
|
|
(void)close(fp->fd);
|
|
fp->fd = to;
|
|
}
|
|
|
|
static const struct flgnames {
|
|
const char *name;
|
|
uint16_t minch;
|
|
uint32_t value;
|
|
} nv[] = {
|
|
#ifdef O_APPEND
|
|
{ "append", 2, O_APPEND },
|
|
#else
|
|
# define O_APPEND 0
|
|
#endif
|
|
#ifdef O_ASYNC
|
|
{ "async", 2, O_ASYNC },
|
|
#else
|
|
# define O_ASYNC 0
|
|
#endif
|
|
#ifdef O_SYNC
|
|
{ "sync", 2, O_SYNC },
|
|
#else
|
|
# define O_SYNC 0
|
|
#endif
|
|
#ifdef O_NONBLOCK
|
|
{ "nonblock", 3, O_NONBLOCK },
|
|
#else
|
|
# define O_NONBLOCK 0
|
|
#endif
|
|
#ifdef O_FSYNC
|
|
{ "fsync", 2, O_FSYNC },
|
|
#else
|
|
# define O_FSYNC 0
|
|
#endif
|
|
#ifdef O_DSYNC
|
|
{ "dsync", 2, O_DSYNC },
|
|
#else
|
|
# define O_DSYNC 0
|
|
#endif
|
|
#ifdef O_RSYNC
|
|
{ "rsync", 2, O_RSYNC },
|
|
#else
|
|
# define O_RSYNC 0
|
|
#endif
|
|
#ifdef O_ALT_IO
|
|
{ "altio", 2, O_ALT_IO },
|
|
#else
|
|
# define O_ALT_IO 0
|
|
#endif
|
|
#ifdef O_DIRECT
|
|
{ "direct", 2, O_DIRECT },
|
|
#else
|
|
# define O_DIRECT 0
|
|
#endif
|
|
#ifdef O_NOSIGPIPE
|
|
{ "nosigpipe", 3, O_NOSIGPIPE },
|
|
#else
|
|
# define O_NOSIGPIPE 0
|
|
#endif
|
|
|
|
#define ALLFLAGS (O_APPEND|O_ASYNC|O_SYNC|O_NONBLOCK|O_DSYNC|O_RSYNC|\
|
|
O_ALT_IO|O_DIRECT|O_NOSIGPIPE)
|
|
|
|
#ifndef O_CLOEXEC
|
|
# define O_CLOEXEC ((~ALLFLAGS) ^ ((~ALLFLAGS) & ((~ALLFLAGS) - 1)))
|
|
#endif
|
|
|
|
/* for any system we support, close on exec is always defined */
|
|
{ "cloexec", 2, O_CLOEXEC },
|
|
{ 0, 0, 0 }
|
|
};
|
|
|
|
#ifndef O_ACCMODE
|
|
# define O_ACCMODE 0
|
|
#endif
|
|
#ifndef O_RDONLY
|
|
# define O_RDONLY 0
|
|
#endif
|
|
#ifndef O_WRONLY
|
|
# define O_WRONLY 0
|
|
#endif
|
|
#ifndef O_RWDR
|
|
# define O_RWDR 0
|
|
#endif
|
|
#ifndef O_SHLOCK
|
|
# define O_SHLOCK 0
|
|
#endif
|
|
#ifndef O_EXLOCK
|
|
# define O_EXLOCK 0
|
|
#endif
|
|
#ifndef O_NOFOLLOW
|
|
# define O_NOFOLLOW 0
|
|
#endif
|
|
#ifndef O_CREAT
|
|
# define O_CREAT 0
|
|
#endif
|
|
#ifndef O_TRUNC
|
|
# define O_TRUNC 0
|
|
#endif
|
|
#ifndef O_EXCL
|
|
# define O_EXCL 0
|
|
#endif
|
|
#ifndef O_NOCTTY
|
|
# define O_NOCTTY 0
|
|
#endif
|
|
#ifndef O_DIRECTORY
|
|
# define O_DIRECTORY 0
|
|
#endif
|
|
#ifndef O_REGULAR
|
|
# define O_REGULAR 0
|
|
#endif
|
|
/*
|
|
* flags that F_GETFL might return that we want to ignore
|
|
*
|
|
* F_GETFL should not actually return these, they're all just open()
|
|
* modifiers, rather than state, but just in case...
|
|
*/
|
|
#define IGNFLAGS (O_ACCMODE|O_RDONLY|O_WRONLY|O_RDWR|O_SHLOCK|O_EXLOCK| \
|
|
O_NOFOLLOW|O_CREAT|O_TRUNC|O_EXCL|O_NOCTTY|O_DIRECTORY|O_REGULAR)
|
|
|
|
static int
|
|
getflags(int fd, int p)
|
|
{
|
|
int c, f;
|
|
|
|
if (sh_fd(fd) != NULL) {
|
|
if (!p)
|
|
return -1;
|
|
error("Can't get status for fd=%d (%s)", fd,
|
|
"Bad file descriptor"); /*XXX*/
|
|
}
|
|
|
|
if ((c = fcntl(fd, F_GETFD)) == -1) {
|
|
if (!p)
|
|
return -1;
|
|
error("Can't get status for fd=%d (%s)", fd, strerror(errno));
|
|
}
|
|
if ((f = fcntl(fd, F_GETFL)) == -1) {
|
|
if (!p)
|
|
return -1;
|
|
error("Can't get flags for fd=%d (%s)", fd, strerror(errno));
|
|
}
|
|
f &= ~IGNFLAGS; /* clear anything we know about, but ignore */
|
|
if (c & FD_CLOEXEC)
|
|
f |= O_CLOEXEC;
|
|
return f;
|
|
}
|
|
|
|
static void
|
|
printone(int fd, int p, int verbose, int pfd)
|
|
{
|
|
int f = getflags(fd, p);
|
|
const struct flgnames *fn;
|
|
|
|
if (f == -1)
|
|
return;
|
|
|
|
if (pfd)
|
|
outfmt(out1, "%d: ", fd);
|
|
for (fn = nv; fn->name; fn++) {
|
|
if (f & fn->value) {
|
|
outfmt(out1, "%s%s", verbose ? "+" : "", fn->name);
|
|
f &= ~fn->value;
|
|
} else if (verbose)
|
|
outfmt(out1, "-%s", fn->name);
|
|
else
|
|
continue;
|
|
if (f || (verbose && fn[1].name))
|
|
outfmt(out1, ",");
|
|
}
|
|
if (verbose && f) /* f should be normally be 0 */
|
|
outfmt(out1, " +%#x", f);
|
|
outfmt(out1, "\n");
|
|
}
|
|
|
|
static void
|
|
parseflags(char *s, int *p, int *n)
|
|
{
|
|
int *v, *w;
|
|
const struct flgnames *fn;
|
|
size_t len;
|
|
|
|
*p = 0;
|
|
*n = 0;
|
|
for (s = strtok(s, ","); s; s = strtok(NULL, ",")) {
|
|
switch (*s++) {
|
|
case '+':
|
|
v = p;
|
|
w = n;
|
|
break;
|
|
case '-':
|
|
v = n;
|
|
w = p;
|
|
break;
|
|
default:
|
|
error("Missing +/- indicator before flag %s", s-1);
|
|
}
|
|
|
|
len = strlen(s);
|
|
for (fn = nv; fn->name; fn++)
|
|
if (len >= fn->minch && strncmp(s,fn->name,len) == 0) {
|
|
*v |= fn->value;
|
|
*w &=~ fn->value;
|
|
break;
|
|
}
|
|
if (fn->name == 0)
|
|
error("Bad flag `%s'", s);
|
|
}
|
|
}
|
|
|
|
static void
|
|
setone(int fd, int pos, int neg, int verbose)
|
|
{
|
|
int f = getflags(fd, 1);
|
|
int n, cloexec;
|
|
|
|
if (f == -1)
|
|
return;
|
|
|
|
cloexec = -1;
|
|
if ((pos & O_CLOEXEC) && !(f & O_CLOEXEC))
|
|
cloexec = FD_CLOEXEC;
|
|
if ((neg & O_CLOEXEC) && (f & O_CLOEXEC))
|
|
cloexec = 0;
|
|
|
|
if (cloexec != -1 && fcntl(fd, F_SETFD, cloexec) == -1)
|
|
error("Can't set status for fd=%d (%s)", fd, strerror(errno));
|
|
|
|
pos &= ~O_CLOEXEC;
|
|
neg &= ~O_CLOEXEC;
|
|
f &= ~O_CLOEXEC;
|
|
n = f;
|
|
n |= pos;
|
|
n &= ~neg;
|
|
if (n != f && fcntl(fd, F_SETFL, n) == -1)
|
|
error("Can't set flags for fd=%d (%s)", fd, strerror(errno));
|
|
if (verbose)
|
|
printone(fd, 1, verbose, 1);
|
|
}
|
|
|
|
int
|
|
fdflagscmd(int argc, char *argv[])
|
|
{
|
|
char *num;
|
|
int verbose = 0, ch, pos = 0, neg = 0;
|
|
char *setflags = NULL;
|
|
|
|
optreset = 1; optind = 1; /* initialize getopt */
|
|
while ((ch = getopt(argc, argv, ":vs:")) != -1)
|
|
switch ((char)ch) {
|
|
case 'v':
|
|
verbose = 1;
|
|
break;
|
|
case 's':
|
|
if (setflags)
|
|
goto msg;
|
|
setflags = optarg;
|
|
break;
|
|
case '?':
|
|
default:
|
|
msg:
|
|
error("Usage: fdflags [-v] [-s <flags> fd] [fd...]");
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
argc -= optind, argv += optind;
|
|
|
|
if (setflags)
|
|
parseflags(setflags, &pos, &neg);
|
|
|
|
if (argc == 0) {
|
|
int i;
|
|
|
|
if (setflags)
|
|
goto msg;
|
|
|
|
for (i = 0; i <= max_user_fd; i++)
|
|
printone(i, 0, verbose, 1);
|
|
return 0;
|
|
}
|
|
|
|
while ((num = *argv++) != NULL) {
|
|
int fd = number(num);
|
|
|
|
while (num[0] == '0' && num[1] != '\0') /* skip 0's */
|
|
num++;
|
|
if (strlen(num) > 5)
|
|
error("%s too big to be a file descriptor", num);
|
|
|
|
if (setflags)
|
|
setone(fd, pos, neg, verbose);
|
|
else
|
|
printone(fd, 1, verbose, argc > 1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#undef MAX /* in case we inherited them from somewhere */
|
|
#undef MIN
|
|
|
|
#define MIN(a,b) (/*CONSTCOND*/((a)<=(b)) ? (a) : (b))
|
|
#define MAX(a,b) (/*CONSTCOND*/((a)>=(b)) ? (a) : (b))
|
|
|
|
/* now make the compiler work for us... */
|
|
#define MIN_REDIR MIN(MIN(MIN(MIN(NTO,NFROM), MIN(NTOFD,NFROMFD)), \
|
|
MIN(MIN(NCLOBBER,NAPPEND), MIN(NHERE,NXHERE))), NFROMTO)
|
|
#define MAX_REDIR MAX(MAX(MAX(MAX(NTO,NFROM), MAX(NTOFD,NFROMFD)), \
|
|
MAX(MAX(NCLOBBER,NAPPEND), MAX(NHERE,NXHERE))), NFROMTO)
|
|
|
|
static const char *redir_sym[MAX_REDIR - MIN_REDIR + 1] = {
|
|
[NTO - MIN_REDIR]= ">",
|
|
[NFROM - MIN_REDIR]= "<",
|
|
[NTOFD - MIN_REDIR]= ">&",
|
|
[NFROMFD - MIN_REDIR]= "<&",
|
|
[NCLOBBER - MIN_REDIR]= ">|",
|
|
[NAPPEND - MIN_REDIR]= ">>",
|
|
[NHERE - MIN_REDIR]= "<<",
|
|
[NXHERE - MIN_REDIR]= "<<",
|
|
[NFROMTO - MIN_REDIR]= "<>",
|
|
};
|
|
|
|
int
|
|
outredir(struct output *out, union node *n, int sep)
|
|
{
|
|
if (n == NULL)
|
|
return 0;
|
|
if (n->type < MIN_REDIR || n->type > MAX_REDIR ||
|
|
redir_sym[n->type - MIN_REDIR] == NULL)
|
|
return 0;
|
|
|
|
if (sep)
|
|
outc(sep, out);
|
|
|
|
/*
|
|
* ugly, but all redir node types have "fd" in same slot...
|
|
* (and code other places assumes it as well)
|
|
*/
|
|
if ((redir_sym[n->type - MIN_REDIR][0] == '<' && n->nfile.fd != 0) ||
|
|
(redir_sym[n->type - MIN_REDIR][0] == '>' && n->nfile.fd != 1))
|
|
outfmt(out, "%d", n->nfile.fd);
|
|
|
|
outstr(redir_sym[n->type - MIN_REDIR], out);
|
|
|
|
switch (n->type) {
|
|
case NHERE:
|
|
outstr("'...'", out);
|
|
break;
|
|
case NXHERE:
|
|
outstr("...", out);
|
|
break;
|
|
case NTOFD:
|
|
case NFROMFD:
|
|
if (n->ndup.dupfd < 0)
|
|
outc('-', out);
|
|
else
|
|
outfmt(out, "%d", n->ndup.dupfd);
|
|
break;
|
|
default:
|
|
outstr(n->nfile.expfname, out);
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|