diff mbox series

[v3,2/2] Use O_IGNORE_CTTY where appropriate

Message ID 20230604204258.2026816-3-bugaevc@gmail.com
State New
Headers show
Series O_IGNORE_CTTY everywhere | expand

Commit Message

Sergey Bugaev June 4, 2023, 8:42 p.m. UTC
* getpt, openpty: Opening an unused pty, which can't be our ctty
* shm_open, sem_open: These don't work with ttys
* opendir: Directories are unlikely to be ttys

Signed-off-by: Sergey Bugaev <bugaevc@gmail.com>
---
 catgets/open_catalog.c      | 4 ++--
 elf/dl-load.c               | 2 +-
 elf/dl-misc.c               | 2 +-
 elf/dl-profile.c            | 2 +-
 gmon/gmon.c                 | 4 ++--
 iconv/gconv_cache.c         | 3 ++-
 locale/loadarchive.c        | 7 ++++---
 locale/loadlocale.c         | 4 ++--
 login/openpty.c             | 2 +-
 login/utmp_file.c           | 7 ++++---
 misc/daemon.c               | 2 +-
 nss/nss_db/db-open.c        | 3 ++-
 rt/shm_open.c               | 2 +-
 shadow/lckpwdf.c            | 2 +-
 sysdeps/mach/hurd/opendir.c | 2 +-
 sysdeps/pthread/sem_open.c  | 4 ++--
 sysdeps/unix/bsd/getpt.c    | 4 ++--
 17 files changed, 30 insertions(+), 26 deletions(-)

Comments

Florian Weimer June 5, 2023, 9:28 a.m. UTC | #1
* Sergey Bugaev:

> * getpt, openpty: Opening an unused pty, which can't be our ctty

Please add this as a comment to the open operation.

> * shm_open, sem_open: These don't work with ttys
> * opendir: Directories are unlikely to be ttys

I scrolled through the patch, and it seems to me that all these open
calls could use O_NOCTTY.  Do you still need a flag because the
performance optimization is not about changing the controlling terminal,
but about detecting the controlling terminal as such?

Thanks,
Florian
Sergey Bugaev June 5, 2023, 11:55 a.m. UTC | #2
Hello,

On Mon, Jun 5, 2023 at 12:28 PM Florian Weimer <fweimer@redhat.com> wrote:
> > * shm_open, sem_open: These don't work with ttys
> > * opendir: Directories are unlikely to be ttys
>
> I scrolled through the patch, and it seems to me that all these open
> calls could use O_NOCTTY.

Perhaps they could use O_NOCTTY indeed, though that would be a fix /
change for correctness (on non-Hurd) and not a performance
optimization. Would you prefer me to also add O_NOCTTY to all these
calls? Or maybe I should even define O_IGNORE_CTTY to O_NOCTTY
(instead of 0) on non-Hurd?

Please note that O_NOCTTY is 0 on the Hurd, and opening a tty never
makes it our ctty; you can only gain a ctty by explicitly requesting
that (using TCIOSCTTY). An alternative way to think of this: O_NOCTTY
is always in effect on the Hurd.

> Do you still need a flag because the
> performance optimization is not about changing the controlling terminal,
> but about detecting the controlling terminal as such?

Yes, exactly. The point of this patchset is skipping runtime ctty
detection when we statically know for sure that the file being opened
can not be our current ctty. This is not supposed to alter observable
behavior, i.e. code that _can_ reasonably be reopening the current
ctty should still work the same. But code that we know does not reopen
our ctty should work faster, by skipping the check.

Please also see the v1 cover letter [0].

[0]: https://sourceware.org/pipermail/libc-alpha/2023-April/147355.html


It also helps to look with rpctrace at what this changes in practice.
In case you don't readily have access to a Hurd system, I've uploaded
the rpctrace of uname on Debian GNU/Hurd here: [1]. Unfortunately
rpctrace output format is rather messy compared to strace; I've had a
branch that attempted to improve that (along with many other fixes to
rpctrace), but it's stalled/lost, so the current format will have to
do for now.

[1]: https://paste.gg/p/anonymous/68f3b1efa3384bf5807affde8fb83d83

You you grep / Ctrl-F for 'ctty', you can see there:

  15<--28(pid1601)->term_getctty () = 0    4<--34(pid1601)
task13(pid1601)->mach_port_deallocate (pn{ 10}) = 0
  15<--28(pid1601)->term_open_ctty (0 0) = 0x40000016 (Invalid argument)
  15<--28(pid1601)->term_getctty () = 0    4<--34(pid1601)
task13(pid1601)->mach_port_deallocate (pn{ 10}) = 0
  15<--28(pid1601)->term_open_ctty (0 0) = 0x40000016 (Invalid argument)
  15<--28(pid1601)->term_getctty () = 0    4<--34(pid1601)
task13(pid1601)->mach_port_deallocate (pn{ 10}) = 0
  15<--28(pid1601)->term_open_ctty (0 0) = 0x40000016 (Invalid argument)

This is the initial process startup. This is obviously broken -- it
calls term_getctty/term_open_ctty on the same port (/dev/ttyp0 --
here, "15<--28(pid1601)") three times, getting an error each time!

I've fixed the EINVALs in 346b6eab3c14ead0b716d53e2235464b822f48f2
"hurd: Run init_pids () before init_dtable ()". Also since the port
for fds 0, 1, 2 is the same, it makes sense to only do the check once,
and not once per fd -- that I have done in
e55a55acb19400a26db4e7eec6d4649e364bc8d4 "hurd: Avoid extra ctty RPCs
in init_dtable ()".

But then later you find these:

12<--31(pid1601)->dir_lookup
("usr/lib/locale/C.utf8/LC_IDENTIFICATION" 4194305 0) = 0 1 ""
45<--22(pid1601)
45<--22(pid1601)->term_getctty () = 0xfffffed1 ((ipc/mig) bad request
message ID)

12<--31(pid1601)->dir_lookup
("usr/lib/i386-gnu/gconv/gconv-modules.cache" 1 0) = 0 1 ""
45<--48(pid1601)
45<--48(pid1601)->term_getctty () = 0xfffffed1 ((ipc/mig) bad request
message ID)

(and many, many more). Fixing these is exactly what this patchset is
about: we *know* that gconv-modules.cache is not our ctty; no need to
check at runtime.

Hope this makes sense!

Sergey
Paul Eggert June 5, 2023, 8:58 p.m. UTC | #3
On 2023-06-04 13:42, Sergey Bugaev via Libc-alpha wrote:
> -  int flags = O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC;
> +  int flags = O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_IGNORE_CTTY;

Why is O_IGNORE_CTTY useful here? O_DIRECTORY means that the file cannot 
be a tty. (If GNU/Hurd is sending an extra RPC in this case, surely 
that's a performance bug that should be fixed in _hurd_port2fd or 
whatever, not in every 'open' caller.)

> -	  open_flags = O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC;
> +	  open_flags = O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC | O_IGNORE_CTTY;

Similarly, why is O_IGNORE_CTTY useful here? O_CREAT | O_EXCL means that 
the file cannot be a tty. (There are a couple of instances of this.)

> I'm still interested in whether there could be a way to pass
> O_IGNORE_CTTY when using fopen () too -- but that doesn't have to block
> this patchset either.

I suppose fopen could add a new 'T' flag, as a GNU extension, that would 
add O_IGNORE_CTTY to the open flags.
Sergey Bugaev June 6, 2023, 9:21 a.m. UTC | #4
Hello,

On Mon, Jun 5, 2023 at 11:58 PM Paul Eggert <eggert@cs.ucla.edu> wrote:
>
> On 2023-06-04 13:42, Sergey Bugaev via Libc-alpha wrote:
> > -  int flags = O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC;
> > +  int flags = O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_IGNORE_CTTY;
>
> Why is O_IGNORE_CTTY useful here? O_DIRECTORY means that the file cannot
> be a tty. (If GNU/Hurd is sending an extra RPC in this case, surely
> that's a performance bug that should be fixed in _hurd_port2fd or
> whatever, not in every 'open' caller.)
>
> > -       open_flags = O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC;
> > +       open_flags = O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC | O_IGNORE_CTTY;
>
> Similarly, why is O_IGNORE_CTTY useful here? O_CREAT | O_EXCL means that
> the file cannot be a tty. (There are a couple of instances of this.)

So what you're saying is the Hurd parts of glibc could/should try to
infer O_IGNORE_CTTY in some cases based on other flags? That's a great
point! -- especially because that would benefit other software that is
not updated to pass O_IGNORE_CTTY explicitly.

So:
- O_DIRECTORY
- O_CREAT | O_EXCL
- O_TMPFILE, too
should imply O_IGNORE_CTTY.

I'm not very sure where this should be handled: _hurd_port2fd and
_hurd_intern_fd are both public APIs, documented with

"FLAGS are as for `open'; only O_IGNORE_CTTY and O_CLOEXEC are meaningful"

so probably it's the code that calls _hurd_intern_fd that should do
the conversion; but then there are several variants of open () alone
(__libc_open, __open_nocancel, __openat, __openat_nocancel), and it
would make sense to share the logic between them. Maybe I should make
a new helper for this in fcntl-internal.h.

But also, regardless of potentially inferring O_IGNORE_CTTY from other
flags, I think it's a good practice to always pass O_IGNORE_CTTY where
it makes sense to -- a lot like it's a good practice to always pass
O_CLOEXEC. Well, in case of O_CLOEXEC it's a historical mistake that
the default is not the other way around and a flag has to be passed
explicitly to opt in, whereas O_IGNORE_CTTY is only appropriate in
some cases, and the default should indeed be doing the checks for
ctty. But when you know that you're not reopening your ctty I do think
it makes sense to pass O_IGNORE_CTTY, even if it would be inferred
otherwise.

On the other hand I'm under no illusion that I'd be able to convince
various third-party projects to use a Hurd-specific flag. The ones
that I would like to see updated most are gcc/cpp (opening headers)
and git (opening files in .git/ and the working tree) because they
both open files in bulk:

$ rpctrace gcc --version |& grep -c ctty
12
$ rpctrace gcc hello.c |& grep -c ctty
145
$ rpctrace git --version |& grep -c ctty
12
$ rpctrace git status |& grep -c ctty
158

Maybe with GCC there is a chance, considering it's a GNU project?

> > I'm still interested in whether there could be a way to pass
> > O_IGNORE_CTTY when using fopen () too -- but that doesn't have to block
> > this patchset either.
>
> I suppose fopen could add a new 'T' flag, as a GNU extension, that would
> add O_IGNORE_CTTY to the open flags.

What would be the compatibility implications of this? -- what if POSIX
later declares that 'T' must mean something else? I was thinking it
could be possible to add some character without making it a public
API/extension, and only using it inside glibc.

Sergey
Paul Eggert June 9, 2023, 6:37 p.m. UTC | #5
On 2023-06-06 02:21, Sergey Bugaev wrote:

> _hurd_port2fd and
> _hurd_intern_fd are both public APIs, documented with
> 
> "FLAGS are as for `open'; only O_IGNORE_CTTY and O_CLOEXEC are meaningful"
> 

You could change the documentation so that it now says that flags that 
imply O_IGNORE_CTTY are also meaningful. That should be fine.


> I think it's a good practice to always pass O_IGNORE_CTTY where
> it makes sense to

Does your patch do that, for every 'open'-like call?


> Maybe with GCC there is a chance, considering it's a GNU project?

Sure. I expect even 'git' will do it if you write the patch, as they 
care about performance. Also tar, coreutils, grep, and other "open files 
a lot" apps should benefit.


>> I suppose fopen could add a new 'T' flag, as a GNU extension, that would
>> add O_IGNORE_CTTY to the open flags.
> 
> What would be the compatibility implications of this? -- what if POSIX
> later declares that 'T' must mean something else?

I wouldn't worry overmuch about that. We could ask for forgiveness later.

Thinking bigger - why does Hurd mess with this stuff at all? Wouldn't it 
be better if O_IGNORE_CTTY was the default, and a different flag 
(O_PAY_ATTENTION_TO_CTTY, say :-) enables the ctty dance? POSIX would 
allow that behavior, as it does not require the controlling terminal to 
be required if O_NOCTTY is not set.
Sergey Bugaev June 9, 2023, 9:13 p.m. UTC | #6
Hello -- that is a much more positive response than I was expecting to
get! Thank you!

On Fri, Jun 9, 2023 at 9:37 PM Paul Eggert <eggert@cs.ucla.edu> wrote:
> You could change the documentation so that it now says that flags that
> imply O_IGNORE_CTTY are also meaningful. That should be fine.

Perhaps... but there's another reason I don't particularly like the
idea of doing it on that level.

_hurd_port2fd () and _hurd_intern_fd () is something that you call
once you already have an io port. O_CREAT and O_DIRECTORY and the rest
are the flags that impact how you look it up. _hurd_port2fd would have
to second-guess "this io port is said to have been opened with O_CREAT
| O_EXCL, so it can't be a ctty". It'd be better to have the caller
(open) -- that "can see" both looking the port up and interning it --
implement this bit of logic. Not that this matters for anything,
because it would still behave the same way no matter which level we
implement it at; but it seems more appropriate to me to implement it
at that level.

Samuel, what do you think?

> > I think it's a good practice to always pass O_IGNORE_CTTY where
> > it makes sense to
>
> Does your patch do that, for every 'open'-like call?

It does. I grepped glibc for open[at]64[_nocancel] and added
O_IGNORE_CTTY everywhere where it made sense, other than in
Linux-specific code (e.g. not inside sysdeps/unix/sysv/linux). I have
also found a bunch of places where O_CLOEXEC was missing, and that
lead to commit 533deafbdf189f5fbb280c28562dd43ace2f4b0f "Use O_CLOEXEC
in more places (BZ #15722)". It might be that I have missed some more
places where O_IGNORE_CTTY could be used, of course.

> > Maybe with GCC there is a chance, considering it's a GNU project?
>
> Sure. I expect even 'git' will do it if you write the patch, as they
> care about performance. Also tar, coreutils, grep, and other "open files
> a lot" apps should benefit.

Cool, so I should try writing the patches then and see what comes out of it.

> >> I suppose fopen could add a new 'T' flag, as a GNU extension, that would
> >> add O_IGNORE_CTTY to the open flags.
> >
> > What would be the compatibility implications of this? -- what if POSIX
> > later declares that 'T' must mean something else?
>
> I wouldn't worry overmuch about that. We could ask for forgiveness later.

I'm going to look into adding it into fopen then, thanks!

> Thinking bigger - why does Hurd mess with this stuff at all? Wouldn't it
> be better if O_IGNORE_CTTY was the default, and a different flag
> (O_PAY_ATTENTION_TO_CTTY, say :-) enables the ctty dance? POSIX would
> allow that behavior, as it does not require the controlling terminal to
> be required if O_NOCTTY is not set.

Sorry, I'm failing to parse/interpret that last sentence, could you
please rephrase it? Do you perhaps mean that POSIX does not require a
newly opened terminal to become your ctty even if you don't pass
O_NOCTTY?

Please note (if you haven't already) the difference between O_NOCTTY
and O_IGNORE_CTTY: O_NOCTTY is "always in effect" on the Hurd, and
defined to 0 (so I sure hope POSIX allows this). What O_IGNORE_CTTY
does is -- well, technically it just turns off one check; but if you
*don't* pass O_IGNORE_CTTY and the check succeeds and detects that the
file you're opening (or rather: the port you're interning) refers to
your ctty (that you already have set), then it does special things to
make the new fd behave like an fd to your ctty is supposed to behave
-- you e.g. get SIGINT when ^C is typed, and you get SIGTTIN / SIGTTOU
/ EBACKGROUND when trying to do I/O on the fd while in background.

So if you do pass O_IGNORE_CTTY and the file is not your ctty, you
just get a speedup. If you do pass O_IGNORE_CTTY and the file is your
ctty, you get an fd that refers to your ctty... but doesn't behave
like a ctty fd. Why you would want the latter, I have no idea (but
also it of course wasn't me who came up with O_IGNORE_CTTY, so perhaps
there is a use case).

So it is important that (re)opening /dev/tty or /dev/stdout or just
/dev/ttyp1 (by an explicit name) behaves as expected. Requiring an
explicit flag for this would be wrong, and also there'd be even less
of a chance that anyone would actually pass it in practice than there
is now with O_IGNORE_CTTY.

Sergey
Paul Eggert June 9, 2023, 9:35 p.m. UTC | #7
On 6/9/23 14:13, Sergey Bugaev wrote:
> Perhaps... but there's another reason I don't particularly like the
> idea of doing it on that level.

Yes, your points make sense. No big deal either way,


> Do you perhaps mean that POSIX does not require a
> newly opened terminal to become your ctty even if you don't pass
> O_NOCTTY?

Yes, that's right. The openat rationale mentions this topic.


> So if you do pass O_IGNORE_CTTY and the file is not your ctty, you
> just get a speedup. If you do pass O_IGNORE_CTTY and the file is your
> ctty, you get an fd that refers to your ctty... but doesn't behave
> like a ctty fd. Why you would want the latter, I have no idea (but
> also it of course wasn't me who came up with O_IGNORE_CTTY, so perhaps
> there is a use case).

I don't see why anybody would care if the O_IGNORE_CTTY behavior became 
the default. And if nobody cares, let's just make it the default. That 
way, you won't have to change glibc, Gnulib, git, coreutils, etc.

Do you have a scenario whereby making O_IGNORE_CTTY the default would 
break things? (It wouldn't break things as far as POSIX is concerned.)
Sergey Bugaev June 10, 2023, 4:13 p.m. UTC | #8
Hello,

On Sat, Jun 10, 2023 at 12:36 AM Paul Eggert <eggert@cs.ucla.edu> wrote:
> > Do you perhaps mean that POSIX does not require a
> > newly opened terminal to become your ctty even if you don't pass
> > O_NOCTTY?
>
> Yes, that's right. The openat rationale mentions this topic.

do you mean this?:

"The O_NOCTTY flag was added to allow applications to avoid
unintentionally acquiring a controlling terminal as a side-effect of
opening a terminal file. This volume of POSIX.1-2017 does not specify
how a controlling terminal is acquired, but it allows an
implementation to provide this on open() if the O_NOCTTY flag is not
set and other conditions specified in XBD General Terminal Interface
are met."

That paragraph is indeed about O_NOCTTY and acquiring a ctty if you
don't yet have one. It says nothing about whether the O_IGNORE_CTTY
behavior is allowed. To reiterate: O_IGNORE_CTTY is not about
acquiring a ctty if you don't yet have one (that never happens
implicitly on the Hurd), but about (re)opening your current ctty.

> I don't see why anybody would care if the O_IGNORE_CTTY behavior became
> the default. And if nobody cares, let's just make it the default. That
> way, you won't have to change glibc, Gnulib, git, coreutils, etc.
>
> Do you have a scenario whereby making O_IGNORE_CTTY the default would
> break things? (It wouldn't break things as far as POSIX is concerned.)

But it would break things as far as POSIX is concerned: see the
description of how I/O on a ctty should behave (namely generating
SIGTTIN / SIGTTOU for a background process) in "11.1.4 Terminal Access
Control".

I don't know whether any programs actually care about this ctty
feature. Presumably users care? -- as I understand it, this is
intended to be used with job control in the shell, so you can launch
some long-running batch job in the background, and have it stopped
with SIGTTIN when it tries to read from your terminal (instead of it
fighting for input with whatever foreground job at the time); you can
then resume it and give it the desired input. Although this all (job
control as a whole) has largely faded away with the proliferation of
tabs in terminal emulators. But it still works, and is supposed to
work, on today's Unixes, and is required to work by POSIX.

Also I don't think it'd be possible to flip the default like that even
if we wanted to. O_IGNORE_CTTY is not some new feature, it's been in
glibc as far back as Git history goes ("initial import" in 1995 --
it's one of those many things about the Hurd that are older than me
:). This has been the behavior and a public API for 25+ years, we
cannot just change it to the opposite now.

Sergey
Paul Eggert June 11, 2023, 4:54 a.m. UTC | #9
On 6/10/23 12:13, Sergey Bugaev wrote:

> O_IGNORE_CTTY is not about
> acquiring a ctty if you don't yet have one (that never happens
> implicitly on the Hurd), but about (re)opening your current ctty.

OK, I'm starting to see the distinction now.



> I don't know whether any programs actually care about this ctty
> feature. Presumably users care? -- as I understand it, this is
> intended to be used with job control in the shell

If so, it's a well-kept secret, as Bash doesn't use O_IGNORE_CTTY.

The only program I know of that uses O_IGNORE_CTTY is Emacs, and it's 
for what appears to be relatively minor optimization when it is opening 
a file that is a tty. On non-Hurd platforms Emacs instead uses setsid to 
remove the controlling tty entirely (since the notion of controlling 
terminal doesn't mix well with how Emacs operates).
Sergey Bugaev June 13, 2023, 10:54 a.m. UTC | #10
Hello,

On Sun, Jun 11, 2023 at 7:54 AM Paul Eggert <eggert@cs.ucla.edu> wrote:
> OK, I'm starting to see the distinction now.

So you did misunderstand it! That means not only is the explanation in
the glibc manual (reproduced below) unclear, but my previous attempts
to describe it and its differences to O_NOCTTY were unclear as well.

"Do not recognize the named file as the controlling terminal, even if
it refers to the process’s existing controlling terminal device.
Operations on the new file descriptor will never induce job control
signals."

This is an opportunity to improve the docs! -- please tell me how we
could improve it so it would have been clear to you from the start.
And in any case I should add a blurb about it making open faster and a
recommendation to use it whenever possible.

> > I don't know whether any programs actually care about this ctty
> > feature. Presumably users care? -- as I understand it, this is
> > intended to be used with job control in the shell
>
> If so, it's a well-kept secret, as Bash doesn't use O_IGNORE_CTTY.

There seems to be a miscommunication here as well: I'm replying to
your question of whether anybody cares about O_IGNORE_CTTY behavior
*not* being the default, i.e. the default behavior being the opposite
(honoring ctty). Programs don't actively use O_IGNORE_CTTY to get that
behavior exactly because they get that behavior by default. So you
can't get an estimate for whether any software cares by grepping it
for O_IGNORE_CTTY.

The "this" in my "this is intended to be used with job control in the
shell" refers to the feature of cttys that a background process trying
to do I/O on the terminal results in a signal (and then an error if
the signal is ignored); and not to O_IGNORE_CTTY (which turns this
feature *off*).

> The only program I know of that uses O_IGNORE_CTTY is Emacs, and it's
> for what appears to be relatively minor optimization when it is opening
> a file that is a tty. On non-Hurd platforms Emacs instead uses setsid to
> remove the controlling tty entirely (since the notion of controlling
> terminal doesn't mix well with how Emacs operates).

Speaking of Emacs, and seeing that Emacs already defines O_IGNORE_CTTY
to 0 on non-Hurd, and that you're one of the Emacs maintainers (is
that right?): could perhaps Emacs benefit from using O_IGNORE_CTTY
more broadly too? I imagine loading all the .el files in ~/.emacs.d
involves way too many pointless ctty checks, for example.

Sergey
Paul Eggert June 14, 2023, 5:40 a.m. UTC | #11
On 6/13/23 03:54, Sergey Bugaev wrote:

> "Do not recognize the named file as the controlling terminal, even if
> it refers to the process’s existing controlling terminal device.
> Operations on the new file descriptor will never induce job control
> signals."
> 
> This is an opportunity to improve the docs!7

The first-quoted sentence is confusing and arguably wrong. If the named 
file is the process's existing controlling terminal, it will continue to 
be recognized as the controlling terminal.

What's different is that I/O to the new fd won't induce SIGTTIN etc. So 
the second sentence is correct and the first is wrong. Perhaps the first 
sentence could be replaced by:

"Cause operations on the new file descriptor to act as if the named file 
is not the process's controlling terminal, even if it is."

> you
> can't get an estimate for whether any software cares by grepping it
> for O_IGNORE_CTTY.

Fair enough. Still, I still doubt many would care if O_IGNORE_CTTY 
became the default, particularly since O_NOCTTY is 0. What's the 
scenario whereby a Bash user would care, for example?


> Speaking of Emacs, and seeing that Emacs already defines O_IGNORE_CTTY
> to 0 on non-Hurd, and that you're one of the Emacs maintainers (is
> that right?): could perhaps Emacs benefit from using O_IGNORE_CTTY
> more broadly too? I imagine loading all the .el files in ~/.emacs.d
> involves way too many pointless ctty checks, for example.

It might, I suppose. Got a patch?
Sergey Bugaev June 16, 2023, 4:26 p.m. UTC | #12
Hello,

On Wed, Jun 14, 2023 at 8:40 AM Paul Eggert <eggert@cs.ucla.edu> wrote:
> The first-quoted sentence is confusing and arguably wrong. If the named
> file is the process's existing controlling terminal, it will continue to
> be recognized as the controlling terminal.

Thanks, I can see how that can sound confusing: the *file* will
continue being recognized as the ctty, but the newly opened fd won't
be recognized as one that refers to a ctty.

> What's different is that I/O to the new fd won't induce SIGTTIN etc. So
> the second sentence is correct and the first is wrong. Perhaps the first
> sentence could be replaced by:
>
> "Cause operations on the new file descriptor to act as if the named file
> is not the process's controlling terminal, even if it is."

So how about this?

"Cause operations on the new file descriptor to act as if the named
file is not the process's controlling terminal, even if it is.
@xref{Job Control}.

When @code{O_IGNORE_CTTY} is not set, @code{open} has to perform a
runtime check for the named file being the process's controlling
terminal; setting @code{O_IGNORE_CTTY} allows @code{open} to skip this
check.  In case the named file is statically known not to be the
process's controlling terminal, for example when opening a
configuration or a cache file using a well-known path, setting
@code{O_IGNORE_CTTY} will lead to improved @code{open} performance and
no behavior change.  For this reason, it is good practice to always
set @code{O_IGNORE_CTTY} when opening files, unless there is a
possibility that the file being opened could be the process's
controlling terminal."

> Still, I still doubt many would care if O_IGNORE_CTTY
> became the default, particularly since O_NOCTTY is 0. What's the
> scenario whereby a Bash user would care, for example?

They would run a program/job in the background, and wouldn't want input
or output from the background program to mess up what they're doing.
With !O_IGNORE_CTTY, when the background program attempts to do I/O on
the terminal (only input by default, both with 'stty tostop'), its gets
stopped, and Bash reports:

[1]+ pid-here Stopped (tty input)

The user would then resume it with 'fg %' (or just '%') when they're
ready, and it would do its I/O then. This is described in "Unix Power
Tools" [0], the Bash manual [1], and Zsh guide [2], so it's not even
that obscure of a feature.

[0]: https://docstore.mik.ua/orelly/unix3/upt/ch23_09.htm
[1]: https://www.gnu.org/software/bash/manual/html_node/Job-Control-Basics.html
[2]: https://zsh.sourceforge.io/Guide/zshguide03.html#l39

Although O_IGNORE_CTTY would only matter if the program reopens the tty
explicitly, perhaps as /dev/tty or /dev/stdout, not for the file
descriptors inherited across exec. sudo does this (reopening the
terminal), for example, so if you have a 'sudo xxxx' line in a script
that you run as a background job, it'd steal your input if O_IGNORE_CTTY
behavior was the default.

> > Speaking of Emacs, and seeing that Emacs already defines O_IGNORE_CTTY
> > to 0 on non-Hurd, and that you're one of the Emacs maintainers (is
> > that right?): could perhaps Emacs benefit from using O_IGNORE_CTTY
> > more broadly too? I imagine loading all the .el files in ~/.emacs.d
> > involves way too many pointless ctty checks, for example.
>
> It might, I suppose. Got a patch?

Hmm, I see there's a emacs_open[at] wrapper that automatically adds
O_CLOEXEC (and also O_BINARY). So now I've got the same question for
you: does Emacs ever care about the default, !O_IGNORE_CTTY behavior?
Would anything break if I just make emacs_openat always add in
O_IGNORE_CTTY?

OT1H the answer surely must be no, since Emacs is an interactive
editor, it doesn't just read and write its stdin/stdout/stderr as
streams like classic Unix utilities do. OTOH I see that it does deal
with cttys and SIGTTOU in several places...

Sergey
Paul Eggert June 17, 2023, 8:22 p.m. UTC | #13
On 2023-06-16 09:26, Sergey Bugaev wrote:
> Hello,

> So how about this?
> 
> "Cause operations on the new file descriptor to act as if the named
> file is not the process's controlling terminal, even if it is.
> @xref{Job Control}.
> 
> When @code{O_IGNORE_CTTY} is not set, @code{open} has to perform a
> runtime check for the named file being the process's controlling
> terminal; setting @code{O_IGNORE_CTTY} allows @code{open} to skip this
> check.  In case the named file is statically known not to be the

"In case the named file is statically known not to" ->
"If the named file cannot"

> @code{O_IGNORE_CTTY} will lead to improved @code{open} performance and
> no behavior change.  For this reason, it is good practice to always
> set @code{O_IGNORE_CTTY} when opening files, unless there is a
> possibility that the file being opened could be the process's
> controlling terminal."

Replace this with just "@code{O_IGNORE_CTTY} improves performance on the 
Hurd." as the rest is redundant.


> Although O_IGNORE_CTTY would only matter if the program reopens the tty
> explicitly, perhaps as /dev/tty or /dev/stdout, not for the file
> descriptors inherited across exec. sudo does this (reopening the
> terminal), for example, so if you have a 'sudo xxxx' line in a script
> that you run as a background job, it'd steal your input if O_IGNORE_CTTY
> behavior was the default.

Fine, so add an O_KEEP_CTTY flag for programs like sudo that want to 
play tricks with /dev/tty, and add a feature-test macro like 
_DEFAULT_IGNORE_CTTY to let applications choose whether O_IGNORE_CTTY or 
O_KEEP_CTTY is the default. If done right this would be an upward 
compatible API and ABI change, and would let people fix their apps with 
a simple '#define _DEFAULT_IGNORE_CTTY 1' before their other #include 
directives, instead of wandering through all their source code looking 
for places to add O_IGNORE_CTTY here and there.


> Hmm, I see there's a emacs_open[at] wrapper that automatically adds
> O_CLOEXEC (and also O_BINARY). So now I've got the same question for
> you: does Emacs ever care about the default, !O_IGNORE_CTTY behavior?
> Would anything break if I just make emacs_openat always add in
> O_IGNORE_CTTY?

Haven't a clue. Why don't you try it and run it for a while? But better 
yet, let's change the API as described above, and then try that with Emacs.
Samuel Thibault June 18, 2023, 9:55 p.m. UTC | #14
Hello,

Sergey Bugaev, le sam. 10 juin 2023 00:13:22 +0300, a ecrit:
> On Fri, Jun 9, 2023 at 9:37 PM Paul Eggert <eggert@cs.ucla.edu> wrote:
> > You could change the documentation so that it now says that flags that
> > imply O_IGNORE_CTTY are also meaningful. That should be fine.
> 
> Perhaps... but there's another reason I don't particularly like the
> idea of doing it on that level.
> 
> _hurd_port2fd () and _hurd_intern_fd () is something that you call
> once you already have an io port. O_CREAT and O_DIRECTORY and the rest
> are the flags that impact how you look it up. _hurd_port2fd would have
> to second-guess "this io port is said to have been opened with O_CREAT
> | O_EXCL, so it can't be a ctty". It'd be better to have the caller
> (open) -- that "can see" both looking the port up and interning it --
> implement this bit of logic. Not that this matters for anything,
> because it would still behave the same way no matter which level we
> implement it at; but it seems more appropriate to me to implement it
> at that level.
> 
> Samuel, what do you think?

It looks better to me to add a shared helper that adds O_IGNORE_CTTY
whenever the flags contain something that implies it. Callers of
_hurd_intern_fd / _hurd_port2fd can then easily use it (or even just
always pass O_IGNORE_CTTY, when creating a socket for instance).

Samuel
Sergey Bugaev June 19, 2023, 8:57 a.m. UTC | #15
Hello,

On Mon, Jun 19, 2023 at 12:55 AM Samuel Thibault
<samuel.thibault@gnu.org> wrote:
> > Samuel, what do you think?
>
> It looks better to me to add a shared helper that adds O_IGNORE_CTTY
> whenever the flags contain something that implies it. Callers of
> _hurd_intern_fd / _hurd_port2fd can then easily use it

That's exactly what I was thinking, thank you.

> (or even just
> always pass O_IGNORE_CTTY, when creating a socket for instance).

It does that already, yes.

Sergey
diff mbox series

Patch

diff --git a/catgets/open_catalog.c b/catgets/open_catalog.c
index 46c444d2..129f4662 100644
--- a/catgets/open_catalog.c
+++ b/catgets/open_catalog.c
@@ -49,7 +49,7 @@  __open_catalog (const char *cat_name, const char *nlspath, const char *env_var,
   char *buf = NULL;
 
   if (strchr (cat_name, '/') != NULL || nlspath == NULL)
-    fd = __open_nocancel (cat_name, O_RDONLY | O_CLOEXEC);
+    fd = __open_nocancel (cat_name, O_RDONLY | O_CLOEXEC | O_IGNORE_CTTY);
   else
     {
       const char *run_nlspath = nlspath;
@@ -177,7 +177,7 @@  __open_catalog (const char *cat_name, const char *nlspath, const char *env_var,
 
 	  if (bufact != 0)
 	    {
-	      fd = __open_nocancel (buf, O_RDONLY | O_CLOEXEC);
+	      fd = __open_nocancel (buf, O_RDONLY | O_CLOEXEC | O_IGNORE_CTTY);
 	      if (fd >= 0)
 		break;
 	    }
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 9a87fda9..f58fa95e 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -1620,7 +1620,7 @@  open_verify (const char *name, int fd,
 
   if (fd == -1)
     /* Open the file.  We always open files read-only.  */
-    fd = __open64_nocancel (name, O_RDONLY | O_CLOEXEC);
+    fd = __open64_nocancel (name, O_RDONLY | O_CLOEXEC | O_IGNORE_CTTY);
 
   if (fd != -1)
     {
diff --git a/elf/dl-misc.c b/elf/dl-misc.c
index 5b84adc2..85931c7c 100644
--- a/elf/dl-misc.c
+++ b/elf/dl-misc.c
@@ -36,7 +36,7 @@  _dl_sysdep_read_whole_file (const char *file, size_t *sizep, int prot)
 {
   void *result = MAP_FAILED;
   struct __stat64_t64 st;
-  int fd = __open64_nocancel (file, O_RDONLY | O_CLOEXEC);
+  int fd = __open64_nocancel (file, O_RDONLY | O_CLOEXEC | O_IGNORE_CTTY);
   if (fd >= 0)
     {
       if (__fstat64_time64 (fd, &st) >= 0)
diff --git a/elf/dl-profile.c b/elf/dl-profile.c
index 8be0065f..040734d1 100644
--- a/elf/dl-profile.c
+++ b/elf/dl-profile.c
@@ -325,7 +325,7 @@  _dl_start_profile (void)
   __stpcpy (__stpcpy (cp, GLRO(dl_profile)), ".profile");
 
   fd = __open64_nocancel (filename, O_RDWR | O_CREAT | O_NOFOLLOW
-			  | O_CLOEXEC, DEFFILEMODE);
+			  | O_CLOEXEC | O_IGNORE_CTTY, DEFFILEMODE);
   if (fd == -1)
     {
       char buf[400];
diff --git a/gmon/gmon.c b/gmon/gmon.c
index 6439ed1c..ed13b3ce 100644
--- a/gmon/gmon.c
+++ b/gmon/gmon.c
@@ -385,13 +385,13 @@  write_gmon (void)
 	char buf[len + 20];
 	__snprintf (buf, sizeof (buf), "%s.%u", env, __getpid ());
 	fd = __open_nocancel (buf, O_CREAT | O_TRUNC | O_WRONLY | O_NOFOLLOW
-			      | O_CLOEXEC, 0666);
+			      | O_CLOEXEC | O_IGNORE_CTTY, 0666);
       }
 
     if (fd == -1)
       {
 	fd = __open_nocancel ("gmon.out", O_CREAT | O_TRUNC | O_WRONLY
-			      | O_NOFOLLOW | O_CLOEXEC, 0666);
+			      | O_NOFOLLOW | O_CLOEXEC | O_IGNORE_CTTY, 0666);
 	if (fd < 0)
 	  {
 	    char buf[300];
diff --git a/iconv/gconv_cache.c b/iconv/gconv_cache.c
index 87136e24..c8d972c8 100644
--- a/iconv/gconv_cache.c
+++ b/iconv/gconv_cache.c
@@ -58,7 +58,8 @@  __gconv_load_cache (void)
     return -1;
 
   /* See whether the cache file exists.  */
-  fd = __open_nocancel (GCONV_MODULES_CACHE, O_RDONLY | O_CLOEXEC, 0);
+  fd = __open_nocancel (GCONV_MODULES_CACHE, O_RDONLY
+			| O_CLOEXEC | O_IGNORE_CTTY, 0);
   if (__builtin_expect (fd, 0) == -1)
     /* Not available.  */
     return -1;
diff --git a/locale/loadarchive.c b/locale/loadarchive.c
index 5b857d5d..f88ff8b8 100644
--- a/locale/loadarchive.c
+++ b/locale/loadarchive.c
@@ -202,7 +202,8 @@  _nl_load_locale_from_archive (int category, const char **namep)
       archmapped = &headmap;
 
       /* The archive has never been opened.  */
-      fd = __open_nocancel (archfname, O_RDONLY|O_LARGEFILE|O_CLOEXEC);
+      fd = __open_nocancel (archfname, O_RDONLY | O_LARGEFILE
+			    | O_CLOEXEC | O_IGNORE_CTTY);
       if (fd < 0)
 	/* Cannot open the archive, for whatever reason.  */
 	return NULL;
@@ -397,8 +398,8 @@  _nl_load_locale_from_archive (int category, const char **namep)
 	  if (fd == -1)
 	    {
 	      struct __stat64_t64 st;
-	      fd = __open_nocancel (archfname,
-				    O_RDONLY|O_LARGEFILE|O_CLOEXEC);
+	      fd = __open_nocancel (archfname, O_RDONLY | O_LARGEFILE
+				    | O_CLOEXEC | O_IGNORE_CTTY);
 	      if (fd == -1)
 		/* Cannot open the archive, for whatever reason.  */
 		return NULL;
diff --git a/locale/loadlocale.c b/locale/loadlocale.c
index 671e71cf..582144ed 100644
--- a/locale/loadlocale.c
+++ b/locale/loadlocale.c
@@ -240,7 +240,7 @@  _nl_load_locale (struct loaded_l10nfile *file, int category)
   file->decided = 1;
   file->data = NULL;
 
-  fd = __open_nocancel (file->filename, O_RDONLY | O_CLOEXEC);
+  fd = __open_nocancel (file->filename, O_RDONLY | O_CLOEXEC | O_IGNORE_CTTY);
   if (__builtin_expect (fd, 0) < 0)
     /* Cannot open the file.  */
     return;
@@ -267,7 +267,7 @@  _nl_load_locale (struct loaded_l10nfile *file, int category)
 			    "/SYS_", 5), _nl_category_names_get (category),
 		 _nl_category_name_sizes[category] + 1);
 
-      fd = __open_nocancel (newp, O_RDONLY | O_CLOEXEC);
+      fd = __open_nocancel (newp, O_RDONLY | O_CLOEXEC | O_IGNORE_CTTY);
       if (__builtin_expect (fd, 0) < 0)
 	return;
 
diff --git a/login/openpty.c b/login/openpty.c
index 1e44852a..a89555b2 100644
--- a/login/openpty.c
+++ b/login/openpty.c
@@ -117,7 +117,7 @@  __openpty (int *pptmx, int *pterminal, char *name,
       if (pts_name (ptmx, &buf, sizeof (_buf)))
         goto on_error;
 
-      terminal = __open64 (buf, O_RDWR | O_NOCTTY);
+      terminal = __open64 (buf, O_RDWR | O_NOCTTY | O_IGNORE_CTTY);
       if (terminal == -1)
         goto on_error;
     }
diff --git a/login/utmp_file.c b/login/utmp_file.c
index 7055041d..bdf88b51 100644
--- a/login/utmp_file.c
+++ b/login/utmp_file.c
@@ -142,7 +142,7 @@  __libc_setutent (void)
 
       file_writable = false;
       file_fd = __open_nocancel
-	(file_name, O_RDONLY | O_LARGEFILE | O_CLOEXEC);
+	(file_name, O_RDONLY | O_LARGEFILE | O_CLOEXEC | O_IGNORE_CTTY);
       if (file_fd == -1)
 	return 0;
     }
@@ -354,7 +354,7 @@  __libc_pututline (const struct utmp *data)
       const char *file_name = TRANSFORM_UTMP_FILE_NAME (__libc_utmp_file_name);
 
       int new_fd = __open_nocancel
-	(file_name, O_RDWR | O_LARGEFILE | O_CLOEXEC);
+	(file_name, O_RDWR | O_LARGEFILE | O_CLOEXEC | O_IGNORE_CTTY);
       if (new_fd == -1)
 	return NULL;
 
@@ -463,7 +463,8 @@  __libc_updwtmp (const char *file, const struct utmp *utmp)
   int fd;
 
   /* Open WTMP file.  */
-  fd = __open_nocancel (file, O_WRONLY | O_LARGEFILE | O_CLOEXEC);
+  fd = __open_nocancel (file, O_WRONLY | O_LARGEFILE
+			| O_CLOEXEC | O_IGNORE_CTTY);
   if (fd < 0)
     return -1;
 
diff --git a/misc/daemon.c b/misc/daemon.c
index 14577e40..2f653ec7 100644
--- a/misc/daemon.c
+++ b/misc/daemon.c
@@ -67,7 +67,7 @@  daemon (int nochdir, int noclose)
     {
       struct __stat64_t64 st;
 
-      fd = __open_nocancel (_PATH_DEVNULL, O_RDWR, 0);
+      fd = __open_nocancel (_PATH_DEVNULL, O_RDWR | O_IGNORE_CTTY, 0);
       if (fd != -1 && __glibc_likely (__fstat64_time64 (fd, &st) == 0))
         {
           if (__builtin_expect (S_ISCHR (st.st_mode), 1) != 0
diff --git a/nss/nss_db/db-open.c b/nss/nss_db/db-open.c
index a5279dc0..2a996a6e 100644
--- a/nss/nss_db/db-open.c
+++ b/nss/nss_db/db-open.c
@@ -36,7 +36,8 @@  internal_setent (const char *file, struct nss_db_map *mapping)
 {
   enum nss_status status = NSS_STATUS_UNAVAIL;
 
-  int fd = __open_nocancel (file, O_RDONLY | O_LARGEFILE | O_CLOEXEC);
+  int fd = __open_nocancel (file, O_RDONLY | O_LARGEFILE
+			    | O_CLOEXEC | O_IGNORE_CTTY);
   if (fd != -1)
     {
       struct nss_db_header header;
diff --git a/rt/shm_open.c b/rt/shm_open.c
index fc1dc96b..7fd62cf3 100644
--- a/rt/shm_open.c
+++ b/rt/shm_open.c
@@ -37,7 +37,7 @@  __shm_open (const char *name, int oflag, mode_t mode)
       return -1;
     }
 
-  oflag |= O_NOFOLLOW | O_CLOEXEC;
+  oflag |= O_NOFOLLOW | O_CLOEXEC | O_IGNORE_CTTY;
 #if defined (SHM_ANON) && defined (O_TMPFILE)
   if (name == SHM_ANON)
     oflag |= O_TMPFILE;
diff --git a/shadow/lckpwdf.c b/shadow/lckpwdf.c
index 3b36b2eb..4a623c41 100644
--- a/shadow/lckpwdf.c
+++ b/shadow/lckpwdf.c
@@ -96,7 +96,7 @@  __lckpwdf (void)
   /* Prevent problems caused by multiple threads.  */
   __libc_lock_lock (lock);
 
-  int oflags = O_WRONLY | O_CREAT | O_CLOEXEC;
+  int oflags = O_WRONLY | O_CREAT | O_CLOEXEC | O_IGNORE_CTTY;
   lock_fd = __open (PWD_LOCKFILE, oflags, 0600);
   if (lock_fd == -1)
     /* Cannot create lock file.  */
diff --git a/sysdeps/mach/hurd/opendir.c b/sysdeps/mach/hurd/opendir.c
index 39c805bc..07260d22 100644
--- a/sysdeps/mach/hurd/opendir.c
+++ b/sysdeps/mach/hurd/opendir.c
@@ -73,7 +73,7 @@  __opendirat (int dfd, const char *name)
        but `open' might like it fine.  */
     return __hurd_fail (ENOENT), NULL;
 
-  int flags = O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC;
+  int flags = O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_IGNORE_CTTY;
   int fd;
 #if IS_IN (rtld)
   assert (dfd == AT_FDCWD);
diff --git a/sysdeps/pthread/sem_open.c b/sysdeps/pthread/sem_open.c
index e5db929d..5a248ebb 100644
--- a/sysdeps/pthread/sem_open.c
+++ b/sysdeps/pthread/sem_open.c
@@ -65,7 +65,7 @@  __sem_open (const char *name, int oflag, ...)
   /* If the semaphore object has to exist simply open it.  */
   if ((oflag & O_CREAT) == 0 || (oflag & O_EXCL) == 0)
     {
-      open_flags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
+      open_flags = O_RDWR | O_NOFOLLOW | O_CLOEXEC | O_IGNORE_CTTY;
       open_flags |= (oflag & ~(O_CREAT|O_ACCMODE));
     try_again:
       fd = __open (dirname.name, open_flags);
@@ -135,7 +135,7 @@  __sem_open (const char *name, int oflag, ...)
 	    }
 
 	  /* Open the file.  Make sure we do not overwrite anything.  */
-	  open_flags = O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC;
+	  open_flags = O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC | O_IGNORE_CTTY;
 	  fd = __open (tmpfname, open_flags, mode);
 	  if (fd == -1)
 	    {
diff --git a/sysdeps/unix/bsd/getpt.c b/sysdeps/unix/bsd/getpt.c
index 8369f958..48f3d07a 100644
--- a/sysdeps/unix/bsd/getpt.c
+++ b/sysdeps/unix/bsd/getpt.c
@@ -76,7 +76,7 @@  __bsd_openpt (int oflag)
 int
 __getpt (void)
 {
-  return __bsd_openpt (O_RDWR);
+  return __bsd_openpt (O_RDWR | O_IGNORE_CTTY);
 }
 libc_hidden_def (__getpt)
 weak_alias (__getpt, getpt)
@@ -84,6 +84,6 @@  weak_alias (__getpt, getpt)
 int
 __posix_openpt (int oflag)
 {
-  return __bsd_openpt (oflag);
+  return __bsd_openpt (oflag | O_IGNORE_CTTY);
 }
 weak_alias (__posix_openpt, posix_openpt)