Failure to permanently redirect I/O for the shell itself, i.e. when no
command is executed, must trigger the ERR trap like any other failure,
but that is currently not happening:

(non-zero exit code is trigger, no redirection)
        $ ksh -c 'trap "echo ERR" ERR ; false'
        ERR

(failed redirection is trigger, 'echo' was not executed)
        $ ksh -c 'trap "echo ERR" ERR ; echo >/'
        ksh: cannot create /: Is a directory
        ERR

(failed redirection, no execution, trap was NOT triggered)
        $ ksh -c 'trap "echo ERR" ERR ; exec >/'
        ksh: cannot create /: Is a directory


bash(1) prints "ERR" in all three cases, as expected.
ksh93 behaves like our ksh(1).

>From the manual:
        exec [command [arg ...]]
                The command is executed without forking, replacing the shell
                process.

                If no command is given except for I/O redirection, the I/O
                redirection is permanent and the shell is not replaced.  Any 
file
                descriptors greater than 2 which are opened or dup(2)'d in this
                way are not made available to other executed commands (i.e.
                commands that are not built-in to the shell).  Note that the
                Bourne shell differs here; it does pass these file descriptors
                on.

`exec' is a builtin (CSHELL), but also special (SPEC_BI):
        $ type alias
        alias is a shell builtin
        $ type exec
        exec is a special shell builtin

But compared to other special builtins (e.g. `eval'), it is the only one
implementing permanent I/O redirection.

This especially special case seems to be overlooked in exec.c:execute()
where iosetup() failure is handled for all types.

`exec' without command (and arguments) but redirections alone has its
own c_sh.c:c_exec() function, which must which trap handling and such
but currently aborts in the too broad CSHELL && SPEC_BI case.

Account for this to make permanent redirection failure trigger the ERR
trap:

        $ ./obj/ksh -c 'trap "echo ERR" ERR ; exec >/'
        ksh: cannot create /: Is a directory
        ERR

Also add three new regress cases covering this;  others keep passing.

I haven't put this through a bulk build or any other broader testing
besides ksh regress and the actual script I was working on (which now
behaves as expected).

Feedback?
Did I miss anything?


Index: bin/ksh/exec.c
===================================================================
RCS file: /cvs/src/bin/ksh/exec.c,v
retrieving revision 1.75
diff -u -p -r1.75 exec.c
--- bin/ksh/exec.c      24 Oct 2021 21:24:21 -0000      1.75
+++ bin/ksh/exec.c      8 Oct 2022 15:14:20 -0000
@@ -114,10 +114,12 @@ execute(struct op *volatile t,
                for (iowp = t->ioact; *iowp != NULL; iowp++) {
                        if (iosetup(*iowp, tp) < 0) {
                                exstat = rv = 1;
-                               /* Redirection failures for special commands
+                               /* Except in the permanent case (exec 2>afile),
+                                * redirection failures for special commands
                                 * cause (non-interactive) shell to exit.
                                 */
-                               if (tp && tp->type == CSHELL &&
+                               if (tp && tp->val.f != c_exec &&
+                                   tp->type == CSHELL &&
                                    (tp->flag & SPEC_BI))
                                        errorf(NULL);
                                /* Deal with FERREXIT, quitenv(), etc. */
Index: regress/bin/ksh/trap.t
===================================================================
RCS file: regress/bin/ksh/trap.t
diff -N regress/bin/ksh/trap.t
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ regress/bin/ksh/trap.t      8 Oct 2022 14:32:03 -0000
@@ -0,0 +1,49 @@
+#      $OpenBSD: $
+
+#
+# Check that I/O redirection failure triggers the ERR trap.
+# stderr patterns are minimal to match all of bash, ksh and ksh93.
+# Try writing the root directory to guarantee EISDIR.
+#
+
+name: failed-redirect-triggers-ERR-restricted
+description:
+       Check that restricted mode prevents valid redirections that may write.
+arguments: !-r!
+stdin:
+       trap 'echo ERR' ERR
+       true >/dev/null
+expected-stdout:
+       ERR
+expected-stderr-pattern:
+       /restricted/
+expected-exit: e != 0
+---
+
+
+name: failed-redirect-triggers-ERR-command
+description:
+       Redirect standard output for a single command.
+stdin:
+       trap 'echo ERR' ERR
+       true >/
+expected-stdout:
+       ERR
+expected-stderr-pattern:
+       /Is a directory/
+expected-exit: e != 0
+---
+
+
+name: failed-redirect-triggers-ERR-permanent
+description:
+       Permanently redirect standard output of the shell without execution.
+stdin:
+       trap 'echo ERR' ERR
+       exec >/
+expected-stdout:
+       ERR
+expected-stderr-pattern:
+       /Is a directory/
+expected-exit: e != 0
+---

Reply via email to