Skip to content

Instantly share code, notes, and snippets.

@w568w
Created June 24, 2025 08:00
Show Gist options
  • Select an option

  • Save w568w/d423ef9e1b473c928d19e7c49e521f8a to your computer and use it in GitHub Desktop.

Select an option

Save w568w/d423ef9e1b473c928d19e7c49e521f8a to your computer and use it in GitHub Desktop.
Patch Zig standard library to support old Linux devices

Patch Zig Stdlib

Zig is great. But its aggressive minimum Linux kernel support policy—only supporting versions newer than the current Debian LTS kernel, 5.10—makes it unusable on older systems like Kindles, Termux on Android, and old servers.

This guide introduces a simple, minimally invasive patch for the standard library to maximize compatibility with these older devices.

What not supported?

The specific syscall that's breaking compatibility with old kernels is statx, which was introduced in Zig 0.14 (replacing POSIX's fstat).

It appears to be called in only two places in the standard library, both of which are guarded by an os == .linux check.

Prerequisites

  • The latest version of Zig is installed and in your PATH (i.e., zig runs correctly).
  • You are on Linux.
  • You have jq installed for parsing JSON.

Usage

  1. Download zigw.bash to your project root, for example, as ./zigw.
  2. Run ./zigw once from your project root. This will create a patched_std_lib directory containing the standard library.
  3. Modify the standard library in this directory as needed.
  4. From now on, use ./zigw as a drop-in replacement for zig. For example, run ./zigw build to compile your program.

A pre-made patch for statx compatibility is provided in the statx.diff file below. You can apply it with patch -p2 < statx.diff. Activate it by using a target triplet with a lower min version specifier:

$ ./zigw build -Dtarget=arm-linux.4.1

Q&A

Q: Why modify a copy in the project directory instead of the original std lib?

A: Patches should be project-specific and not alter the system-wide std lib directly.

Q: Why use a bind mount to remap the std lib path instead of a portable argument like --zig-lib-dir ./patched_lib?

A: (1) I only discovered this flag in zig build --help after I had already written the script and was too lazy to refactor it. (2) A bind mount theoretically offers better compatibility.

#!/bin/bash
# shellcheck disable=SC2317
set -e
unshare --map-root-user --mount bash -c "$(tail --lines +8 "${BASH_SOURCE[0]}")" "$0" "$@"
exit $?
# below is executed in a separate mount namespace
readonly PATCHED_STD_LIB="./patched_std_lib"
STD_LIB=$(zig env | jq --raw-output .std_dir)
readonly STD_LIB
if [[ ! -d "${PATCHED_STD_LIB}" ]]; then
echo "Copying std lib to ${PATCHED_STD_LIB}"
cp --recursive "${STD_LIB}" "${PATCHED_STD_LIB}"
echo "Patched std lib copied to ${PATCHED_STD_LIB}"
exit 0
fi
mount --bind "${PATCHED_STD_LIB}" "${STD_LIB}"
zig "$@"
umount "${STD_LIB}"
diff --git a/./std_lib/fs/Dir.zig b/./patched_std_lib/fs/Dir.zig
index e3c5568..ca02836 100644
--- a/./std_lib/fs/Dir.zig
+++ b/./patched_std_lib/fs/Dir.zig
@@ -2689,7 +2689,7 @@ pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
const st = try std.os.fstatat_wasi(self.fd, sub_path, .{ .SYMLINK_FOLLOW = true });
return Stat.fromWasi(st);
}
- if (native_os == .linux) {
+ if (native_os == .linux and std.fs.File.support_statx) {
const sub_path_c = try posix.toPosixPath(sub_path);
var stx = std.mem.zeroes(linux.Statx);
diff --git a/./std_lib/fs/File.zig b/./patched_std_lib/fs/File.zig
index 9797a1b..c7691a7 100644
--- a/./std_lib/fs/File.zig
+++ b/./patched_std_lib/fs/File.zig
@@ -21,6 +21,12 @@ pub const Kind = enum {
unknown,
};
+pub const support_statx = builtin.os.version_range.linux.isAtLeast(.{
+ .major = 4,
+ .minor = 11,
+ .patch = 0,
+}) orelse false;
+
/// This is the default mode given to POSIX operating systems for creating
/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,
/// since most people would expect "-rw-r--r--", for example, when using
@@ -528,7 +534,7 @@ pub fn stat(self: File) StatError!Stat {
return Stat.fromWasi(st);
}
- if (builtin.os.tag == .linux) {
+ if (builtin.os.tag == .linux and support_statx) {
var stx = std.mem.zeroes(linux.Statx);
const rc = linux.statx(
@@ -748,7 +754,13 @@ pub const Metadata = struct {
/// Exposes platform-specific functionality.
inner: switch (builtin.os.tag) {
.windows => MetadataWindows,
- .linux => MetadataLinux,
+ .linux => blk: {
+ if (support_statx) {
+ break :blk MetadataLinux;
+ } else {
+ break :blk MetadataUnix;
+ }
+ },
.wasi => MetadataWasi,
else => MetadataUnix,
},
@@ -1061,6 +1073,12 @@ pub fn metadata(self: File) MetadataError!Metadata {
};
},
.linux => blk: {
+ if (!support_statx) {
+ // If statx is not supported, fall back to fstat
+ break :blk .{
+ .stat = try posix.fstat(self.handle),
+ };
+ }
var stx = std.mem.zeroes(linux.Statx);
// We are gathering information for Metadata, which is meant to contain all the
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment