Trigger ERR trap on permanent I/O redirection failure
authorkn <kn@openbsd.org>
Mon, 10 Oct 2022 14:57:48 +0000 (14:57 +0000)
committerkn <kn@openbsd.org>
Mon, 10 Oct 2022 14:57:48 +0000 (14:57 +0000)
The following three cases behave identical in bash(1), but our ksh
(ksh93 also) fails to run the trap in the last case:

(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).

In ksh `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

Without command and redirection alone, `exec' permanently redirects I/O for
the shell itself, not executing anything;  it is the only (special) builtin
with such a special use-case, implemented as c_sh.c:c_exec().

This corner-case is overlooked in exec.c:execute() which handles iosetup()
failure for all commands, incl. builtins.

Exclude c_exec() from the rest of special builtins to ensure it runs the
ERR trap as expected:

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

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

OK millert

bin/ksh/exec.c
regress/bin/ksh/trap.t [new file with mode: 0644]

index d2c27e9..887f6f9 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: exec.c,v 1.75 2021/10/24 21:24:21 deraadt Exp $       */
+/*     $OpenBSD: exec.c,v 1.76 2022/10/10 14:57:48 kn Exp $    */
 
 /*
  * execute command tree
@@ -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. */
diff --git a/regress/bin/ksh/trap.t b/regress/bin/ksh/trap.t
new file mode 100644 (file)
index 0000000..624f37a
--- /dev/null
@@ -0,0 +1,49 @@
+#      $OpenBSD: trap.t,v 1.1 2022/10/10 14:57:48 kn Exp $
+
+#
+# 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
+---