The way I read the POSIX spec, isatty should return 1 if an fd refers to a tty, regardless if that tty is hung up or not. But right now it returns EIO on a hung up one. That's because the Linux kernel sets the ioctl handler of hung up ttys, to one that basically always returns EIO, and glibc just passes that on as is. Not sure what the right fix is. It might suffice to simply turn any EIO that glibc gets back from the ioctl into a 1. musl also got this wrong btw, but even worse: it turns all errors it gets into ENOTTY, including EIO. Hence, glibc just doesn't give any useful answer for such cases, but musl gives an outright wrong one. This came up here: https://github.com/systemd/systemd/pull/34039
Returning 1 would be wrong. The EIO error does not establish that the device is/was a tty, and could come from the kernel doing the same hack on any other device that's disconnected or in a defunct state. Falsely claiming a non-tty device is a tty is far worse than falsely claiming that a device which began its life as a tty, but that has enterred some defunct state, is not a tty - or even less offensively, just reporting that we can't make a positive determination. POSIX does allow error codes in addition to the standard ones, so I think the current glibc behavior is conforming. EIO is an error condition that prevents determining whether the fd refers to a tty, and as such is a reasonable, reportable error given the limitations of the Linux kernel here. The musl behavior should probably be alterred to do the same. In fact it used to do the same, but this was changed as part of only tangentially related work fixing the error behavior of ttyname[_r].
Detecting whether something is a TTY with a ioctl() call is always a bit icky, because on Linux ioctl numbers are not assigned uniquely across all subsystems, but reused in different subsystems. Now, given that isatty() was based on an ioctl() call since basically forever, let's assume that at least for this special case, i.e. for the ioctl() glibc uses for isatty() is understood to never be reused for any other subsystem, in order not to break isatty(). or in other words: let's assume TCGETS only every will return something != ENOTTY on true ttys, because if that wasn#t the case, then isatty() as it is now would already be massively broken, because it would then also recognize other devices as ttys that really aren't. But if that is so, then it also means that return 1 in case of EIO is also safe, because we just established that no other subsystem would reuse this specific ioctl number anyway, and hence no confusion is possible. Hence, I think turning EIO into returning 1 is really the right way to go. (oh, and i also think musl should be changed to reuse the same ioctl for detecting ttys as glibc, because asking kernel devs to never reuse the numeric value of TCGETS on any other subsystem is a much simpler request than asking it to avoid assignment of two ioctl codes.
> POSIX does allow error codes in addition to the standard ones, so I think the current glibc behavior is conforming. EIO is an error condition that prevents determining whether the fd refers to a tty, and as such is a reasonable, reportable error given the limitations of the Linux kernel here. Dunno. I don't think glibc's isatty() should return "huuuh?" just because a tty is hung up. After all hanging up a tty is a temporary thing, i.e. it's a real mess that when you issue vhangup() we open small time window where according to glibc a tty is neither a tty nor not a tty, but just something unrecognizable. I mean, i agree, what glibc does here is less of an issue than musl's behaviour (which says "not a tty"), but i think both of them should somehow be fixed.
The collision should be avoided by not using 't' ioctl numbers for anything that isn't terminal-related. There's just some overlap with PPP devices that I can see, and that is probably okay. It's not clear to me if POSIX expects tcgetattr, ttyname_r etc. to work on terminals that are in the hung up state. If not, it's probably just a matter of switching isatty to one of the state-independent TTY ioctls. Maybe TIOCGWINSZ? Still it would make sense to coordinate this switch with the kernel.
(In reply to Lennart Poettering from comment #2) > Detecting whether something is a TTY with a ioctl() call is always a bit > icky, because on Linux ioctl numbers are not assigned uniquely across all > subsystems, but reused in different subsystems. Now, given that isatty() was > based on an ioctl() call since basically forever, let's assume that at least > for this special case, i.e. for the ioctl() glibc uses for isatty() is > understood to never be reused for any other subsystem, in order not to break > isatty(). or in other words: let's assume TCGETS only every will return > something != ENOTTY on true ttys, because if that wasn#t the case, then > isatty() as it is now would already be massively broken, because it would > then also recognize other devices as ttys that really aren't. FWiW, Linux ioctl numbers for terminals are not unique, see https://www.kernel.org/doc/Documentation/ioctl/ioctl-number.txt In particular, on most of Linux architectures TCGETS has the same number as SNDCTL_TMR_START, so that e.g. strace has to look at device numbers to resolve collisions, see https://github.com/strace/strace/blob/master/src/term.c#L455
> It's not clear to me if POSIX expects tcgetattr, ttyname_r etc. to work on > terminals that are in the hung up state. If not, it's probably just a matter of > switching isatty to one of the state-independent TTY ioctls. Maybe TIOCGWINSZ? > Still it would make sense to coordinate this switch with the kernel. the kernel tty layer switches out the whole fops structure as long as a tty is in hungup state. the ioctl handler in there returns EIO for *any* ioctl. (even for unknown ioctls!). It's kinda broken if you ask me. But in other words, there *is* no ioctl glibc could call here that wasn't affected by the hangup.
> In particular, on most of Linux architectures TCGETS has the same number as > SNDCTL_TMR_START, so that e.g. strace has to look at device numbers to resolve > collisions, see https://github.com/strace/strace/blob/master/src/term.c#L455 Ouch, that's bad. So i guess this means one can probably trick glibc to return 1 when calling isatty() on alsa timer device fds? urks.
(In reply to Lennart Poettering from comment #7) > > In particular, on most of Linux architectures TCGETS has the same number as > > SNDCTL_TMR_START, so that e.g. strace has to look at device numbers to resolve > > collisions, see https://github.com/strace/strace/blob/master/src/term.c#L455 > > Ouch, that's bad. So i guess this means one can probably trick glibc to > return 1 when calling isatty() on alsa timer device fds? urks. Ugh, ALSA uses _SIO instead of _IO/_IOW/IO_R, so I had missed it. I agree that's bad. Let's hope we can get proper system calls for this eventually.
TCGETS is defined to 0x5401 in <asm-generic/ioctl.h> which does not conflict.
(In reply to Andreas Schwab from comment #9) > TCGETS is defined to 0x5401 in <asm-generic/ioctl.h> which does not conflict. Sorry, I must have confused TCGETS with TCSETS.
> But if that is so, then it also means that return 1 in case of EIO is also > safe, because we just established that no other subsystem would reuse this > specific ioctl number anyway, and hence no confusion is possible. The kernel has already demonstrated that they're willing to make a device return a non-ENOTTY error code for ioctl numbers wildly outside of its own ioctl number range: the very example we're talking about, hung up ttys. Being that they were this sloppy with hung up ttys, to make them EIO for *any ioctl number* rather than just tty ones, I find it completely implausible to believe they won't do the same thing in the future for other device types in a "defunct" state of some sort. I would even be rather surprised if they're not already doing it somewhere. Does anyone want to do the research to establish that they're not? In the absence of evidence, the default assumption should be that they are. > (oh, and i also think musl should be changed to reuse the same ioctl for > detecting ttys as glibc, because asking kernel devs to never reuse the > numeric value of TCGETS on any other subsystem is a much simpler request than > asking it to avoid assignment of two ioctl codes. When the choice was originally made, it was based on a misunderstanding (same one in this thread, probably based on old strace(1) output) that TCGETS overlaps with an OSS MIDI ioctl number. However, I don't see any strong reason to change it. The 't' ioctl block is reserved for tty use and is not intended to be assigned to anything else; OSS using it was a legacy consideration that came from old non-Linux stuff. Adding new collisions would face backlash as "breaking userspace". The much higher risk is the above: devices just making a default response to *all* ioctl numbers, even ones not in their own ranges. And to this, either approach is equally susceptible. Ideally Linux would prevent this at a higher layer before passing the ioctl to the device backend, returning -ENOTTY if the ioctl type doesn't match the device class.
The only way I can see it being safe to conclude from EIO that the device is a tty is if (1) research determines that no existing drivers return -EIO for ioctls outside their own ranges, and (2) the kernel adds a safeguard so that non-tty drivers never see tty ioctls, so that there's no risk of this invariant suddenly being violated by a sloppy future driver.
The value of TCGETS differs widely between architectures, but none of them conflict with other current uses AFAICS.