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 @@ | |
| 1 | 1 | const std = @import("std"); | |
| 2 | + | const posix = std.posix; | |
| 2 | 3 | ||
| 3 | 4 | pub const LogSystem = struct { | |
| 4 | 5 | file: ?std.fs.File = null, |
| ... | ... | @@ -22,7 +23,9 @@ pub const LogSystem = struct { | |
| 22 | 23 | else => return err, | |
| 23 | 24 | }; | |
| 24 | 25 | ||
| 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); | |
| 26 | 29 | try file.seekTo(end_pos); | |
| 27 | 30 | self.current_size = end_pos; | |
| 28 | 31 | self.file = file; |
+5,
-2
| ... | ... | @@ -44,11 +44,14 @@ pub fn cleanupStaleSocket(dir: std.fs.Dir, session_name: []const u8) void { | |
| 44 | 44 | } | |
| 45 | 45 | ||
| 46 | 46 | 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) { | |
| 48 | 51 | error.FileNotFound => return false, | |
| 49 | 52 | else => return err, | |
| 50 | 53 | }; | |
| 51 | - | if (stat.kind != .unix_domain_socket) { | |
| 54 | + | if (!posix.S.ISSOCK(stat.mode)) { | |
| 52 | 55 | return error.FileNotUnixSocket; | |
| 53 | 56 | } | |
| 54 | 57 | return true; |