Re: eval doesn't close file descriptor?

2013-02-13 Thread Matei David
Another thing I tried was to open a fd with <() and use it later in a shell
function. Surprisingly, the fd disappears (is closed) if the shell executes
something unrelated in subshell:

$ bash -c 'xxx () { echo "got arg: $1" >&2; ls -gG /proc/$BASHPID/fd >&2;
(echo "subprocess" >&2); ls -gG /proc/$BASHPID/fd >&2; cat $1; }; . lfd.sh;
xxx <(echo "hi")'
got arg: /dev/fd/63
total 0
lrwx-- 1 64 Feb 13 12:28 0 -> /dev/pts/9
lrwx-- 1 64 Feb 13 12:28 1 -> /dev/pts/9
lrwx-- 1 64 Feb 13 12:28 2 -> /dev/pts/9
lr-x-- 1 64 Feb 13 12:28 63 -> pipe:[5474849]
lr-x-- 1 64 Feb 13 12:28 8 -> /proc/4520/auxv
subprocess
total 0
lrwx-- 1 64 Feb 13 12:28 0 -> /dev/pts/9
lrwx-- 1 64 Feb 13 12:28 1 -> /dev/pts/9
lrwx-- 1 64 Feb 13 12:28 2 -> /dev/pts/9
lr-x-- 1 64 Feb 13 12:28 8 -> /proc/4520/auxv
cat: /dev/fd/63: No such file or directory
$

Note how 63 is open before '(echo)' and closed after. Is this expected?


On Wed, Feb 13, 2013 at 12:06 PM, Matei David  wrote:

> Thank you for the explanation.
>
>
> On Tue, Feb 12, 2013 at 8:32 PM, Chet Ramey  wrote:
>
>> On 2/12/13 11:40 AM, Pierre Gaston wrote:
>> > On Tue, Feb 12, 2013 at 6:07 PM, Matei David 
>> wrote:
>> >
>> >> Ok, but I see the same behaviour when eval runs in a subshell:
>> >>
>> >> $ bash -c 'llfd () { echo "pid:$BASHPID" >&2; ls -l /proc/$BASHPID/fd/
>> >>> &2; }; x=3; eval "exec $x>/dev/null"; llfd; echo | eval "llfd $x>&-"'
>> >> [same output, fd 10 open, pointing to /dev/null, even though it's a
>> >> subshell]
>> >>
>> >
>> > eval runs in a subshell, but it's the same thing inside this subshell.
>> > eg you could have: echo | { eval "llfd "$x>&-"; echo blah >&3; }
>> >
>> > Bash could optimize this once it realizes there's only one command, but
>> > it's probably not that simple to implement.
>>
>> The basic flow is like this for any builtin command or shell function that
>> has a redirection (let's choose 'llfd 3>&-').
>>
>> 1.  The redirection is performed in the current shell, noting that it
>> should be `undoable'.  That takes three steps:
>>
>> 1a. In this case, since fd 3 is in use, we dup it (to fd 10) and mark fd
>> 10 as close-on-exec.  We add a separate redirection to an internal
>> list  that basically says "close fd 10".  Then we add another
>> redirection to the front of the same internal list that says "dup fd
>> 10 back to fd 3".  Let's call this list "redirection_undo_list".  We
>> will use it to restore the original state after the builtin or
>> function completes.
>>
>> 1b. Take the first redirection from step 1a and add it to a separate
>> internal list that will clean up internal redirections in the case
>> that exec causes the redirections to be preserved, and not undone.
>> Let's call this list "exec_redirection_undo_list".
>>
>> 1c. Perform the redirection.  Here, that means close fd 3.
>>
>> [perform step 1 for each redirection associated with the command]
>>
>> 2.  If we're running the exec builtin, throw away the list from 1a.  If
>> we're not running the exec builtin, throw away the list from 1b.  Save
>> a handle to the list we didn't discard.
>>
>> 3.  Run the function or builtin.
>>
>> 4.  Take the list saved in step 2 and perform the redirections to
>> restore the previous state.  Here, that means we dup fd 10 back to fd
>> 3, then close fd 10.
>>
>> If you look at the steps, it should be clear why fd 10 is still open when
>> llfd executes.
>>
>> Bash `cheats' when running builtins or shell functions in pipelines or
>> other subshells.  It knows it's already going to be in a child process
>> when it performs the redirections, so it doesn't bother setting up the
>> structures to undo them.
>>
>> Chet
>>
>> --
>> ``The lyf so short, the craft so long to lerne.'' - Chaucer
>>  ``Ars longa, vita brevis'' - Hippocrates
>> Chet Ramey, ITS, CWRUc...@case.edu
>> http://cnswww.cns.cwru.edu/~chet/
>>
>
>


Re: eval doesn't close file descriptor?

2013-02-13 Thread Matei David
Thank you for the explanation.


On Tue, Feb 12, 2013 at 8:32 PM, Chet Ramey  wrote:

> On 2/12/13 11:40 AM, Pierre Gaston wrote:
> > On Tue, Feb 12, 2013 at 6:07 PM, Matei David 
> wrote:
> >
> >> Ok, but I see the same behaviour when eval runs in a subshell:
> >>
> >> $ bash -c 'llfd () { echo "pid:$BASHPID" >&2; ls -l /proc/$BASHPID/fd/
> >>> &2; }; x=3; eval "exec $x>/dev/null"; llfd; echo | eval "llfd $x>&-"'
> >> [same output, fd 10 open, pointing to /dev/null, even though it's a
> >> subshell]
> >>
> >
> > eval runs in a subshell, but it's the same thing inside this subshell.
> > eg you could have: echo | { eval "llfd "$x>&-"; echo blah >&3; }
> >
> > Bash could optimize this once it realizes there's only one command, but
> > it's probably not that simple to implement.
>
> The basic flow is like this for any builtin command or shell function that
> has a redirection (let's choose 'llfd 3>&-').
>
> 1.  The redirection is performed in the current shell, noting that it
> should be `undoable'.  That takes three steps:
>
> 1a. In this case, since fd 3 is in use, we dup it (to fd 10) and mark fd
> 10 as close-on-exec.  We add a separate redirection to an internal
> list  that basically says "close fd 10".  Then we add another
> redirection to the front of the same internal list that says "dup fd
> 10 back to fd 3".  Let's call this list "redirection_undo_list".  We
> will use it to restore the original state after the builtin or
> function completes.
>
> 1b. Take the first redirection from step 1a and add it to a separate
> internal list that will clean up internal redirections in the case
> that exec causes the redirections to be preserved, and not undone.
> Let's call this list "exec_redirection_undo_list".
>
> 1c. Perform the redirection.  Here, that means close fd 3.
>
> [perform step 1 for each redirection associated with the command]
>
> 2.  If we're running the exec builtin, throw away the list from 1a.  If
> we're not running the exec builtin, throw away the list from 1b.  Save
> a handle to the list we didn't discard.
>
> 3.  Run the function or builtin.
>
> 4.  Take the list saved in step 2 and perform the redirections to
> restore the previous state.  Here, that means we dup fd 10 back to fd
> 3, then close fd 10.
>
> If you look at the steps, it should be clear why fd 10 is still open when
> llfd executes.
>
> Bash `cheats' when running builtins or shell functions in pipelines or
> other subshells.  It knows it's already going to be in a child process
> when it performs the redirections, so it doesn't bother setting up the
> structures to undo them.
>
> Chet
>
> --
> ``The lyf so short, the craft so long to lerne.'' - Chaucer
>  ``Ars longa, vita brevis'' - Hippocrates
> Chet Ramey, ITS, CWRUc...@case.edu
> http://cnswww.cns.cwru.edu/~chet/
>


Re: eval doesn't close file descriptor?

2013-02-12 Thread Matei David
Hi Chet,

Conceptually, the availability of a single flag "close-on-exec" (BTW, is
there a way to check those flags in bash or using /proc?) should create
only 2, not 3 types of file descriptors- that's what I find confusing. Does
subprocess creation as in '(llfd)' entail an execve() call? My guess is
that no, because the "extended environment" like arrays and function
definitions would not survive it, so you'd have to copy them by hand.
Either way:

If subprocess creation DOES NOT perform execve(), then I don't understand
fd's of type 2, like 60&63: they should continue to exist in a subshell.
If subprocess creation DOES perform execve(), then I don't understand fd's
of type 3: they should not exist in a subshell.

My best guess here is that type 2 is non-standard? Meaning that they are
closed by bash during subprocess creation, even though there is no
execve()? I only came across those when trying to use coproc... I wonder
why this decision was made.


Generally speaking, it took me quite some time to figure out how to
properly create a "double pipe" without using any intermediate files or
fifos. The concept is very easy: in -> tee -> two parallel, independent
pipes -> join -> out. A first complication is the limited pipe capacity,
and the possibility of one side getting stuck if the other stops pulling in
data. I then wrote a cat-like program which doesn't block on stdout, but
keeps reading stdin, buffering in as much data as needed. I used it at the
very end of either pipe. But then I had to create the actual processes, and
then I stumbled upon all these issues with coproc and file descriptors. You
leave the wrong one open and the thing gets stuck... I wish there was a
howto on this subject.

Thanks,
M




On Tue, Feb 12, 2013 at 2:50 PM, Chet Ramey  wrote:

> On 2/12/13 2:07 PM, Matei David wrote:
>
> > ... there seem to be not 2 but 3(!) types of file descriptors:
> > 1. fds which are copied across both subshells and exec; like 4
> > 2. fds which are not copied across subshells; like 60&63
> > 3. fds which are copied across subshells, but not exec; like 10
> >
> > I knew about types 1&2, but not about type 3. Apparently with your first
> > suggestion, fd 10 is created and survives a subshell creation. Is this
> > correct??
>
> Yes, file descriptors used to save the state of other file descriptors
> are set close-on-exec.  `man fcntl' for a description.
>
> Chet
>
> --
> ``The lyf so short, the craft so long to lerne.'' - Chaucer
>  ``Ars longa, vita brevis'' - Hippocrates
> Chet Ramey, ITS, CWRUc...@case.edu
> http://cnswww.cns.cwru.edu/~chet/
>


Re: eval doesn't close file descriptor?

2013-02-12 Thread Matei David
So in other words, you're saying I should use '... | eval "exec $x>&-;
llfd"' instead of '... | eval "llfd $x>&-"'. This way the subshell won't be
assuming I might use $x later. That works, but I still find it
counterintuitive that with the original syntax the subshell doesn't realize
there's nothing left to execute after $x>&-.

Also, I tried your other suggestions. The second one 'llfd () { bash -c
 }' works, but the other 'llfd () ( ... )' doesn't! I tried to
understand why...

Looking at this:
$ bash -c 'llfd () { echo "pid:$BASHPID" >&2; ls -gG /proc/$BASHPID/fd >&2;
}; f () { llfd; (llfd); bash -c "echo pid:\$\$ >&2; ls -gG /proc/\$\$/fd
>&2"; }; x=3; exec 4>/tmp/fd_4; coproc cat; eval "exec $x>/tmp/fd_3"; llfd;
echo | eval "f $x>&-"'
pid:14920
total 0
lrwx-- 1 64 Feb 12 14:03 0 -> /dev/pts/2
lrwx-- 1 64 Feb 12 14:03 1 -> /dev/pts/2
lrwx-- 1 64 Feb 12 14:03 2 -> /dev/pts/2
l-wx-- 1 64 Feb 12 14:03 3 -> /tmp/fd_3
l-wx-- 1 64 Feb 12 14:03 4 -> /tmp/fd_4
l-wx-- 1 64 Feb 12 14:03 60 -> pipe:[5010928]
lr-x-- 1 64 Feb 12 14:03 63 -> pipe:[5010927]
lr-x-- 1 64 Feb 12 14:03 8 -> /proc/4520/auxv
pid:14924
total 0
lr-x-- 1 64 Feb 12 14:03 0 -> pipe:[5007145]
lrwx-- 1 64 Feb 12 14:03 1 -> /dev/pts/2
l-wx-- 1 64 Feb 12 14:03 10 -> /tmp/fd_3
lrwx-- 1 64 Feb 12 14:03 2 -> /dev/pts/2
l-wx-- 1 64 Feb 12 14:03 4 -> /tmp/fd_4
lr-x-- 1 64 Feb 12 14:03 8 -> /proc/4520/auxv
pid:14926
total 0
lr-x-- 1 64 Feb 12 14:03 0 -> pipe:[5007145]
lrwx-- 1 64 Feb 12 14:03 1 -> /dev/pts/2
l-wx-- 1 64 Feb 12 14:03 10 -> /tmp/fd_3
lrwx-- 1 64 Feb 12 14:03 2 -> /dev/pts/2
l-wx-- 1 64 Feb 12 14:03 4 -> /tmp/fd_4
lr-x-- 1 64 Feb 12 14:03 8 -> /proc/4520/auxv
pid:14928
total 0
lr-x-- 1 64 Feb 12 14:03 0 -> pipe:[5007145]
lrwx-- 1 64 Feb 12 14:03 1 -> /dev/pts/2
lrwx-- 1 64 Feb 12 14:03 2 -> /dev/pts/2
l-wx-- 1 64 Feb 12 14:03 4 -> /tmp/fd_4
lr-x-- 1 64 Feb 12 14:03 8 -> /proc/4520/auxv


... there seem to be not 2 but 3(!) types of file descriptors:
1. fds which are copied across both subshells and exec; like 4
2. fds which are not copied across subshells; like 60&63
3. fds which are copied across subshells, but not exec; like 10

I knew about types 1&2, but not about type 3. Apparently with your first
suggestion, fd 10 is created and survives a subshell creation. Is this
correct??


On Tue, Feb 12, 2013 at 11:40 AM, Pierre Gaston wrote:

>
>
> On Tue, Feb 12, 2013 at 6:07 PM, Matei David wrote:
>
>> Ok, but I see the same behaviour when eval runs in a subshell:
>>
>> $ bash -c 'llfd () { echo "pid:$BASHPID" >&2; ls -l /proc/$BASHPID/fd/
>> >&2; }; x=3; eval "exec $x>/dev/null"; llfd; echo | eval "llfd $x>&-"'
>> [same output, fd 10 open, pointing to /dev/null, even though it's a
>> subshell]
>>
>
> eval runs in a subshell, but it's the same thing inside this subshell.
> eg you could have: echo | { eval "llfd "$x>&-"; echo blah >&3; }
>
> Bash could optimize this once it realizes there's only one command, but
> it's probably not that simple to implement.
>
> Try with a function that spawns a subshell eg:
> llfd () (  echo "pid:$BASHPID" >&2; ls -l /proc/$BASHPID/fd/ >&2; )
>
> or llfd () { bash -c 'ls -l /proc/$$/fd' ; }
>
>
>


Re: eval doesn't close file descriptor?

2013-02-12 Thread Matei David
Wow, thanks, I didn't know that. So this syntax achieves a bit more than I
asked for- it allows you to get a new unused file descriptor, right? It
seems that the only useful way to use a non-closing form (>&-, <&-) is with
exec, as in 'exec {new_fd}>&2'. (Why would I want the fd in a variable
otherwise.)

Too bad the "natural" syntax 'llfd $x>&-' doesn't work, but I guess this
will do.


On Tue, Feb 12, 2013 at 11:12 AM, Greg Wooledge  wrote:

> On Tue, Feb 12, 2013 at 11:07:06AM -0500, Matei David wrote:
> > On a different but related note, I hate having to do eval to manipulate
> an
> > fd stored in a variable. Why doesn't 'llfd $x>&-' work, especially since
> > 'llfd >&$x' works just fine... so by the time >& is handled, the variable
> > substitutions seem to be done already.
>
>   Each redirection that may be preceded by a file descriptor number may
>   instead be preceded by a word of the form {varname}.  In this case,
>   for each redirection operator except >&- and <&-, the shell will
>   allocate a file descriptor greater than 10 and assign it to varname.
>   If >&- or <&- is preceded by {varname}, the value of varname defines
>   the file descriptor to close.
>
> This was added in Bash 4.1.
>


Re: eval doesn't close file descriptor?

2013-02-12 Thread Matei David
Ok, but I see the same behaviour when eval runs in a subshell:

$ bash -c 'llfd () { echo "pid:$BASHPID" >&2; ls -l /proc/$BASHPID/fd/ >&2;
}; x=3; eval "exec $x>/dev/null"; llfd; echo | eval "llfd $x>&-"'
[same output, fd 10 open, pointing to /dev/null, even though it's a
subshell]

$ bash -c 'llfd () { echo "pid:$BASHPID" >&2; ls -l /proc/$BASHPID/fd/ >&2;
}; x=3; eval "exec $x>/dev/null"; llfd; echo | llfd 3>&-'
[not the same output; no fds pointing to /dev/null, as expected]

When eval is run by the main shell and I want $x closed, I can just do
'eval "exec $x>&-"'. However, I cannot do that with eval runs in a
subshell. In my script I needed $x open for other processes in that
pipeline.


On a different but related note, I hate having to do eval to manipulate an
fd stored in a variable. Why doesn't 'llfd $x>&-' work, especially since
'llfd >&$x' works just fine... so by the time >& is handled, the variable
substitutions seem to be done already.


On Tue, Feb 12, 2013 at 2:13 AM, Pierre Gaston wrote:

>
>
> On Tue, Feb 12, 2013 at 1:54 AM,  wrote:
>
>> With the script below, I'd expect any fd pointing to /dev/null to be
>> closed when the second llfd() is executed. Surprisingly, fd 3 is closed,
>> but fd 10 is now open, pointing to /dev/null, as if eval copied it instead
>> of closing it. Is this a bug?
>>
>> Thanks,
>> M
>>
>>
>> $ bash -c 'llfd () { ls -l /proc/$BASHPID/fd/; }; x=3; eval "exec
>> $x>/dev/null"; llfd; eval "llfd $x>&-"'
>> total 0
>> lrwx-- 1 matei matei 64 Feb 11 18:36 0 -> /dev/pts/2
>> lrwx-- 1 matei matei 64 Feb 11 18:36 1 -> /dev/pts/2
>> lrwx-- 1 matei matei 64 Feb 11 18:36 2 -> /dev/pts/2
>> l-wx-- 1 matei matei 64 Feb 11 18:36 3 -> /dev/null
>> lr-x-- 1 matei matei 64 Feb 11 18:36 8 -> /proc/4520/auxv
>> total 0
>> lrwx-- 1 matei matei 64 Feb 11 18:36 0 -> /dev/pts/2
>> lrwx-- 1 matei matei 64 Feb 11 18:36 1 -> /dev/pts/2
>> l-wx-- 1 matei matei 64 Feb 11 18:36 10 -> /dev/null
>> lrwx-- 1 matei matei 64 Feb 11 18:36 2 -> /dev/pts/2
>> lr-x-- 1 matei matei 64 Feb 11 18:36 8 -> /proc/4520/auxv
>> $ bash --version
>> GNU bash, version 4.2.24(1)-release (x86_64-pc-linux-gnu)
>> Copyright (C) 2011 Free Software Foundation, Inc.
>> License GPLv3+: GNU GPL version 3 or later <
>> http://gnu.org/licenses/gpl.html>
>>
>> This is free software; you are free to change and redistribute it.
>> There is NO WARRANTY, to the extent permitted by law.
>> $
>>
>
> Note that the same happens without using eval:
> $ llfd 3>&-
> total 0
> lrwx-- 1 pgas pgas 64 Feb 12 08:00 0 -> /dev/pts/0
> lrwx-- 1 pgas pgas 64 Feb 12 08:00 1 -> /dev/pts/0
> l-wx-- 1 pgas pgas 64 Feb 12 08:00 10 -> /dev/null
> lrwx-- 1 pgas pgas 64 Feb 12 08:00 2 -> /dev/pts/0
> lrwx-- 1 pgas pgas 64 Feb 12 08:00 255 -> /dev/pts/0
>
> But you need to consider what process you are examining, you use a
> function and you examine the file descriptors of the process where this
> function runs.
>
> A function runs in the same process as the parent shell, if it simply
> closes 3 then there will be no more fd opened on >/dev/null in the parent
> shell when the function returns
> So what bash does is a little juggling with the file descriptors, moving 3
> temporarily to be able to restore it.
>
>
>
>
>
>
>
>


eval doesn't close file descriptor?

2013-02-11 Thread matei . david
With the script below, I'd expect any fd pointing to /dev/null to be closed 
when the second llfd() is executed. Surprisingly, fd 3 is closed, but fd 10 is 
now open, pointing to /dev/null, as if eval copied it instead of closing it. Is 
this a bug?

Thanks,
M


$ bash -c 'llfd () { ls -l /proc/$BASHPID/fd/; }; x=3; eval "exec 
$x>/dev/null"; llfd; eval "llfd $x>&-"'
total 0
lrwx-- 1 matei matei 64 Feb 11 18:36 0 -> /dev/pts/2
lrwx-- 1 matei matei 64 Feb 11 18:36 1 -> /dev/pts/2
lrwx-- 1 matei matei 64 Feb 11 18:36 2 -> /dev/pts/2
l-wx-- 1 matei matei 64 Feb 11 18:36 3 -> /dev/null
lr-x-- 1 matei matei 64 Feb 11 18:36 8 -> /proc/4520/auxv
total 0
lrwx-- 1 matei matei 64 Feb 11 18:36 0 -> /dev/pts/2
lrwx-- 1 matei matei 64 Feb 11 18:36 1 -> /dev/pts/2
l-wx-- 1 matei matei 64 Feb 11 18:36 10 -> /dev/null
lrwx-- 1 matei matei 64 Feb 11 18:36 2 -> /dev/pts/2
lr-x-- 1 matei matei 64 Feb 11 18:36 8 -> /proc/4520/auxv
$ bash --version
GNU bash, version 4.2.24(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ 


Re: why does errexit exist in its current utterly useless form?

2012-12-15 Thread matei . david
On Saturday, December 15, 2012 5:23:04 PM UTC-5, Chet Ramey wrote:
> There is already a proposal for a new option similar to what you want; you
> can read the discussion at
> 
> http://austingroupbugs.net/view.php?id=537

Thank you for all the references, I'll have a look!


Re: why does errexit exist in its current utterly useless form?

2012-12-15 Thread matei . david
On Friday, December 14, 2012 6:23:41 PM UTC-5, Eric Blake wrote:
> Short answer: historical compatibility.  'set -e' has been specified to
> behave the way it did 30 years ago in one reference implementation, and
> while you can argue till you are blue in the face that the reference
> implementation is not intuitive, you have to at least appreciate that
> having a standard means you are likely to get the same (non-intuitive)
> behavior among several competing shell implementations that all strive
> to conform to POSIX.

Thanks for the reply. I understand the benefits of a standard. In this case, it 
seems to me that the problem we're talking about- stopping a script as soon as 
a command returns an unexpected non-0 code- is a very basic feature that many 
could benefit from, if implemented right. I'm trying to understand whether or 
not fixing this problem requires changing the standard or not.

My question 5 is about whether the standard itself requires that subsequent 
attempts to set errexit should be ignored, even assuming that errexit should be 
turned off once in a while for the sake of the standard. The alternative is 
that this is just a historical decision of bash that could be mended without 
breaking compliance.

If indeed the standard requires all further attempts to set errexit be ignored 
(which I think is a terrible idea), wouldn't it be a good idea to provide in 
bash another option doing the same thing, but correctly? Something like set -o 
strong_errexit, something that anybody writing a new script can choose to use 
or not, of course understanding that it is a bashism, but the right kind of 
bashism.

Also, my questions 1-3 relate to the current implementation...


why does errexit exist in its current utterly useless form?

2012-12-14 Thread matei . david
I recently worked on a project involving many bash scripts, and I've been 
trying to use errexit to stop various parts of a script as soon as anything 
returns a non-0 return code. As it turns out, this is an utterly useless 
endeavour. In asking this question on this forum, I hope somebody out there can 
help me, who understands bash, POSIX, and why decisions were made to arrive at 
the current situation.

To recapitulate, errexit is turned on by "set -e" or "set -o errexit". This is 
what TFM says about it:

"Exit immediately if a pipeline (see Pipelines), which may consist of a single 
simple command (see Simple Commands), a subshell command enclosed in 
parentheses (see Command Grouping), or one of the commands executed as part of 
a command list enclosed by braces (see Command Grouping) returns a non-zero 
status. The shell does not exit if the command that fails is part of the 
command list immediately following a while or until keyword, part of the test 
in an if statement, part of any command executed in a && or || list except the 
command following the final && or ||, any command in a pipeline but the last, 
or if the command’s return status is being inverted with !. A trap on ERR, if 
set, is executed before the shell exits. This option applies to the shell 
environment and each subshell environment separately (see Command Execution 
Environment), and may cause subshells to exit before executing all the commands 
in the subshell."

Let's leave pipelines aside, because that adds more complexity to an already 
messy problem. So we're talking just simple commands.

My initial gripe about errexit (and its man page description) is that the 
following doesn't behave as a newbie would expect it to:

set -e
f() {
  false
  echo "NO!!"
}
f || { echo "f failed" >&2; exit 1; }

Above, "false" usually stands for some complicated command, or part of a 
sequence of many commands, and "echo NO!!" stands for a statement releasing a 
lock, for instance. The newbie assumes that the lock won't be released unless 
executing f goes well. Moreover, the newbie likes many error messages, hence 
the extra message in the main script.

Running it, you get:

NO!!

First of all, f is called as the LHS of ||, so we don't want the entire shell 
to crash if f returns non-0. That much, a not entirely dumb newbie can 
understand. But, lo and behold, NO!! gets printed. Do you see this explained in 
TFM, because I don't.

Question 1: Is this a bug in the manual itself?

As the hours of debugging pass by, the newbie learns about shells and subshells 
and subprocesses and what not. Also, apparently that one can see the current 
shell settings with $- or $SHELLOPTS. So the newbie changes f to:

f() {
  echo $-
  false
  echo "NO!!"
}

You get:

ehB
NO!!

This is now getting confusing: errexit seems to be active as bash executes f, 
but still it doesn't stop.

Question 2: Is this a bug in how $- is maintained by bash?

Next, the newbie thinks, oh, I'll just set errexit again inside f. How about:

f() {
  set -e
  echo $-
  false
  echo "NO!!"
}

You get:

ehB
NO!!

At this point, the newbie thinks, perhaps errexit isn't working after all.

Question 3: Under the current design (which I consider flawed), why doesn't 
bash at least print a warning that errexit is not active, when the user tries 
to set it?

As even more hours pass by, the newbie learns things about various other 
shells, POSIX mode, standards, etc. Useful things, but arguably useless for the 
task at hand. So, from what I the newbie gathered so far...

One can work around this by using && to connect all statements in f, or using 
"|| return 1" after each of them. This is ok if f is 2 lines, not if it's 200.

I also learned one can actually write a tiny function which tests if the ERR 
signal is active, and if it is not, to executed the invoking function (f) in a 
different shell, passing the entire environment, including function defs, with 
typeset. This is really awkward, but possible. However, it only works for 
functions, not for command lists run in a subshell, as in:

( false; echo "NO!!" ) || { echo "failed" >&2; exit 1; }

The common suggestion I see on various forums is- don't use errexit. I now 
understand this from a user perspective, and that's why I call errexit "utterly 
useless" in the subject. But, if I may ask, why is bash in this position?

Question 4: Back to the original f, why did bash (or even POSIX) decide to 
protect the "false" statement? Protecting f is clearly necessary, for otherwise 
|| would be useless. But why the "false"?

Question 4a (perhaps the same): TFM says: "the shell does not exit if the 
command that fails is part of the command list immediately following a while". 
Why protect commands in such a list other than the last?

And independent of the question(s) 4, the last one:

Question 5: Even assuming bash/POSIX decides to protect lists of commands where 
only the last is tested, why does bash entirely disable the errexit option?