From 5b9147ed59b22edf7f891cf30e8b77223121da4f Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Tue, 22 Sep 2026 18:46:38 -0700 Subject: [PATCH] process.currentPathAlloc: deal with the reality that cwd can be larger than max_path_bytes The assumption currentPathAlloc was making (that the path can never exceed max_path_bytes) was incorrect. What exactly that means depends on (1) the system, (2) whether or not libc is linked, and (3) (if libc is linked) the libc implementation of `getcwd`. The POSIX spec says that ERANGE is returned when the buffer is smaller than necessary. https://pubs.opengroup.org/onlinepubs/9799919799/ However, on Linux, the getcwd *syscall* is limited by PATH_MAX, so the getcwd syscall will return ENAMETOOLONG if the syscall is unable to provide the full cwd path. https://github.com/torvalds/linux/blob/e4b275531887fef7f7d8a7284bfc32f0fbbd4208/fs/d_path.c#L406-L407 That doesn't mean the full cwd path can't be gotten, but it means it would need to be reconstructed piece-by-piece by walking the filesystem. Many libc implementations will do that filesystem walking automatically (musl doesn't; gnu libc, freebsd, etc do), so the libc being linked also determines whether or not ENAMETOOLONG is possible. FreeBSD: https://cgit.freebsd.org/src/tree/lib/libc/gen/getcwd.c GNU libc: https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/posix/getcwd.c;h=91c6ffd4d8208a0a8efb9c3fcb9dca0ca1007f33;hb=HEAD Instead of adding recovery to these APIs (e.g. expanding the buffer and retrying, or walking the filesystem to construct the path independently), `currentPathAlloc` is now explicitly a convenience function, and the error sets of both `currentPath` and `currentPathAlloc` have been expanded to give the caller the ability to disambiguate between the possible ways they may be able to recover and let them handle recovery if deeply nested cwd paths are something they want to support. Closes #36928 --- lib/std/Io/Threaded.zig | 10 ++++++++++ lib/std/process.zig | 17 +++++++++-------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 5b1e5ba3fa3cc5168467322a799220096ebba8a5..3b688ecc93c36631fa0c0bad423155abb8020eeb 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -14176,6 +14176,16 @@ fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathEr .SUCCESS => return std.mem.findScalar(u8, buffer, 0).?, .NOENT => return error.CurrentDirUnlinked, .RANGE => return error.NameTooLong, + // On Linux, this indicates that the buffer may have been large enough, but the path + // is too long for the syscall to handle since it exceeds PATH_MAX. + // https://github.com/torvalds/linux/blob/e4b275531887fef7f7d8a7284bfc32f0fbbd4208/fs/d_path.c#L406-L407 + // + // This is returned as a distinct error to differentiate it from RANGE, since + // this NAMETOOLONG return is never recoverable by increasing the buffer size. + // The way to recover from it would be to reconstruct the cwd piece-by-piece by e.g. + // walking the filesystem backwards starting from "." (many libc implementations + // of `getcwd` do this fallback-to-filesystem-walking automatically). + .NAMETOOLONG => return error.PathExceedsLimit, .FAULT => |e| return errnoBug(e), .INVAL => |e| return errnoBug(e), else => return posix.unexpectedErrno(err), diff --git a/lib/std/process.zig b/lib/std/process.zig index 4f0b388b80ec5ff6a521bfa0e57009a4c5ba3fbe..651d854edb623b25437e418640fa54f7f4e6c67a 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -58,9 +58,14 @@ pub const Init = struct { }; pub const CurrentPathError = error{ + /// Buffer is too small to contain the current path. NameTooLong, /// Not possible on Windows. Always returned on WASI. CurrentDirUnlinked, + /// The current path cannot be retrieved due to a limitation of the + /// underlying libc or syscall implementation. Retrying with a larger + /// buffer will not succeed. + PathExceedsLimit, } || Io.Cancelable || Io.UnexpectedError; /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). @@ -70,11 +75,10 @@ pub fn currentPath(io: Io, buffer: []u8) CurrentPathError!usize { return io.vtable.processCurrentPath(io.userdata, buffer); } -pub const CurrentPathAllocError = Allocator.Error || error{ - /// Not possible on Windows. Always returned on WASI. - CurrentDirUnlinked, -} || Io.Cancelable || Io.UnexpectedError; +pub const CurrentPathAllocError = Allocator.Error || CurrentPathError; +/// Shortcut for calling `currentPath` with a buffer of size `max_path_bytes`. +/// /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On other platforms, the result is an opaque sequence of bytes with no /// particular encoding. @@ -82,10 +86,7 @@ pub const CurrentPathAllocError = Allocator.Error || error{ /// Caller owns returned memory. pub fn currentPathAlloc(io: Io, allocator: Allocator) CurrentPathAllocError![:0]u8 { var buffer: [max_path_bytes]u8 = undefined; - const n = currentPath(io, &buffer) catch |err| switch (err) { - error.NameTooLong => unreachable, - else => |e| return e, - }; + const n = try currentPath(io, &buffer); return allocator.dupeSentinel(u8, buffer[0..n], 0); } -- 2.54.0