Skip to content

glibc 2.42 changed the definitions of baud rate constants (B50, B75, ..., B9600, B57600, ...) #4692

Closed

Description

@de-vri-es

In glibc 2.41 and before, baud rate constants like B50, B75, ..., B9600, B57600, B115200, ... were enum-like values. B50 was 1, B75 was 2, etc.

In glibc 2.42 they have been redefined. B50 is now just 50, and B75 is now just 75. Additionally, the tcset{i,o,}speed and tcget{i,o,}speed functions are adjusted to deal with this internally (the kernel struct going to the ioctl still needs the legacy values, or a custom baud rate with BOTHER).

The old baud rate constants are still available as __B50, __B75, ...

https://inbox.sourceware.org/libc-announce/5906001.DvuYhMxLoT@pinacolada/T/#u

  • On Linux, the <termios.h> interface now supports arbitrary baud rates; speed_t is redefined to simply be the baud rate specified as an unsigned int, which matches the kernel interface.

So.. is there a clean way to deal with this in the libc crate? The only portable thing for an application to do now is to manually do the TC{G,S}ETS2 ioctl and completely ignore the baud rate constants.

Activity

  1. devnexen commented on Sep 15, 2025

    @devnexen
    Contributor

    Pb with glibc version detection, it can be done at runtime only, ie gnu_get_libc_version() (or directly __libc_version symbol), not too great neither since it s a string.

  2. hpax commented on Sep 16, 2025

    @hpax

    See also discussion at: nix-rust/nix#2672

  3. de-vri-es commented on Sep 16, 2025

    @de-vri-es
    ContributorAuthor

    Maybe explicitly linking to an old version of the cf{g,s}et{i,o,}speed functions that support the old baud rate constants will work for now? In the future, when glibc 2.42 is widespread enough, the constants can be updated and the functions can be switched to the new ones.

    I guess the only question is: which version should we use now? :]

    objdump -T libc.so.6 | grep cfsetispeed
    000000000010eea0 g    DF .text	000000000000002a  GLIBC_2.42  cfsetispeed
    000000000016d0f0 g    DF .text	0000000000000054 (GLIBC_2.2.5) cfsetispeed
    

    I guess the GLIBC_2.2.25 version.

  4. hpax commented on Sep 16, 2025

    @hpax

    That would be a very poor solution, and will make users do what they have done in C code, poorly, for a long time now: invoke ioctl() directly, usually incorrectly.

    See my notes in the other linked thread.

    The legacy version number varies depending on architecture; also, on MIPS and SPARC the tcsetattr() and tcgetattr() symbols are also versioned, as the format of struct termios has changed.

    There are also the new cfget/setbaud() interfaces. If I'm not entirely mistaken, you might be able to do a weak undefined symbol to pick them up if available.

    Finally, here is a trick to probe at initialization time: create a dummy struct termios, and call something like cfsetospeed(&dummy_termios, 0x3fffffff) – if it succeeds, you are using the new glibc (in fact, any constant greater than 0x100f should fail on the old system.) This will not cause any system calls.

    You do know that the legacy baud rates constants, as well as struct termios format, are architecture-dependent, right?!

  5. de-vri-es commented on Sep 16, 2025

    @de-vri-es
    ContributorAuthor

    That would be a very poor solution, and will make users do what they have done in C code, poorly, for a long time now: invoke ioctl() directly, usually incorrectly.

    In Rust, people mostly don't use the libc crate directly. This is mostly a problem for library authors. Those already have to deal with the mess of serial ports, and they can not rely on glibc 2.42 being available. Not yet anyway.

    The legacy version number varies depending on architecture; also, on MIPS and SPARC the tcsetattr() and tcgetattr() symbols are also versioned, as the format of struct termios has changed.

    Thanks. And yeah. I did just figure out that the version symbols are architecture specific when trying to figure out the authoritative source for the right number (x86_64, aarch64).

    Finally, here is a trick to probe at initialization time: create an empty struct termios, and call something like cfsetospeed(&dummy_termios, 0x40000000) – of of succeeds, you are using the new glibc.

    Sadly, this will not be usable to change the actual value of the baud rate constants. This can only be done at build time because they are constants.

    You do know that the legacy baud rates constants, as well as struct termios format, are architecture-dependent, right?!

    I'm sure this is well intended, but I would appreciate it if you phrased a bit less aggressively. There's nothing new about that, it has already been the case before glibc 2.42.

    Anyway, when trying to use the old symbols from pre-2.42, I already run into the issue that the auto generated tests will fail because they will still use the latest version. So now I think the best thing to do is just to do a build time check which glibc is being used, base the value of the baud rate constants on that, and fail to link at runtime if glibc 2.42 is not available.

  6. de-vri-es commented on Sep 16, 2025

    @de-vri-es
    ContributorAuthor

    I also think it would be good if the libc crate uses versioned symbols correctly for glibc, but that will be a major overhaul.

  7. hpax commented on Sep 16, 2025

    @hpax

    That is consistent with glibc policy (you have to build and link against the headers and the .so file from the oldest version of glibc you want to run against.)

    Apologies if I'm a bit snippy about this; I have just seen a lot of ABI abuse recently.

  8. de-vri-es commented on Sep 16, 2025

    @de-vri-es
    ContributorAuthor

    No problem, thanks for the useful insights and information :)

  9. hpax commented on Sep 16, 2025

    @hpax

    As far as I can tell, it looks like the immediate consumer is the "unix" crate... which presumably should then get pulled by someone else...

  10. tgross35 commented on Sep 18, 2025

    @tgross35
    Member

    I'm trying to understand the exact situation here given it is evolving, but is there a specific symbol version that we would be able to link and continue using the old (octal) constants? If so, I think that would be the best solution here.

    I also think it would be good if the libc crate uses versioned symbols correctly for glibc, but that will be a major overhaul.

    We don't have a general mechanism for link-time symbol versioning as we haven't really needed it, but we can specify a version on a case-by-case basis. Currently there isn't anywhere this is used for glibc, but it is for some other platforms https://github.com/search?q=repo%3Arust-lang%2Flibc%20%2Flink_name.*%40%2F&type=code.

  11. de-vri-es commented on Sep 18, 2025

    @de-vri-es
    ContributorAuthor

    I'm trying to understand the exact situation here given it is evolving, but is there a specific symbol version that we would be able to link and continue using the old (octal) constants? If so, I think that would be the best solution here.

    There are specific versions, but it's architecture specific which version that is. Personally I'm in no mood to figure out the correct versions for each architecture and move the functions into the architecture specific submodules. And adjust the tests for each architecture.

    If this is the desired solution, I understand, but I don't have the time to do it.

    However, if we do, the behavior of the libc crate will not match the documentation of glibc. Since the libc crate does not come with it's own documentation, I also think this also somewhat confusing.

    Lastly, if we stick with the pre 2.42 versions, then it is not possible to set custom baud rates on serial ports through libc. The only possibility is to bypass libc and do raw ioctls. People will likley mix raw ioctls with using libc APIS (I did), and that will lead to undefined behaviour.

    I also think it would be good if the libc crate uses versioned symbols correctly for glibc, but that will be a major overhaul.

    We don't have a general mechanism for link-time symbol versioning as we haven't really needed it, but we can specify a version on a case-by-case basis. Currently there isn't anywhere this is used for glibc, but it is for some other platforms https://github.com/search?q=repo%3Arust-lang%2Flibc%20%2Flink_name.*%40%2F&type=code.

    Doing this case-by-case can lead to undefined behaviour: libc is upgraded, a rust program compiles with a new glibc, it uses new versions of symbols that have not explicitly been pinned to a version. However, constants and struct layouts in the libc crate have not been updated to match the ABI change. (This is exactly what happened to serial2, fixed here.)

  12. tgross35 commented on Sep 18, 2025

    @tgross35
    Member

    There are specific versions, but it's architecture specific which version that is. Personally I'm in no mood to figure out the correct versions for each architecture and move the functions into the architecture specific submodules. And adjust the tests for each architecture.

    No need to move anything around, it would look something like adding this to each of the relevant functions, like we do for similar cases:

    #[cfg_attr(all(
            target_env = "gnu",
            any(target_arch = "...")
        ),
        link_name = "...@GLIBC_2.0"
    )]
    #[cfg_attr(all(
            target_env = "gnu",
            any(target_arch = "...")
        ),
        link_name = "...@GLIBC_2.2"
    )]
    // ...

    With output collected from rg 'cf(get|set)[io]speed' -g '**/*.abilist' in glibc sources. A bit tedious, but of course it's not your responsibility if you're not up for it.

    However, if we do, the behavior of the libc crate will not match the documentation of glibc. Since the libc crate does not come with it's own documentation, I also think this also somewhat confusing.

    Agreed that this isn't ideal, but I don't think we can do much better without proper symbol versioning. If we pin a version, adding a note in the docs to indicate that is reasonable.

    Lastly, if we stick with the pre 2.42 versions, then it is not possible to set custom baud rates on serial ports through libc. The only possibility is to bypass libc and do raw ioctls. People will likley mix raw ioctls with using libc APIS (I did), and that will lead to undefined behaviour.

    Where is the impedance mismatch here? The kernel seems to use enum-style baud variables https://github.com/torvalds/linux/blob/cbf658dd09419f1ef9de11b9604e950bdd5c170b/include/uapi/asm-generic/termbits-common.h#L30-L45, so I would expect the 2.42+ definitions to be more problematic if mixing and matching there.

    Doing this case-by-case can lead to undefined behaviour: libc is upgraded, a rust program compiles with a new glibc, it uses new versions of symbols that have not explicitly been pinned to a version. However, constants and struct layouts in the libc crate have not been updated to match the ABI change. (This is exactly what happened to serial2, fixed here.)

    To be clear: the concern is that case-by-case isn't sufficient compared to versioning all symbols, correct? As opposed to case-by-case versioning introducing UB that isn't possible otherwise. I opened #4700 to continue this discussion separate from baud rates.

  13. de-vri-es commented on Sep 18, 2025

    @de-vri-es
    ContributorAuthor

    Where is the impedance mismatch here? The kernel seems to use enum-style baud variables https://github.com/torvalds/linux/blob/cbf658dd09419f1ef9de11b9604e950bdd5c170b/include/uapi/asm-generic/termbits-common.h#L30-L45, so I would expect the 2.42+ definitions to be more problematic if mixing and matching there.

    The problem comes when passing glibc structs to the kernel or passing kernel structs to glibc. And you're forced to do in glibc before 2.42 because it didn't support custom baud rates. (Okay, not forced, you should be translating between them properly yourself, but they used to be ABI compatible, I think.)

    I mean, it has always been messy dealing with serial ports. glibc 2.42 improved it a lot, but now we end up with even more chaos in the libc crate :]

    To be clear: the concern is that case-by-case isn't sufficient compared to versioning all symbols, correct? As opposed to case-by-case versioning introducing UB that isn't possible otherwise. I opened #4700 to continue this discussion separate from baud rates.

    Yeah. And I would be worried that cherry-picking some symbols can easily lead to a set of linked functions in libc that don't agree on struct definitions or constants.

    But, on the other hand, right now we also end up with functions that don't agree with the struct definitions and constants defined in libc.

  14. hpax commented on Oct 3, 2025

    @hpax
  15. tgross35 commented on Jan 15, 2026

    @tgross35
    Member

    This issue should have been resolved as of the 0.2.179 since the symbols now pin the old glibc version, so I'm going to close this.

    There is one followup issue if anyone has any ideas, seems like things still aren't working properly specifically on ppc64le #4921.

  16. de-vri-es commented on Jan 16, 2026

    @de-vri-es
    ContributorAuthor

    As mentioned on the other issue: I'm not sure that pinning only tc[gs]et[io]speed is enough: for some platforms the layout of the termios struct changed, so all functions taking a termios should be pinned. Especially tc[gs]etattr.

  17. tgross35 commented on Jul 9, 2026

    @tgross35
    Member

    I believe that with 6abfe75 those should have been taken care of. Feel free to correct me again if there are others :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions