Tzdata, glibc, and FHS

On most Linux systems, tzdata is installed at /usr/share/zoneinfo, and glibc looks for it there by default. On NixOS systems, tzdata is installed to the nix store, a series of symlinks /etc/zoneinfo/etc/static/zoneinfo${etc}/zoneinfo${tzdata}/share/zoneinfo points to it, glibc looks for it by default in a ${glibc}/share/zoneinfo that never exists, and the TZDIR environment variable is used to point it to /etc/zoneinfo.

FHS prescribes that tzdata should be at /usr/share/zoneinfo, and software that doesn’t implement TZDIR or software that runs in an environment that loses TZDIR will normally expect to find tzdata there and fail on current NixOS. When using buildFHSEnv, the only thing that changes is that the symbolic links become /etc/zoneinfo/.host-etc/zoneinfo/etc/static/zoneinfo${etc}/zoneinfo${tzdata}/share/zoneinfo. /usr/share/zoneinfo does not exist in the created FHS environment. This can be fixed by creating an extra symlink $out/usr/share/zoneinfo${tzdata}/share/zoneinfo when building the FHS environment, and adding TZDIR=/usr/share/zoneinfo to the profile. Then software that doesn’t check TZDIR should just work in the FHS environment.

The current system breaks when using foreign filesystem images. When launching a container, it’s possible that TZDIR=/etc/zoneinfo actually creates the problem that it usually solves because /etc/zoneinfo doesn’t exist or cannot be resolved within the container environment but the environment variable gets propagated into the container and tells software in the container to look for tzdata where it isn’t. NixOS has a patch to flatpak to unset TZDIR for this reason.

Things get more complicated with software like Steam which currently starts a container environment where TZDIR is set to /etc/zoneinfo, /etc/zoneinfo can’t be resolved, and a copy of tzdata exists at /usr/share/zoneinfo, but unsetting TZDIR as is done for flatpak doesn’t work because glibc is the NixOS glibc that defaults to the missing ${glibc}/share/zoneinfo path. In this case, TZDIR needs to be set to /usr/share/zoneinfo, which doesn’t actually exist at the last moment NixOS can set it when launching Steam and won’t exist until later on when Steam sets up its environment, by which time some other Steam code may have tried and failed to access tzdata.

Can we make this better by:

  1. Changing the glibc build so that it defaults to the standard /usr/share/zoneinfo instead of ${glibc}/share/zoneinfo. Neither path normally exists on NixOS, but /usr/share/zoneinfo is more normal and has a better chance of working when TZDIR is unset.
  2. Ensuring /usr/share/zoneinfo exists in FHS environments and TZDIR points to it (or is unset).

This would fix Steam without making any Steam-specific changes, and improve the chances that other foreign software works as expected when launched in NixOS FHS environments. I don’t think it’s a complicated change, but it seems like a significant enough change that it’d be better to create a discussion first instead of just creating a PR.

See also ValveSoftware/steam-runtime#795.

5 Likes

#1 would worsen NixOS, #2 seems relatively fine though.

1 Like

#1 would also “break” nixpkgs packages on other distros, where that path does exist, and therefore results in all the issues you get when interacting with globals.

1 Like

I hadn’t considered this, and I wasn’t sure how it worked. It turns out nixpkgs packages on other distros is already broken and IMO #1 would be the most reasonable fix.

NixOS glibc does not work unless TZDIR is set because its default tzdata location is guaranteed not to exist, but most distros don’t set TZDIR and rely on the default path compiled into glibc. Does installing Nix on another distro set TZDIR? Does it set it to a Nix profile location, hijacking control of the timezone database in all applications? Does it set it to /usr/share/zoneinfo, the same as the proposed default? I created a new Debian 13 VM and I installed Nix using the multi-user installation and all default settings. Running the Debian date command with TZ=America/New_York returned the expected “EST” timezone. Running the Nix date command with the same TZ=America/New_York returned the incorrect “America” timezone unless I also set TZDIR to an appropriate value. This is consistent with the behavior on NixOS regarding TZDIR. (this doesn’t happen when getting the local timezone using /etc/localtime)

Having the Nix installer set TZDIR to a location controlled by Nix would be a surprising system configuration change, and would cause problems for occasional Nix users or users that try Nix and decide it isn’t for them, because then their tzdata goes out of date and updating their system packages doesn’t fix it. To avoid that, either Nix packages running outside NixOS need to use the host system’s tzdata or they need an alternate, Nix-specific way of locating tzdata which doesn’t impact non-Nix software on the same system, and that would need to be reimplemented several times because not all packages use glibc for timezones.

But then in that case for #2 it’s kind of weird that outside an FHS environment everything uses the host distro tzdata, including Nix packages, but inside FHS environments it’s the Nix tzdata. Maybe it’s just inherently strange using Nix FHS environments on top of an FHS Linux distro. I guess it could be avoided if the script for entering the FHS environment mounted any existing /usr/share/zoneinfo over the FHS root FS.

1 Like

This failure on non-NixOS systems already has a ticket: glibc looking for zoneinfo files in the wrong place; failing silently · Issue #65415 · NixOS/nixpkgs · GitHub

I posted a separate topic about this but I want to make sure you see it: I’ve drafted a FHS major revision, licensing store-based Unixes and I’m looking for feedback. Currently it does not cover zoneinfo at all and I’m very open to suggestions on this front. The case where Nix is overlaid on another distribution is also not really covered. And the new language about “store-based” distributions assumes that everything that’s a “static” file goes in the store, and /usr might not even exist; which is abstractly pure but may not be the best practical option in cases like the one you bring up.

2 Likes