authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-09-22 18:46:38-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-25 01:35:01+02:00
log5b9147ed59b22edf7f891cf30e8b77223121da4f
tree55883aa889e8725b821d78eae187efbd09fcebf5
parent2a03d6b007163fbd83567a6b0907a1866c1f92e8

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

2 files changed, 19 insertions(+), 8 deletions(-)

lib/std/Io/Threaded.zig+10
......@@ -14176,6 +14176,16 @@ fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathEr
1417614176 .SUCCESS => return std.mem.findScalar(u8, buffer, 0).?,
1417714177 .NOENT => return error.CurrentDirUnlinked,
1417814178 .RANGE => return error.NameTooLong,
14179 // On Linux, this indicates that the buffer may have been large enough, but the path
14180 // is too long for the syscall to handle since it exceeds PATH_MAX.
14181 // https://github.com/torvalds/linux/blob/e4b275531887fef7f7d8a7284bfc32f0fbbd4208/fs/d_path.c#L406-L407
14182 //
14183 // This is returned as a distinct error to differentiate it from RANGE, since
14184 // this NAMETOOLONG return is never recoverable by increasing the buffer size.
14185 // The way to recover from it would be to reconstruct the cwd piece-by-piece by e.g.
14186 // walking the filesystem backwards starting from "." (many libc implementations
14187 // of `getcwd` do this fallback-to-filesystem-walking automatically).
14188 .NAMETOOLONG => return error.PathExceedsLimit,
1417914189 .FAULT => |e| return errnoBug(e),
1418014190 .INVAL => |e| return errnoBug(e),
1418114191 else => return posix.unexpectedErrno(err),
lib/std/process.zig+9-8
......@@ -58,9 +58,14 @@ pub const Init = struct {
5858};
5959
6060pub const CurrentPathError = error{
61 /// Buffer is too small to contain the current path.
6162 NameTooLong,
6263 /// Not possible on Windows. Always returned on WASI.
6364 CurrentDirUnlinked,
65 /// The current path cannot be retrieved due to a limitation of the
66 /// underlying libc or syscall implementation. Retrying with a larger
67 /// buffer will not succeed.
68 PathExceedsLimit,
6469} || Io.Cancelable || Io.UnexpectedError;
6570
6671/// 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 {
7075 return io.vtable.processCurrentPath(io.userdata, buffer);
7176}
7277
73pub const CurrentPathAllocError = Allocator.Error || error{
74 /// Not possible on Windows. Always returned on WASI.
75 CurrentDirUnlinked,
76} || Io.Cancelable || Io.UnexpectedError;
78pub const CurrentPathAllocError = Allocator.Error || CurrentPathError;
7779
80/// Shortcut for calling `currentPath` with a buffer of size `max_path_bytes`.
81///
7882/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
7983/// On other platforms, the result is an opaque sequence of bytes with no
8084/// particular encoding.
......@@ -82,10 +86,7 @@ pub const CurrentPathAllocError = Allocator.Error || error{
8286/// Caller owns returned memory.
8387pub fn currentPathAlloc(io: Io, allocator: Allocator) CurrentPathAllocError![:0]u8 {
8488 var buffer: [max_path_bytes]u8 = undefined;
85 const n = currentPath(io, &buffer) catch |err| switch (err) {
86 error.NameTooLong => unreachable,
87 else => |e| return e,
88 };
89 const n = try currentPath(io, &buffer);
8990 return allocator.dupeSentinel(u8, buffer[0..n], 0);
9091}
9192