authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-03-07 22:17:20-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-09 03:19:11+01:00
log6be202f46633d02e20d0f068a32296113ecb95ca
tree9a22fba7d28337a6072123e6737cf7ea96922b13
parent09bf51092ba8f11e342af7bd7d148af63f371709

Io: Add processSetCurrentPath

The logic used to allow providing a path for setting the CWD of a child process in https://codeberg.org/ziglang/zig/pulls/31090 applies here as well: - Windows must provide a path when setting the CWD, so the path of an `Io.Dir` must be resolved before actually calling RtlSetCurrentDirectory_U - A directory handle may have multiple paths associated with it, so providing the CWD as a string retains a legitimate use case in cases where the precise path matters

7 files changed, 77 insertions(+), 8 deletions(-)

lib/std/Io.zig+1
......@@ -218,6 +218,7 @@ pub const VTable = struct {
218218 unlockStderr: *const fn (?*anyopaque) void,
219219 processCurrentPath: *const fn (?*anyopaque, buffer: []u8) std.process.CurrentPathError!usize,
220220 processSetCurrentDir: *const fn (?*anyopaque, Dir) std.process.SetCurrentDirError!void,
221 processSetCurrentPath: *const fn (?*anyopaque, []const u8) std.process.SetCurrentPathError!void,
221222 processReplace: *const fn (?*anyopaque, std.process.ReplaceOptions) std.process.ReplaceError,
222223 processReplacePath: *const fn (?*anyopaque, Dir, std.process.ReplaceOptions) std.process.ReplaceError,
223224 processSpawn: *const fn (?*anyopaque, std.process.SpawnOptions) std.process.SpawnError!std.process.Child,
lib/std/Io/Dispatch.zig+2-1
......@@ -434,6 +434,7 @@ pub fn io(ev: *Evented) Io {
434434 .unlockStderr = unlockStderr,
435435 .processCurrentPath = processCurrentPath,
436436 .processSetCurrentDir = processSetCurrentDir,
437 .processSetCurrentPath = processSetCurrentPath,
437438 .processReplace = processReplace,
438439 .processReplacePath = processReplacePath,
439440 .processSpawn = processSpawn,
......@@ -4046,7 +4047,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr
40464047 };
40474048}
40484049
4049fn processSetCurrentPath(userdata: ?*anyopaque, dir_path: []const u8) ChdirError!void {
4050fn processSetCurrentPath(userdata: ?*anyopaque, dir_path: []const u8) process.SetCurrentPathError!void {
40504051 const ev: *Evented = @ptrCast(@alignCast(userdata));
40514052 _ = ev;
40524053 var path_buffer: [c.PATH_MAX]u8 = undefined;
lib/std/Io/Threaded.zig+39
......@@ -1841,6 +1841,7 @@ pub fn io(t: *Threaded) Io {
18411841 .unlockStderr = unlockStderr,
18421842 .processCurrentPath = processCurrentPath,
18431843 .processSetCurrentDir = processSetCurrentDir,
1844 .processSetCurrentPath = processSetCurrentPath,
18441845 .processReplace = processReplace,
18451846 .processReplacePath = processReplacePath,
18461847 .processSpawn = processSpawn,
......@@ -2006,6 +2007,7 @@ pub fn ioBasic(t: *Threaded) Io {
20062007 .unlockStderr = unlockStderr,
20072008 .processCurrentPath = processCurrentPath,
20082009 .processSetCurrentDir = processSetCurrentDir,
2010 .processSetCurrentPath = processSetCurrentPath,
20092011 .processReplace = processReplace,
20102012 .processReplacePath = processReplacePath,
20112013 .processSpawn = processSpawn,
......@@ -14176,6 +14178,43 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr
1417614178 return fchdir(dir.handle);
1417714179}
1417814180
14181fn processSetCurrentPath(userdata: ?*anyopaque, path: []const u8) process.SetCurrentPathError!void {
14182 const t: *Threaded = @ptrCast(@alignCast(userdata));
14183 _ = t;
14184
14185 if (native_os == .wasi) return error.OperationUnsupported;
14186
14187 if (is_windows) {
14188 var path_w_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
14189 const len = std.unicode.calcWtf16LeLen(path) catch return error.InvalidWtf8;
14190 if (len > path_w_buf.len) return error.NameTooLong;
14191 const path_w_len = std.unicode.wtf8ToWtf16Le(&path_w_buf, path) catch |err| switch (err) {
14192 error.InvalidWtf8 => unreachable, // already validated
14193 };
14194 const path_w = path_w_buf[0..path_w_len];
14195
14196 const syscall: Syscall = try .start();
14197 while (true) switch (windows.ntdll.RtlSetCurrentDirectory_U(&.init(path_w))) {
14198 .SUCCESS => return syscall.finish(),
14199 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
14200 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
14201 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
14202 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
14203 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),
14204 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
14205 .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err),
14206 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
14207 .CANCELLED => {
14208 try syscall.checkCancel();
14209 continue;
14210 },
14211 else => |status| return syscall.unexpectedNtstatus(status),
14212 };
14213 }
14214
14215 return chdir(path);
14216}
14217
1417914218pub const PosixAddress = extern union {
1418014219 any: posix.sockaddr,
1418114220 in: posix.sockaddr.in,
lib/std/Io/Uring.zig+2-1
......@@ -752,6 +752,7 @@ pub fn io(ev: *Evented) Io {
752752 .unlockStderr = unlockStderr,
753753 .processCurrentPath = processCurrentPath,
754754 .processSetCurrentDir = processSetCurrentDir,
755 .processSetCurrentPath = processSetCurrentPath,
755756 .processReplace = processReplace,
756757 .processReplacePath = processReplacePath,
757758 .processSpawn = processSpawn,
......@@ -4176,7 +4177,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr
41764177 return fchdir(&sync, dir.handle);
41774178}
41784179
4179fn processSetCurrentPath(userdata: ?*anyopaque, dir_path: []const u8) ChdirError!void {
4180fn processSetCurrentPath(userdata: ?*anyopaque, dir_path: []const u8) process.SetCurrentPathError!void {
41804181 const ev: *Evented = @ptrCast(@alignCast(userdata));
41814182 var path_buffer: [PATH_MAX]u8 = undefined;
41824183 const dir_path_posix = try pathToPosix(dir_path, &path_buffer);
lib/std/process.zig+29
......@@ -900,6 +900,35 @@ pub fn setCurrentDir(io: Io, dir: Io.Dir) !void {
900900 return io.vtable.processSetCurrentDir(io.userdata, dir);
901901}
902902
903pub const SetCurrentPathError = error{
904 AccessDenied,
905 SymLinkLoop,
906 SystemResources,
907 BadPathName,
908 FileNotFound,
909 FileSystem,
910 NoDevice,
911 NotDir,
912 NameTooLong,
913 OperationUnsupported,
914 /// Windows-only. The path is invalid WTF-8.
915 /// https://wtf-8.codeberg.page/
916 InvalidWtf8,
917} || Io.Cancelable || Io.UnexpectedError;
918
919/// Changes the current working directory to the given path.
920/// Corresponds to "chdir" in libc.
921///
922/// This modifies global process state and can have surprising effects in
923/// multithreaded applications. Most applications and especially libraries
924/// should not call this function as a general rule, however it can have use
925/// cases in, for example, implementing a shell, or child process execution.
926///
927/// Calling this function makes code less portable and less reusable.
928pub fn setCurrentPath(io: Io, path: []const u8) !void {
929 return io.vtable.processSetCurrentPath(io.userdata, path);
930}
931
903932pub const LockMemoryError = error{
904933 UnsupportedOperation,
905934 PermissionDenied,
test/standalone/posix/cwd.zig+3-3
......@@ -37,7 +37,7 @@ fn test_chdir_self(io: Io) !void {
3737 const old_cwd = old_cwd_buf[0..try std.process.currentPath(io, &old_cwd_buf)];
3838
3939 // Try changing to the current directory
40 try std.Io.Threaded.chdir(old_cwd);
40 try std.process.setCurrentPath(io, old_cwd);
4141 try expect_cwd(io, old_cwd);
4242}
4343
......@@ -48,7 +48,7 @@ fn test_chdir_absolute(io: Io) !void {
4848 const parent = std.fs.path.dirname(old_cwd) orelse unreachable; // old_cwd should be absolute
4949
5050 // Try changing to the parent via a full path
51 try std.Io.Threaded.chdir(parent);
51 try std.process.setCurrentPath(io, parent);
5252
5353 try expect_cwd(io, parent);
5454}
......@@ -68,7 +68,7 @@ fn test_chdir_relative(gpa: Allocator, io: Io, tmp_dir: Io.Dir) !void {
6868 defer gpa.free(expected_path);
6969
7070 // change current working directory to new test directory
71 try std.Io.Threaded.chdir(subdir_path);
71 try std.process.setCurrentPath(io, subdir_path);
7272
7373 var new_cwd_buf: [path_max]u8 = undefined;
7474 const new_cwd = new_cwd_buf[0..try std.process.currentPath(io, &new_cwd_buf)];
test/standalone/windows_spawn/main.zig+1-3
......@@ -9,8 +9,6 @@ pub fn main(init: std.process.Init) !void {
99 const gpa = init.gpa;
1010 const io = init.io;
1111 const process_cwd_path = try std.process.currentPathAlloc(io, init.arena.allocator());
12 var initial_process_cwd = try Io.Dir.cwd().openDir(io, ".", .{});
13 defer initial_process_cwd.close(io);
1412
1513 var it = try init.minimal.args.iterateAllocator(gpa);
1614 defer it.deinit();
......@@ -127,7 +125,7 @@ pub fn main(init: std.process.Init) !void {
127125
128126 // Now let's set the tmp dir as the cwd and set the path only include the "something" sub dir
129127 try std.process.setCurrentDir(io, tmp_dir);
130 defer std.process.setCurrentDir(io, initial_process_cwd) catch {};
128 defer std.process.setCurrentPath(io, process_cwd_path) catch {};
131129 const something_subdir_abs_path = try std.mem.concatWithSentinel(gpa, u16, &.{ tmp_absolute_path_w, utf16Literal("\\something") }, 0);
132130 defer gpa.free(something_subdir_abs_path);
133131