rust-cross / cargo-zigbuild

Compile Cargo project with zig as linker
MIT License
1.57k stars 56 forks source link

Compiling libgit2 on glibc 2.17 stat error #255

Open NobodyXu opened 5 months ago

NobodyXu commented 5 months ago

Related: martinvonz/jj#3844

According to this comment by @yuja

For example, git_config_add_file_ondisk calls statically-linked stat64->fstatat64. OTOH, it refers to dynamically-linked errno.

which might. be the problem.

polarathene commented 2 months ago

TL;DR: Zig prior to 0.12.0 didn't handle linking glibc correctly if the target version was 2.32 or lower due to how these symbols were handled (not actual function calls).

You can skip the remainder of this comment, relevant details are covered in the follow-up comment.


Original response (suspected relevance to glibc 2.32 vs glibc 2.33) If it helps, this reminded me of when I was looking into a glibc version linking behaviour: https://github.com/rust-cross/cargo-zigbuild/pull/232#discussion_r1519253113 At the bottom of that linked comment is a collapsed section "Dynamic linking differences (resolved)", which has some output from another collapsed section "Reproduction example" earlier in the comment. It's a very simple reproduction that shows how `fstat64` changed from glibc 2.33 onwards: ```console # Same output for 2.17: $ cargo zigbuild --release --target x86_64-unknown-linux-gnu.2.32 $ readelf -W --version-info --dyn-syms target/x86_64-unknown-linux-gnu/release/example | grep stat64 61: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __fxstat64@GLIBC_2.2.5 (2) 62: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __xstat64@GLIBC_2.2.5 (2) ``` ```console $ cargo zigbuild --release --target x86_64-unknown-linux-gnu.2.33 $ readelf -W --version-info --dyn-syms target/x86_64-unknown-linux-gnu/release/example | grep stat64 25: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fstat64@GLIBC_2.33 (7) 40: 0000000000000000 0 FUNC GLOBAL DEFAULT UND stat64@GLIBC_2.33 (7) ```

Initial investigation

For jj, there was not enough information for how to reproduce the broken quickinstall build:

Inspecting the broken jj 0.18.0 quickinstall build ```console $ cd /tmp $ curl -fsSL \ https://github.com/cargo-bins/cargo-quickinstall/releases/download/jj-cli-0.18.0/jj-cli-0.18.0-x86_64-unknown-linux-gnu.tar.gz \ | tar -xz -C /tmp $ file jj ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.0.0, with debug_info, not stripped # The broken glibc build doesn't link openssl (static linked?) or interpreter: $ patchelf --print-needed jj libm.so.6 libpthread.so.0 libc.so.6 libdl.so.2 $ patchelf --print-interpreter jj /lib64/ld-linux-x86-64.so.2 $ readelf -W --version-info --syms jj | grep 'Name: GLIBC' | sed -re 's/.*GLIBC_(.+) Flags.*/\1/g' | sort -t . -k1,1n -k2,2n | tail -n 1 2.17 $ readelf -W --version-info --syms jj | grep stat64 27228: 0000000000000000 0 FILE LOCAL DEFAULT ABS stat64.c 27229: 0000000000000000 0 FILE LOCAL DEFAULT ABS fstat64.c 27232: 0000000000000000 0 FILE LOCAL DEFAULT ABS lstat64.c 27323: 0000000001274b90 49 FUNC WEAK DEFAULT 15 fstat64 27408: 0000000001274b70 23 FUNC WEAK DEFAULT 15 stat64 27434: 0000000001274bd0 26 FUNC WEAK DEFAULT 15 lstat64 33015: 0000000001274b70 23 FUNC GLOBAL DEFAULT 15 __stat64 33018: 0000000001274b90 49 FUNC GLOBAL DEFAULT 15 __fstat64 33020: 0000000001274bd0 26 FUNC GLOBAL DEFAULT 15 __lstat64 # No output (the problem?): $ strip jj && readelf -W --version-info --syms jj | grep stat64 # NOTE: clang 15 was released in Sep 2022, zig 0.10 was used to build $ readelf -p .comment jj String dump of section '.comment': [ 1] clang version 15.0.7 (https://github.com/ziglang/zig-bootstrap a3a6e85f9ec95b1772f5ace363e46df2f336c6b8) [ 6a] Linker: LLD 15.0.7 [ 7d] rustc version 1.78.0 (9b00956e5 2024-04-29) ```
Local JJ build ```console # zig 0.13.0 rust 1.81.0 openssl 3.2.2 $ dnf install -y patchelf git gcc rustup zig perl openssl-devel openssl-devel-engine $ rustup-init -y --profile minimal && source "$HOME/.cargo/env" $ cargo install cargo-zigbuild $ git clone --depth 1 https://github.com/martinvonz/jj /tmp/jj && cd /tmp/jj $ RUSTFLAGS="-L /usr/lib64 -C strip=none" cargo zigbuild --release --target x86_64-unknown-linux-gnu.2.17 $ patchelf --print-needed target/x86_64-unknown-linux-gnu/release/jj libssl.so.3 libcrypto.so.3 libm.so.6 libpthread.so.0 libc.so.6 libdl.so.2 ld-linux-x86-64.so.2 $ patchelf --print-interpreter target/x86_64-unknown-linux-gnu/release/jj /lib64/ld-linux-x86-64.so.2 $ readelf -W --version-info --syms target/x86_64-unknown-linux-gnu/release/jj | grep 'Name: GLIBC' | sed -re 's/.*GLIBC_(.+) Flags.*/\1/g' | sort -t . -k1,1n -k2,2n | tail -n 1 2.17 $ readelf -W --version-info --syms target/x86_64-unknown-linux-gnu/release/jj | grep stat64 399: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __lxstat64@GLIBC_2.2.5 (2) 400: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __fxstat64@GLIBC_2.2.5 (2) 402: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __xstat64@GLIBC_2.2.5 (2) 29175: 0000000000000000 0 FILE LOCAL DEFAULT ABS stat64-2.32.c 29176: 00000000010e0680 21 FUNC LOCAL HIDDEN 16 stat64 29177: 0000000000000000 0 FILE LOCAL DEFAULT ABS fstat64-2.32.c 29178: 00000000010e06a0 20 FUNC LOCAL HIDDEN 16 fstat64 29179: 0000000000000000 0 FILE LOCAL DEFAULT ABS lstat64-2.32.c 29180: 00000000010e06c0 21 FUNC LOCAL HIDDEN 16 lstat64 33897: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __lxstat64 33898: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __fxstat64 33900: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __xstat64 # Stripped: $ strip target/x86_64-unknown-linux-gnu/release/jj $ readelf -W --version-info --syms target/x86_64-unknown-linux-gnu/release/jj | grep stat64 402: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __lxstat64@GLIBC_2.2.5 (2) 404: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __xstat64@GLIBC_2.2.5 (2) 405: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __fxstat64@GLIBC_2.2.5 (2) # With glibc 2.33 target: $ RUSTFLAGS="-L /usr/lib64 -C strip=none" cargo zigbuild --release --target x86_64-unknown-linux-gnu.2.33 $ readelf -W --version-info --syms target/x86_64-unknown-linux-gnu/release/jj | grep stat64 349: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fstat64@GLIBC_2.33 (12) 363: 0000000000000000 0 FUNC GLOBAL DEFAULT UND stat64@GLIBC_2.33 (12) 364: 0000000000000000 0 FUNC GLOBAL DEFAULT UND lstat64@GLIBC_2.33 (12) 33699: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fstat64 33748: 0000000000000000 0 FUNC GLOBAL DEFAULT UND stat64 33749: 0000000000000000 0 FUNC GLOBAL DEFAULT UND lstat64 ```

Minimal reproduction attempt

Initial environment setup **`Cargo.toml`:** ```toml [package] name = "example" version = "0.1.0" edition = "2021" [dependencies] libc = "0.2.153" ``` **`src/main.rs`:** ```rs use std::fs::File; use std::os::unix::io::AsRawFd; fn main() { let file = File::open("hello.txt").expect("`hello.txt` should exist"); // Placeholder value: let mut uid = 42; unsafe { let mut stat: libc::stat64 = std::mem::zeroed(); // Returns c_int, 0 is success, -1 is failure: if libc::fstat64(file.as_raw_fd(), &mut stat) == 0 { uid = stat.st_uid; } } println!("`hello.txt` is owned by UID: {uid}"); } ``` ```console # Older release before glibc 2.32: $ docker run --rm -it fedora:31 # Glibc 2.30 $ ldd --version ldd (GNU libc) 2.30 Copyright (C) 2019 Free Software Foundation, Inc. # Install deps: $ dnf install -y gcc pip nano $ pip install ziglang $ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh $ . "$HOME/.cargo/env" $ cargo install cargo-zigbuild # Create demo project: $ mkdir -p /example/src $ nano /example/Cargo.toml $ nano /example/src/main.rs ```
Build attempts with zig ```console ## glibc 2.17 target # NOTE: fstat64 has 2.32 version even though build host is glibc 2.30? However, these are only from debug symbols # UPDATE: This was identified as a zig patch/workaround for proper glibc <= 2.32 support $ RUSTFLAGS="-C strip=none" cargo zigbuild --release --target x86_64-unknown-linux-gnu.2.17 $ readelf -W --version-info --syms target/x86_64-unknown-linux-gnu/release/example | grep stat64 60: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __fxstat64@GLIBC_2.2.5 (2) 61: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __xstat64@GLIBC_2.2.5 (2) 745: 0000000000000000 0 FILE LOCAL DEFAULT ABS stat64-2.32.c 746: 000000000005a4a0 21 FUNC LOCAL HIDDEN 15 stat64 747: 0000000000000000 0 FILE LOCAL DEFAULT ABS fstat64-2.32.c 748: 000000000005a4c0 20 FUNC LOCAL HIDDEN 15 fstat64 749: 0000000000000000 0 FILE LOCAL DEFAULT ABS lstat64-2.32.c 1045: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __fxstat64 1046: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __xstat64 $ readelf -W --version-info --syms target/x86_64-unknown-linux-gnu/release/example | grep 'Name: GLIBC' | sed -re 's/.*GLIBC_(.+) Flags.*/\1/g' | sort -t . -k1,1n -k2,2n | tail -n 1 2.16 ## glibc 2.32 target # Same as above: $ RUSTFLAGS="-C strip=none" cargo zigbuild --release --target x86_64-unknown-linux-gnu.2.32 $ readelf -W --version-info --syms target/x86_64-unknown-linux-gnu/release/example | grep stat64 60: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __fxstat64@GLIBC_2.2.5 (2) 61: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __xstat64@GLIBC_2.2.5 (2) 745: 0000000000000000 0 FILE LOCAL DEFAULT ABS stat64-2.32.c 746: 000000000005a4d0 21 FUNC LOCAL HIDDEN 15 stat64 747: 0000000000000000 0 FILE LOCAL DEFAULT ABS fstat64-2.32.c 748: 000000000005a4f0 20 FUNC LOCAL HIDDEN 15 fstat64 749: 0000000000000000 0 FILE LOCAL DEFAULT ABS lstat64-2.32.c 1045: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __fxstat64 1046: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __xstat64 # Min glibc version did go up: $ readelf -W --version-info --syms target/x86_64-unknown-linux-gnu/release/example | grep 'Name: GLIBC' | sed -re 's/.*GLIBC_(.+) Flags.*/\1/g' | sort -t . -k1,1n -k2,2n | tail -n 1 2.28 # NOTE: Without version should be equivalent to host glibc, thus 2.30, but outputs is same as for 2.32?: # UPDATE: The 2.32 annotation to debug symbols is zig specific, not related to actual glibc min version requirements $ RUSTFLAGS="-C strip=none" cargo zigbuild --release --target x86_64-unknown-linux-gnu ## glibc 2.33 target # Min glibc version 2.33 now required, zig will build correctly for targets of glibc that are newer than the host glibc: $ RUSTFLAGS="-C strip=none" cargo zigbuild --release --target x86_64-unknown-linux-gnu.2.33 readelf -W --version-info --syms target/x86_64-unknown-linux-gnu/release/example | grep stat64 3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fstat64@GLIBC_2.33 (3) 39: 0000000000000000 0 FUNC GLOBAL DEFAULT UND stat64@GLIBC_2.33 (3) 769: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fstat64 950: 0000000000000000 0 FUNC GLOBAL DEFAULT UND stat64 $ readelf -W --version-info --syms target/x86_64-unknown-linux-gnu/release/example | grep 'Name: GLIBC' | sed -re 's/.*GLIBC_(.+) Flags.*/\1/g' | sort -t . -k1,1n -k2,2n | tail -n 1 2.33 $ readelf -p .comment /example/target/x86_64-unknown-linux-gnu/release/example String dump of section '.comment': [ 0] Linker: LLD 18.1.6 [ 13] clang version 18.1.6 (https://github.com/ziglang/zig-bootstrap 98bc6bf4fc4009888d33941daf6b600d20a42a56) [ 7d] rustc version 1.81.0 (eeb90cda1 2024-09-04) ```
Build attempt without zig ```console # `cargo build` (Rust 1.81, uses fedora 31 provided glibc 2.30): # Symbols differ a bit vs zig: $ RUSTFLAGS="-C strip=none" cargo build --release --target x86_64-unknown-linux-gnu $ readelf -W --version-info --syms target/x86_64-unknown-linux-gnu/release/example | grep stat64 20: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __xstat64@GLIBC_2.2.5 (2) 34: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __fxstat64@GLIBC_2.2.5 (2) 59: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __lxstat64@GLIBC_2.2.5 (2) 587: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __xstat64@@GLIBC_2.2.5 603: 0000000000046120 19 FUNC GLOBAL HIDDEN 13 fstat64 612: 0000000000046100 20 FUNC GLOBAL HIDDEN 13 stat64 658: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __fxstat64@@GLIBC_2.2.5 685: 0000000000046140 20 FUNC GLOBAL HIDDEN 13 lstat64 767: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __lxstat64@@GLIBC_2.2.5 $ readelf -W --version-info --syms target/x86_64-unknown-linux-gnu/release/example | grep 'Name: GLIBC' | sed -re 's/.*GLIBC_(.+) Flags.*/\1/g' | sort -t . -k1,1n -k2,2n | tail -n 1 2.28 ```

With -C strip=symbols to remove debug symbols, you'll get the same two line outputs for cargo build / cargo zigbuild as I showed for the other reproduction example.

In an attempt to match the jj build environment a bit better, I switched to zig 0.10.0 (which is what quickinstall is using) and Rust 1.78.0:

Build attempt with zig 0.10.0 + Rust 1.78.0 ```console # From the same build Fedora 31 environment above: $ dnf install -y xz patchelf && mkdir /opt/zig $ curl -fsSL https://ziglang.org/download/0.10.0/zig-linux-x86_64-0.10.0.tar.xz \ | tar -xJ -C /opt/zig --strip-components=1 $ export PATH="/opt/zig:${PATH}" # zigbuild prefers python installs before checking for binary installs, uninstall the zig 0.13.0 python package: $ pip uninstall ziglang $ rustup toolchain install 1.78.0 && rustup default 1.78.0 $ RUSTFLAGS="-C strip=none" cargo zigbuild --release --target x86_64-unknown-linux-gnu.2.17 # Close enough?: $ readelf -p .comment target/x86_64-unknown-linux-gnu/release/example String dump of section '.comment': [ 1] clang version 15.0.3 (git@github.com:ziglang/zig-bootstrap.git 85033a9aa569b41658404d0e8a5ab887b81d537b) [ 6a] Linker: LLD 15.0.3 [ 7d] rustc version 1.78.0 (9b00956e5 2024-04-29) # Now we have output for symbols weakly linked, similar to the broken jj release build, huzzah!: $ readelf -W --version-info --syms target/x86_64-unknown-linux-gnu/release/example | grep stat64 752: 0000000000000000 0 FILE LOCAL DEFAULT ABS stat64.c 753: 0000000000000000 0 FILE LOCAL DEFAULT ABS fstat64.c 756: 0000000000000000 0 FILE LOCAL DEFAULT ABS lstat64.c 781: 000000000006a070 49 FUNC WEAK DEFAULT 15 fstat64 967: 000000000006a050 23 FUNC WEAK DEFAULT 15 stat64 1149: 000000000006a050 23 FUNC GLOBAL DEFAULT 15 __stat64 1153: 000000000006a070 49 FUNC GLOBAL DEFAULT 15 __fstat64 # No symbols output (expected): $ strip target/x86_64-unknown-linux-gnu/release/example $ readelf -W --version-info --syms target/x86_64-unknown-linux-gnu/release/example | grep stat64 # However it still functions correctly (unexpected): # UPDATE: This was my mistake, I was testing the fstat64 call, not the failure condition with incorrect errno returned as detailed later $ touch hello.txt && chown 77:77 hello.txt $ target/x86_64-unknown-linux-gnu/release/example `hello.txt` is owned by UID: 77 $ patchelf --print-needed target/x86_64-unknown-linux-gnu/release/example libpthread.so.0 libc.so.6 libdl.so.2 ld-linux-x86-64.so.2 ```

So... something else is going on that I'm missing... as I can't reproduce whatever else was going on 🤷‍♂️

polarathene commented 2 months ago

Actually... I just realized the bug report wasn't about this fstat64 call at all, it was about the git paths being different / deprecated, thus the paths themselves didn't exist. (EDIT: As covered below in linked issue, the fstat64 call was misleadingly returning an errno of 0 when the checked file path did not exist)

zig 0.11.0 is the last release it fails. Seems to have been fixed from zig 0.12.0 onwards, but I don't know if it was described in the release notes 🤔 Oh it seems like it might have been this issue (regarding errno and fstatat64). That aligns with the findings at the cited jj comment 🎉

Proper minimal reproduction

The zig issues provide reproduction in other languages, once I learned it was about errno I could reproduce in rust too (well via unsafe libc calls):

src/main.rs:

// Reproduction requires building with zig < 0.12.0 and glibc target < 2.33
fn main() {
    unsafe {
        // Reproduction requires the checked file path to be invalid:
        let filepath = std::ffi::CString::new("this_file_does_not_exist").unwrap();
        let mut stat: libc::stat64 = std::mem::zeroed();
        let ptr = filepath.as_ptr();

        // Returns c_int, 0 is success, -1 is failure:
        let ret = libc::stat64(ptr, &mut stat);
        // This and other stat calls will fail in the same way:
        //let ret = libc::fstatat64(libc::AT_FDCWD, ptr, &mut stat as *mut libc::stat64, 0);

        let errno = std::io::Error::last_os_error();
        // NOTE: `errno.raw_os_error().unwrap();` can provide just the error code itself

        match ret {
            // Example of working functionality when successful:
            0 => {
                let uid = stat.st_uid;
                println!("The file is owned by UID: {uid}");
            },
            // The `errno` code will incorrectly be 0 (Success) despite actually failing (`ret == -1`):
            _ => println!("Failure!\n| ret: {ret:?}\n| err: {errno:?}")
        }
    }
}

Initial environment setup:

$ docker run --rm -it fedora:41

# Add zig releases:
$ mkdir /opt/zig-11 /opt/zig-12
$ curl -fsSL https://ziglang.org/download/0.11.0/zig-linux-x86_64-0.11.0.tar.xz \
  | tar -xJ -C /opt/zig-11 --strip-components=1
$ curl -fsSL https://ziglang.org/download/0.12.0/zig-linux-x86_64-0.12.0.tar.xz \
  | tar -xJ -C /opt/zig-12 --strip-components=1

# Add rust:
$ dnf install -y gcc rustup nano
$ rustup-init -y --profile minimal && source "$HOME/.cargo/env"
$ cargo install cargo-zigbuild

# Prep project:
$ cargo init /tmp/example && cd /tmp/example
$ cargo add libc
# Replace main.rs with above rust snippet:
$ rm -f src/main.rs && nano src/main.rs

Here are the results:

# `ret -1` is expected, but the `err` code should be `2`, not `0` as it wasn't actually successful:
$ CARGO_ZIGBUILD_ZIG_PATH=/opt/zig-11/zig cargo zigbuild --release --target x86_64-unknown-linux-gnu.2.32
$ target/x86_64-unknown-linux-gnu/release/example
Failure!
| ret: -1
| err: Os { code: 0, kind: Uncategorized, message: "Success" }

# Correct for glibc 2.33+ where the fstat symbol changed to real function calls:
$ rm -rf target
$ CARGO_ZIGBUILD_ZIG_PATH=/opt/zig-11/zig cargo zigbuild --release --target x86_64-unknown-linux-gnu.2.33
Failure!
| ret: -1
| err: Os { code: 2, kind: NotFound, message: "No such file or directory" }

# glibc 2.32 is working correctly from zig 0.12.0+:
$ rm -rf target
$ CARGO_ZIGBUILD_ZIG_PATH=/opt/zig-12/zig cargo zigbuild --release --target x86_64-unknown-linux-gnu.2.32
Failure!
| ret: -1
| err: Os { code: 2, kind: NotFound, message: "No such file or directory" }

References

Release dates for context to the October 2020 glibc change referenced below:

Quoted reference links for technical background (glibc 2.32 below issue with fstat and errno + zig patch) > https://github.com/ziglang/zig/issues/17034#issuecomment-1761898889 > > Stepping through the generated code in a debugger, the `fstatat` syscall executes, fails, returns the correct status code, then that code is converted into an `errno` and stashed in a thread-local variable. > The call returns and Zig code fetches `errno`, but seems to fetch it from a different place than where `errno` was stored. > https://github.com/ziglang/zig/issues/17034#issuecomment-1762252393 > > So "fstatat64" is the undefined symbol in the main object file (`confused-tls.o`). Oddly the "libc.so.6" binary doesn't have any `fstatat` symbols. > Then `libc_nonshared.a` has undefined references to "__fstatat64", and also includes "__fstatat64" and a weak "fstatat64". > I believe this weak symbol is resolving the undefined symbol in the main binary. > https://github.com/ziglang/zig/issues/17034#issuecomment-1762636088 > > The other thing that doesn't make sense is that the `libc_nonshared.c` `fstat` implementation was a raw syscall. > The `libc_noshared` implementation is meant to forward to the "real" `libc`, via a non-standard "xstat()" implementation. (The whole point of the `nonshared.a` library is to link in stubs that jump into the dynamically linked library using a version-aware API....) > > It looks like the `libc_nonshared` (_also called "static-only-routines" in the `Makefile`s_) `fstat*` wrappers were [excised from `libc_nonshared` in Oct 2020](https://sourceware.org/git/?p=glibc.git;a=commit;h=4d97cc8cf3da925fd06fc37d4daebafce3247719) A related issue also cites `libgit2`: > https://github.com/ziglang/zig/issues/11878#issuecomment-1183334272 > > This breaks using `libgit2` with `zig cc` for me, since it relies on the `lstat` `errno` when creating the directory for cloning a repository Associated PR fix [adds some docs](https://github.com/ziglang/zig/commit/45836678d76014f94d35a2ffea983c21a57426c4#diff-8d44499ac9cec919e151625cdd974e6eb1772f161359d050c2e86eb563b71516R70-R73): > Like the public headers, these files contain a couple customizations for Zig to be able to build for any supported glibc version. > E.g., for glibc versions before v2.32, `libc_nonshared.a` contained stubs that directed the `fstat()` call to a versioned `__fxstat()` call. Which also [explains where the `2.32` version pinning was coming from](https://github.com/ziglang/zig/commit/45836678d76014f94d35a2ffea983c21a57426c4#diff-554736e2b190fe108074f898c2084548b6182679d02b2826034effaab1efc7b4R158-R161) with my earlier output of newer zig builds below glibc `2.33`: > Zig patch. > `weak_hidden_alias` was removed from glibc v2.36 (v2.37?), > Zig needs it for the v2.32 and earlier `{f,l,}stat` wrappers, so only include in this header for 2.32 and earlier. And another reference I came across while looking into the glibc 2.33 change (_before landing on that zig issue above_): > https://github.com/wheybags/glibc_version_header/issues/32#issuecomment-1334060517 > > Until glibc 2.32, `stat64` etc. were redirected to `__xstat64` etc. by **inline functions or macros**; glibc 2.33 changed them to **true functions**. > As a result, code compiled against **glibc 2.32 or older** will get the old `__xstat64` etc. implementation that is still present, whereas code compiled against **glibc 2.33** will get the new `stat64` etc. implementation.