> Actually what you're telling us is that there's a defect in (presumably
Linux)
> which should be documented in read(2).  Once you've gotten it documented
in
> the proper place (Linux manpages), then others can use the documentation.

Dear Thomas,

Thank you for your response and for the clarification regarding the
underlying OS behavior.

I understand your point that the root of the reporting originates in how
the kernel handles read() on a deleted pseudo-terminal.
However, from the perspective of an application developer, there is a
"lossy abstraction" occurring at the library level.
While the kernel distinguishes between a timeout (where poll() returns 0
and no read() is attempted) and an EOF (where poll() indicates readiness
and read() returns 0), getch() currently collapses both of these distinct
states into the single ERR constant.
This makes it impossible for the application to distinguish between "no
input yet" and "terminal is gone" without resorting to external state or
signals.

I realize that changing the getch() interface or its return values at this
stage would be a significant undertaking and likely undesirable for
backward compatibility.
That said, it might still be helpful for future developers if the getch(3X)
man page explicitly documented that ERR is returned for both timeout and
EOF conditions, and that applications requiring this distinction should
monitor SIGHUP or check the file descriptor state directly.

Thank you again for your time and for all the work you do maintaining
ncurses.

On Sun, Mar 29, 2026 at 11:09 AM Thomas Dickey <[email protected]>
wrote:

> On Sun, Mar 29, 2026 at 09:05:20AM +0200, Klaas van Aarsen wrote:
> > Thank you.
> >
> > We inspected /proc/<pid>/fd and observed that file descriptor 0 becomes a
> > dangling reference to a deleted pseudo-terminal after disconnect.
> > The descriptor itself remains valid, but read() returns EOF (0) rather
> than
> > an error, so errno is not set.
> >
> > This means getch() collapsing both timeout and EOF into ERR leaves no
> > reliable way to distinguish “no input” from “terminal gone.”
> >
> > Closing STDIN_FILENO in a SIGHUP handler does not appear to be a robust
> > solution.
> > It introduces FD reuse risks and shifts responsibility into
> > application-level bookkeeping without actually solving the ambiguity at
> the
> > getch() level.
> >
> > Given this, we will proceed with a sig_atomic_t flag set on SIGHUP and
> use
> > that to break out of the input loop.
> >
> > It may be worth documenting that getch() does not provide a mechanism to
> > distinguish timeout from EOF/disconnected terminal, and that applications
> > are expected to handle this via signals or external state.
>
> Actually what you're telling us is that there's a defect in (presumably
> Linux)
> which should be documented in read(2).  Once you've gotten it documented in
> the proper place (Linux manpages), then others can use the documentation.
>
> > On Sat, Mar 28, 2026 at 11:51 PM Thomas Dickey <
> [email protected]>
> > wrote:
> >
> > > On Sat, Mar 28, 2026 at 07:24:44AM +0100, Klaas van Aarsen wrote:
> > > > Thanks for your timely responses.
> > > >
> > > > However, it seems we cannot use errno after getch() returns ERR. In
> our
> > > > testing, when a SIGHUP occurs, the underlying read() call simply
> returns
> > > 0
> > > > (EOF) rather than a system error.
> > > > Since ncurses appears to map both EOF and a halfdelay timeout to the
> same
> > > > ERR value without setting errno, we lose the ability to distinguish
> > > between
> > > > 'no input' and 'terminal gone.'
> > > >
> > > > We could use napms() to throttle the CPU, but that leaves the process
> > > alive
> > > > in a 'zombie' state until a user manually kills it.
> > > > We really want to detect the disconnection and terminate gracefully.
> > > >
> > > > It looks as if we are forced to set a global volatile sig_atomic_t
> flag
> > > on
> > > > SIGHUP and check it manually every time getch() returns ERR.
> > >
> > > If fileno(stdin) is actually closed, the read should set errno to EBADF
> > > (9).
> > > You could close it in a signal handler for SIGHUP (that's safe), and
> check
> > > error on ERR return from wgetch.
> > >
> > > I seem to recall some discussion many years ago (e.g., ~20) where the
> > > terminal was defunct, but didn't actually close the input -- I think
> > > the responsibility for that should be in the calling application since
> > > _it_ has to know what's a graceful way to shut down.
> > >
> > > But if you're already getting ERR from wgetch (as indicated in the
> initial
> > > report), I don't see what state stdin might be in if there's no errno
> set.
> > >
> > > > Does ncurses provide any internal mechanism to distinguish an EOF
> from a
> > > > timeout, or is the signal-flag approach the standard way to handle
> this
> > > in
> > > > a curses application?
> > > >
> > > >
> > > >
> > > > On Sat, Mar 28, 2026 at 12:30 AM G. Branden Robinson <
> > > > [email protected]> wrote:
> > > >
> > > > > At 2026-03-27T18:23:57-0400, Thomas Dickey wrote:
> > > > > > On Fri, Mar 27, 2026 at 06:40:42PM +0100, Klaas van Aarsen wrote:
> > > > > > > We have a bug in the Angband code related to getch of ncurses (
> > > > > > > https://github.com/angband/angband/pull/6559).
> > > > > > > It turns out that getch returns ERR when the terminal
> connection is
> > > > > > > broken (SIGHUP). The code currently interprets it as a timeout
> (set
> > > > > > > by halfdelay) and spins on input, leaving a zombie process with
> > > 100%
> > > > > > > CPU.  The man page on getch only mentions that errno might be
> EINTR
> > > > > > > if the getch call is interrupted, but nothing on a broken
> terminal
> > > > > > > connection.  How might we detect that the terminal connection
> is
> > > > > > > broken?
> > > > > [...]
> > > > > > ...mixing getch with fgets doesn't appear to be a good idea,
> since
> > > > > > ncurses doesn't use the input stream -- only the file descriptor.
> > > > > > Expect some difference in behavior.
> > > > >
> > > > > The ncurses man pages explain this, too.
> > > > >
> > > > > ncurses(3NCURSES):
> > > > >
> > > > > ENVIRONMENT
> > > > > ...
> > > > >    NCURSES_NO_SETBUF
> > > > >      (Obsolete) Prior to internal changes developed in ncurses 5.9
> > > > >      (patches 20120825 through 20130126), the library used
> setbuf(3) to
> > > > >      enable fully buffered output when initializing the terminal.
> This
> > > > >      was done, as in SVr4 curses, to increase performance.  For
> testing
> > > > >      purposes, both of ncurses and of certain applications, this
> > > feature
> > > > >      was made optional.  Setting this variable disabled output
> > > > >      buffering, leaving the output stream in the original (usually
> > > line‐
> > > > >      buffered) mode.
> > > > >
> > > > >      Nowadays, ncurses performs its own buffering and does not
> require
> > > > >      this workaround; it does not modify the buffering of the
> standard
> > > > >      output stream.  This approach makes the library’s handling of
> > > > >      keyboard‐initiated signals more robust.  A drawback is that
> > > certain
> > > > >      unconventional programs mixed stdio(3) calls with ncurses
> calls
> > > and
> > > > >      (usually) got the behavior they expected.  This is no longer
> the
> > > > >      case; ncurses does not write to the standard output file
> > > descriptor
> > > > >      through a stdio‐buffered stream.
> > > > >
> > > > >      As a special case, low‐level API calls such as putp(3NCURSES)
> > > still
> > > > >      use the standard output stream.  High‐level curses calls such
> as
> > > > >      printw(3NCURSES) do not.
> > > > >
> > > > > Regards,
> > > > > Branden
> > > > >
> > >
> > > --
> > > Thomas E. Dickey <[email protected]>
> > > https://invisible-island.net
> > >
>
> --
> Thomas E. Dickey <[email protected]>
> https://invisible-island.net
>

Reply via email to