Commit 93247e9

Chris Rose  ·  2026-07-04 16:41:39 -0400 EDT
parent 444fa72
fix: avoid statx syscall so zmx runs on pre-4.11 kernels (#186)

zmx crashed with `error: Unexpected` on every invocation on a Synology
DiskStation (Linux 4.4.302). `error.Unexpected` is what Zig's std returns
for an unrecognized errno; here the errno is ENOSYS (38) from the `statx`
syscall, which only exists on Linux >= 4.11.

Two code paths issued statx via Zig std, which calls the raw statx syscall
directly and does not fall back like libc does:

  - log.zig: LogSystem.init() called file.getEndPos() -> File.stat() -> statx.
    This runs before command parsing, so it broke *every* subcommand, not
    just attach.
  - socket.zig: sessionExists() called Dir.statFile() -> statx.

Both now use fd/at-based stat instead:

  - LogSystem.init() uses posix.fstat(file.handle).
  - sessionExists() uses posix.fstatatZ() + S.ISSOCK().

Because zmx links musl, these resolve to SYS_fstat / SYS_fstatat (musl on
x86_64 skips statx entirely), which are available on 4.4. Verified by
disassembling the release binary: no reachable statx call remains on the
attach path (the only residual references are the DWARF stack-trace
unwinder and sendFile fast-paths, neither of which zmx exercises).
2 files changed,  +9, -3
+4, -1
......@@ -1,4 +1,5 @@
11 const std = @import("std");
2+const posix = std.posix;
23
34 pub const LogSystem = struct {
45 file: ?std.fs.File = null,
......@@ -22,7 +23,9 @@ pub const LogSystem = struct {
2223 else => return err,
2324 };
2425
25- const end_pos = try file.getEndPos();
26+ // fstat (not getEndPos) to avoid the statx syscall; see #186.
27+ const st = try posix.fstat(file.handle);
28+ const end_pos: u64 = @intCast(st.size);
2629 try file.seekTo(end_pos);
2730 self.current_size = end_pos;
2831 self.file = file;
+5, -2
......@@ -44,11 +44,14 @@ pub fn cleanupStaleSocket(dir: std.fs.Dir, session_name: []const u8) void {
4444 }
4545
4646 pub fn sessionExists(dir: std.fs.Dir, name: []const u8) !bool {
47- const stat = dir.statFile(name) catch |err| switch (err) {
47+ // fstatatZ (not statFile) to avoid the statx syscall
48+ // https://github.com/neurosnap/zmx/issues/186
49+ const name_c = try posix.toPosixPath(name);
50+ const stat = posix.fstatatZ(dir.fd, &name_c, posix.AT.SYMLINK_NOFOLLOW) catch |err| switch (err) {
4851 error.FileNotFound => return false,
4952 else => return err,
5053 };
51- if (stat.kind != .unix_domain_socket) {
54+ if (!posix.S.ISSOCK(stat.mode)) {
5255 return error.FileNotUnixSocket;
5356 }
5457 return true;