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