authorgravatar for readcuttingt@gmail.comTom Read Cutting <readcuttingt@gmail.com> 2025-12-03 07:06:16+00:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-02-02 01:21:11-08:00
log83abd73801a6ab3125c1b0b8ba432be188c9e7ca
tree3dae948af47e654a894de3881f8d3df9aa1d1be3
parentaacf8ce03d2b52e52830873c40eabb1cb1eb8272

Windows: Support directory handle for cwd instead of string for Child.process

This implementation is a bit of a hacky workaround, as we use a ntdll API to grab the full path of the directory handle. As far as I can tell this might be the only solution to the problem, as kernel32.CreateProcessW takes a directory path as a string only. I might be wrong though as haven't researched the problem thoroughly.

2 files changed, 25 insertions(+), 6 deletions(-)

lib/std/Io/Threaded.zig+22-6
......@@ -15193,7 +15193,26 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1519315193 defer arena_allocator.deinit();
1519415194 const arena = arena_allocator.allocator();
1519515195
15196 const cwd_w = if (options.cwd) |cwd| try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd) else null;
15196 const cwd_w = cwd_w: {
15197 if (options.cwd_dir) |cwd_dir| {
15198 var dir_path_buffer = try arena.alloc(u16, windows.PATH_MAX_WIDE + 1);
15199 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
15200 try Thread.checkCancel();
15201 const dir_path = try windows.GetFinalPathNameByHandle(
15202 cwd_dir.handle,
15203 .{},
15204 dir_path_buffer[0..windows.PATH_MAX_WIDE],
15205 );
15206 dir_path_buffer[dir_path.len] = 0;
15207 // Shrink the allocation down to just the path buffer + sentinel
15208 dir_path_buffer = try arena.realloc(dir_path_buffer, dir_path.len + 1);
15209 break :cwd_w dir_path_buffer[0..dir_path.len :0];
15210 } else if (options.cwd) |cwd| {
15211 break :cwd_w try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd);
15212 } else {
15213 break :cwd_w null;
15214 }
15215 };
1519715216 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
1519815217
1519915218 const maybe_envp_buf = if (options.environ_map) |environ_map| try environ_map.createBlockWindows(arena) else null;
......@@ -15204,16 +15223,13 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1520415223
1520515224 // The cwd provided by options is in effect when choosing the executable
1520615225 // path to match POSIX semantics.
15207 var cwd_path_w_needs_free = false;
1520815226 const cwd_path_w = x: {
1520915227 // If the app name is absolute, then we need to use its dirname as the cwd
1521015228 if (app_name_is_absolute) {
15211 cwd_path_w_needs_free = true;
1521215229 const dir = Dir.path.dirname(app_name_wtf8).?;
1521315230 break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, dir);
15214 } else if (options.cwd) |cwd| {
15215 cwd_path_w_needs_free = true;
15216 break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd);
15231 } else if (cwd_w) |cwd| {
15232 break :x cwd;
1521715233 } else {
1521815234 break :x &[_:0]u16{}; // empty for cwd
1521915235 }
lib/std/process.zig+3
......@@ -359,6 +359,9 @@ pub const SpawnError = error{
359359 /// children of the calling process and the child had already performed an
360360 /// image replacement.
361361 ProcessAlreadyExec,
362 /// On Windows, the volume does not contain a recognized file system. File
363 /// system drivers might not be loaded, or the volume may be corrupt.
364 UnrecognizedVolume,
362365} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
363366
364367pub const SpawnOptions = struct {