Navigation Menu

Skip to content

Commit

Permalink
Add support for set -o pipefail
Browse files Browse the repository at this point in the history
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@
  • Loading branch information
jcourreges committed Jul 7, 2020
1 parent 72b0de7 commit cbb0b32
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 8 deletions.
30 changes: 28 additions & 2 deletions 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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)) {
Expand Down
12 changes: 9 additions & 3 deletions 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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion 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
Expand Down Expand Up @@ -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 },
Expand Down
3 changes: 2 additions & 1 deletion 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
Expand Down Expand Up @@ -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 */
Expand Down
90 changes: 89 additions & 1 deletion 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
Expand Down Expand Up @@ -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
---

0 comments on commit cbb0b32

Please sign in to comment.