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 +---