>> Regarding the suggestion of EBADF, it is my understanding that the kernel >> must keep file descriptor 0 open (valid) even after a disconnect to prevent >> immediate FD reuse. > either it's closed, or it's not. > ncurses doesn't reopen it. > Since ncurses doesn't reopen it, I don't see any reason why the dangling > symlink would be relevant.
To clarify my earlier point regarding EBADF: If the kernel were to simply close file descriptor 0 upon a disconnect, the very next file opened by the process (or a library) would be assigned FD 0, as it is the lowest available slot. This would cause catastrophic confusion, as code expecting stdin would suddenly be reading from a random file or socket. Instead, the descriptor remains allocated to the process but points to a defunct terminal (often following a SIGHUP). This is why read() returns 0 (EOF) and errno remains 0, rather than triggering EBADF. We can detect this specific "ghost" state using isatty(STDIN_FILENO). Upon disconnect, isatty will return 0 (false) because the underlying terminal device is no longer associated with the descriptor, even though the descriptor itself hasn't been "closed." This approach doesn't work with a piped file though. My mention of the dangling symlink was simply to illustrate this state: the process holds a valid descriptor to a path/device that no longer exists in the active filesystem. On Sun, Mar 29, 2026 at 6:38 PM Klaas van Aarsen <[email protected]> wrote: > > If you're able to reproduce the problem, and modify the program to report > > the errno value, then we can discuss whether the errno value matches the > > expected behavior. > > I have run the requested tests to capture errno during both a pipe EOF and > a forced SSH disconnect. > > Environment > ----------- > Ubuntu 22.04.5 LTS in WSL2 > ncurses 6.3.20211021 > > Test Program > ------------ > #include <ncurses.h> > #include <errno.h> > #include <stdio.h> > > int main() { > FILE *f = fopen("trace.log", "w"); > initscr(); > halfdelay(5); > for (int i = 0; i < 100; ++i) { > errno = 0; > int ch = getch(); > int err = errno; > fprintf(f, "getch: %d, errno: %d\n", ch, err); > fflush(f); > } > endwin(); > fclose(f); > } > > > Scenario 1: Pipe EOF > -------------------- > $ echo "hi" | ./a.out ; cat trace.log > > Results show getch: 104, 105, 10 followed by consistent getch: -1, errno: > 0. > > > Scenario 2: SSH Disconnect > -------------------------- > 1. SSH into host. > 2. Run ./a.out. > 3. Hard-close the terminal window (sending no logout/exit). > 4. Inspect log after 30 seconds. > > Trace Log Result: > getch: 72, errno: 0 (H) > getch: 105, errno: 0 (i) > getch: -1, errno: 0 > getch: -1, errno: 0 > ... [continues until loop end] ... > getch: -1, errno: 0 > > > Scenario 3: Manually closing stdin (FD 0) > ----------------------------------------- > To test if getch() would report EBADF, I added close(0) inside the loop. > > Result: The loop completed instantly (ignoring the halfdelay timeout). > getch() returned -1 for every iteration, but errno remained 0. > > This demonstrates that even when the file descriptor is explicitly invalid > (EBADF condition), getch() does not pass that error information through to > the caller. > > > Conclusion > ---------- > In all cases, ncurses reports ERR but the system does not report EBADF or > any other error via errno. > This suggests that the terminal disconnect is being treated as a > persistent EOF (0) rather than a file descriptor error. > > On Sun, Mar 29, 2026 at 5:32 PM Thomas Dickey <[email protected]> > wrote: > >> On Sun, Mar 29, 2026 at 12:15:45PM +0200, Klaas van Aarsen wrote: >> > > man read(2) >> > > EBADF fd is not a valid file descriptor or is not open for >> reading. >> > > Klaas is describing a condition which isn't in the manpage - ymmv. >> > >> > Regarding the suggestion of EBADF, it is my understanding that the >> kernel >> > must keep file descriptor 0 open (valid) even after a disconnect to >> prevent >> > immediate FD reuse. >> >> either it's closed, or it's not. >> >> ncurses doesn't reopen it. >> >> Since ncurses doesn't reopen it, I don't see any reason why the dangling >> symlink would be relevant. >> >> Checking errno after the failed call would be more useful than making >> a signal handler :-) >> >> > If read() were to return EBADF while the descriptor is still allocated >> to >> > the process, it would technically contradict the POSIX definition of >> that >> > error. >> >> If you're able to reproduce the problem, and modify the program to report >> the errno value, then we can discuss whether the errno value matches the >> expected behavior. >> >> > While the kernel could conceivably return an error like EIO or ENXIO to >> > signal a "dead" device, treating a terminal disconnect as an "end of >> > stream" (returning 0) appears to be the standard behavior for >> > pseudo-terminals across most modern Unix-like systems. >> > This ensures the stream is closed gracefully without releasing the >> > descriptor slot prematurely. >> >> ncurses is using the read directly (stream usually refers to things >> opened via fopen). >> >> initscr (used in this program) calls newterm >> >> if (newterm(name, stdout, stdin) == NULL) { >> >> newterm copies the file descriptor: >> >> SP_PARM->_ifd = fileno(_ifp); >> >> and after that, it ignores any buffering that the caller may have on that >> file descriptor. ncurses never closes that file descriptor. If you got >> EBADF, then that would indicate that the connection was closed (either >> end of a connection can close it). >> >> > I appreciate the technical dialogue on this. >> > >> > >> > On Sun, Mar 29, 2026 at 11:59 AM Thomas Dickey < >> [email protected]> >> > wrote: >> > >> > > On Sun, Mar 29, 2026 at 11:40:56AM +0200, Stian Skjelstad wrote: >> > > > > 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. >> > > > >> > > > It is normal for read() to return 0, without an error when EOF is >> > > reached. >> > > > This is how you detect EOF/closed on sockets and files. Should TTY >> behave >> > > > differently? >> > > >> > > man read(2) >> > > >> > > EBADF fd is not a valid file descriptor or is not open for >> reading. >> > > >> > > Klaas is describing a condition which isn't in the manpage - ymmv. >> > > >> > > -- >> > > Thomas E. Dickey <[email protected]> >> > > https://invisible-island.net >> > > >> >> -- >> Thomas E. Dickey <[email protected]> >> https://invisible-island.net >> >
