[HN Gopher] The perils of transition to 64-bit time_t
       ___________________________________________________________________
        
       The perils of transition to 64-bit time_t
        
       Author : todsacerdoti
       Score  : 273 points
       Date   : 2024-09-28 16:31 UTC (1 days ago)
        
 (HTM) web link (blogs.gentoo.org)
 (TXT) w3m dump (blogs.gentoo.org)
        
       | kccqzy wrote:
       | My biggest takeaway (and perhaps besides-the-point) is this:
       | 
       | > Musl has already switched to that, glibc supports it as an
       | option. A number of other distributions such as Debian have taken
       | the leap and switched. Unfortunately, source-based distributions
       | such as Gentoo don't have it that easy.
       | 
       | While I applaud their efforts I just think as a user I want to be
       | over and done with this problem by switching to a non source-
       | based distribution such as Debian.
        
         | wtallis wrote:
         | It sounds like the difficulty for source-based distributions
         | comes from trying to do an in-place upgrade that makes
         | incompatible changes to the ABI. So changing to an entirely
         | different distribution would be at least as disruptive (though
         | possibly less time consuming) as doing a clean install of
         | Gentoo using a new ABI.
        
           | akira2501 wrote:
           | Starting a few years ago I partition all my drives with two
           | root partitions. Once is used and the other is blank. For
           | precisely this reason. Sometimes it's easier to just roll out
           | a brand new stage3 onto the unused partition, build an
           | entirely new root, and then just move over to that once it's
           | finished.
           | 
           | The bonus is you can build your new system from your old
           | system using just chroot. Very convenient.
        
             | jasomill wrote:
             | Several OSes I use on a daily basis (FreeBSD, Fedora
             | CoreOS, and Fedora Kinoite) adopt related strategies as
             | part of their regular (binary) update processes:
             | 
             | https://wiki.freebsd.org/BootEnvironments
             | 
             | https://coreos.github.io/rpm-ostree/
        
             | naitgacem wrote:
             | Android adopted an approach in a similar spirit.
             | 
             | https://source.android.com/docs/core/ota/ab
        
         | ordu wrote:
         | There is an easy way to deal with this in Gentoo. Boot from usb
         | or something, run mkfs.ext4 (or .whatever fs you use) on your /
         | and /usr partitions, mount them, unpack stage3 on them, chroot
         | into them and run `emerge $all-my-packages-that-where-
         | installed-before-mkfs`.
         | 
         | You can install new copy of Gentoo instead of upgrading it
         | incrementally.
        
           | robin_reala wrote:
           | "easy"
        
             | ordu wrote:
             | It is easy. It is just a tl;dr version of "how to install
             | gentoo". :)
        
               | lnxg33k1 wrote:
               | I hope I will get a day where I have the will to
               | recompile everything in the next 14 years, I think I had
               | the same install for the past 10 or so on the gentoo
               | desktop. The only think I reinstalled recently has been
               | the laptop to switch from arch to sid
        
         | viraptor wrote:
         | > by switching to a non source-based distribution such as
         | Debian.
         | 
         | The distinction has more nuance. Source based distros like
         | nixos don't have the same issue. The problem is more in how
         | Gentoo builds/installs the packages than in building from
         | source.
         | 
         | Also with third party closed source software, you're still
         | going to have issues, even on binary systems. Actually you
         | could even have issues with the first party packages if they're
         | installed in separate independent steps.
        
       | n_plus_1_acc wrote:
       | I'm no expert on C, but I was under the impression that type
       | aliases like off_t are introduced to have the possibility to
       | change then later. This clearly doesn't work. Am I wrong?
        
         | beached_whale wrote:
         | at the source level it does, but when you have compiled
         | libraries it breaks.
        
         | wtallis wrote:
         | Source vs binary compatibility. Using typedefs like off_t mean
         | you usually don't have to re-write code, but you do have to re-
         | compile _everything_ that uses that type.
        
           | rblatz wrote:
           | But isn't the point of a source only distribution like
           | Gentoo, to build everything yourself? Who is running gentoo
           | but also lugging around old precompiled stuff they don't have
           | the source for?
        
             | mananaysiempre wrote:
             | As the post describes, the problem is that on Gentoo you
             | can't really build everything then switch binaries for
             | everything, or at least that's not what happens when you
             | update things the usual way.
             | 
             | Instead, dependencies are built and then installed, then
             | dependents are built against the installed versions and
             | then installed, etc. In the middle of this process, the
             | system can technically be half-broken, because it could be
             | attempting to run older dependents against newer
             | dependencies. Usually this is not a real problem. Because
             | of how pervasive time_t is, though, the half-broken state
             | here is potentially _very_ broken to the point that you may
             | be unable to resume the update process if anything breaks
             | for any reason.
        
         | progbits wrote:
         | Edit: nevermind others raise better points.
        
           | cataphract wrote:
           | That's not the problem. If time_t was a struct with a single
           | int32_t, you'd be in the same situation when you changed it
           | to int64_t (ABI incompatibility: you need more space to store
           | the value now).
           | 
           | In order not to have this problem you'd need the API to use
           | opaque struts, for which only pointers would be exchanged.
        
         | jonathrg wrote:
         | It does work, the problem with ABI changes is that when you
         | change it, you have to change it everywhere at the same time.
         | By default there's nothing stopping you from linking one
         | library built using 32-bit off_t with another library built
         | using 64-bit off_t, and the resulting behaviour can be
         | incredibly unpredictable.
        
         | smueller1234 wrote:
         | They make it easier, but just at a source code level. They're
         | not a real (and certainly not full) abstraction. An example
         | that'll be making it obvious: if you replace the underlying
         | type with a floating point type, the semantics would change
         | dramatically, fully visible to the user code.
         | 
         | With larger types that otherwise have similar semantics, you
         | can still have breakage. A straightforward one would be padding
         | in structs. Another one is that a lot of use cases convert
         | pointers to integers and back, so if you change the underlying
         | representation, that's guaranteed to break. Whether that's a
         | good or not is another question, but it's certainly not
         | uncommon.
         | 
         | (Edit: sibling comments make the same point much more
         | succinctly: ABI compatibility!)
        
         | SkiFire13 wrote:
         | Yeah, the problem in general with type aliases is that they're
         | just that, aliases, not proper types. This means that they leak
         | all the details of the underlying type, and don't work for
         | proper encapsulation, which is what's needed for being able to
         | change what should be implementation details.
        
           | tsimionescu wrote:
           | The problem here would be exactly the same regardless of what
           | form this type took. The issue is fundamental to any low
           | level language: the size of a type is a part of its public
           | API (well, ABI), by definition. This is true in C++ with
           | private fields as well, for example: if you add a private
           | field to a class, or change the type of a private field with
           | one of a different size, all code using that class has to be
           | re-built. The only way to abstract this is to use types
           | strictly through pointers, the way Java does.
        
         | ordu wrote:
         | It kinda work, but not in a source based distro. If you can
         | atomically rebuild @world with changed definition of off_t,
         | then there will be no problem. But source based distro doesn't
         | rebuild @world atomically. It rebuild one package at time, so
         | there would be inconveniences like libc.so has 64-bit off_t,
         | while gcc was build for 32-bit off_t, so gcc stops working. Or
         | maybe bash, coreutils, make, binutils or any other package that
         | is needed for rebuilding @world. At this point you are stuck.
         | 
         | So such upgrade needs care.
        
           | codys wrote:
           | > there would be inconveniences like libc.so has 64-bit off_t
           | 
           | glibc specifically has support for the 32-bit and 64-bit
           | time_t abi simultaneously.
           | 
           | From the post:
           | 
           | > What's important here is that a single glibc build remains
           | compatible with all three variants. However, libraries that
           | use these types in their API are not.
        
             | ordu wrote:
             | Yeah, glibc was a bad example. Probably libz.so or
             | something would be better.
        
               | codys wrote:
               | Yep, agreed. Though it does expose an option here which
               | would be "have glibc provide a mechanism for other
               | libraries (likely more core/widely used libs) to support
               | both ABIs simultaneously.
               | 
               | Presumably if that had been done in glibc when 64-bit
               | time_t support was added, we could have had multi-size-
               | time ABI support in things like zlib by now. Seems like a
               | mistake on glibc's part not to create that initially
               | (years ago).
               | 
               | Though if other distros have already switched, I'd posit
               | that perhaps Gentoo needs to rethink its design a bit so
               | it doesn't run into this issue instead.
        
               | jasomill wrote:
               | This sounds like something similar to NeXTSTEP/macOS fat
               | binaries, only with the possibility of code sharing
               | between "architectures".
               | 
               | I like it, though it sounds like something that'd be
               | unlikely to see adoption in the Linux world without the
               | endorsement of multiple major distros.
        
               | codys wrote:
               | Fat binaries (packing together 2 entirely different
               | objects and picking one of them) could potentially work
               | here, though I suspect we'd run into issues of mixed
               | 32-bit/64-bit time_t things.
               | 
               | Another option that's closer to how things work with LFS
               | (large file support) (mostly in the past now) is to use
               | create different interfaces to support 64-bit time_t, and
               | pick defaults for time_t at compile time with a macro
               | that picks the right impl.
               | 
               | Also possible could be having something like a version
               | script
               | (https://sourceware.org/binutils/docs/ld/VERSION.html) to
               | tell the linker what symbols to use when 64-bit time_t
               | was enabled. While this one might have some benefits,
               | generally folks avoid using version scripts when
               | possible, and would require changes in glibc, making it
               | unlikely.
               | 
               | Both of those options (version script extention, and LFS-
               | like pattern) could allow re-using the same binary (ie:
               | smaller file size, no need to build code twice in
               | general), and potentially enable mixing 32-bit time_t and
               | 64-bit time_t code together in a single executable (not
               | desirable, but does remove weird link issues).
        
               | jasomill wrote:
               | I was thinking along the lines of a simple extension to
               | the Mach-O fat binary concept to allow each member of the
               | fat binary archive to be associated with one or more ABIs
               | rather than just a single architecture.
               | 
               | Then all the time_t size-independent code would go into a
               | member associated with both 32-bit and 64-bit time_t
               | ABIs, and all the time_t size-dependent code would be
               | separately compiled into single-ABI members, one for each
               | ABI.
               | 
               | For both executables and libraries, name conflicts
               | between members associated with the same ABI would be
               | prohibited at compile/link time.
               | 
               | The effective symbol table for both compile time and
               | runtime linking would then be the union of symbol tables
               | defined by all members associated with the active ABI.
               | 
               | Language-level mechanisms that allow compilers to
               | recognize and separately compile time_t-dependent symbols
               | would be required, ideally implemented in ways that allow
               | such dependence to be inferred in most if not all cases.
               | 
               | While compilers would be free to size-optimize code by
               | splitting functions into dependent and independent
               | subparts, I see no immediate reason why this needs to be
               | explicitly supported at link level.
               | 
               | Finally, as I imagine it, mixing 32- and 64-bit time_t
               | ABIs would be prohibited -- implicitly, as 64-bit time_t
               | symbols aren't ever visible to 32-bit time_t code and
               | vice versa -- with code that needs to support non-native
               | time_t values (for I/O, IPC, etc.) left its own devices,
               | just like code dealing with, _e.g.,_ non-native integer
               | and floating-point formats today.
               | 
               | Admittedly this sounds like a lot of unnecessary work vs.
               | similar alternatives built on existing multi-arch
               | mechanisms, but it's still an interesting idea to
               | contemplate.
        
               | o11c wrote:
               | > have glibc provide a mechanism for other libraries
               | (likely more core/widely used libs) to support both ABIs
               | simultaneously
               | 
               | The linked article is wrong to imply this isn't possible
               | - and it really doesn't depend on "GLIBC provide a
               | mechanism". All you have to do is:
               | 
               | * Compile the library itself with traditional time_t
               | (32-bit or 64-bit depending on platform), but convert and
               | call time64 APIs internally.
               | 
               | * Make a copy of every public structure that embeds a
               | time_t (directly or indirectly), using time64_t (or
               | whatever contains it) instead.
               | 
               | * Make a copy of every function that takes any time-using
               | type, to use the time64 types.
               | 
               | * In the public headers, check if everything is compiled
               | with 64-bit time_t, and if so make all the traditional
               | types/functions aliases for the time64 versions.
               | 
               | * Disable most of this on platforms that already used
               | 64-bit time_t. Instead (for convenience of external
               | callers) make all the time64 names aliases for the
               | traditional names.
               | 
               | It's just that this is a lot of work, and little benefit
               | to any particular library. the GCC 5 std::string
               | transition is probably a better story than LFS, in
               | particular the compiler-supported `abi_tag` to help
               | detect errors (but I think that's only for C++, ugh -
               | language without room for automatic mangling suck).
               | 
               | (minor note: using typedefs rather than struct tags for
               | your API makes this easier)
        
               | AshamedCaptain wrote:
               | This is a nightmare to implement (unless you go library
               | by library and do it by hand, or you have significant
               | compiler help), as e.g. functions may not directly use a
               | struct with time_t but indirectly assume something about
               | the size of the struct.
               | 
               | Note that for example the std::string transition did not
               | do this.
        
           | Athas wrote:
           | Why not essentially treat it as a cross compilation scenario?
           | NixOS is also source based, but I don't think such a
           | migration would be particularly difficult. You'd use the
           | 32-bit off_t gcc to compile a glibc with 64-bit off_t, then
           | compile a 64-bit off_t gcc linked against that new glibc, and
           | so on. The host compiler shouldn't matter.
           | 
           | I always understood the challenge as _binary_ compatibility,
           | when you can 't just switch the entire world at once.
        
             | codys wrote:
             | Nixos has it easier here because they don't require
             | packages to be "installed" before building code against
             | them. For Gentoo, none of their build scripts (ebuilds) are
             | written to support that. It's plausible that they might
             | change the embuild machinery so that this kind of build
             | (against non-installed packages) could work, but it would
             | need investigation and might be a difficult lift to get it
             | working for all packages.
             | 
             | "treat it as a cross compilation scenario" is essentially
             | what the post discusses when they mention "use a different
             | CHOST". A CHOST is a unique name identifying a system
             | configuration, like "x86_64-unknown-linux-gnu" (etc).
             | Gentoo treats building for different CHOSTs as cross
             | compiling.
        
             | andrewaylett wrote:
             | NixOS isn't the same kind of source-based. At some level,
             | even Debian could be said to be source based: there's
             | nothing stopping you from deciding to build every package
             | from source before installing it, and obviously the
             | packages are themselves built from source at some point.
             | 
             | NixOS sits between Debian and Gentoo, as it maintains an
             | output that's capable of existing independently of the rest
             | of the system (like Debian) but is designed to use the
             | current host as a builder (like Gentoo). Gentoo doesn't
             | have any way to keep individual builds separate from the
             | system as a whole, as intimated in the article, so you need
             | to work out how to keep the two worlds separate while you
             | do the build.
             | 
             | I think what they're suggesting winds up being pretty
             | similar to what you suggest, just with the right plumbing
             | to make it work in a Gentoo system. NixOS would need
             | different plumbing, I'm not sure whether they've done it
             | yet or how but I can easily imagine it being more
             | straightforward than what Gentoo is needing to do.
        
               | tadfisher wrote:
               | There absolutely will be problems with different Nix
               | profiles that aren't updated together; for example, if
               | you update some packages installed in your user's profile
               | but not the running system profile. But this is common
               | enough with other glibc ABI breakage that folks tend to
               | update home and system profiles together, or know that
               | they need to reboot.
               | 
               | Where it will be hell is running Nix-built packages on a
               | non-NixOS system with non-ABI-compatible glibc. That is
               | something that desperately needs fixing on the glibc
               | side, mostly from the design of nss and networking, that
               | prevent linking against glibc statically.
        
               | oasisaimlessly wrote:
               | > Where it will be hell is running Nix-built packages on
               | a non-NixOS system with non-ABI-compatible glibc.
               | 
               | This isn't a thing. Nix-built binaries all each use the
               | hardcoded glibc they were built with. You can have any
               | number of glibc's simultaneously in use.
        
           | ArsenArsen wrote:
           | this is just as much of a problem on binary distros mind you.
           | just because there might be a smaller time delta does not
           | mean that the problem is solved
        
           | codys wrote:
           | nixos is an example of a distro that is source based and can
           | do the atomic rebuild here because it has a way to build
           | packages against other packages that aren't "installed". In
           | nixos, this is because there are very few things that get
           | "installed" as the global, only thing to use. But one could
           | imagine that Gentoo could build something that would allow
           | them to at least build up one set of new packages without
           | installing them and then install the files all at once.
        
           | jasomill wrote:
           | As someone whose nearest exposure to a "source based distro"
           | is FreeBSD, this sounds like madness, as it means a broken
           | build could not only impair attempts to repair the build, but
           | render the system unbootable and/or unusable.
           | 
           | And as someone who regularly uses traditional Linux distros
           | like Debian and Fedora, the idea that a package management
           | system would allow a set of packages known to be incompatible
           | with one another to be installed without force, or that core
           | package maintainers would knowingly specify incorrect
           | requirements, is terrifying.
           | 
           | While I'm not familiar with Gentoo, my reading of this
           | article suggests that its maintainers are well aware of these
           | sorts of problems and that Gentoo does not, in fact, suffer
           | from them (intentionally; inevitably mistakes happen just as
           | they occasionally do on the bleeding edge of FreeBSD and
           | other Linux distros).
        
             | AshamedCaptain wrote:
             | FreeBSD does not use package management for the core system
             | (i.e. freebsd itself, not ports). Gentoo does.
        
         | raldi wrote:
         | I think the main reason they're introduced is to provide a hint
         | to the programmer and maybe some typesafety against
         | accidentally passing, say, a file descriptor.
        
         | jandrese wrote:
         | It's only the first step of the puzzle. And arguably only a
         | half step. As the article points out anytime that off_t is
         | stuffed into a struct, or used in a function call, or
         | integrated into a protocol, the abstraction is lost and the
         | actual size matters. Mixing old and new code, either by loading
         | a library or communicating over a protocol, means you get
         | offsets wrong and things start crashing. Ultimately the
         | changeover requires everybody to segregate their programs
         | between "legacy" and "has been ported or at least looked over",
         | which is incredibly painful.
        
         | devit wrote:
         | The C library uses macros so that the referenced symbols are
         | different depending on ABI (e.g. open becomes open64, etc.),
         | but most 3rd party libraries don't bother with that, so they
         | break if their API uses time_t/off_t.
        
       | BoingBoomTschak wrote:
       | A very thoughtful way of handling a problem much trickier than
       | the earlier /usr merge. Had thought about 2 but not 1 and 3. I
       | also had kinda forgotten that 2038 thing, time sure is flying!
       | 
       | I must say, mgorny's posts are always a treat for those who like
       | to peer under the hood! (The fact that Gentoo has remained my
       | happy place for years doesn't influence this position, though
       | there's probably some correlation)
        
       | fred_is_fred wrote:
       | Besides epoch time and the LFS support mentioned, are there any
       | other 32-bit bombs waiting for Linux systems like this?
        
         | fph wrote:
         | IPv4, technically, is another 32-bit bomb, but that's not
         | Linux-specific.
        
           | samatman wrote:
           | I wouldn't call this a bomb at all. Bombs are events, not
           | processes.
           | 
           | Resource contention for IPv4 has been around for a long time,
           | with a number of workarounds and the ultimate out of
           | supporting IPv6. There has been, to date, no moment of
           | crisis, nor do I expect one in the future.
           | 
           | It will just get steadily more annoying/expensive to use IPv4
           | and IPv6 will relieve that pressure incrementally. We're at
           | least two decades into that process already.
        
             | poincaredisk wrote:
             | Two decades in, and IPv6 is still the more annoying option.
             | 
             | I wish they were less ambitious and just increased address
             | sizes when designing ipv6.
        
               | icedchai wrote:
               | If more people bothered to configure IPv6 instead of
               | complaining about it, V4 would be a thing of the past
               | already.
        
               | o11c wrote:
               | A lot of people don't have a choice.
               | 
               | Just last(?) year I finally switched to an ISP whose
               | equipment supports IPv6. But I still can't actually use
               | it since my wifi router's support for IPv6 somehow fails
               | to talk to the ISP equipment.
               | 
               | Hm, there's a firmware upgrade ... (installs it) well,
               | re-trying all those options again, it looks like _one_ of
               | them lets it finally work now. The others (including the
               | default) still fail with arcane errors though!
        
               | icedchai wrote:
               | I mean, you are right. ISPs also need to stop complaining
               | about supporting it and get off their butts. All
               | enterprise routing equipment for the past 15+ years has
               | supported IPv6. There is no excuse not to support it.
               | Once you understand it, it is actually simpler than IPv4
               | + NAT.
        
         | tredre3 wrote:
         | The ext file system uses many 32bit counters. Admittedly,
         | version 4 fixed most of that (when formatted with the correct
         | options).
        
           | kbolino wrote:
           | In a similar vein, inodes can run out. On most conventional
           | Linux file systems, inode numbers are 32 bits.
           | 
           | For many, this is not going to be a practical problem yet, as
           | real volumes will run out of usable space before exhausting
           | 2^32 inodes. However, it is theoretically possible with a
           | volume as small as ~18 TiB (using 16 TiB for 2^32 4096-byte
           | or smaller files, 1-2 TiB for 2^32 256- or 512-byte inodes,
           | plus file system overheads).
           | 
           | Anticipating this problem, most newer file systems use 64-bit
           | inode numbers, and some older ones have been retrofitted
           | (e.g. inode64 option in XFS). I don't think ext4 is one of
           | them, though.
        
             | lclarkmichalek wrote:
             | It does happen in prod. Usually due to virtual FSes that
             | rely on get_next_ino: https://lkml.org/lkml/2020/7/13/1078
        
               | Dylan16807 wrote:
               | That method is wrapping and not checking for collisions?
               | I would not call that a problem of _running out_ then. It
               | 's a cheap but dumb generator that needs extra bits to
               | not break itself.
        
       | loeg wrote:
       | The C standard does not require time_t to be signed (nor does
       | POSIX). Just changing the 32-bit type to unsigned would (in some
       | senses) extend the lifetime of the type out to 2106. You could at
       | least avoid some classes of ABI breakage in this way. (On the
       | other hand, Glibc has explicitly documented that its time_t is
       | always signed. So they do not have this option.)
        
         | umanwizard wrote:
         | You would then lose the ability to represent times before Jan.
         | 1st, 1970. Which is not just a theoretical concern; those times
         | appear e.g. in databases with people's date of birth.
        
           | nobluster wrote:
           | And a signed 32 bit time_t with an epoch of 1970 cannot
           | represent dates before 1902. Using time_t to store legacy
           | dates is not advisable - even if you ignore all the issues
           | with time zones and changing local laws pertaining to offsets
           | from UTC and daylight saving time.
        
             | pwg wrote:
             | While true, that limitation has always existed, so everyone
             | has already implemented whatever was necessary to represent
             | dates earlier than that.
             | 
             | Changing 32-bit time_t to unsigned suddenly makes all dates
             | from 1902 to Jan 1 1970 which were stored using time_t
             | (even if it was non-advisable, it still will have occurred)
             | appear to teleport into the future beyond 2038.
        
               | cryptonector wrote:
               | That's alright, see, because I have no filesystems, no
               | tarballs, no backups, no files on any kind of media with
               | timestamps before 1970, and indeed, no one can except by
               | manually setting those timestamps -- and why bother doing
               | that?!
               | 
               | So any pre-1970 32-bit signed timestamps will be in...
               | not spreadsheets, in what? In databases? No, not either.
               | So in some sort of documents, so software consuming those
               | will need fixing, but we're not going to see much
               | teleporting of 1902-1970 timestamps to 2038-2106. I'm not
               | concerned.
        
               | jasomill wrote:
               | Many important software projects predate UNIX. Perhaps
               | you want to create a Git repository for one of them, with
               | historically accurate commit timestamps?
        
               | loeg wrote:
               | Git represents timestamps in ASCII strings, like "12345."
               | Not time_t. (I am not sure if it even allows negative
               | integers in this format.)
        
               | jasomill wrote:
               | I was just using Git as an example of an application
               | where pre-1970 timestamps could be useful. But as it
               | turns out,                 $ git commit -m foo
               | --date='1969-12-31 00:00:00'       [main 25b6e63] foo
               | Date: Sun Dec 30 23:00:00 2012 -0500        1 file
               | changed, 0 insertions(+), 0 deletions(-)        create
               | mode 100644 bar
        
               | cryptonector wrote:
               | Well, Git doesn't seem to set file timestamps when
               | cloning. And as the sibling comment says, Git doesn't use
               | 32-bit signed integers to store timestamps. So this is
               | not a problem.
               | 
               | If, however, Git were to some day get an option to set
               | file mtimes and atimes at clone time to the last-modified
               | time of the files based on commit history, then you could
               | always just use a 64-bit system where `time_t` is 64-bit.
        
               | cesarb wrote:
               | More importantly than a git repository, you might have
               | archives (a .tar or equivalent) of these software
               | projects, and might want to unpack them while preserving
               | the timestamps contained within these archives.
        
               | cryptonector wrote:
               | Unix did not exist before 1970. Neither did tar. There
               | are no tar files with files with timestamps before 1970
               | _unless_ you've used `touch -t 195001010000` or
               | `utime(2)` or `futimes(3)` to set the files' time to
               | before 1970. That seems pretty unlikely.
        
           | bloak wrote:
           | Serious question: do people use time_t for representing a
           | date of birth?
           | 
           | To me that wouldn't seem right: a date of birth isn't a
           | timestamp and you typically receive it without a
           | corresponding place or time zone so there's no reasonable way
           | to convert it into a timestamp.
           | 
           | (The other problem is that a signed 32-bit time_t only goes
           | back to 1901. You might not have to deal with a date of birth
           | before 1901 today, unless you're doing genealogy, of course,
           | but until fairly recently it's something you'd probably want
           | to be able to handle.)
        
             | nomel wrote:
             | My birth certificate has location, day, year, hour, and
             | minute of birth. Birth is an event in time, perfectly
             | represented with a (UTC) timestamp.
        
               | Merad wrote:
               | Your birth is an event that happened at an instant in
               | time, but very few systems concern themselves with that
               | detail. The vast majority need to store birth _date_ and
               | have no interest in the time or location.
        
               | nomel wrote:
               | > have no interest in the time
               | 
               | Setting the bottom couple of bytes to zero achieves this,
               | while maintaining nearly universal consistency with all
               | other events in time that might need to be related with a
               | packing of bits. People do it because it's what's being
               | used at nearly every other level of the stack.
        
               | cryptonector wrote:
               | > Birth is an event in time, perfectly represented with a
               | (UTC) timestamp.
               | 
               | That's not the same thing as `time_t` though. `time_t` is
               | UTC. UTC is not `time_t`.
        
             | umanwizard wrote:
             | > Serious question: do people use time_t for representing a
             | date of birth?
             | 
             | I have seen people in real life use seconds since the UNIX
             | epoch to represent DOB, yes.
        
           | loeg wrote:
           | Databases do not and can not use system time_t. Consider how
           | their on-disk state would be impacted by a change from 32-bit
           | time_t to 64-bit! Instead they use specific or variable size
           | integer types.
        
           | cryptonector wrote:
           | Those databases might not even use `time_t`. It's their
           | problem anyways, not the OS's.
        
           | pezezin wrote:
           | Does any database actually use time_t? PostgreSQL uses its
           | own datatype, the number of microseconds since 4713 BC. I am
           | sure that other databases do the same.
           | 
           | https://www.postgresql.org/docs/current/datatype-
           | datetime.ht...
        
         | scheme271 wrote:
         | If time_t is unsigned, how do times before the unix epoch get
         | represented?
        
           | loeg wrote:
           | They don't, much like they already do not. 32-bit time_t has
           | always been finite, and 1970 was a long, long time ago. (See
           | "in some senses" in my earlier comment.)
        
             | ykonstant wrote:
             | >1970 was a long long time
             | 
             | I thought it was a time_t! (>=*<=)
        
         | pwg wrote:
         | While that avoids 2038 as a "drop dead" date for 32-bit time_t,
         | it also removes the ability to represent any date/time prior to
         | 00:00:00 UTC on 1 January 1970 using 32-bit time_t because you
         | lose the ability to represent negative values.
         | 
         | Having all existing stored date/times that are currently prior
         | to the epoch suddenly become dates post 2038 is also not a good
         | scenario.
        
           | loeg wrote:
           | > it also removes the ability to represent any date/time
           | prior to 00:00:00 UTC on 1 January 1970 using 32-bit time_t
           | 
           | Yes, of course. This is probably not the main use of negative
           | values with signed time_t, though -- which is just
           | representing the result of subtraction when the operand
           | happened before the subtrahend.
           | 
           | > Having all existing stored date/times that are currently
           | prior to the epoch suddenly become dates post 2038 is also
           | not a good scenario.
           | 
           | In practice, there are ~zero of these on systems with 32-bit
           | time_t and a challenging migration path as we approach 2038.
        
             | cryptonector wrote:
             | > Yes, of course. This is probably not the main use of
             | negative values with signed time_t, though -- which is just
             | representing the result of subtraction when the operand
             | happened before the subtrahend.
             | 
             | This is definitely a bigger concern, yes. One has to be
             | very careful with subtraction of timestamps. But to be fair
             | one already had to be very careful before because POSIX
             | doesn't say what the size or signedness of `time_t` is to
             | begin with.
             | 
             | Indeed, in POSIX `time_t` can even be `float` or
             | `double`[0]!                 time_t and clock_t shall be
             | integer or real-floating types.
             | 
             | Though on all Unix, BSD, Linux, and any Unix-like systems
             | thankfully `time_t` is always integral. It's really only
             | size and signedness that one has to be careful with.
             | 
             | Thus one should always subtract only the smaller value from
             | the larger, and cast the result to a signed integer. And
             | one has to be careful with overflow. Fortunately
             | `difftime()` exists in POSIX. And there's a reason that
             | `difftime()` returns a `double`: to avoid having the caller
             | have to deal with overflows.
             | 
             | Basically working safely with `time_t` arithmetic is a real
             | PITA.                 [0] https://pubs.opengroup.org/online
             | pubs/009696799/basedefs/sys/types.h.html
        
               | loeg wrote:
               | > Indeed, in POSIX `time_t` can even be `float` or
               | `double`[0]!
               | 
               | Standard C, yes. Newer POSIX (your link is to the 2004
               | version) requires time_t be an integer type: https://pubs
               | .opengroup.org/onlinepubs/9699919799/basedefs/sy...
               | 
               | > Though on all Unix, BSD, Linux, and any Unix-like
               | systems thankfully `time_t` is always integral. It's
               | really only size and signedness that one has to be
               | careful with. Thus one should always subtract only the
               | smaller value from the larger, and cast the result to a
               | signed integer. And one has to be careful with overflow.
               | Fortunately `difftime()` exists in POSIX. And there's a
               | reason that `difftime()` returns a `double`: to avoid
               | having the caller have to deal with overflows.
               | 
               | > Basically working safely with `time_t` arithmetic is a
               | real PITA.
               | 
               | Yes.
        
               | cryptonector wrote:
               | Yes, I know that was older POSIX. But we're talking about
               | old code, the unstated context is portability over a long
               | period of time, and I wanted to make a point :)
               | 
               | So use `difftime()`, don't assume signedness or size, but
               | do assume that it's an integral type.
        
           | cryptonector wrote:
           | Stored _where_ exactly?
        
         | petee wrote:
         | Openbsd did just this for 32bit compat when they changed to
         | 64bit time 12 years ago, and seems to have worked out fine.
        
           | brynet wrote:
           | No, time_t is a signed 64-bit type on all architectures,
           | 64-bit architectures have no "32bit compat".
           | 
           | https://www.openbsd.org/55.html
        
             | petee wrote:
             | From the notes, this is what i was referring to, I think I
             | just mistook the meaning -
             | 
             |  _Parts of the system that could not use 64-bit time_t were
             | converted to use unsigned 32-bit instead, so they are good
             | till the year 2106_
        
       | codys wrote:
       | There are a few options for Gentoo not discussed in the post,
       | possibly because for Gentoo they would be a larger amount of work
       | due to the design of their system:
       | 
       | 1. Allow building against packages without installing them. The
       | core issue here is that Gentoo package build and install happen
       | as a single step: one can't "build a bunch of things that depend
       | on one another" and then "atomically install all the build items
       | into place". This means that Gentoo can easily be partially
       | broken when one is doing updates when an ABI change occurs
       | (modulo so versioning, see next option). This issue with 64-bit
       | time_t is an example of an ABI change that folks are very aware
       | of and is very widespread. It's also an example of something that
       | causes an ABI change that isn't handled by the normal `.so`
       | versioning scheme (see next option).
       | 
       | 2. Extend the normal `.so` versioning to capture changes to the
       | ABI of packages caused by packages they depend on. Normally,
       | every `.so` (shared object/library) embeds a version number
       | within it, and is also installed with that version number in the
       | file name (`libfoo.so.1.0.0`, for example, would be the real
       | `.so` file, and would have a symlink from `libfoo.so` to tell the
       | linker which `.so` to use). This shared object version is
       | normally managed by the package itself internally (iow: every
       | package decides on their ABI version number to enable them to
       | track their own internal ABI breakages). This allows Gentoo to
       | upgrade without breaking everything while an update is going on
       | as long as every package manages their `.so` version perfectly
       | correctly (not a given, but does help in many cases). There is a
       | process in Gentoo to remove old `.so.x.y.z` files that are no
       | longer used after an install completes. What we'd need to do to
       | support 64-bit time_t is add another component to this version
       | that can be controlled by the inherited ABI of dependencies of
       | each `.so`. This is very similar in result to the "use a
       | different libdir" option from the post, but while it has the
       | potential to set things up to enable the same kinds of ABI
       | changes to be made in the future, it's likely that fixing this
       | would be more invasive than using a different libdir.
        
         | akira2501 wrote:
         | > Allow building against packages without installing them.
         | 
         | A partial staged update system would work the best. I should be
         | able to schedule the build of several new packages, have them
         | built into a sandbox, and have new compilations use the sandbox
         | first and then fallback to the system through a union, and then
         | once everything is build finally package up all the pieces and
         | then move them out of the sandbox and into the system at large.
         | 
         | You could make all gentoo updates transactional that way which
         | would be a huge boon in many other ways.
        
         | aaronmdjones wrote:
         | Gentoo already allows option 1 by specifying a directory to
         | ultimately install the finished image to (normally it is set to
         | /) [1]. One can do a complete rebuild of everything in @system
         | and @world, install it to the specified subdirectory, and then
         | sync it all over in one shot. Preferably you would do this from
         | a live session, although in theory you could also bind-mount /
         | onto a subdirectory of the place you reinstalled everything to,
         | chroot into it, and then sync it (what is now /) into the bind-
         | mounted real upper /.
         | 
         | [1] https://devmanual.gentoo.org/ebuild-writing/variables/#root
        
           | codys wrote:
           | It's true that Gentoo has some pieces of what they need to
           | build an implementation of option 1 (build packages that
           | depend on other packages before completeing an install), but
           | it's currently the case that that is not what happens when
           | one runs `emerge` (the gentoo packaging build/installing
           | tool), as you've noted one would need to write scripts that
           | wrap emerge (or do the same work manually) to attempt to
           | accomplish this today.
           | 
           | I suspect that using bind mounts and overlays (to allow the
           | "building packages" chroot a view of the "installed" root)
           | could be used to accomplish this, or alternately some
           | filesystem snapshotting features if we're thinking about this
           | from the "external to emerge" angle. (It's my understanding
           | that for Gentoo to do this ABI change, though, they need some
           | solution integrated into emerge).
           | 
           | To some extent, this kind of potential model also reminds me
           | of systems that integrate their package updating with
           | filesystem snapshotting to allow rollbacks and actually-
           | atomic upgrades of many files. I think one or more of the
           | solaris distributions did this?
        
             | aaronmdjones wrote:
             | > but it's currently the case that that is not what happens
             | when one runs `emerge` (the gentoo packaging
             | build/installing tool)
             | 
             | This would be a simple matter of changing
             | /etc/portage/make.profile to point to a profile that uses
             | the new ABI and setting (and exporting) the ROOT variable
             | in the shell you type "emerge" into (or add this
             | ROOT=/whatever/ line to /etc/portage/make.conf), is what I
             | was getting at. There's no need to write any wrappers.
        
               | codys wrote:
               | Simply changing `ROOT` doesn't magically enable us to
               | have `emerge` update our normal `ROOT=/`. There's extra
               | steps here (the syncing, the bind mounting, etc) for the
               | `emerge` with a custom `ROOT` to do an update to `/`
               | itself.
               | 
               | And that's if we have a `ROOT` that allows us to source
               | dependencies (libraries to link against, includes, etc).
               | Currently it's unclear to me how exactly `ROOT` works,
               | probably meaning there needs to be a bit more
               | documentation there and potentially indicating that it
               | might not be used much right now.
        
               | aaronmdjones wrote:
               | > There's extra steps here (the syncing, the bind
               | mounting, etc) for the `emerge` with a custom `ROOT` to
               | do an update to `/` itself.
               | 
               | Those extra steps are handled by telling people what to
               | do, in news items. This happened for example in the 17.1
               | -> 23.0 profile migration [1]; emerge wasn't updated to
               | help people do this in any way whatsoever, users are
               | expected to do everything themselves. This is the Gentoo
               | way.
               | 
               | > And that's if we had a `ROOT` that allowed us to source
               | dependencies (libraries to link against, includes, etc).
               | 
               | You don't need to source anything in ROOT because
               | everything you're _rebuilding_ is _already_ in  /.
               | 
               | If you intended to build a program that depended upon a
               | library you did not already have (in /), you're right,
               | this wouldn't work. That isn't the case here. The dynamic
               | linker (ld-linux.so or whatever you fancy, the ELF
               | interpreter, the thing that runs before main(), the thing
               | that will look at the list of dependent libraries, try to
               | load them, and balk at any mismatches) isn't executed
               | until the program is, and setting ROOT merely tells
               | portage where to put them; it doesn't execute them. This
               | I alluded to in the other comment thread about building a
               | userland that your CPU can't even execute.
               | 
               | [1] https://www.gentoo.org/support/news-
               | items/2024-03-22-new-23-...
        
               | codys wrote:
               | Huh. If it's OK to have instructions like those for users
               | to run through, then I guess I don't understand what's
               | holding up switching to 64-bit time_t for gentoo. Seems
               | like they could make a new profile and tell folks to deal
               | with it. Perhaps I don't understand what the requirements
               | are here, which might indicate that the requirements
               | aren't clear for others too.
        
           | rini17 wrote:
           | The third paragraph says following, which means it's not
           | suitable solution to build whole system:
           | 
           | When building a package, ROOT should not be used to satisfy
           | the required dependencies on libraries, headers files etc.
           | Instead, the files on the build system should be specified
           | using /.
        
             | aaronmdjones wrote:
             | Yes, that means it will look in the host system (/) for
             | those dependencies. But when you're also recompiling and
             | installing those dependencies to /whatever/ as well anyway,
             | and /whatever/ is eventually going to end up being /, this
             | is fine. The original / doesn't get touched until
             | everything is done, and /whatever/ is all built to the new
             | ABI, which it can be precisely because nothing in
             | /whatever/ is getting consulted or used during the rebuild
             | process.
        
               | codys wrote:
               | No, what rini17 pointed out is a problem here, and I
               | think my previous comments that setting ROOT might be
               | "part of a solution" are less accurate (as it seems
               | `ROOT` is even less of a complete solution here).
               | 
               | Lets consider this hypothetical representation of the
               | 64-bit time issue:
               | 
               | In `ROOT=/`, we have a `libA.so` with 32-bit time_t, and
               | a `libB.so` with 32-bit time_t that depends on `libA`
               | (ie: `libB.so` -> `libA.so`).
               | 
               | We rebuild package `A` with a special root
               | (`ROOT=/tmp/new`) which has 64-bit time_t. We now have a
               | `/tmp/new` with `libA.so` built for 64-bit time_t. We now
               | want to build `B` for `ROOT=/tmp/new` with 64-bit time_t
               | and have it link against the new `/tmp/new/lib/libA.so`.
               | 
               | But setting `ROOT=/tmp/new` doesn't cause us to use the
               | `/tmp/new` to as our source to link against things. So
               | (as it currently stands) we'll end up trying to link
               | `libB.so` (64-bit time_t) against `/lib/libA.so` (32-bit
               | time_t), and things just won't work.
               | 
               | If gentoo changes/extends ROOT (or adds something else
               | like ROOT but used to locate dependencies for linking),
               | then it could be a piece of a solution. But it sounds
               | like it's pretty far from that in intention/practice
               | right now.
        
               | aaronmdjones wrote:
               | > But setting `ROOT=/tmp/new` doesn't cause us to use the
               | `/tmp/new` to as our source to link against things
               | 
               | That's a good thing, because it won't be /tmp/new/ when
               | we're finished.
               | 
               | > So (as it currently stands) we'll end up trying to link
               | `libB.so` (64-bit time_t) against `/lib/libA.so` (32-bit
               | time_t), and things just won't work.
               | 
               | It won't work until /tmp/new/ becomes /, which it doesn't
               | have to until you're done.
               | 
               | Remember that this is just for building and installing
               | the software. It doesn't get executed which is precisely
               | what you want _because_ it wouldn 't work properly
               | otherwise, as you point out.
               | 
               | You can even use this method to build a complete userland
               | for an instruction set architecture that your CPU doesn't
               | even support. All you need do is also set CHOST to the
               | tuple identifying the target architecture in addition to
               | setting ROOT for where it should be installed [1]. You
               | just wouldn't be able to chroot into it obviously.
               | 
               | [1] https://wiki.gentoo.org/wiki/Embedded_Handbook/Genera
               | l/Intro...
        
               | codys wrote:
               | Before getting into the details here about why we need to
               | read `libA.so` at build time: Perhaps the documentation
               | gentoo provides about `ROOT` that was noted by rini17 is
               | incorrect/misleading and `ROOT` is used as the source of
               | build time dependencies automatically and the
               | documentation was intended for ebuild writers and not
               | emerge users? (in which case, Gentoo should fix/clarify
               | it).
               | 
               | "link" has 2 meanings:
               | 
               | 1. As you've pointed at, at runtime we have linking occur
               | that reads files.
               | 
               | 2. Additionally, building software with `gcc`/etc, we
               | tell it what libraries it needs to link against (`-lA`).
               | When we do that, `gcc` as part of the build process
               | searches for `libA.so` (in various locations dependent on
               | its configuration and arguments), reads the `libA.so`
               | file, and uses that info to determine what to emit in the
               | object file that `gcc` is building. So we need to read a
               | shared library. The same is true for headers used at
               | build time. How much this actually ends up mattering
               | varies depending on architectural details, and whether
               | libraries have different symbols exposed (or generate
               | different header files) depending on what the bit-width
               | of time_t is. It may well be that for most packages we
               | could have the build-time linking occur against a 32-bit
               | time_t object as long as runtime linking is done to the
               | 64-bit time_t object (assuming one is using identical
               | package versions or at least versions with a compatible
               | ABI).
        
               | aaronmdjones wrote:
               | > and whether libraries have different symbols exposed
               | (or generate different header files) depending on what
               | the bit-width of time_t is
               | 
               | Hmm. You have a good point there; I hadn't thought about
               | that...
               | 
               | Perhaps they can take an existing stage3 with 32-bit
               | time_t, build a new stage1 through 3 with a 64-bit
               | time_t, and compare the symbols and header files between
               | them to identify any potential trouble spots. This would
               | help them regardless of what migration course of action
               | they decide upon.
               | 
               | Any software people have installed that isn't included in
               | the stage3 (e.g. desktop window managers and everything
               | else on top) wouldn't be included here and so is a
               | potential pain point, but everything in @system would be,
               | so at that point you can just rebuild @world again to fix
               | everything.
        
       | nobluster wrote:
       | For a large legacy 32 bit unix system dealing with forward dates
       | I replaced all the signed 32 bit time_t libc functions with
       | unsigned 32 bit time_t equivalents. This bought the system
       | another 68 years beyond 2038 - long after I'll be gone. The
       | downside is that it cannot represent dates before the unix epoch,
       | 1970, but as it was a scheduling system it wasn't an issue.
       | 
       | If legacy dates were a concern one could shift the epoch by a
       | couple of decades, or even reduce the time granularity from 1
       | second to 2 seconds. Each alternative has subtle problems of
       | their own. It depends on the use case.
        
         | suprjami wrote:
         | If you can change the whole system from signed to unsigned, why
         | not change to 64-bit?
        
           | Dylan16807 wrote:
           | The main problem here is gradual upgrades. Signed and
           | unsigned code act exactly the same on dates before 2038, so
           | you can upgrade piece by piece at your leisure.
           | 
           | The suggestions for messing with epoch or the timescale
           | wouldn't work nearly as well, for those just switch to 64
           | bit.
        
             | suprjami wrote:
             | It sounds like the parent poster controls the whole system,
             | so there is no gradual upgrade.
             | 
             | If they're changing signed time to unsigned time, it seems
             | to me they can change the size of time as well.
        
       | dark-star wrote:
       | Are there other distros where the switch to 64-bit time_t has
       | already happened? What's the easiest way to figure out whether
       | $distro uses 32-bit or 64-bit time_t? Is there something
       | easier/quicker than writing a program to print `sizeof(struct
       | stat)` and check if that's 88, 96 or 1098 bytes (as hinted in the
       | article)?
        
         | teddyh wrote:
         | Like the article says, Debian has already switched.
        
         | jonathrg wrote:
         | printf '#include <stdio.h>\n#include <time.h>\nint main() {
         | printf("time_t is %%zu-bit\\\n", sizeof(time_t)*8); }\n' | gcc
         | -x c -o timesize - && ./timesize && rm ./timesize
         | 
         | If you're wondering about a specific distro that you're not
         | using right now - just look it up.
        
         | SAI_Peregrinus wrote:
         | NixOS switched. And they're source-based, but with actual
         | dependency tracking and all the (insanely complex) machinery
         | needed to allow different programs to use different C library
         | versions simultaneously.
         | 
         | `printf("sizeof (time_t) = %zu, %zu bits", sizeof (time_t),
         | sizeof (time_t) * CHAR_BIT);` gives you the size in bytes and
         | in bits. Needs time.h, stddef.h, and stdio.h.
        
       | grantla wrote:
       | > A number of other distributions such as Debian have taken the
       | leap and switched. Unfortunately, source-based distributions such
       | as Gentoo don't have it that easy.
       | 
       | For Debian it was extremely painful. A few people probably burned
       | out. Lots of people pointed to source-based distributions and
       | said "they will have it very easy".
        
         | Denvercoder9 wrote:
         | > For Debian it was extremely painful.
         | 
         | Do you have any references that elaborate on that? From an
         | outsider perspective, the time64 transition in Debian seemed to
         | have been relatively uncontroversial and smooth. Way better
         | than e.g. the /usr-merge.
        
           | jlarocco wrote:
           | https://lwn.net/Articles/812767/
           | 
           | https://wiki.debian.org/ReleaseGoals/64bit-time
           | 
           | I'm continually impressed by Debian. My Debian systems are
           | pretty boring, so maybe it's to be expected, but as an end-
           | user neither the /usr merge or time64 transition broke
           | anything for me, and I'm using Testing.
        
         | ajsnigrutin wrote:
         | > For Debian it was extremely painful. A few people probably
         | burned out. Lots of people pointed to source-based
         | distributions and said "they will have it very easy".
         | 
         | 'easy' in a way "just tell the user to rebuild everything in
         | one go"? :)
        
         | cbmuser wrote:
         | > For Debian it was extremely painful.
         | 
         | I did the transition for m68k, powerpc and sh4 and partially
         | for hppa with the help of some other Debian Developers here and
         | there and I'm still alive ;-).
        
       | cryptonector wrote:
       | The simplest thing to do is to make any 32-bit `time_t`s be
       | unsigned. That buys another 68 years to get the transition done.
       | Not that that's exactly easy, but it's easier than switching to
       | 64-bit time_t.
        
         | rini17 wrote:
         | You believe it will get somehow easier in 60 years?
        
           | cryptonector wrote:
           | Yes!
           | 
           | For one, none of us will be the ones dealing with it, but
           | more seriously, by then 32-bit Unix/Linux ABIs will be gone.
        
       | jeffbee wrote:
       | Are we still keeping shared libraries? They are a complex
       | solution to a problem that arguably stopped existing 20 years
       | ago. Might be time to rethink the entire scheme.
        
         | filmor wrote:
         | On Gentoo, definitely. I really don't want to rebuild my whole
         | system whenever some foundational library fixes a bug. It
         | already annoys me quite a bit that I need to set up sccache to
         | get half-way reasonable compile times out of Rust projects (and
         | I'm saying that as someone who enjoys gradually replacing
         | significant portions of the userspace with Rust tools).
        
           | AshamedCaptain wrote:
           | This by itself is half the reason I think Nix's approach is a
           | step backwards.
        
         | otabdeveloper4 wrote:
         | The Docker image layers you so dearly love are an
         | implementation of shared libraries, except done in a broken way
         | that's a thousand times less performant and more insecure.
        
       | wpollock wrote:
       | I'm probably naive, but I see another way forward. If all the t64
       | binaries are static linked, they have no danger of mixing abis.
       | After 2038, all the t32 code is broken anyway so there's no
       | _additional_ risk for going back to dynamic linking then. I feel
       | if this was a solution the author would have mentioned it but I
       | 'm willing to look foolish to hear what others will say.
        
         | layer8 wrote:
         | Static linking is probably impractical for applying security
         | updates, as often you'd have to recompile/relink basically the
         | whole system. In some cases it could also significantly
         | increase memory usage.
        
           | GeorgeTirebiter wrote:
           | with fast networks, huge disks, fast processors --- it seems
           | wasteful to me to even consider shared libraries. Shared
           | libraries is a technology that was useful when we were memory
           | starved. We are no longer memory starved. So you replace the
           | static binary? Big deal, size is not an issue (for 99% of the
           | cases) given what we have today.
           | 
           | Recall, too, that the "link" step of those .o files is
           | actually a "link / edit" step, where routines in the
           | libraries not used are not linked.
        
             | layer8 wrote:
             | It's much more straightforward to ensure consistency with
             | shared libraries, and not having to rebuild stuff. Wasting
             | disk space, RAM, network bandwidth and processing time is
             | what seems wasteful to me.
        
             | gotoeleven wrote:
             | Are you by chance a javascript programmer?
        
             | jiggawatts wrote:
             | Static linking is the root cause for "modern" apps based on
             | Electron taking minutes to start up and be useful. They're
             | statically linking almost an entire operating system
             | (Chromium), an entire web server, server runtime framework,
             | _and_ an equivalent client framework for good measure.
             | 
             | On the fastest PC that money can buy this is somewhere
             | between "slow" and "molasses".
             | 
             | I miss the good old days when useful GUI programs were mere
             | kilobytes in size and launched instantly.
        
             | messe wrote:
             | Remember that this is for 32-bit systems. Size can be an
             | issue there, especially on an embedded system.
        
             | cbmuser wrote:
             | > with fast networks, huge disks, fast processors --- it
             | seems wasteful to me to even consider shared libraries.
             | 
             | The problem is that you will always have to rebuild _all_
             | packages that link against a certain library when a
             | security update is released so that if the user installs a
             | security update, they will have to download several
             | gigabytes instead of just a few megabytes.
        
         | troad wrote:
         | Considering the most important remaining use case for 32-bit is
         | embedded systems, wouldn't static linking be a non-starter due
         | to space and performance constraints?
        
       | layer8 wrote:
       | > The second part is much harder. Obviously, as soon as we're
       | past the 2038 cutoff date, all 32-bit programs -- using system
       | libraries or not -- will simply start failing in horrible ways.
       | One possibility is to work with faketime to control the system
       | clock. Another is to run a whole VM that's moved back in time.
       | 
       | Yet another is to freeze the time for those programs at the last
       | 32-bit POSIX second. They would just appear to execute incredibly
       | fast :). Of course some will still break, and it's obviously not
       | suitable for many use cases (but neither is running in the past),
       | but some might be just fine.
        
       | ndesaulniers wrote:
       | I think we should start putting details of the ABI in ELF (not
       | the compiler flags as a string). Wait.. Who owns the ELF spec??
       | 
       | Then the linker and loader could error if two incompatible
       | objects with different ABIs were attempted to be linked together.
       | 
       | For instance, I suspect you could have fields to denote the size
       | of certain types. I guess DWARF has that... But DWARF is optional
       | and sucks to parse.
        
       | ddoolin wrote:
       | Why was it 32 bits to begin with? Wasn't it known that 2038 would
       | be the cutoff?
        
         | NegativeLatency wrote:
         | Ultimately probably hardware? I suspect it's also been like
         | this for a long time.
        
         | jdndhdhd wrote:
         | Why is it 64 bit now? Isn't it know that 292277026596 would be
         | the cutoff?
        
         | int_19h wrote:
         | It was not exactly a big concern when this was designed in
         | early 70s, especially when you consider that Unix itself was
         | kind of a hack at the time.
        
         | wongarsu wrote:
         | It has been that way since Unix V4 which released in 1973. Back
         | then moving to something that breaks after 65 years was a
         | massive upgrade over the old format that wrapped after two and
         | a half years. And at least from the standpoint of Unix
         | engineers 32 bits was more than enough: Nobody is using Unix V4
         | in production in 2024, never mind 2038.
         | 
         | Why it made it into Posix and wasn't updated is a different
         | question that's a bit more difficult to answer
        
         | modeless wrote:
         | In the 1970s people probably would have laughed at the idea
         | that Unix would still be running in the year 2038.
        
           | ddoolin wrote:
           | I get that; I am surprised to find my own software running 7
           | years later, let alone ~60 and given the context.
        
             | PHGamer wrote:
             | well sometimes the old stuff is better than the new stuff.
             | it really is hilarious though. things in the software world
             | have regressed in some ways especially when it comes to
             | privacy and locking down data.
        
       | eschaton wrote:
       | The way this was handled on Mac OS X for `off_t` and `ino_t`
       | might provide some insight: The existing calls and structures
       | using the types retained their behavior, new calls and types with
       | `64` suffixes were added, and you could use a preprocessor macro
       | to choose which calls and structs were actually referenced--but
       | they were hardly ever used directly.
       | 
       | Instead, the OS and its SDK are versioned, and at build time you
       | can _also_ specify the _earliest_ OS version your compiled binary
       | needes to run on. So using this, the headers ensured the proper
       | macros were selected automatically. (This is the same mechanism
       | by which new /deprecated-in-some-version annotations would get
       | set to enable weak linking for a symbol or to generate warnings
       | for it respectively.)
       | 
       | And it was all handled initially via the preprocessor, though now
       | the compilers have a much more sophisticated understanding of
       | what Apple refers to as "API availability." So it should be
       | feasible to use the same mechanisms on any other platform too.
        
         | db48x wrote:
         | Lol, that only works if you can force everyone on the platform
         | to go along with it. It is a nice solution, but it requires you
         | to control the c library. Gentoo doesn't control what libc
         | does; that's either GNU libc or MUSL or some other thing that
         | the user wants to use.
        
           | dmitrygr wrote:
           | > requires you to control the c library
           | 
           | Which is why basically every other same operating system does
           | that. BSDs, macOS, WinNT. Having the stable boundary be the
           | kernel system call interface is fucking insane. And somehow
           | the Linux userspace people keep failing to learn this lesson,
           | no matter how many times they get clobbered in the face by
           | the consequences of not learning it.
        
             | eschaton wrote:
             | And yet it would still work out for Linux if musl, glibc,
             | et al just adopted `API_VERSION_MIN` and `API_VERSION_MAX`
             | macros themselves, it doesn't actually have to be handled
             | entirely at the `-isysroot` level.
        
             | db48x wrote:
             | It's extra work, but I don't know that it is necessarily
             | insane. If libc was under the complete control of the
             | kernel developers, then that gives other languages fewer
             | options. Go famously (or infamously) uses certain syscalls
             | without going through libc, for example. Sometimes the
             | choices made for the C library just aren't compatible with
             | other languages. Frankly the C library, as it exists today,
             | is insane. Maybe the solution is to split it in half: one
             | for the syscalls and another for things like strings and
             | environment variables and locales and all the other junk.
        
               | mort96 wrote:
               | Splitting libc into one "stable kernel API" part and one
               | "library with things which are useful to C programmers"
               | part would honestly make a ton of sense. OSes which
               | declare their syscall interface to be unstable would be
               | more convincing if they actually provided a stable API
               | that's intended for other consumers than C.
        
               | AshamedCaptain wrote:
               | Frankly I don't see the point of complaining that libc is
               | not useful for non-C consumers. Sure, there are some
               | ancillary functions in libc you'll likely never call. But
               | what's the issue with making syscalls through libc?
               | Again, the only difference is what the very last call
               | instruction is. If your runtime can marshal arguments for
               | the kernel, then it surely can marshal arguments for
               | libc. They are almost always practically the same ABI.
               | 
               | And you can avoid the need for VDSO-like hacks which is
               | basically the kernel exposing a mini-libc to userspace.
        
               | db48x wrote:
               | This is the reason why Rust compiles every program
               | statically, except that the resulting "static" binary
               | still has to link against libc.
        
             | clhodapp wrote:
             | Making the stable boundary be C _headers_ is insane!
             | 
             | It means that there's not actually any sort of ABI, only a
             | C source API.
        
               | westurner wrote:
               | cibuildwheel builds _manylinux_ packages for glibc >= and
               | musl because of this ABI.
               | 
               | manylinux: https://github.com/pypa/manylinux :
               | 
               | > _Python wheels that work on any linux (almost)_
               | 
               | Static Go binaries that make direct syscalls and do not
               | depend upon libc or musl run within very minimal
               | containers.
               | 
               | Fuschia's syscall docs are nice too; Linux plus
               | additional syscalls.
        
               | kazinator wrote:
               | Headers can do the magic to select the right ABI more or
               | less transparently, based on preprocessor symbols which
               | indicate the selection: is a certain type 32 or 64.
               | 
               | This is similar to what Microsoft did in Win32 with the
               | _W_ and _A_ functions (wide char and ascii /ansi). You
               | just call _MessageBox_ and the header file will map that
               | to _MessageBoxA_ or _MessageBoxW_ based on whether you
               | are a _UNICODE_ build.
               | 
               | The character element type in the strings accepted by
               | _MessageBox_ is _TCHAR_. That can either be _CHAR_ or
               | _WCHAR_.
               | 
               | Once the code is compiled, it depends on the appropriate
               | ABI: MessageBoxA or MessageBoxW; MessageBox doesn't
               | exist; it is a source-level fiction, so to speak.
        
             | AshamedCaptain wrote:
             | > no matter how many times they get clobbered in the face
             | by the consequences of not learning it.
             | 
             | While I do think that the boundary should be set at the
             | libc level just out of design cleanliness, I fail to see
             | what the "consequence" of not doing so is. You're just
             | changing the instruction binaries use for syscalls from a
             | relative jump to a trap (or whatever), but you still have
             | all the problems with data type sizes, function "versions"
             | and the like which are what this discussion is about.
        
               | dmitrygr wrote:
               | > I fail to see what the "consequence" of not doing so
               | is.
               | 
               | TFA ?
        
               | AshamedCaptain wrote:
               | No. How would it benefit TFA _at all_?
               | 
               | For all practical purposes Linux currently has _two_
               | stable boundaries, libc (glibc) and kernel. If you move
               | it so that the stable boundary is only the kernel, you
               | still have this problem. If you move it so that the
               | stable boundary is only libc, you still have this
               | problem.
               | 
               | In fact, TFA's problem comes from applications passing
               | time_ts around, which is strictly a userspace problem,
               | and the syscall interface is almost entirely ortogonal.
               | Heck, the 32-bit glibc time_t functions probably use the
               | 64-bit time_t syscalls these days...
        
             | asveikau wrote:
             | > WinNT
             | 
             | This isn't true. There is an msvcrt in the OS, but it's
             | mainly there for binaries that are part of Windows. The CRT
             | is released as part of Visual Studio, out of band from the
             | Windows release schedule.
             | 
             | Although CRT's place is in the layering a little different,
             | because of so many things talking directly to Windows APIs.
        
               | dmitrygr wrote:
               | IIRC, the CRT in windows makes NO system calls. Those are
               | all in ntdll.dll, and in ntdll.dll _ONLY_
        
               | asveikau wrote:
               | That's just a legacy of the strategy where NT native APIs
               | were considered a private API, and you were meant to code
               | against Win32 instead, to target both NT and 9x.
               | 
               | Syscalls are in ntdll, layered above that is kernel32
               | (today kernelbase), then most user mode code including
               | CRT is above that. In 9x kernel32 were syscalls, in NT
               | they were user mode shims above ntdll.
               | 
               | That's _for most things_. Things like gdi have kernel
               | entry points too afaik.
               | 
               | Anyway my point is that the C library is developed out of
               | band from that.
        
           | eschaton wrote:
           | It's entirely opt-in, Apple doesn't force it. If you just do
           | `cc mything.c -o mything` you get a binary whose minimum
           | required OS is the version of the SDK you built it against,
           | just as with any other UNIX-like OS. It's just giving the
           | developer the option to build something they know will run on
           | an earlier version too.
           | 
           | And since it was all initially done with the preprocessor
           | rather than adding knowledge to the compilers, there's no
           | reason individual libraries can't handle API versioning in
           | exactly this way, including things like differing `ino_t` and
           | `off_t` sizes.
        
         | AshamedCaptain wrote:
         | This does not solve the main issue as explained by TFA, which
         | is that now applications that use a different "compiled OS
         | version" cannot link with each other anymore. Your application
         | X which declares to run on OS v.B cannot link anymore with
         | application Y which declared to run on OS v.A , even when both
         | are running under OS v.B .
         | 
         | In fact, what you describe is basically what every platform is
         | doing.... as doing anything else would immediately break
         | compatibility with all current binaries.
        
         | 38 wrote:
         | your comment has the tone of this being an elegant solution,
         | but it reads to me like an awful hack. typeless macros are a
         | nightmare I never want to deal with again.
        
           | eschaton wrote:
           | These are very straightforward and do in fact serve as an
           | elegant solution.
        
             | smackeyacky wrote:
             | Elegant maybe in the 1980s before compilers got properly
             | complicated. Now they're a weird anachronism.
             | 
             | I'm old enough to have worked on c++ when it was a
             | precompiler called cfront and let me tell you trying to
             | work out whether your macro was doing something weird or
             | the cfront processor did something weird was very
             | frustrating. I swore off macros after that.
        
               | eschaton wrote:
               | This is a case where you're seeing the word "macro" and
               | reacting to that when it's really not warranted. It's not
               | using macros for complicated code generation, just
               | selecting from a couple alternatives based on one or two
               | possible compiler arguments.
               | 
               | I'm also old enough to remember CFront and this isn't
               | that.
        
               | ykonstant wrote:
               | I am not an expert in these things, but the discussion
               | here reminds me of
               | https://news.ycombinator.com/item?id=41182917
        
         | kazinator wrote:
         | The problem is that secondary libraries, that are not glibc,
         | will not have multiply defined functions for different size of
         | off_t and will not have the switch in their header file to
         | transparently select the correct set of functions based on what
         | the client program wants.
         | 
         | Yet, somehow the article emphasizes that this is more of a
         | problem for time_t than off_t.
         | 
         | This is believable and it's brobably it's because time_t is
         | more pervasive. Whereas off_t is a POSIX thing involved in a
         | relatively small number of interfaces, time_ t is ISO C and is
         | all over the place.
         | 
         | On top of everything, lots of C code assumes that time_t is an
         | integer type, equal in width to int. A similar assumption for
         | off_t is less common.
        
           | eschaton wrote:
           | Since the different sizes are ultimately a platform thing,
           | they need to support multiple variants on those platforms, or
           | limit their support to a new enough base set of APIs that
           | they can rely on 64-bit types being there.
        
           | _kst_ wrote:
           | Code that assumes time_t is the same width as int is already
           | broken, and won't work on typical 64-bit systems were int is
           | 32 bits and time_t is 64 bits.
           | 
           | In any case, I'm not sure I've ever seen any such code.
        
       | panzi wrote:
       | The only place where this is relevant for me is running old
       | Windows games via wine. Wonder how wine is handling this? Might
       | as well re-map the date for 32bit wine to the late 90s/early
       | 2000s, where my games are from. Heck, with faketime I can do that
       | already, but don't need it yet.
        
       | ok123456 wrote:
       | What about making time_t 64-bit across all profiles and
       | incrementing all the profile versions by one? Gentoo users are
       | used to breaking changes when upgrading profiles.
        
       | mhandley wrote:
       | _" Let's consider a trivial example:_                 struct {
       | int a;           time_t b;           int c;       };
       | 
       | _With 32-bit time_t, the offset of c is 8. With the 64-bit type,
       | it's 12. "_
       | 
       | Surely it's 16 not 12, as b needs to be 64-bit aligned, so
       | padding is added between a and b? Which also kind of makes the
       | point the author is trying to make stronger.
        
         | AshamedCaptain wrote:
         | Most x86 ABIs do not mandate padding for 64-bit types, due to
         | the lack of 64-bit loads (at the time).
        
           | 256_ wrote:
           | I thought you were right, but I tested it on x86_64 GNU/Linux
           | with this struct:                   struct ts {
           | int32_t dword1;                 int64_t qword1;
           | int32_t dword2;         };
           | 
           | Writing -1 to all 3 members and printing out the individual
           | bytes up to sizeof(struct ts), it produces this with -O0 and
           | -O3:
           | 
           | FF FF FF FF 00 00 00 00 FF FF FF FF FF FF FF FF FF FF FF FF
           | 00 00 00 00
           | 
           | With -Os, I get some garbage data after the last member
           | (always those 2 byte positions, with different values every
           | time):
           | 
           | FF FF FF FF 00 00 00 00 FF FF FF FF FF FF FF FF FF FF FF FF
           | 45 76 00 00
           | 
           | I feel like I'm missing something obvious here. Or maybe my
           | system's ABI is a weird exception.
        
             | jabl wrote:
             | Was this with x86_64? The point of the parent was about
             | x86_32, I believe.
        
               | 256_ wrote:
               | Oh yes, I really was missing something obvious. And you
               | did, too, since I mentioned the architecture in the first
               | line of my post.
        
               | jabl wrote:
               | I did see it, but was unsure if you meant you were
               | compiling for a 32-bit target on a 64-bit host.
        
             | AshamedCaptain wrote:
             | In addition to the confusion about x86 and x86_64/amd64,
             | you should also know that -O[s123] cannot possibly
             | influence the padding of structures, or it would mean you
             | could not link a library compiled with -Os with a program
             | compiled with -O3 (or viceversa).
        
           | EdSchouten wrote:
           | Exactly. This is also highly problematic if you try to
           | perform atomic operations against bare 64 bit integers on
           | those systems, because the atomic instructions do require
           | them to be 8 byte aligned. In C11 and later it's not an
           | issue, because _Atomic(int64_t)'s alignment may be stricter
           | than int64_t.
           | 
           | This was also an issue in Go, because it tries to use the
           | same alignment rules as C to make cgo work. There they also
           | solved it by adding dedicated atomic types.
           | 
           | https://github.com/golang/go/discussions/47141
        
       | rini17 wrote:
       | Hope Gentoo alone won't get overburdened with the development and
       | maintenance of such hybrid toolchain, to be able to compile and
       | run time32 processes alongside time64. When everyone else has
       | moved on. Better do a complete reinstall from stage3.
        
       | malkia wrote:
       | Microsoft's famous (infamous?) "A" / "W" functions and macros
       | controlling them -... but it works and one can link two different
       | versions no problem. Wonder if that was possible here?
        
       | kazinator wrote:
       | All those arguments apply to off_t also. If a small-file
       | executable with 32 bit off_t uses a large-file library with 64
       | bit off_t, there is no protection. Only glibc has the multiply
       | implemented functions, selected by header file macrology.
        
       | shmerl wrote:
       | LFS should have become mandatory.
        
       | darkhelmet wrote:
       | Every time I see people struggling with this, I am so incredibly
       | glad that I forced the issue for FreeBSD when I did the initial
       | amd64 port. I got to set the fundamental types in the ABI and
       | decided to look forward rather than backward.
       | 
       | The amd64 architecture did have some interesting features that
       | made this much easier than it might have been for other cpu
       | architectures. One of which was the automatic cast of 32 bit
       | function arguments to 64 bit during the function call. In most
       | cases, if you passed a 32 bit time integer to a function
       | expecting a 64 bit time_t, it Just Worked(TM) during the platform
       | bringup. This meant that a lot of the work on the loose ends
       | could be deferred.
       | 
       | We did have some other 64 bit platforms at the time, but they did
       | not have a 64 bit time_t. FreeBSD/amd64 was the first in its
       | family, back in 2003/2004/2005. If I remember correctly, sparc64
       | migrated to 64 bit time_t.
       | 
       | The biggest problem that I faced was that (at the time) tzcode
       | was not 64 bit safe. It used some algorithms in its 'struct tm'
       | normalization that ended up in some rather degenerate conditions,
       | eg: iteratively trying to calculate the day/month/year for
       | time_t(2^62). IIRC, I cheated rather than change tzcode
       | significantly, and made it simply fail for years before approx
       | 1900, or after approx 10000. I am pretty sure that this has been
       | fixed upstream in tzcode long ago.
       | 
       | We did have a few years of whackamole with occasional 32/64 time
       | mixups where 3rd party code would be sloppy in its handling of
       | int/long/time_t when handling data structures in files or on the
       | network.
       | 
       | But for the most part, it was a non-issue for us. Being able to
       | have 64 bit time_t on day 1 avoided most of the problem. Doing it
       | from the start was easy. Linux missed a huge opportunity to do
       | the same when it began its amd64/x86_64 port.
       | 
       | Aside: I did not finish 64 bit ino_t at the time. 32 bit inode
       | numbers were heavily exposed in many, many places. Even on-disk
       | file systems, directory structures in UFS, and many, many more.
       | There was no practical way to handle it for FreeBSD/amd64 from
       | the start while it was a low-tier platform without being
       | massively disruptive to the other tier-1 architectures. I did the
       | work - twice - but somebody else eventually finished it - and
       | fixed a number of other unfortunately short constants as well
       | (eg: mountpoint path lengths etc).
        
         | jabl wrote:
         | AFAIU all 64-bit Linux ports have used 64-bit time_t, off_t,
         | ino_t from the beginning. All this is about transitioning
         | 32-bit Linux to 64-bit time_t.
        
       | seanhunter wrote:
       | In the original BSD man pages, the "Bugs" section for "tunefs"
       | had the famous joke "You can tune a file system, but you can't
       | tune a fish." but according to "Expert C Programming"[1], the
       | source code for this manpage had a comment next to the joke
       | saying                  > Take this out and a UNIX Demon will dog
       | your steps from now        > until the time_t's wrap around.
       | 
       | Obviously back in the 70s when that was written 2038 seemed
       | unimaginably far in the future.
       | 
       | [1] https://progforperf.github.io/Expert_C_Programming.pdf
        
       | M95D wrote:
       | I belive this problem is irrelevant. 32bit platforms are mostly
       | unsupported, with Gentoo doing eforts to keep them working and
       | mostly failing. Until 2038, 32bit systems will be as rare as an
       | i286 today.
        
         | 2ndbigbang wrote:
         | This is not the case for embedded systems where 32-bit ARM is
         | still used.
        
           | M95D wrote:
           | Can you give some examples of embedded systems that run
           | Gentoo?
        
       | somat wrote:
       | Just pull the bandaid off, it will hurt a little, but not as bad
       | as this endless agonizing about the problem.
       | 
       | For what it's worth, on openbsd this problem is fixed, all
       | architectures, even the 32bit ones, have a 64bit time_t.
       | 
       | http://www.openbsd.org/55.html
        
       ___________________________________________________________________
       (page generated 2024-09-29 23:01 UTC)