Module Name:    src
Committed By:   kre
Date:           Sun Nov 10 01:22:24 UTC 2024

Modified Files:
        src/bin/sh: eval.c redir.c redir.h sh.1 show.c

Log Message:
exec builtin command redirection fixes

Several changes, all related to the exec special built in command,
or to close on exec, one way or another.   (Except a few white space
and comment additions, KNF, etc)

1. The bug found by Edgar Fuß reported in:
      http://mail-index.netbsd.org/tech-userlevel/2024/11/05/msg014588.html
   has been fixed, now "exec N>whatever" will set close-on-exec for fd N
   (as do ksh versions, and allowed by POSIX, though other shells do not)
   which has happened now for many years.   But "exec cmd N>whatever"
   (which looks like the same command when redirections are processed)
   which was setting close-on-exec on N, now no longer does, so fd N
   can be passed to cmd as an open fd.

   For anyone who cares, the big block of change just after "case CMDBUILTIN:"
   in evalcommand() in eval.c is the fix for this (one line replaced by
   about 90 ... though most of that is comments or #if 0'd example code
   for later).   It is a bit ugly, and will get worse if our exec command
   ever gets any options, as others have, but it does work.

2. when the exec builtin utility is used to alter the shell's redirections
   it is now "all or nothing".   Previously the redirections were executed
   left to right.  If one failed, no more were attempted, but the earlier
   ones remained.   This makes no practical difference to a non-interactive
   shell, as a redirection error causes that shell to exit, but it makes
   a difference to interactive shells.   Now if a redirection fails, any
   earlier ones which had been performed are undone.   Note however that
   side-effects of redirections (like creating, or truncating, files in
   the filesystem, cannot be reversed - just the shell's file descriptors
   returned to how they were before the error).

   Similarly usage errors on exec now exist .. our exec takes no options
   (but does handle "--" as POSIX says it must - has done for ages).
   Until now, that was the only magic piece of exec, running
        exec -a name somecommand
   (which several other shells support) would attempt to exec the "-a"
   command, and most likely fail, causing immediate exit from the shell.
   Now that is a usage error - a non-interactive shell still exits, as
   exec is a special builtin, and any error from a special builtin causes
   a non-interactive shell to exit.   But now, an interactive shell will
   no longer exit (and any redirections that were on the command will be
   undone, the same as for a redirection error).

3. When a "close on exec" file descriptor is temporarily saved, so the
   same fd can be redirected for another command (only built-in commands
   and functions matter, redirects for file system commands happen after
   a fork() and at that stage if anything goes wrong, the child simply
   exits - but for non-forking commands, doing something like printf >file
   required the previous stdout to be saved elsewhere, "file" opened to
   be the new stdout, then when printf is finished, the old stdout moved
   back.   Anyway, if the fd being moved had close on exec set, then
   when it was moved back, the close on exec was lost.  That is now fixed.

4. The fdflags command no longer allows setting close on exec on stdin,
   stdout, or stderr - POSIX requires that those 3 fd's always be open
   (to something) when any normal command is invoked.  With close-on-exec
   set on one of these, that is impossible, so simply refuse it (when
   "exec N>file" sets close on exec, it only does it for N>2).

Minor changes (should be invisible)

a. The shell now keeps track of the highest fd number it sees doing
   normal operations (there are a few internal pipe() calls that aren't
   monitored and a couple of others, but in general the shell will now
   know the highest fd it ever saw allocated to it).  This is mostly
   for debugging.

b. calls to fcntl() passing an int as the "arg" are now all properly
   cast to the void * that the fcntl kernel is expecting to receive.
   I suspect that makes no actual difference to anything, but ...


To generate a diff of this commit:
cvs rdiff -u -r1.194 -r1.195 src/bin/sh/eval.c
cvs rdiff -u -r1.72 -r1.73 src/bin/sh/redir.c
cvs rdiff -u -r1.26 -r1.27 src/bin/sh/redir.h
cvs rdiff -u -r1.267 -r1.268 src/bin/sh/sh.1
cvs rdiff -u -r1.56 -r1.57 src/bin/sh/show.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/bin/sh/eval.c
diff -u src/bin/sh/eval.c:1.194 src/bin/sh/eval.c:1.195
--- src/bin/sh/eval.c:1.194	Mon Oct 21 15:31:34 2024
+++ src/bin/sh/eval.c	Sun Nov 10 01:22:24 2024
@@ -1,4 +1,4 @@
-/*	$NetBSD: eval.c,v 1.194 2024/10/21 15:31:34 kre Exp $	*/
+/*	$NetBSD: eval.c,v 1.195 2024/11/10 01:22:24 kre Exp $	*/
 
 /*-
  * Copyright (c) 1993
@@ -37,7 +37,7 @@
 #if 0
 static char sccsid[] = "@(#)eval.c	8.9 (Berkeley) 6/8/95";
 #else
-__RCSID("$NetBSD: eval.c,v 1.194 2024/10/21 15:31:34 kre Exp $");
+__RCSID("$NetBSD: eval.c,v 1.195 2024/11/10 01:22:24 kre Exp $");
 #endif
 #endif /* not lint */
 
@@ -658,7 +658,7 @@ evalredir(union node *n, int flags)
 
 		handler = savehandler;
 		e = exception;
-		popredir();
+		popredir(POPREDIR_UNDO);
 		if (PS4 != NULL) {
 			outxstr(PS4);
 			/* { */ outxstr("} failed\n");
@@ -681,7 +681,7 @@ evalredir(union node *n, int flags)
 	}
 	INTOFF;
 	handler = savehandler;
-	popredir();
+	popredir(POPREDIR_UNDO);
 	INTON;
 
 	if (PS4 != NULL) {
@@ -1251,7 +1251,7 @@ evalcommand(union node *cmd, int flgs, s
 				shellparam = saveparam;
 			}
 			if (saved)
-				popredir();
+				popredir(POPREDIR_UNDO);
 			unreffunc(savefunc);
 			poplocalvars();
 			localvars = savelocalvars;
@@ -1296,7 +1296,7 @@ evalcommand(union node *cmd, int flgs, s
 		shellparam = saveparam;
 		handler = savehandler;
 		if (saved)
-			popredir();
+			popredir(POPREDIR_UNDO);
 		INTON;
 		if (evalskip == SKIPFUNC) {
 			evalskip = SKIPNONE;
@@ -1311,18 +1311,100 @@ evalcommand(union node *cmd, int flgs, s
 	case CMDBUILTIN:
 		VXTRACE(DBG_EVAL, ("builtin command [%d]%s:  ", argc,
 		    vforked ? " VF" : ""), trargs(argv));
-		mode = (cmdentry.u.bltin == execcmd) ? 0 : REDIR_PUSH;
+
+		if (cmdentry.u.bltin == execcmd) {
+			char **ap;
+
+			/*
+			 * Work out how we should process redirections
+			 * on the "exec" command.   We need REDIR_KEEP
+			 * if we must not set close-on-exec, and REDIR_PUSH
+			 * if we need to be able to undo them (in the
+			 * exec command, only on  some kind of error).
+			 *
+			 * Skip "exec" (argv[0]) then examine args.
+			 *
+			 * This must be done manually, as nextopt()
+			 * hasn't been init'd for this command yet.
+			 * And it won't be until after redirections are done.
+			 *
+			 * "exec" currently takes no options (except "--"),
+			 * but might one day, and this needs to keep working,
+			 * so do it, kind of, properly.
+			 *
+			 * Note in the common cases argv[1] will be NULL
+			 * (for exec just setting up redirectons) or will
+			 * not start with a '-' ("exec cmd") so normally
+			 * this loop will either never start or will break
+			 * at the first test of the first iteration.
+			 */
+			for (ap = argv + 1; *ap != NULL; ap++) {
+
+				if (ap[0][0] != '-')
+					break;
+
+				if (ap[0][1] == '\0')	/* "exec -" */
+					break;		/* or continue ?? */
+
+				if (ap[0][1] == '-' && ap[0][2] == '\0') {
+					ap++;
+					break;
+				}
+
+#if defined(DUMMY_EXAMPLE_CODE) && 0
+				/*
+				 * if options are added to "exec" then
+				 * any which take an arg (like the common
+				 * in other shells "-a cmdname") need to
+				 * be recognised here, lest "cmdname" be
+				 * thought to be the cmd to exec
+				 */
+
+				for (char *op = ap[0] + 1; *op; op++) {
+					switch (*op) {
+					case 'a':
+					case any others similar:
+						/* options needing an optarg */
+						if (op[1] == '\0' && ap[1])
+							ap++;
+						break;
+
+					default:
+						/* options with no optarg */
+						continue;
+					}
+					break;
+				}
+#endif /* DUMMY EXAMPLE CODE */
+			}
+
+			if (*ap != NULL)
+				mode = REDIR_KEEP;	/* exec cmd <... */
+			else
+				mode = 0;		/* exec < .... */
+
+			/*
+			 * always save old fd setup in case of error()
+			 * execcmd() will undo this if no error occurs
+			 * (that is, in the case the shell has not vanished)
+			 */
+			mode |= REDIR_PUSH;
+		} else			/* any builtin execpt "exec" */
+			mode = REDIR_PUSH;
+
 		if (flags == EV_BACKCMD) {
 			memout.nleft = 0;
 			memout.nextc = memout.buf;
 			memout.bufsize = 64;
 			mode |= REDIR_BACKQ;
 		}
+
 		e = -1;
 		savecmdname = commandname;
 		savetopfile = getcurrentfile();
 		savehandler = handler;
 		temp_path = 0;
+
 		if (!setjmp(jmploc.loc)) {
 			handler = &jmploc;
 
@@ -1395,8 +1477,10 @@ evalcommand(union node *cmd, int flgs, s
 			popfilesupto(savetopfile);
 			FORCEINTON;
 		}
+
 		if (cmdentry.u.bltin != execcmd)
-			popredir();
+			popredir(POPREDIR_UNDO);
+
 		if (flags == EV_BACKCMD) {
 			backcmd->buf = memout.buf;
 			backcmd->nleft = memout.nextc - memout.buf;
@@ -1699,7 +1783,13 @@ truecmd(int argc, char **argv)
 int
 execcmd(int argc, char **argv)
 {
-	(void) nextopt(NULL);		/* ignore a leading "--" */
+	/*
+	 * BEWARE: if any options are added here, they must
+	 * also be added in evalcommand(), look for "DUMMY_EXAMPLE_CODE"
+	 * for example code for there.   Here the options would be
+	 * processed completely normally.
+	 */
+	(void) nextopt("");		/* ignore a leading "--" */
 
 	if (*argptr) {
 		struct strlist *sp;
@@ -1710,7 +1800,9 @@ execcmd(int argc, char **argv)
 		for (sp = cmdenviron; sp; sp = sp->next)
 			setvareq(sp->text, VDOEXPORT|VEXPORT|VSTACK);
 		shellexec(argptr, environment(), pathval(), 0, 0);
+		/* NOTREACHED */
 	}
+	popredir(POPREDIR_PERMANENT);	/* make any redirections permanent */
 	return 0;
 }
 

Index: src/bin/sh/redir.c
diff -u src/bin/sh/redir.c:1.72 src/bin/sh/redir.c:1.73
--- src/bin/sh/redir.c:1.72	Mon Nov 22 05:17:43 2021
+++ src/bin/sh/redir.c	Sun Nov 10 01:22:24 2024
@@ -1,4 +1,4 @@
-/*	$NetBSD: redir.c,v 1.72 2021/11/22 05:17:43 kre Exp $	*/
+/*	$NetBSD: redir.c,v 1.73 2024/11/10 01:22:24 kre Exp $	*/
 
 /*-
  * Copyright (c) 1991, 1993
@@ -37,7 +37,7 @@
 #if 0
 static char sccsid[] = "@(#)redir.c	8.2 (Berkeley) 5/4/95";
 #else
-__RCSID("$NetBSD: redir.c,v 1.72 2021/11/22 05:17:43 kre Exp $");
+__RCSID("$NetBSD: redir.c,v 1.73 2024/11/10 01:22:24 kre Exp $");
 #endif
 #endif /* not lint */
 
@@ -70,8 +70,8 @@ __RCSID("$NetBSD: redir.c,v 1.72 2021/11
 #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
@@ -84,17 +84,22 @@ __RCSID("$NetBSD: redir.c,v 1.72 2021/11
 
 #ifndef F_DUPFD_CLOEXEC
 #define F_DUPFD_CLOEXEC	F_DUPFD
-#define CLOEXEC(fd)	(fcntl((fd), F_SETFD, fcntl((fd),F_GETFD) | FD_CLOEXEC))
+#define CLOEXEC(fd)	(fcntl((fd), F_SETFD,				\
+			     (fcntl_int)(fcntl((fd), F_GETFD) | FD_CLOEXEC)))
 #else
-#define CLOEXEC(fd)
+#define CLOEXEC(fd)	__nothing
 #endif
 
+/* yes, this is correct, bizarre parens and all -- used only as a cast */
+#define	fcntl_int	void *)(intptr_t
+
 
 MKINIT
 struct renamelist {
 	struct renamelist *next;
 	int orig;
 	int into;
+	int cloexec;	/* orig had FD_CLOEXEC set (into always does) */
 };
 
 MKINIT
@@ -118,9 +123,10 @@ STATIC int fd0_redirected = 0;
  * way of user defined fds (normally)
  */
 STATIC int big_sh_fd = 0;
+STATIC int biggest_sh_fd = 2;
 
 STATIC const struct renamelist *is_renamed(const struct renamelist *, int);
-STATIC void fd_rename(struct redirtab *, int, int);
+STATIC void fd_rename(struct redirtab *, int, int, int);
 STATIC int * saved_redirected_fd(int);
 STATIC void free_rl(struct redirtab *, int);
 STATIC void openredirect(union node *, char[10], int);
@@ -178,15 +184,35 @@ free_rl(struct redirtab *rt, int reset)
 			fd0_redirected--;
 		VTRACE(DBG_REDIR, ("popredir %d%s: %s",
 		    rl->orig, rl->orig==0 ? " (STDIN)" : "",
-		    reset ? "" : "no reset\n"));
-		if (reset) {
+		    reset == 1  ? "" :
+		    reset == 2  ? "make permanent" :
+				  "no reset\n"));
+
+		switch (reset) {
+		case 1:
 			if (rl->into < 0) {
-				VTRACE(DBG_REDIR, ("closed\n"));
+				VTRACE(DBG_REDIR, (" closed\n"));
 				close(rl->orig);
 			} else {
-				VTRACE(DBG_REDIR, ("from %d\n", rl->into));
-				movefd(rl->into, rl->orig);
+				VTRACE(DBG_REDIR,
+				    (" from %d%s\n", rl->into,
+				       rl->cloexec ? " (colexec)" : ""));
+				copyfd(rl->into, rl->orig, rl->cloexec);
 			}
+			break;
+		case 2:
+			if (rl->into < 0) {
+				VTRACE(DBG_REDIR, (" was closed\n"));
+				/* nothing to do */
+			} else {
+				VTRACE(DBG_REDIR,
+				    (" close savefd %d\n", rl->into));
+				close(rl->into);
+			}
+			break;
+		default:
+			/* nothing to do */
+			break;
 		}
 		ckfree(rl);
 	}
@@ -194,16 +220,22 @@ free_rl(struct redirtab *rt, int reset)
 }
 
 STATIC void
-fd_rename(struct redirtab *rt, int from, int to)
+fd_rename(struct redirtab *rt, int from, int to, int cloexec)
 {
 	/* XXX someday keep a short list (8..10) of freed renamelists XXX */
 	struct renamelist *rl = ckmalloc(sizeof(struct renamelist));
 
+	/*
+	 * Note this list is operated as LIFO so saved fd's are
+	 * undone in the opposite order to that they were saved
+	 * (needed to ensure correct results)
+	 */
 	rl->next = rt->renamed;
 	rt->renamed = rl;
 
 	rl->orig = from;
 	rl->into = to;
+	rl->cloexec = cloexec;
 }
 
 /*
@@ -224,6 +256,7 @@ redirect(union node *redir, int flags)
 	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;
@@ -253,7 +286,7 @@ redirect(union node *redir, int flags)
 				error("fd %d: %s", fd, strerror(EBADF));
 			/* redirect from/to same file descriptor */
 			/* make sure it stays open */
-			if (fcntl(fd, F_SETFD, 0) < 0)
+			if (fcntl(fd, F_SETFD, (fcntl_int)0) < 0)
 				error("fd %d: %s", fd, strerror(errno));
 			continue;
 		}
@@ -270,13 +303,21 @@ redirect(union node *redir, int flags)
 
 		if ((flags & REDIR_PUSH) && !is_renamed(sv->renamed, fd)) {
 			int bigfd;
+			int cloexec;
+
+			cloexec = fcntl(fd, F_GETFD);
+			if (cloexec >= 0)
+				cloexec &= FD_CLOEXEC;
+			else
+				cloexec = 0;
 
 			INTOFF;
 			if (big_sh_fd < 10)
 				find_big_fd();
 			if ((bigfd = big_sh_fd) < max_user_fd)
 				bigfd = max_user_fd;
-			if ((i = fcntl(fd, F_DUPFD, bigfd + 1)) == -1) {
+			if ((i = fcntl(fd, F_DUPFD,
+			    (fcntl_int)(bigfd + 1))) == -1) {
 				switch (errno) {
 				case EBADF:
 					i = CLOSED;
@@ -284,11 +325,13 @@ redirect(union node *redir, int flags)
 				case EMFILE:
 				case EINVAL:
 					find_big_fd();
-					i = fcntl(fd, F_DUPFD, big_sh_fd);
+					i = fcntl(fd, F_DUPFD, 
+					          (fcntl_int) big_sh_fd);
 					if (i >= 0)
 						break;
 					if (errno == EMFILE || errno == EINVAL)
-						i = fcntl(fd, F_DUPFD, 3);
+						i = fcntl(fd, F_DUPFD,
+							  (fcntl_int) 3);
 					if (i >= 0)
 						break;
 					/* FALLTHRU */
@@ -297,10 +340,13 @@ redirect(union node *redir, int flags)
 					/* NOTREACHED */
 				}
 			}
+			if (i > biggest_sh_fd)
+				biggest_sh_fd = i;
 			if (i >= 0)
-				(void)fcntl(i, F_SETFD, FD_CLOEXEC);
-			fd_rename(sv, fd, i);
-			VTRACE(DBG_REDIR, ("fd %d saved as %d ", fd, i));
+				(void)fcntl(i, F_SETFD, (fcntl_int) FD_CLOEXEC);
+			fd_rename(sv, fd, i, cloexec);
+			VTRACE(DBG_REDIR, ("fd %d saved as %d%s ", fd, i,
+			    cloexec ? "+" : ""));
 			INTON;
 		}
 		VTRACE(DBG_REDIR, ("%s\n", fd == 0 ? "STDIN" : ""));
@@ -344,7 +390,8 @@ openredirect(union node *redir, char mem
 		VTRACE(DBG_REDIR, ("openredirect(< '%s') -> %d [%#x]",
 		    fname, f, eflags));
 		if (eflags)
-			(void)fcntl(f, F_SETFL, fcntl(f, F_GETFL, 0) & ~eflags);
+			(void)fcntl(f, F_SETFL,
+				  (fcntl_int)(fcntl(f, F_GETFL) & ~eflags));
 		break;
 	case NFROMTO:
 		fname = redir->nfile.expfname;
@@ -418,6 +465,9 @@ openredirect(union node *redir, char mem
 		abort();
 	}
 
+	if (f > biggest_sh_fd)
+		biggest_sh_fd = f;
+
 	cloexec = fd > 2 && (flags & REDIR_KEEP) == 0 && !posix;
 	if (f != fd) {
 		VTRACE(DBG_REDIR, (" -> %d", fd));
@@ -431,7 +481,7 @@ openredirect(union node *redir, char mem
 		}
 		close(f);
 	} else if (cloexec)
-		(void)fcntl(f, F_SETFD, FD_CLOEXEC);
+		(void)fcntl(f, F_SETFD, (fcntl_int) FD_CLOEXEC);
 	VTRACE(DBG_REDIR, ("%s\n", cloexec ? " cloexec" : ""));
 
 	INTON;
@@ -459,6 +509,8 @@ openhere(const union node *redir)
 
 	if (pipe(pip) < 0)
 		error("Pipe call failed");
+	if (pip[1] > biggest_sh_fd)
+		biggest_sh_fd = pip[1];
 	len = strlen(redir->nhere.text);
 	VTRACE(DBG_REDIR, ("openhere(%p) [%d] \"%.*s\"%s\n", redir, len,
 	    (len < 40 ? len : 40), redir->nhere.text, (len < 40 ? "" : "...")));
@@ -481,7 +533,7 @@ openhere(const union node *redir)
 		_exit(0);
 	}
 	VTRACE(DBG_REDIR, ("openhere (closing %d)", pip[1]));
- out:
+ out:;
 	close(pip[1]);
 	VTRACE(DBG_REDIR, (" (pipe fd=%d)", pip[0]));
 	return pip[0];
@@ -490,16 +542,21 @@ openhere(const union node *redir)
 
 
 /*
- * Undo the effects of the last redirection.
+ * if (reset == POPREDIR_UNDO)
+ *	Undo the effects of the last redirection.
+ * else if (reset == POPREDIR_PERMANENT)
+ *	Make the last redirection permanent
+ * else / * reset == POPREDIR_DISCARD * /
+ *	Just throw away the redirection
  */
 
 void
-popredir(void)
+popredir(int reset)
 {
 	struct redirtab *rp = redirlist;
 
 	INTOFF;
-	free_rl(rp, 1);
+	free_rl(rp, reset);
 	redirlist = rp->next;
 	ckfree(rp);
 	INTON;
@@ -515,7 +572,7 @@ INCLUDE "redir.h"
 
 RESET {
 	while (redirlist)
-		popredir();
+		popredir(POPREDIR_UNDO);
 }
 
 SHELLPROC {
@@ -543,7 +600,7 @@ clearredir(int vforked)
 
 	for (rp = redirlist ; rp ; rp = rp->next) {
 		if (!vforked)
-			free_rl(rp, 0);
+			free_rl(rp, POPREDIR_DISCARD);
 		else for (rl = rp->renamed; rl; rl = rl->next)
 			if (rl->into >= 0)
 				close(rl->into);
@@ -568,11 +625,15 @@ copyfd(int from, int to, int cloexec)
 		newfd = dup3(from, to, O_CLOEXEC);
 #else
 		newfd = dup2(from, to);
-		fcntl(newfd, F_SETFD, fcntl(newfd,F_GETFD) | FD_CLOEXEC);
+		fcntl(newfd, F_SETFD,
+		    (fcntl_int)(fcntl(newfd, F_GETFD) | FD_CLOEXEC));
 #endif
 	} else
 		newfd = dup2(from, to);
 
+	if (newfd > biggest_sh_fd)
+		biggest_sh_fd = newfd;
+
 	return newfd;
 }
 
@@ -588,6 +649,11 @@ copyfd(int from, int to, int cloexec)
 int
 movefd(int from, int to)
 {
+	if (from > biggest_sh_fd)
+		biggest_sh_fd = from;
+	if (to > biggest_sh_fd)
+		biggest_sh_fd = to;
+
 	if (from == to)
 		return to;
 
@@ -613,7 +679,9 @@ find_big_fd(void)
 		last_start++;
 
 	for (i = (1 << last_start); i >= 10; i >>= 1) {
-		if ((fd = fcntl(0, F_DUPFD, i - 1)) >= 0) {
+		if ((fd = fcntl(0, F_DUPFD, (fcntl_int)(i - 1))) >= 0) {
+			if (fd > biggest_sh_fd)
+				biggest_sh_fd = fd;
 			close(fd);
 			break;
 		}
@@ -642,8 +710,10 @@ to_upper_fd(int fd)
 	if (big_sh_fd < 10 || big_sh_fd >= user_fd_limit)
 		find_big_fd();
 	do {
-		i = fcntl(fd, F_DUPFD_CLOEXEC, big_sh_fd);
+		i = fcntl(fd, F_DUPFD_CLOEXEC, (fcntl_int) big_sh_fd);
 		if (i >= 0) {
+			if (i > biggest_sh_fd)
+				biggest_sh_fd = i;
 			if (fd != i)
 				close(fd);
 			VTRACE(DBG_REDIR|DBG_OUTPUT, ("-> %d\n", i));
@@ -659,7 +729,7 @@ to_upper_fd(int fd)
 	 * we certainly do not intend to pass it through exec, even
 	 * if the reassignment failed.
 	 */
-	(void)fcntl(fd, F_SETFD, FD_CLOEXEC);
+	(void)fcntl(fd, F_SETFD, (fcntl_int) FD_CLOEXEC);
 	VTRACE(DBG_REDIR|DBG_OUTPUT, (" fails ->%d\n", fd));
 	return fd;
 }
@@ -712,18 +782,20 @@ pick_new_fd(int fd)
 {
 	int to;
 
-	to = fcntl(fd, F_DUPFD_CLOEXEC, big_sh_fd);
+	to = fcntl(fd, F_DUPFD_CLOEXEC, (fcntl_int) big_sh_fd);
 	if (to == -1 && big_sh_fd >= 22)
-		to = fcntl(fd, F_DUPFD_CLOEXEC, big_sh_fd/2);
+		to = fcntl(fd, F_DUPFD_CLOEXEC, (fcntl_int) (big_sh_fd / 2));
 	if (to == -1)
-		to = fcntl(fd, F_DUPFD_CLOEXEC, fd + 1);
+		to = fcntl(fd, F_DUPFD_CLOEXEC, (fcntl_int) (fd + 1));
 	if (to == -1)
-		to = fcntl(fd, F_DUPFD_CLOEXEC, 10);
+		to = fcntl(fd, F_DUPFD_CLOEXEC, (fcntl_int) 10);
 	if (to == -1)
-		to = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+		to = fcntl(fd, F_DUPFD_CLOEXEC, (fcntl_int)  3);
 	if (to == -1)
 		error("insufficient file descriptors available");
 	CLOEXEC(to);
+	if (to > biggest_sh_fd)
+		biggest_sh_fd = to;
 	return to;
 }
 
@@ -874,11 +946,11 @@ static const struct flgnames {
     O_NOFOLLOW|O_CREAT|O_TRUNC|O_EXCL|O_NOCTTY|O_DIRECTORY|O_REGULAR)
 
 static int
-getflags(int fd, int p)
+getflags(int fd, int p, int all)
 {
 	int c, f;
 
-	if (sh_fd(fd) != NULL || saved_redirected_fd(fd) != NULL) {
+	if (!all && (sh_fd(fd) != NULL || saved_redirected_fd(fd) != NULL)) {
 		if (!p)
 			return -1;
 		error("Can't get status for fd=%d (%s)", fd, strerror(EBADF));
@@ -903,7 +975,7 @@ getflags(int fd, int p)
 static void
 printone(int fd, int p, int verbose, int pfd)
 {
-	int f = getflags(fd, p);
+	int f = getflags(fd, p, verbose > 1);
 	const struct flgnames *fn;
 
 	if (f == -1)
@@ -965,7 +1037,7 @@ parseflags(char *s, int *p, int *n)
 static void
 setone(int fd, int pos, int neg, int verbose)
 {
-	int f = getflags(fd, 1);
+	int f = getflags(fd, 1, 0);
 	int n, cloexec;
 
 	if (f == -1)
@@ -977,7 +1049,9 @@ setone(int fd, int pos, int neg, int ver
 	if ((neg & O_CLOEXEC) && (f & O_CLOEXEC))
 		cloexec = 0;
 
-	if (cloexec != -1 && fcntl(fd, F_SETFD, cloexec) == -1)
+	/* Don't allow O_CLOEXEC on stdin, stdout, or stderr */
+	if ((cloexec > 0 && fd <= 2 && (errno = EINVAL)) ||
+	    (cloexec != -1 && fcntl(fd, F_SETFD, (fcntl_int) cloexec) == -1))
 		error("Can't set status for fd=%d (%s)", fd, strerror(errno));
 
 	pos &= ~O_CLOEXEC;
@@ -986,7 +1060,7 @@ setone(int fd, int pos, int neg, int ver
 	n = f;
 	n |= pos;
 	n &= ~neg;
-	if (n != f && fcntl(fd, F_SETFL, n) == -1)
+	if (n != f && fcntl(fd, F_SETFL, (fcntl_int)n) == -1)
 		error("Can't set flags for fd=%d (%s)", fd, strerror(errno));
 	if (verbose)
 		printone(fd, 1, verbose, 1);
@@ -1000,8 +1074,17 @@ fdflagscmd(int argc, char *argv[])
 	char *setflags = NULL;
 
 	optreset = 1; optind = 1; /* initialize getopt */
-	while ((ch = getopt(argc, argv, ":vs:")) != -1)
+	while ((ch = getopt(argc, argv, ":vs:"
+#ifdef DEBUG
+					     "V"
+#endif
+						)) != -1)
 		switch ((char)ch) {
+#ifdef DEBUG
+		case 'V':
+			verbose = 2;
+			break;
+#endif
 		case 'v':
 			verbose = 1;
 			break;
@@ -1030,6 +1113,9 @@ fdflagscmd(int argc, char *argv[])
 
 		for (i = 0; i <= max_user_fd; i++)
 			printone(i, 0, verbose, 1);
+		if (verbose > 1)
+			while (i <= biggest_sh_fd)
+				printone(i++, 0, verbose, 1);
 
 	} else while ((num = *argv++) != NULL) {
 		int fd = number(num);

Index: src/bin/sh/redir.h
diff -u src/bin/sh/redir.h:1.26 src/bin/sh/redir.h:1.27
--- src/bin/sh/redir.h:1.26	Wed Sep 15 18:29:45 2021
+++ src/bin/sh/redir.h	Sun Nov 10 01:22:24 2024
@@ -1,4 +1,4 @@
-/*	$NetBSD: redir.h,v 1.26 2021/09/15 18:29:45 kre Exp $	*/
+/*	$NetBSD: redir.h,v 1.27 2024/11/10 01:22:24 kre Exp $	*/
 
 /*-
  * Copyright (c) 1991, 1993
@@ -40,9 +40,14 @@
 #define REDIR_VFORK 0x04	/* running under vfork(2), be careful */
 #define REDIR_KEEP  0x08	/* don't close-on-exec */
 
+/* flags passed to popredir and free_rl */
+#define POPREDIR_DISCARD	0	/* just abandon everything */
+#define POPREDIR_UNDO		1	/* undo saved redirects */
+#define POPREDIR_PERMANENT	2	/* keep renamed fd, close saving fd */
+
 union node;
 void redirect(union node *, int);
-void popredir(void);
+void popredir(int);
 int fd0_redirected_p(void);
 void clearredir(int);
 int movefd(int, int);

Index: src/bin/sh/sh.1
diff -u src/bin/sh/sh.1:1.267 src/bin/sh/sh.1:1.268
--- src/bin/sh/sh.1:1.267	Mon Oct 14 08:27:53 2024
+++ src/bin/sh/sh.1	Sun Nov 10 01:22:24 2024
@@ -1,4 +1,4 @@
-.\"	$NetBSD: sh.1,v 1.267 2024/10/14 08:27:53 kre Exp $
+.\"	$NetBSD: sh.1,v 1.268 2024/11/10 01:22:24 kre Exp $
 .\" Copyright (c) 1991, 1993
 .\"	The Regents of the University of California.  All rights reserved.
 .\"
@@ -31,7 +31,7 @@
 .\"
 .\"	@(#)sh.1	8.6 (Berkeley) 5/4/95
 .\"
-.Dd October 14, 2024
+.Dd November 9, 2024
 .Dt SH 1
 .\" everything except c o and s (keep them ordered)
 .ds flags abCEeFfhIiLlmnpqruVvXx
@@ -381,7 +381,7 @@ or if it is a pipeline (or simple comman
 .Dq \&!
 operator.
 With pipelines, only the status of the entire pipeline
-(indicated by the last command it contains)
+(usually generated by the last command it contains)
 is tested when
 .Fl e
 is set to determine if the shell should exit.
@@ -640,7 +640,7 @@ opened using the
 .Ic exec
 built-in command are passed on to utilities executed
 .Dq ( yes
-in posix mode),
+in posix mode, though the POSIX standard does not actually require this),
 whether a colon (:) terminates the user name in tilde (~) expansions
 other than in assignment statements
 .Dq ( no
@@ -2771,11 +2771,18 @@ Any redirections on the
 .Ic exec
 command are marked as permanent, so that they are not undone when the
 .Ic exec
-command finishes.
+command finishes, which only happens if no
+.Ar command
+was given.
+.Pp
 When the
 .Cm posix
 option is not set,
-file descriptors created via such redirections are marked close-on-exec
+file descriptors created via such redirections,
+when no
+.Ar command
+is present,
+are marked close-on-exec
 (see
 .Xr open 2
 .Dv O_CLOEXEC
@@ -2787,7 +2794,8 @@ unless the descriptors refer to the stan
 output, or error (file descriptors 0, 1, 2).
 Traditionally Bourne-like shells
 (except
-.Xr ksh 1 ) ,
+.Xr ksh 1
+and its close relatives),
 made those file descriptors available to exec'ed processes.
 To be assured the close-on-exec setting is off,
 redirect the descriptor to (or from) itself,
@@ -2798,11 +2806,30 @@ or by using
 .Ic exec
 .No as opened it, after the open Pc
 to leave the descriptor open in the shell
-and pass it to all commands invoked subsequently.
+and also pass it to all commands invoked subsequently.
 Alternatively, see the
 .Ic fdflags
-command below, which can set, or clear, this, and other,
+built-in command below, which can set, or clear, this, and other,
 file descriptor flags.
+.Pp
+If there is a usage, or redirection, error,
+.Ic exec
+will not exit from an interactive shell,
+but will restore all modified file descriptors
+to the state they had before the
+.Ic exec
+command was issued.
+But note that side effects of any redirections that
+succeeded, such as creating or truncating files, cannot
+be reversed.
+However if a
+.Ar command
+cannot be executed for any reason, even an
+interactive shell will exit.
+Non-interactive shells will exit on any error,
+as
+.Ic exec
+is a special built-in utility.
 .\"
 .Pp
 .It Ic exit Op Ar exitstatus
@@ -3076,6 +3103,10 @@ and
 .Cm cloexec .
 Unique abbreviations of these names, of at least 2 characters,
 may be used on input.
+It is not permitted to set
+.Cm cloexec
+on file descriptors 0, 1, or 2
+.Pq standard input , standard output , and standard error .
 See
 .Xr fcntl 2
 and

Index: src/bin/sh/show.c
diff -u src/bin/sh/show.c:1.56 src/bin/sh/show.c:1.57
--- src/bin/sh/show.c:1.56	Fri Jul 12 04:45:12 2024
+++ src/bin/sh/show.c	Sun Nov 10 01:22:24 2024
@@ -1,4 +1,4 @@
-/*	$NetBSD: show.c,v 1.56 2024/07/12 04:45:12 kre Exp $	*/
+/*	$NetBSD: show.c,v 1.57 2024/11/10 01:22:24 kre Exp $	*/
 
 /*-
  * Copyright (c) 1991, 1993
@@ -39,7 +39,7 @@
 #if 0
 static char sccsid[] = "@(#)show.c	8.3 (Berkeley) 5/4/95";
 #else
-__RCSID("$NetBSD: show.c,v 1.56 2024/07/12 04:45:12 kre Exp $");
+__RCSID("$NetBSD: show.c,v 1.57 2024/11/10 01:22:24 kre Exp $");
 #endif
 #endif /* not lint */
 
@@ -163,7 +163,7 @@ opentrace(void)
 	 */
 	if (tracefile)
 		(void) fclose(tracefile);	/* also closes tfd */
-	tracefile = fdopen(fd, "a");	/* don't care if it is NULL */
+	tracefile = fdopen(fd, "ae");	/* don't care if it is NULL */
 	if (tracefile)			/* except here... */
 		setlinebuf(tracefile);
 

Reply via email to