summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremie Courreges-Anglas <jca@cvs.openbsd.org>2020-07-07 10:33:59 +0000
committerJeremie Courreges-Anglas <jca@cvs.openbsd.org>2020-07-07 10:33:59 +0000
commitbbdc823dbfcb874c29fa4138c2591f5196aab521 (patch)
tree8e8816f54aaf4c4d0562aae01a7bff98d8a67fb2
parentcdac567044ac69c43238666f5fa17eca2f28138e (diff)
Add support for set -o pipefail
With the pipefail option set, the exit status of a pipeline is 0 if all commands succeed, or the return status of the rightmost command that fails. This can help stronger error checking, but is not a silver bullet. For example, commands will exhibit a non-zero exit status if they're killed by a SIGPIPE when writing to a pipe. Yet pipefail was considered useful enough to be included in the next POSIX standard. This implementation remembers the value of the pipefail option when a pipeline is started, as described as option 1) in https://www.austingroupbugs.net/view.php?id=789#c4102 Requested by ajacoutot@, ok millert@
-rw-r--r--bin/ksh/jobs.c30
-rw-r--r--bin/ksh/ksh.112
-rw-r--r--bin/ksh/misc.c3
-rw-r--r--bin/ksh/sh.h3
-rw-r--r--regress/bin/ksh/obsd-regress.t90
5 files changed, 130 insertions, 8 deletions
diff --git a/bin/ksh/jobs.c b/bin/ksh/jobs.c
index f99c9eb74c8..121c0cfd498 100644
--- a/bin/ksh/jobs.c
+++ b/bin/ksh/jobs.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: jobs.c,v 1.61 2019/06/28 13:34:59 deraadt Exp $ */
+/* $OpenBSD: jobs.c,v 1.62 2020/07/07 10:33:58 jca Exp $ */
/*
* Process and job control
@@ -70,6 +70,7 @@ struct proc {
#define JF_REMOVE 0x200 /* flagged for removal (j_jobs()/j_notify()) */
#define JF_USETTYMODE 0x400 /* tty mode saved if process exits normally */
#define JF_SAVEDTTYPGRP 0x800 /* j->saved_ttypgrp is valid */
+#define JF_PIPEFAIL 0x1000 /* pipefail on when job was started */
typedef struct job Job;
struct job {
@@ -421,6 +422,8 @@ exchild(struct op *t, int flags, volatile int *xerrok,
*/
j->flags = (flags & XXCOM) ? JF_XXCOM :
((flags & XBGND) ? 0 : (JF_FG|JF_USETTYMODE));
+ if (Flag(FPIPEFAIL))
+ j->flags |= JF_PIPEFAIL;
timerclear(&j->usrtime);
timerclear(&j->systime);
j->state = PRUNNING;
@@ -1084,7 +1087,30 @@ j_waitj(Job *j,
j_usrtime = j->usrtime;
j_systime = j->systime;
- rv = j->status;
+
+ if (j->flags & JF_PIPEFAIL) {
+ Proc *p;
+ int status;
+
+ rv = 0;
+ for (p = j->proc_list; p != NULL; p = p->next) {
+ switch (p->state) {
+ case PEXITED:
+ status = WEXITSTATUS(p->status);
+ break;
+ case PSIGNALLED:
+ status = 128 + WTERMSIG(p->status);
+ break;
+ default:
+ status = 0;
+ break;
+ }
+ if (status)
+ rv = status;
+ }
+ } else
+ rv = j->status;
+
if (!(flags & JW_ASYNCNOTIFY) &&
(!Flag(FMONITOR) || j->state != PSTOPPED)) {
diff --git a/bin/ksh/ksh.1 b/bin/ksh/ksh.1
index 7330e9391f8..a91ab51e63b 100644
--- a/bin/ksh/ksh.1
+++ b/bin/ksh/ksh.1
@@ -1,8 +1,8 @@
-.\" $OpenBSD: ksh.1,v 1.208 2019/11/26 22:49:01 jmc Exp $
+.\" $OpenBSD: ksh.1,v 1.209 2020/07/07 10:33:58 jca Exp $
.\"
.\" Public Domain
.\"
-.Dd $Mdocdate: November 26 2019 $
+.Dd $Mdocdate: July 7 2020 $
.Dt KSH 1
.Os
.Sh NAME
@@ -361,7 +361,9 @@ token to form pipelines, in which the standard output of each command but the
last is piped (see
.Xr pipe 2 )
to the standard input of the following command.
-The exit status of a pipeline is that of its last command.
+The exit status of a pipeline is that of its last command, unless the
+.Ic pipefail
+option is set.
A pipeline may be prefixed by the
.Ql \&!
reserved word, which causes the exit status of the pipeline to be logically
@@ -3664,6 +3666,10 @@ See the
and
.Ic pwd
commands above for more details.
+.It Ic pipefail
+The exit status of a pipeline is the exit status of the rightmost
+command in the pipeline that doesn't return 0, or 0 if all commands
+returned a 0 exit status.
.It Ic posix
Enable POSIX mode.
See
diff --git a/bin/ksh/misc.c b/bin/ksh/misc.c
index 8f673085583..7b5889f2a27 100644
--- a/bin/ksh/misc.c
+++ b/bin/ksh/misc.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.c,v 1.73 2019/06/28 13:34:59 deraadt Exp $ */
+/* $OpenBSD: misc.c,v 1.74 2020/07/07 10:33:58 jca Exp $ */
/*
* Miscellaneous functions
@@ -147,6 +147,7 @@ const struct option sh_options[] = {
{ "notify", 'b', OF_ANY },
{ "nounset", 'u', OF_ANY },
{ "physical", 0, OF_ANY }, /* non-standard */
+ { "pipefail", 0, OF_ANY }, /* non-standard */
{ "posix", 0, OF_ANY }, /* non-standard */
{ "privileged", 'p', OF_ANY },
{ "restricted", 'r', OF_CMDLINE },
diff --git a/bin/ksh/sh.h b/bin/ksh/sh.h
index ad97d836fb4..93beef31d46 100644
--- a/bin/ksh/sh.h
+++ b/bin/ksh/sh.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: sh.h,v 1.75 2019/02/20 23:59:17 schwarze Exp $ */
+/* $OpenBSD: sh.h,v 1.76 2020/07/07 10:33:58 jca Exp $ */
/*
* Public Domain Bourne/Korn shell
@@ -158,6 +158,7 @@ enum sh_flag {
FNOTIFY, /* -b: asynchronous job completion notification */
FNOUNSET, /* -u: using an unset var is an error */
FPHYSICAL, /* -o physical: don't do logical cd's/pwd's */
+ FPIPEFAIL, /* -o pipefail: all commands in pipeline can affect $? */
FPOSIX, /* -o posix: be posixly correct */
FPRIVILEGED, /* -p: use suid_profile */
FRESTRICTED, /* -r: restricted shell */
diff --git a/regress/bin/ksh/obsd-regress.t b/regress/bin/ksh/obsd-regress.t
index d94695111cf..b9b33d17655 100644
--- a/regress/bin/ksh/obsd-regress.t
+++ b/regress/bin/ksh/obsd-regress.t
@@ -1,4 +1,4 @@
-# $OpenBSD: obsd-regress.t,v 1.11 2020/05/22 08:45:23 anton Exp $
+# $OpenBSD: obsd-regress.t,v 1.12 2020/07/07 10:33:58 jca Exp $
#
# ksh regression tests from OpenBSD
@@ -514,3 +514,91 @@ description:
stdin:
kill -s SIGINFO $$
---
+
+name: pipeline-pipefail-1
+description:
+ check pipeline return status
+stdin:
+ set -o pipefail
+ true | true
+---
+
+name: pipeline-pipefail-2
+description:
+ check pipeline return status
+stdin:
+ set -o pipefail
+ false | true
+expected-exit: e == 1
+---
+
+name: pipeline-pipefail-3
+description:
+ check pipeline return status
+stdin:
+ set -o pipefail
+ true | false
+expected-exit: e == 1
+---
+
+name: pipeline-pipefail-4
+description:
+ check pipeline return status
+stdin:
+ set -o pipefail
+ ! false | true
+---
+
+name: pipeline-pipefail-errexit-1
+description:
+ check pipeline return status
+stdin:
+ set -e
+ false | true
+ echo "ok"
+expected-stdout: ok
+---
+
+name: pipeline-pipefail-errexit-2
+description:
+ check pipeline return status
+stdin:
+ set -e
+ set -o pipefail
+ false | true
+ echo "should not print"
+expected-exit: e == 1
+expected-stdout:
+---
+
+name: pipeline-pipefail-errexit-3
+description:
+ check pipeline return status
+stdin:
+ set -e
+ set -o pipefail
+ false | true || echo "ok"
+expected-stdout: ok
+---
+
+name: pipeline-pipefail-check-time-1
+description:
+ check pipeline return status
+stdin:
+ false | true &
+ p=$!
+ set -o pipefail
+ wait $p
+---
+
+name: pipeline-pipefail-check-time-2
+description:
+ check pipeline return status
+stdin:
+ set -o pipefail
+ false | true &
+ p=$!
+ set +o pipefail
+ wait $p
+expected-exit: e == 1
+---