Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 24 Mar 2019 22:10:26 +0000 (UTC)
From:      Jilles Tjoelker <jilles@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-12@freebsd.org
Subject:   svn commit: r345487 - in stable/12/bin/sh: . tests/execution
Message-ID:  <201903242210.x2OMAQIT016012@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: jilles
Date: Sun Mar 24 22:10:26 2019
New Revision: 345487
URL: https://svnweb.freebsd.org/changeset/base/345487

Log:
  MFC r344502: sh: Add set -o pipefail
  
  The pipefail option allows checking the exit status of all commands in a
  pipeline more easily, at a limited cost of complexity in sh itself. It works
  similarly to the option in bash, ksh93 and mksh.
  
  Like ksh93 and unlike bash and mksh, the state of the option is saved when a
  pipeline is started. Therefore, even in the case of commands like
    A | B &
  a later change of the option does not affect the exit status, the same way
    (A | B) &
  works.
  
  Since SIGPIPE is not handled specially, more work in the script is required
  for a proper exit status for pipelines containing commands such as head that
  may terminate successfully without reading all input. This can be something
  like
  
  (
  	cmd1
  	r=$?
  	if [ "$r" -gt 128 ] && [ "$(kill -l "$r")" = PIPE ]; then
  		exit 0
  	else
  		exit "$r"
  	fi
  ) | head
  
  PR:		244270
  Relnotes:	yes

Added:
  stable/12/bin/sh/tests/execution/pipefail1.0
     - copied unchanged from r344502, head/bin/sh/tests/execution/pipefail1.0
  stable/12/bin/sh/tests/execution/pipefail2.42
     - copied unchanged from r344502, head/bin/sh/tests/execution/pipefail2.42
  stable/12/bin/sh/tests/execution/pipefail3.42
     - copied unchanged from r344502, head/bin/sh/tests/execution/pipefail3.42
  stable/12/bin/sh/tests/execution/pipefail4.42
     - copied unchanged from r344502, head/bin/sh/tests/execution/pipefail4.42
  stable/12/bin/sh/tests/execution/pipefail5.42
     - copied unchanged from r344502, head/bin/sh/tests/execution/pipefail5.42
  stable/12/bin/sh/tests/execution/pipefail6.42
     - copied unchanged from r344502, head/bin/sh/tests/execution/pipefail6.42
  stable/12/bin/sh/tests/execution/pipefail7.0
     - copied unchanged from r344502, head/bin/sh/tests/execution/pipefail7.0
Modified:
  stable/12/bin/sh/jobs.c
  stable/12/bin/sh/options.h
  stable/12/bin/sh/sh.1
  stable/12/bin/sh/tests/execution/Makefile
Directory Properties:
  stable/12/   (props changed)

Modified: stable/12/bin/sh/jobs.c
==============================================================================
--- stable/12/bin/sh/jobs.c	Sun Mar 24 20:43:21 2019	(r345486)
+++ stable/12/bin/sh/jobs.c	Sun Mar 24 22:10:26 2019	(r345487)
@@ -104,6 +104,7 @@ struct job {
 	char changed;		/* true if status has changed */
 	char foreground;	/* true if running in the foreground */
 	char remembered;	/* true if $! referenced */
+	char pipefail;		/* pass any non-zero status */
 #if JOBS
 	char jobctl;		/* job running under job control */
 	struct job *next;	/* job used after this one */
@@ -143,6 +144,7 @@ static void setcurjob(struct job *);
 static void deljob(struct job *);
 static struct job *getcurjob(struct job *);
 #endif
+static int getjobstatus(const struct job *);
 static void printjobcmd(struct job *);
 static void showjob(struct job *, int);
 
@@ -340,6 +342,20 @@ jobscmd(int argc __unused, char *argv[] __unused)
 	return (0);
 }
 
+static int getjobstatus(const struct job *jp)
+{
+	int i, status;
+
+	if (!jp->pipefail)
+		return (jp->ps[jp->nprocs - 1].status);
+	for (i = jp->nprocs - 1; i >= 0; i--) {
+		status = jp->ps[i].status;
+		if (status != 0)
+			return (status);
+	}
+	return (0);
+}
+
 static void
 printjobcmd(struct job *jp)
 {
@@ -376,7 +392,7 @@ showjob(struct job *jp, int mode)
 	}
 #endif
 	coredump = "";
-	status = jp->ps[jp->nprocs - 1].status;
+	status = getjobstatus(jp);
 	if (jp->state == 0) {
 		statestr = "Running";
 #if JOBS
@@ -555,7 +571,7 @@ waitcmdloop(struct job *job)
 	do {
 		if (job != NULL) {
 			if (job->state == JOBDONE) {
-				status = job->ps[job->nprocs - 1].status;
+				status = getjobstatus(job);
 				if (WIFEXITED(status))
 					retval = WEXITSTATUS(status);
 				else
@@ -780,6 +796,7 @@ makejob(union node *node __unused, int nprocs)
 	jp->nprocs = 0;
 	jp->foreground = 0;
 	jp->remembered = 0;
+	jp->pipefail = pipefailflag;
 #if JOBS
 	jp->jobctl = jobctl;
 	jp->next = NULL;
@@ -1075,7 +1092,7 @@ waitforjob(struct job *jp, int *signaled)
 	if (jp->state == JOBSTOPPED)
 		setcurjob(jp);
 #endif
-	status = jp->ps[jp->nprocs - 1].status;
+	status = getjobstatus(jp);
 	if (signaled != NULL)
 		*signaled = WIFSIGNALED(status);
 	/* convert to 8 bits */

Modified: stable/12/bin/sh/options.h
==============================================================================
--- stable/12/bin/sh/options.h	Sun Mar 24 20:43:21 2019	(r345486)
+++ stable/12/bin/sh/options.h	Sun Mar 24 22:10:26 2019	(r345487)
@@ -67,9 +67,10 @@ struct shparam {
 #define	Pflag optval[17]
 #define	hflag optval[18]
 #define	nologflag optval[19]
+#define	pipefailflag optval[20]
 
 #define NSHORTOPTS	19
-#define NOPTS		20
+#define NOPTS		21
 
 extern char optval[NOPTS];
 extern const char optletter[NSHORTOPTS];
@@ -97,6 +98,7 @@ static const unsigned char optname[] =
 	"\010physical"
 	"\010trackall"
 	"\005nolog"
+	"\010pipefail"
 ;
 #endif
 

Modified: stable/12/bin/sh/sh.1
==============================================================================
--- stable/12/bin/sh/sh.1	Sun Mar 24 20:43:21 2019	(r345486)
+++ stable/12/bin/sh/sh.1	Sun Mar 24 22:10:26 2019	(r345487)
@@ -32,7 +32,7 @@
 .\"	from: @(#)sh.1	8.6 (Berkeley) 5/4/95
 .\" $FreeBSD$
 .\"
-.Dd July 19, 2018
+.Dd February 24, 2019
 .Dt SH 1
 .Os
 .Sh NAME
@@ -343,6 +343,18 @@ Useful for debugging.
 .It Li nolog
 Another do-nothing option for POSIX compliance.
 It only has a long name.
+.It Li pipefail
+Change the exit status of a pipeline to the last non-zero exit status of
+any command in the pipeline, if any.
+Since an exit due to
+.Dv SIGPIPE
+counts as a non-zero exit status,
+this option may cause non-zero exit status for successful pipelines
+if a command such as
+.Xr head 1
+in the pipeline terminates with status 0 without reading its
+input completely.
+This option only has a long name.
 .El
 .Pp
 The
@@ -856,12 +868,15 @@ If the keyword
 .Ic !\&
 does not precede the pipeline, the
 exit status is the exit status of the last command specified
-in the pipeline.
+in the pipeline if the
+.Cm pipefail
+option is not set or all commands returned zero,
+or the last non-zero exit status of any command in the pipeline otherwise.
 Otherwise, the exit status is the logical
-NOT of the exit status of the last command.
+NOT of that exit status.
 That is, if
-the last command returns zero, the exit status is 1; if
-the last command returns greater than zero, the exit status
+that status is zero, the exit status is 1; if
+that status is greater than zero, the exit status
 is zero.
 .Pp
 Because pipeline assignment of standard input or standard

Modified: stable/12/bin/sh/tests/execution/Makefile
==============================================================================
--- stable/12/bin/sh/tests/execution/Makefile	Sun Mar 24 20:43:21 2019	(r345486)
+++ stable/12/bin/sh/tests/execution/Makefile	Sun Mar 24 22:10:26 2019	(r345487)
@@ -30,6 +30,13 @@ ${PACKAGE}FILES+=		killed2.0
 ${PACKAGE}FILES+=		not1.0
 ${PACKAGE}FILES+=		not2.0
 ${PACKAGE}FILES+=		path1.0
+${PACKAGE}FILES+=		pipefail1.0
+${PACKAGE}FILES+=		pipefail2.42
+${PACKAGE}FILES+=		pipefail3.42
+${PACKAGE}FILES+=		pipefail4.42
+${PACKAGE}FILES+=		pipefail5.42
+${PACKAGE}FILES+=		pipefail6.42
+${PACKAGE}FILES+=		pipefail7.0
 ${PACKAGE}FILES+=		redir1.0
 ${PACKAGE}FILES+=		redir2.0
 ${PACKAGE}FILES+=		redir3.0

Copied: stable/12/bin/sh/tests/execution/pipefail1.0 (from r344502, head/bin/sh/tests/execution/pipefail1.0)
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ stable/12/bin/sh/tests/execution/pipefail1.0	Sun Mar 24 22:10:26 2019	(r345487, copy of r344502, head/bin/sh/tests/execution/pipefail1.0)
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+set -o pipefail
+: && : | : && : | : | : && : | : | : | :

Copied: stable/12/bin/sh/tests/execution/pipefail2.42 (from r344502, head/bin/sh/tests/execution/pipefail2.42)
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ stable/12/bin/sh/tests/execution/pipefail2.42	Sun Mar 24 22:10:26 2019	(r345487, copy of r344502, head/bin/sh/tests/execution/pipefail2.42)
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+set -o pipefail
+(exit 42) | :

Copied: stable/12/bin/sh/tests/execution/pipefail3.42 (from r344502, head/bin/sh/tests/execution/pipefail3.42)
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ stable/12/bin/sh/tests/execution/pipefail3.42	Sun Mar 24 22:10:26 2019	(r345487, copy of r344502, head/bin/sh/tests/execution/pipefail3.42)
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+set -o pipefail
+: | (exit 42)

Copied: stable/12/bin/sh/tests/execution/pipefail4.42 (from r344502, head/bin/sh/tests/execution/pipefail4.42)
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ stable/12/bin/sh/tests/execution/pipefail4.42	Sun Mar 24 22:10:26 2019	(r345487, copy of r344502, head/bin/sh/tests/execution/pipefail4.42)
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+set -o pipefail
+(exit 43) | (exit 42)

Copied: stable/12/bin/sh/tests/execution/pipefail5.42 (from r344502, head/bin/sh/tests/execution/pipefail5.42)
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ stable/12/bin/sh/tests/execution/pipefail5.42	Sun Mar 24 22:10:26 2019	(r345487, copy of r344502, head/bin/sh/tests/execution/pipefail5.42)
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+set -o pipefail
+(exit 42) | : &
+wait %+

Copied: stable/12/bin/sh/tests/execution/pipefail6.42 (from r344502, head/bin/sh/tests/execution/pipefail6.42)
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ stable/12/bin/sh/tests/execution/pipefail6.42	Sun Mar 24 22:10:26 2019	(r345487, copy of r344502, head/bin/sh/tests/execution/pipefail6.42)
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+set -o pipefail
+(exit 42) | : &
+set +o pipefail
+wait %+

Copied: stable/12/bin/sh/tests/execution/pipefail7.0 (from r344502, head/bin/sh/tests/execution/pipefail7.0)
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ stable/12/bin/sh/tests/execution/pipefail7.0	Sun Mar 24 22:10:26 2019	(r345487, copy of r344502, head/bin/sh/tests/execution/pipefail7.0)
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+(exit 42) | : &
+set -o pipefail
+wait %+



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201903242210.x2OMAQIT016012>