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...@@ -14176,6 +14176,16 @@ fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathEr
14176 .SUCCESS => return std.mem.findScalar(u8, buffer, 0).?,14176 .SUCCESS => return std.mem.findScalar(u8, buffer, 0).?,
14177 .NOENT => return error.CurrentDirUnlinked,14177 .NOENT => return error.CurrentDirUnlinked,
14178 .RANGE => return error.NameTooLong,14178 .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,
14179 .FAULT => |e| return errnoBug(e),14189 .FAULT => |e| return errnoBug(e),
14180 .INVAL => |e| return errnoBug(e),14190 .INVAL => |e| return errnoBug(e),
14181 else => return posix.unexpectedErrno(err),14191 else => return posix.unexpectedErrno(err),
lib/std/process.zig+9-8
...@@ -58,9 +58,14 @@ pub const Init = struct {...@@ -58,9 +58,14 @@ pub const Init = struct {
58};58};
5959
60pub const CurrentPathError = error{60pub const CurrentPathError = error{
61 /// Buffer is too small to contain the current path.
61 NameTooLong,62 NameTooLong,
62 /// Not possible on Windows. Always returned on WASI.63 /// Not possible on Windows. Always returned on WASI.
63 CurrentDirUnlinked,64 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,
64} || Io.Cancelable || Io.UnexpectedError;69} || Io.Cancelable || Io.UnexpectedError;
6570
66/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).71/// 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 {...@@ -70,11 +75,10 @@ pub fn currentPath(io: Io, buffer: []u8) CurrentPathError!usize {
70 return io.vtable.processCurrentPath(io.userdata, buffer);75 return io.vtable.processCurrentPath(io.userdata, buffer);
71}76}
7277
73pub const CurrentPathAllocError = Allocator.Error || error{78pub const CurrentPathAllocError = Allocator.Error || CurrentPathError;
74 /// Not possible on Windows. Always returned on WASI.
75 CurrentDirUnlinked,
76} || Io.Cancelable || Io.UnexpectedError;
7779
80/// Shortcut for calling `currentPath` with a buffer of size `max_path_bytes`.
81///
78/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).82/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
79/// On other platforms, the result is an opaque sequence of bytes with no83/// On other platforms, the result is an opaque sequence of bytes with no
80/// particular encoding.84/// particular encoding.
...@@ -82,10 +86,7 @@ pub const CurrentPathAllocError = Allocator.Error || error{...@@ -82,10 +86,7 @@ pub const CurrentPathAllocError = Allocator.Error || error{
82/// Caller owns returned memory.86/// Caller owns returned memory.
83pub fn currentPathAlloc(io: Io, allocator: Allocator) CurrentPathAllocError![:0]u8 {87pub fn currentPathAlloc(io: Io, allocator: Allocator) CurrentPathAllocError![:0]u8 {
84 var buffer: [max_path_bytes]u8 = undefined;88 var buffer: [max_path_bytes]u8 = undefined;
85 const n = currentPath(io, &buffer) catch |err| switch (err) {89 const n = try currentPath(io, &buffer);
86 error.NameTooLong => unreachable,
87 else => |e| return e,
88 };
89 return allocator.dupeSentinel(u8, buffer[0..n], 0);90 return allocator.dupeSentinel(u8, buffer[0..n], 0);
90}91}
9192