summaryrefslogtreecommitdiff
path: root/bin/sh/redir.c
diff options
context:
space:
mode:
authorKiyoshi Aman <kiyoshi.aman+adelie@gmail.com>2019-02-01 22:55:37 +0000
committerKiyoshi Aman <kiyoshi.aman+adelie@gmail.com>2019-02-03 18:22:05 -0600
commit5b57d28ffb6e1ef86b50f7d05d977826eae89bfe (patch)
tree154a22fe556b49e6927197336f8bf91b12eacd5e /bin/sh/redir.c
downloaduserland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.gz
userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.bz2
userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.xz
userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.zip
initial population
Diffstat (limited to 'bin/sh/redir.c')
-rw-r--r--bin/sh/redir.c982
1 files changed, 982 insertions, 0 deletions
diff --git a/bin/sh/redir.c b/bin/sh/redir.c
new file mode 100644
index 0000000..751cce7
--- /dev/null
+++ b/bin/sh/redir.c
@@ -0,0 +1,982 @@
+/* $NetBSD: redir.c,v 1.62 2018/11/26 20:03:39 kamil 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.62 2018/11/26 20:03:39 kamil 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
+
+
+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;
+ INTON; /* XXX not needed here ? */
+ 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]);
+ _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)
+ newfd = dup3(from, to, O_CLOEXEC);
+ 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;
+
+#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
+
+ /*
+ * 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 },
+#endif
+#ifdef O_ASYNC
+ { "async", 2, O_ASYNC },
+#endif
+#ifdef O_SYNC
+ { "sync", 2, O_SYNC },
+#endif
+#ifdef O_NONBLOCK
+ { "nonblock", 3, O_NONBLOCK },
+#endif
+#ifdef O_FSYNC
+ { "fsync", 2, O_FSYNC },
+#endif
+#ifdef O_DSYNC
+ { "dsync", 2, O_DSYNC },
+#endif
+#ifdef O_RSYNC
+ { "rsync", 2, O_RSYNC },
+#endif
+#ifdef O_ALT_IO
+ { "altio", 2, O_ALT_IO },
+#endif
+#ifdef O_DIRECT
+ { "direct", 2, O_DIRECT },
+#endif
+#ifdef O_NOSIGPIPE
+ { "nosigpipe", 3, O_NOSIGPIPE },
+#endif
+#ifdef O_CLOEXEC
+ { "cloexec", 2, O_CLOEXEC },
+#endif
+ { 0, 0, 0 }
+};
+#define ALLFLAGS (O_APPEND|O_ASYNC|O_SYNC|O_NONBLOCK|O_DSYNC|O_RSYNC|\
+ O_ALT_IO|O_DIRECT|O_NOSIGPIPE|O_CLOEXEC)
+
+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));
+ }
+ if (c & FD_CLOEXEC)
+ f |= O_CLOEXEC;
+ return f & ALLFLAGS;
+}
+
+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;
+}