| author | |
| committer | |
| log | 6be202f46633d02e20d0f068a32296113ecb95ca |
| tree | 9a22fba7d28337a6072123e6737cf7ea96922b13 |
| parent | 09bf51092ba8f11e342af7bd7d148af63f371709 |
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 matters7 files changed, 77 insertions(+), 8 deletions(-)
lib/std/Io.zig+1| ... | @@ -218,6 +218,7 @@ pub const VTable = struct { | ... | @@ -218,6 +218,7 @@ pub const VTable = struct { |
| 218 | unlockStderr: *const fn (?*anyopaque) void, | 218 | unlockStderr: *const fn (?*anyopaque) void, |
| 219 | processCurrentPath: *const fn (?*anyopaque, buffer: []u8) std.process.CurrentPathError!usize, | 219 | processCurrentPath: *const fn (?*anyopaque, buffer: []u8) std.process.CurrentPathError!usize, |
| 220 | processSetCurrentDir: *const fn (?*anyopaque, Dir) std.process.SetCurrentDirError!void, | 220 | processSetCurrentDir: *const fn (?*anyopaque, Dir) std.process.SetCurrentDirError!void, |
| 221 | processSetCurrentPath: *const fn (?*anyopaque, []const u8) std.process.SetCurrentPathError!void, | ||
| 221 | processReplace: *const fn (?*anyopaque, std.process.ReplaceOptions) std.process.ReplaceError, | 222 | processReplace: *const fn (?*anyopaque, std.process.ReplaceOptions) std.process.ReplaceError, |
| 222 | processReplacePath: *const fn (?*anyopaque, Dir, std.process.ReplaceOptions) std.process.ReplaceError, | 223 | processReplacePath: *const fn (?*anyopaque, Dir, std.process.ReplaceOptions) std.process.ReplaceError, |
| 223 | processSpawn: *const fn (?*anyopaque, std.process.SpawnOptions) std.process.SpawnError!std.process.Child, | 224 | 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 { | ... | @@ -434,6 +434,7 @@ pub fn io(ev: *Evented) Io { |
| 434 | .unlockStderr = unlockStderr, | 434 | .unlockStderr = unlockStderr, |
| 435 | .processCurrentPath = processCurrentPath, | 435 | .processCurrentPath = processCurrentPath, |
| 436 | .processSetCurrentDir = processSetCurrentDir, | 436 | .processSetCurrentDir = processSetCurrentDir, |
| 437 | .processSetCurrentPath = processSetCurrentPath, | ||
| 437 | .processReplace = processReplace, | 438 | .processReplace = processReplace, |
| 438 | .processReplacePath = processReplacePath, | 439 | .processReplacePath = processReplacePath, |
| 439 | .processSpawn = processSpawn, | 440 | .processSpawn = processSpawn, |
| ... | @@ -4046,7 +4047,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr | ... | @@ -4046,7 +4047,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr |
| 4046 | }; | 4047 | }; |
| 4047 | } | 4048 | } |
| 4048 | 4049 | ||
| 4049 | fn processSetCurrentPath(userdata: ?*anyopaque, dir_path: []const u8) ChdirError!void { | 4050 | fn processSetCurrentPath(userdata: ?*anyopaque, dir_path: []const u8) process.SetCurrentPathError!void { |
| 4050 | const ev: *Evented = @ptrCast(@alignCast(userdata)); | 4051 | const ev: *Evented = @ptrCast(@alignCast(userdata)); |
| 4051 | _ = ev; | 4052 | _ = ev; |
| 4052 | var path_buffer: [c.PATH_MAX]u8 = undefined; | 4053 | var path_buffer: [c.PATH_MAX]u8 = undefined; |
lib/std/Io/Threaded.zig+39| ... | @@ -1841,6 +1841,7 @@ pub fn io(t: *Threaded) Io { | ... | @@ -1841,6 +1841,7 @@ pub fn io(t: *Threaded) Io { |
| 1841 | .unlockStderr = unlockStderr, | 1841 | .unlockStderr = unlockStderr, |
| 1842 | .processCurrentPath = processCurrentPath, | 1842 | .processCurrentPath = processCurrentPath, |
| 1843 | .processSetCurrentDir = processSetCurrentDir, | 1843 | .processSetCurrentDir = processSetCurrentDir, |
| 1844 | .processSetCurrentPath = processSetCurrentPath, | ||
| 1844 | .processReplace = processReplace, | 1845 | .processReplace = processReplace, |
| 1845 | .processReplacePath = processReplacePath, | 1846 | .processReplacePath = processReplacePath, |
| 1846 | .processSpawn = processSpawn, | 1847 | .processSpawn = processSpawn, |
| ... | @@ -2006,6 +2007,7 @@ pub fn ioBasic(t: *Threaded) Io { | ... | @@ -2006,6 +2007,7 @@ pub fn ioBasic(t: *Threaded) Io { |
| 2006 | .unlockStderr = unlockStderr, | 2007 | .unlockStderr = unlockStderr, |
| 2007 | .processCurrentPath = processCurrentPath, | 2008 | .processCurrentPath = processCurrentPath, |
| 2008 | .processSetCurrentDir = processSetCurrentDir, | 2009 | .processSetCurrentDir = processSetCurrentDir, |
| 2010 | .processSetCurrentPath = processSetCurrentPath, | ||
| 2009 | .processReplace = processReplace, | 2011 | .processReplace = processReplace, |
| 2010 | .processReplacePath = processReplacePath, | 2012 | .processReplacePath = processReplacePath, |
| 2011 | .processSpawn = processSpawn, | 2013 | .processSpawn = processSpawn, |
| ... | @@ -14176,6 +14178,43 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr | ... | @@ -14176,6 +14178,43 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr |
| 14176 | return fchdir(dir.handle); | 14178 | return fchdir(dir.handle); |
| 14177 | } | 14179 | } |
| 14178 | 14180 | ||
| 14181 | fn 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 | |||
| 14179 | pub const PosixAddress = extern union { | 14218 | pub const PosixAddress = extern union { |
| 14180 | any: posix.sockaddr, | 14219 | any: posix.sockaddr, |
| 14181 | in: posix.sockaddr.in, | 14220 | in: posix.sockaddr.in, |
lib/std/Io/Uring.zig+2-1| ... | @@ -752,6 +752,7 @@ pub fn io(ev: *Evented) Io { | ... | @@ -752,6 +752,7 @@ pub fn io(ev: *Evented) Io { |
| 752 | .unlockStderr = unlockStderr, | 752 | .unlockStderr = unlockStderr, |
| 753 | .processCurrentPath = processCurrentPath, | 753 | .processCurrentPath = processCurrentPath, |
| 754 | .processSetCurrentDir = processSetCurrentDir, | 754 | .processSetCurrentDir = processSetCurrentDir, |
| 755 | .processSetCurrentPath = processSetCurrentPath, | ||
| 755 | .processReplace = processReplace, | 756 | .processReplace = processReplace, |
| 756 | .processReplacePath = processReplacePath, | 757 | .processReplacePath = processReplacePath, |
| 757 | .processSpawn = processSpawn, | 758 | .processSpawn = processSpawn, |
| ... | @@ -4176,7 +4177,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr | ... | @@ -4176,7 +4177,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr |
| 4176 | return fchdir(&sync, dir.handle); | 4177 | return fchdir(&sync, dir.handle); |
| 4177 | } | 4178 | } |
| 4178 | 4179 | ||
| 4179 | fn processSetCurrentPath(userdata: ?*anyopaque, dir_path: []const u8) ChdirError!void { | 4180 | fn processSetCurrentPath(userdata: ?*anyopaque, dir_path: []const u8) process.SetCurrentPathError!void { |
| 4180 | const ev: *Evented = @ptrCast(@alignCast(userdata)); | 4181 | const ev: *Evented = @ptrCast(@alignCast(userdata)); |
| 4181 | var path_buffer: [PATH_MAX]u8 = undefined; | 4182 | var path_buffer: [PATH_MAX]u8 = undefined; |
| 4182 | const dir_path_posix = try pathToPosix(dir_path, &path_buffer); | 4183 | 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 { | ... | @@ -900,6 +900,35 @@ pub fn setCurrentDir(io: Io, dir: Io.Dir) !void { |
| 900 | return io.vtable.processSetCurrentDir(io.userdata, dir); | 900 | return io.vtable.processSetCurrentDir(io.userdata, dir); |
| 901 | } | 901 | } |
| 902 | 902 | ||
| 903 | pub 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. | ||
| 928 | pub fn setCurrentPath(io: Io, path: []const u8) !void { | ||
| 929 | return io.vtable.processSetCurrentPath(io.userdata, path); | ||
| 930 | } | ||
| 931 | |||
| 903 | pub const LockMemoryError = error{ | 932 | pub const LockMemoryError = error{ |
| 904 | UnsupportedOperation, | 933 | UnsupportedOperation, |
| 905 | PermissionDenied, | 934 | PermissionDenied, |
test/standalone/posix/cwd.zig+3-3| ... | @@ -37,7 +37,7 @@ fn test_chdir_self(io: Io) !void { | ... | @@ -37,7 +37,7 @@ fn test_chdir_self(io: Io) !void { |
| 37 | const old_cwd = old_cwd_buf[0..try std.process.currentPath(io, &old_cwd_buf)]; | 37 | const old_cwd = old_cwd_buf[0..try std.process.currentPath(io, &old_cwd_buf)]; |
| 38 | 38 | ||
| 39 | // Try changing to the current directory | 39 | // Try changing to the current directory |
| 40 | try std.Io.Threaded.chdir(old_cwd); | 40 | try std.process.setCurrentPath(io, old_cwd); |
| 41 | try expect_cwd(io, old_cwd); | 41 | try expect_cwd(io, old_cwd); |
| 42 | } | 42 | } |
| 43 | 43 | ||
| ... | @@ -48,7 +48,7 @@ fn test_chdir_absolute(io: Io) !void { | ... | @@ -48,7 +48,7 @@ fn test_chdir_absolute(io: Io) !void { |
| 48 | const parent = std.fs.path.dirname(old_cwd) orelse unreachable; // old_cwd should be absolute | 48 | const parent = std.fs.path.dirname(old_cwd) orelse unreachable; // old_cwd should be absolute |
| 49 | 49 | ||
| 50 | // Try changing to the parent via a full path | 50 | // Try changing to the parent via a full path |
| 51 | try std.Io.Threaded.chdir(parent); | 51 | try std.process.setCurrentPath(io, parent); |
| 52 | 52 | ||
| 53 | try expect_cwd(io, parent); | 53 | try expect_cwd(io, parent); |
| 54 | } | 54 | } |
| ... | @@ -68,7 +68,7 @@ fn test_chdir_relative(gpa: Allocator, io: Io, tmp_dir: Io.Dir) !void { | ... | @@ -68,7 +68,7 @@ fn test_chdir_relative(gpa: Allocator, io: Io, tmp_dir: Io.Dir) !void { |
| 68 | defer gpa.free(expected_path); | 68 | defer gpa.free(expected_path); |
| 69 | 69 | ||
| 70 | // change current working directory to new test directory | 70 | // change current working directory to new test directory |
| 71 | try std.Io.Threaded.chdir(subdir_path); | 71 | try std.process.setCurrentPath(io, subdir_path); |
| 72 | 72 | ||
| 73 | var new_cwd_buf: [path_max]u8 = undefined; | 73 | var new_cwd_buf: [path_max]u8 = undefined; |
| 74 | const new_cwd = new_cwd_buf[0..try std.process.currentPath(io, &new_cwd_buf)]; | 74 | 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 { | ... | @@ -9,8 +9,6 @@ pub fn main(init: std.process.Init) !void { |
| 9 | const gpa = init.gpa; | 9 | const gpa = init.gpa; |
| 10 | const io = init.io; | 10 | const io = init.io; |
| 11 | const process_cwd_path = try std.process.currentPathAlloc(io, init.arena.allocator()); | 11 | 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); | ||
| 14 | 12 | ||
| 15 | var it = try init.minimal.args.iterateAllocator(gpa); | 13 | var it = try init.minimal.args.iterateAllocator(gpa); |
| 16 | defer it.deinit(); | 14 | defer it.deinit(); |
| ... | @@ -127,7 +125,7 @@ pub fn main(init: std.process.Init) !void { | ... | @@ -127,7 +125,7 @@ pub fn main(init: std.process.Init) !void { |
| 127 | 125 | ||
| 128 | // Now let's set the tmp dir as the cwd and set the path only include the "something" sub dir | 126 | // Now let's set the tmp dir as the cwd and set the path only include the "something" sub dir |
| 129 | try std.process.setCurrentDir(io, tmp_dir); | 127 | 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 {}; |
| 131 | const something_subdir_abs_path = try std.mem.concatWithSentinel(gpa, u16, &.{ tmp_absolute_path_w, utf16Literal("\\something") }, 0); | 129 | const something_subdir_abs_path = try std.mem.concatWithSentinel(gpa, u16, &.{ tmp_absolute_path_w, utf16Literal("\\something") }, 0); |
| 132 | defer gpa.free(something_subdir_abs_path); | 130 | defer gpa.free(something_subdir_abs_path); |
| 133 | 131 |