Requested by ajacoutot@, here's an attempt at implementing set -o
pipefail.  As pointed by sthen@ this option should be included in the
next POSIX standard:

  https://www.austingroupbugs.net/view.php?id=789

There are several ways to implement it, the diff I showed to Antoine was
based on option 2) in

  https://www.austingroupbugs.net/view.php?id=789#c4102

whereas the diff below implements option 1) described by kre@NetBSD,
which looks like the most sensible approach.

No, this doesn't add support for a PIPESTATUS array, I'm not sure we need it.

This diff includes manpage and regress bits.
Thoughts, oks?


Index: bin/ksh/jobs.c
===================================================================
RCS file: /d/cvs/src/bin/ksh/jobs.c,v
retrieving revision 1.61
diff -u -p -r1.61 jobs.c
--- bin/ksh/jobs.c      28 Jun 2019 13:34:59 -0000      1.61
+++ bin/ksh/jobs.c      6 Jul 2020 18:10:43 -0000
@@ -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, volatil
                 */
                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)) {
Index: bin/ksh/ksh.1
===================================================================
RCS file: /d/cvs/src/bin/ksh/ksh.1,v
retrieving revision 1.208
diff -u -p -r1.208 ksh.1
--- bin/ksh/ksh.1       26 Nov 2019 22:49:01 -0000      1.208
+++ bin/ksh/ksh.1       6 Jul 2020 16:04:34 -0000
@@ -361,7 +361,9 @@ token to form pipelines, in which the st
 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
Index: bin/ksh/misc.c
===================================================================
RCS file: /d/cvs/src/bin/ksh/misc.c,v
retrieving revision 1.73
diff -u -p -r1.73 misc.c
--- bin/ksh/misc.c      28 Jun 2019 13:34:59 -0000      1.73
+++ bin/ksh/misc.c      6 Jul 2020 16:04:34 -0000
@@ -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 },
Index: bin/ksh/sh.h
===================================================================
RCS file: /d/cvs/src/bin/ksh/sh.h,v
retrieving revision 1.75
diff -u -p -r1.75 sh.h
--- bin/ksh/sh.h        20 Feb 2019 23:59:17 -0000      1.75
+++ bin/ksh/sh.h        6 Jul 2020 16:13:59 -0000
@@ -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 */
Index: regress/bin/ksh/obsd-regress.t
===================================================================
RCS file: /d/cvs/src/regress/bin/ksh/obsd-regress.t,v
retrieving revision 1.11
diff -u -p -r1.11 obsd-regress.t
--- regress/bin/ksh/obsd-regress.t      22 May 2020 08:45:23 -0000      1.11
+++ regress/bin/ksh/obsd-regress.t      6 Jul 2020 20:09:34 -0000
@@ -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
+---


-- 
jca | PGP : 0x1524E7EE / 5135 92C1 AD36 5293 2BDF  DDCC 0DFA 74AE 1524 E7EE

Reply via email to