The need for tcsetattr@GLIBC_2.42.1 and tcgetattr@GLIBC_2.42.1
H. Peter Anvin
hpa@zytor.com
Mon Sep 15 19:55:00 GMT 2025
On 2025-09-15 02:07, H. Peter Anvin wrote:
>
> They are CROSS APPLICATION incompatibilities.
>
> Basically, a broken application goes and modifies only part of the
> kernel state, and expect it to work, based on what kernel state older
> versions of glibc and *MOST* other applications simply didn't know about.
>
Perhaps I should explain this technically in more detail.
(TL;DR: switching normalization should help; there is no reason to
re-version the glibc-application interfaces.)
Depending on how you count, there can be at least 18 equivalent
"spellings" of the same baud rate in the kernel interface. This isn't
even dependent on the termios1 versus termios2 kernel interfaces, except
that on some platforms (including x86) not all of the fields of the
kernel internal termios structure (which is basically termios2) are
accessible.
The kernel doesn't normalize this to user space at all(*). When you
execute TCGETS*[2] you get back exactly what you set with TCSETS*[2],
and if you used TCSETS* on a platform where it isn't a complete
structure, the missing fields are simply unmodified and can thus contain
any garbage left over from previous use.
Therefore, it is the responsibility of user space to handle any of these
"spellings" when you use the kernel ioctl interface. Not at all
surprisingly, a lot of these hacked-up solutions outside glibc don't do
this correctly, and have just been "happening to work" because of what
the kernel-side setting has been more likely to look like. They would
still have tripped over themselves if another ioctl()-using app had set
one of the modes -- like split speed -- they didn't expect.
These are compiled completely separately from glibc, and their only
touch point with glibc is what "spelling" the kernel reports back from
TCGETS[2].
This is why my termios overhaul does normalization in the first place:
so that other applications will have a predictable "spelling", and also
so that current and legacy cf[gs]et[io]speed() can work *inside the same
executable* (e.g. from different dynamic libraries). The old interfaces
report and accept the legacy encodings and the new one the sane
encodings, out of the same data structure - except on the platforms
(MIPS and SPARC) where tc[gs]etattr() had to be versioned. (**)
The problem is that I mistakenly thought that "fill in every box on the
form with the same information" would be the best normalization. It
turns out that was incorrect; the better normalization is (and in
retrospect, this should have been obvious - the egg is totally on my
face on this one) "look as much like the legacy glibc as is practically
possible." This specifically means *not* filling in the input speed
legacy field (CIBAUD) -- leaving it at zero, which means the kernel will
internally set ispeed <- ospeed.
Since split speed is relatively rarely used (most modern physical UART
hardware doesn't even support it, since it would require a second baud
rate generator, and modern modems decouple the RS232 interface from the
line rate anyway; dumb V.23 1200/75 baud modems used for Teledata in the
1980s are ancient history) this should keep these older applications
working "as well as they currently are".
-hpa
(*) Nor can it change to do so, because applications are known that will
interpret it as a failure if TCSETS*[2] followed by TCGETS[2] doesn't
report a bitwise identical copy of the set data. This is based on the
rather daft error-reporting behavior of tcsetattr(); unfortunately THAT
doesn't help either, at least not in Linux, since the kernel barely
validates TCSETS*[2] data at all, and will happily let you set
completely nonsensical data, return success, and the kernel will simply
ignore those settings.
(**) This actually COULD be addressed, too, allowing the new
cf[gs]et[io]speed() interfaces to work against a legacy termios
structure; the key to that would be to hide a flag somewhere in the
structure to indicate the old versus new formats based on which form of
tcgetattr() was invoked. The downside is substantial complexity for two
platforms that today are pretty much only used as embedded platforms,
and the possibility of malfunction if somehow tcsetattr() is called on a
completely synthetic data structure, not being derived from tcgetattr().
As such, it seemed like more hassle than it would be worth.
More information about the Libc-alpha
mailing list