authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-23 22:07:31-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-23 22:15:12-08:00
logfa79d346744308250c165519f842fedfc26a1c14
tree21a007127f3199bca0334c0e959480f5cb7dd0eb
parent98e9716c082aa7e134e11ed14db8c2f631fedc8a

std: add changing cur dir back

There's a good argument to not have this in the std lib but it's more work to remove it than to leave it in, and this branch is already 20,000+ lines changed.

12 files changed, 229 insertions(+), 116 deletions(-)

lib/std/Io.zig+1
...@@ -718,6 +718,7 @@ pub const VTable = struct {...@@ -718,6 +718,7 @@ pub const VTable = struct {
718 lockStderr: *const fn (?*anyopaque, buffer: []u8, ?Terminal.Mode) Cancelable!LockedStderr,718 lockStderr: *const fn (?*anyopaque, buffer: []u8, ?Terminal.Mode) Cancelable!LockedStderr,
719 tryLockStderr: *const fn (?*anyopaque, buffer: []u8, ?Terminal.Mode) Cancelable!?LockedStderr,719 tryLockStderr: *const fn (?*anyopaque, buffer: []u8, ?Terminal.Mode) Cancelable!?LockedStderr,
720 unlockStderr: *const fn (?*anyopaque) void,720 unlockStderr: *const fn (?*anyopaque) void,
721 processSetCurrentDir: *const fn (?*anyopaque, Dir) std.process.SetCurrentDirError!void,
721722
722 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,723 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
723 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,724 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
lib/std/Io/Threaded.zig+71-3
...@@ -648,7 +648,7 @@ pub fn init(...@@ -648,7 +648,7 @@ pub fn init(
648 .main_thread = .{648 .main_thread = .{
649 .signal_id = Thread.currentSignalId(),649 .signal_id = Thread.currentSignalId(),
650 .current_closure = null,650 .current_closure = null,
651 .cancel_protection = undefined,651 .cancel_protection = .unblocked,
652 },652 },
653 .argv0 = options.argv0,653 .argv0 = options.argv0,
654 .environ = options.environ,654 .environ = options.environ,
...@@ -689,7 +689,7 @@ pub const init_single_threaded: Threaded = .{...@@ -689,7 +689,7 @@ pub const init_single_threaded: Threaded = .{
689 .main_thread = .{689 .main_thread = .{
690 .signal_id = undefined,690 .signal_id = undefined,
691 .current_closure = null,691 .current_closure = null,
692 .cancel_protection = undefined,692 .cancel_protection = .unblocked,
693 },693 },
694 .robust_cancel = .disabled,694 .robust_cancel = .disabled,
695 .argv0 = .{},695 .argv0 = .{},
...@@ -742,7 +742,7 @@ fn worker(t: *Threaded) void {...@@ -742,7 +742,7 @@ fn worker(t: *Threaded) void {
742 var thread: Thread = .{742 var thread: Thread = .{
743 .signal_id = Thread.currentSignalId(),743 .signal_id = Thread.currentSignalId(),
744 .current_closure = null,744 .current_closure = null,
745 .cancel_protection = undefined,745 .cancel_protection = .unblocked,
746 };746 };
747 Thread.current = &thread;747 Thread.current = &thread;
748748
...@@ -844,6 +844,7 @@ pub fn io(t: *Threaded) Io {...@@ -844,6 +844,7 @@ pub fn io(t: *Threaded) Io {
844 .lockStderr = lockStderr,844 .lockStderr = lockStderr,
845 .tryLockStderr = tryLockStderr,845 .tryLockStderr = tryLockStderr,
846 .unlockStderr = unlockStderr,846 .unlockStderr = unlockStderr,
847 .processSetCurrentDir = processSetCurrentDir,
847848
848 .now = now,849 .now = now,
849 .sleep = sleep,850 .sleep = sleep,
...@@ -979,6 +980,7 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -979,6 +980,7 @@ pub fn ioBasic(t: *Threaded) Io {
979 .lockStderr = lockStderr,980 .lockStderr = lockStderr,
980 .tryLockStderr = tryLockStderr,981 .tryLockStderr = tryLockStderr,
981 .unlockStderr = unlockStderr,982 .unlockStderr = unlockStderr,
983 .processSetCurrentDir = processSetCurrentDir,
982984
983 .now = now,985 .now = now,
984 .sleep = sleep,986 .sleep = sleep,
...@@ -7370,6 +7372,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex...@@ -7370,6 +7372,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) std.process.Ex
7370 };7372 };
7371 defer w.CloseHandle(h_file);7373 defer w.CloseHandle(h_file);
73727374
7375 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
7373 const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data);7376 const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data);
73747377
7375 const len = std.unicode.calcWtf8Len(wide_slice);7378 const len = std.unicode.calcWtf8Len(wide_slice);
...@@ -10796,6 +10799,71 @@ fn unlockStderr(userdata: ?*anyopaque) void {...@@ -10796,6 +10799,71 @@ fn unlockStderr(userdata: ?*anyopaque) void {
10796 std.process.stderr_thread_mutex.unlock();10799 std.process.stderr_thread_mutex.unlock();
10797}10800}
1079810801
10802fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) std.process.SetCurrentDirError!void {
10803 if (native_os == .wasi) return error.OperationUnsupported;
10804 const t: *Threaded = @ptrCast(@alignCast(userdata));
10805 const current_thread = Thread.getCurrent(t);
10806
10807 if (is_windows) {
10808 try current_thread.checkCancel();
10809 var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined;
10810 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
10811 const dir_path = try windows.GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer);
10812 const path_len_bytes = std.math.cast(u16, dir_path.len * 2) orelse return error.NameTooLong;
10813 try current_thread.checkCancel();
10814 var nt_name: windows.UNICODE_STRING = .{
10815 .Length = path_len_bytes,
10816 .MaximumLength = path_len_bytes,
10817 .Buffer = @constCast(dir_path.ptr),
10818 };
10819 switch (windows.ntdll.RtlSetCurrentDirectory_U(&nt_name)) {
10820 .SUCCESS => return,
10821 .OBJECT_NAME_INVALID => return error.BadPathName,
10822 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
10823 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
10824 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
10825 .INVALID_PARAMETER => |err| return windows.statusBug(err),
10826 .ACCESS_DENIED => return error.AccessDenied,
10827 .OBJECT_PATH_SYNTAX_BAD => |err| return windows.statusBug(err),
10828 .NOT_A_DIRECTORY => return error.NotDir,
10829 else => |status| return windows.unexpectedStatus(status),
10830 }
10831 }
10832
10833 if (dir.handle == posix.AT.FDCWD) return;
10834
10835 try current_thread.beginSyscall();
10836 while (true) {
10837 switch (posix.errno(posix.system.fchdir(dir.handle))) {
10838 .SUCCESS => return current_thread.endSyscall(),
10839 .INTR => {
10840 try current_thread.checkCancel();
10841 continue;
10842 },
10843 .ACCES => {
10844 current_thread.endSyscall();
10845 return error.AccessDenied;
10846 },
10847 .BADF => |err| {
10848 current_thread.endSyscall();
10849 return errnoBug(err);
10850 },
10851 .NOTDIR => {
10852 current_thread.endSyscall();
10853 return error.NotDir;
10854 },
10855 .IO => {
10856 current_thread.endSyscall();
10857 return error.FileSystem;
10858 },
10859 else => |err| {
10860 current_thread.endSyscall();
10861 return posix.unexpectedErrno(err);
10862 },
10863 }
10864 }
10865}
10866
10799pub const PosixAddress = extern union {10867pub const PosixAddress = extern union {
10800 any: posix.sockaddr,10868 any: posix.sockaddr,
10801 in: posix.sockaddr.in,10869 in: posix.sockaddr.in,
lib/std/os/windows.zig-34
...@@ -2939,40 +2939,6 @@ pub fn WriteFile(...@@ -2939,40 +2939,6 @@ pub fn WriteFile(
2939 return bytes_written;2939 return bytes_written;
2940}2940}
29412941
2942pub const SetCurrentDirectoryError = error{
2943 NameTooLong,
2944 FileNotFound,
2945 NotDir,
2946 AccessDenied,
2947 NoDevice,
2948 BadPathName,
2949 Unexpected,
2950};
2951
2952pub fn SetCurrentDirectory(path_name: []const u16) SetCurrentDirectoryError!void {
2953 const path_len_bytes = math.cast(u16, path_name.len * 2) orelse return error.NameTooLong;
2954
2955 var nt_name: UNICODE_STRING = .{
2956 .Length = path_len_bytes,
2957 .MaximumLength = path_len_bytes,
2958 .Buffer = @constCast(path_name.ptr),
2959 };
2960
2961 const rc = ntdll.RtlSetCurrentDirectory_U(&nt_name);
2962 switch (rc) {
2963 .SUCCESS => {},
2964 .OBJECT_NAME_INVALID => return error.BadPathName,
2965 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
2966 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
2967 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
2968 .INVALID_PARAMETER => unreachable,
2969 .ACCESS_DENIED => return error.AccessDenied,
2970 .OBJECT_PATH_SYNTAX_BAD => unreachable,
2971 .NOT_A_DIRECTORY => return error.NotDir,
2972 else => return unexpectedStatus(rc),
2973 }
2974}
2975
2976pub const GetCurrentDirectoryError = error{2942pub const GetCurrentDirectoryError = error{
2977 NameTooLong,2943 NameTooLong,
2978 Unexpected,2944 Unexpected,
lib/std/posix.zig+4-17
...@@ -1171,11 +1171,9 @@ pub const ChangeCurDirError = error{...@@ -1171,11 +1171,9 @@ pub const ChangeCurDirError = error{
1171/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.1171/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
1172pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {1172pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
1173 if (native_os == .wasi and !builtin.link_libc) {1173 if (native_os == .wasi and !builtin.link_libc) {
1174 @compileError("WASI does not support os.chdir");1174 @compileError("unsupported OS");
1175 } else if (native_os == .windows) {1175 } else if (native_os == .windows) {
1176 var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;1176 @compileError("unsupported OS");
1177 const len = try windows.wtf8ToWtf16Le(&wtf16_dir_path, dir_path);
1178 return chdirW(wtf16_dir_path[0..len]);
1179 } else {1177 } else {
1180 const dir_path_c = try toPosixPath(dir_path);1178 const dir_path_c = try toPosixPath(dir_path);
1181 return chdirZ(&dir_path_c);1179 return chdirZ(&dir_path_c);
...@@ -1188,12 +1186,9 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {...@@ -1188,12 +1186,9 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
1188/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.1186/// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding.
1189pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {1187pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
1190 if (native_os == .windows) {1188 if (native_os == .windows) {
1191 const dir_path_span = mem.span(dir_path);1189 @compileError("unsupported OS");
1192 var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
1193 const len = try windows.wtf8ToWtf16Le(&wtf16_dir_path, dir_path_span);
1194 return chdirW(wtf16_dir_path[0..len]);
1195 } else if (native_os == .wasi and !builtin.link_libc) {1190 } else if (native_os == .wasi and !builtin.link_libc) {
1196 return chdir(mem.span(dir_path));1191 @compileError("unsupported OS");
1197 }1192 }
1198 switch (errno(system.chdir(dir_path))) {1193 switch (errno(system.chdir(dir_path))) {
1199 .SUCCESS => return,1194 .SUCCESS => return,
...@@ -1210,14 +1205,6 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {...@@ -1210,14 +1205,6 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
1210 }1205 }
1211}1206}
12121207
1213/// Windows-only. Same as `chdir` except the parameter is WTF16 LE encoded.
1214pub fn chdirW(dir_path: []const u16) ChangeCurDirError!void {
1215 windows.SetCurrentDirectory(dir_path) catch |err| switch (err) {
1216 error.NoDevice => return error.FileSystem,
1217 else => |e| return e,
1218 };
1219}
1220
1221pub const FchdirError = error{1208pub const FchdirError = error{
1222 AccessDenied,1209 AccessDenied,
1223 NotDir,1210 NotDir,
lib/std/process.zig+25
...@@ -2304,3 +2304,28 @@ pub fn exit(status: u8) noreturn {...@@ -2304,3 +2304,28 @@ pub fn exit(status: u8) noreturn {
2304 else => posix.system.exit(status),2304 else => posix.system.exit(status),
2305 }2305 }
2306}2306}
2307
2308pub const SetCurrentDirError = error{
2309 AccessDenied,
2310 BadPathName,
2311 FileNotFound,
2312 FileSystem,
2313 NameTooLong,
2314 NoDevice,
2315 NotDir,
2316 OperationUnsupported,
2317 UnrecognizedVolume,
2318} || Io.Cancelable || Io.UnexpectedError;
2319
2320/// Changes the current working directory to the open directory handle.
2321/// Corresponds to "fchdir" in libc.
2322///
2323/// This modifies global process state and can have surprising effects in
2324/// multithreaded applications. Most applications and especially libraries
2325/// should not call this function as a general rule, however it can have use
2326/// cases in, for example, implementing a shell, or child process execution.
2327///
2328/// Calling this function makes code less portable and less reusable.
2329pub fn setCurrentDir(io: Io, dir: Io.Dir) !void {
2330 return io.vtable.processSetCurrentDir(io.userdata, dir);
2331}
lib/std/testing.zig+1
...@@ -629,6 +629,7 @@ pub const TmpDir = struct {...@@ -629,6 +629,7 @@ pub const TmpDir = struct {
629};629};
630630
631pub fn tmpDir(opts: Io.Dir.OpenOptions) TmpDir {631pub fn tmpDir(opts: Io.Dir.OpenOptions) TmpDir {
632 comptime assert(builtin.is_test);
632 var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;633 var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;
633 std.crypto.random.bytes(&random_bytes);634 std.crypto.random.bytes(&random_bytes);
634 var sub_path: [TmpDir.sub_path_len]u8 = undefined;635 var sub_path: [TmpDir.sub_path_len]u8 = undefined;
test/standalone/posix/cwd.zig+59-13
...@@ -1,6 +1,10 @@...@@ -1,6 +1,10 @@
1const std = @import("std");
2const builtin = @import("builtin");1const builtin = @import("builtin");
32
3const std = @import("std");
4const Io = std.Io;
5const Allocator = std.mem.Allocator;
6const assert = std.debug.assert;
7
4const path_max = std.fs.max_path_bytes;8const path_max = std.fs.max_path_bytes;
59
6pub fn main() !void {10pub fn main() !void {
...@@ -9,13 +13,17 @@ pub fn main() !void {...@@ -9,13 +13,17 @@ pub fn main() !void {
9 return;13 return;
10 }14 }
1115
12 var Allocator = std.heap.DebugAllocator(.{}){};16 var debug_allocator: std.heap.DebugAllocator(.{}) = .{};
13 const a = Allocator.allocator();17 defer assert(debug_allocator.deinit() == .ok);
14 defer std.debug.assert(Allocator.deinit() == .ok);18 const gpa = debug_allocator.allocator();
19
20 var threaded: std.Io.Threaded = .init(gpa, .{});
21 defer threaded.deinit();
22 const io = threaded.io();
1523
16 try test_chdir_self();24 try test_chdir_self();
17 try test_chdir_absolute();25 try test_chdir_absolute();
18 try test_chdir_relative(a);26 try test_chdir_relative(gpa, io);
19}27}
2028
21// get current working directory and expect it to match given path29// get current working directory and expect it to match given path
...@@ -46,20 +54,20 @@ fn test_chdir_absolute() !void {...@@ -46,20 +54,20 @@ fn test_chdir_absolute() !void {
46 try expect_cwd(parent);54 try expect_cwd(parent);
47}55}
4856
49fn test_chdir_relative(a: std.mem.Allocator) !void {57fn test_chdir_relative(gpa: Allocator, io: Io) !void {
50 var tmp = std.testing.tmpDir(.{});58 var tmp = tmpDir(io, .{});
51 defer tmp.cleanup();59 defer tmp.cleanup(io);
5260
53 // Use the tmpDir parent_dir as the "base" for the test. Then cd into the child61 // Use the tmpDir parent_dir as the "base" for the test. Then cd into the child
54 try tmp.parent_dir.setAsCwd();62 try std.process.setCurrentDir(io, tmp.parent_dir);
5563
56 // Capture base working directory path, to build expected full path64 // Capture base working directory path, to build expected full path
57 var base_cwd_buf: [path_max]u8 = undefined;65 var base_cwd_buf: [path_max]u8 = undefined;
58 const base_cwd = try std.posix.getcwd(base_cwd_buf[0..]);66 const base_cwd = try std.posix.getcwd(base_cwd_buf[0..]);
5967
60 const relative_dir_name = &tmp.sub_path;68 const relative_dir_name = &tmp.sub_path;
61 const expected_path = try std.fs.path.resolve(a, &.{ base_cwd, relative_dir_name });69 const expected_path = try std.fs.path.resolve(gpa, &.{ base_cwd, relative_dir_name });
62 defer a.free(expected_path);70 defer gpa.free(expected_path);
6371
64 // change current working directory to new test directory72 // change current working directory to new test directory
65 try std.posix.chdir(relative_dir_name);73 try std.posix.chdir(relative_dir_name);
...@@ -68,8 +76,46 @@ fn test_chdir_relative(a: std.mem.Allocator) !void {...@@ -68,8 +76,46 @@ fn test_chdir_relative(a: std.mem.Allocator) !void {
68 const new_cwd = try std.posix.getcwd(new_cwd_buf[0..]);76 const new_cwd = try std.posix.getcwd(new_cwd_buf[0..]);
6977
70 // On Windows, fs.path.resolve returns an uppercase drive letter, but the drive letter returned by getcwd may be lowercase78 // On Windows, fs.path.resolve returns an uppercase drive letter, but the drive letter returned by getcwd may be lowercase
71 const resolved_cwd = try std.fs.path.resolve(a, &.{new_cwd});79 const resolved_cwd = try std.fs.path.resolve(gpa, &.{new_cwd});
72 defer a.free(resolved_cwd);80 defer gpa.free(resolved_cwd);
7381
74 try std.testing.expectEqualStrings(expected_path, resolved_cwd);82 try std.testing.expectEqualStrings(expected_path, resolved_cwd);
75}83}
84
85pub fn tmpDir(io: Io, opts: Io.Dir.OpenOptions) TmpDir {
86 var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;
87 std.crypto.random.bytes(&random_bytes);
88 var sub_path: [TmpDir.sub_path_len]u8 = undefined;
89 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
90
91 const cwd = Io.Dir.cwd();
92 var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch
93 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir");
94 defer cache_dir.close(io);
95 const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch
96 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir");
97 const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch
98 @panic("unable to make tmp dir for testing: unable to make and open the tmp dir");
99
100 return .{
101 .dir = dir,
102 .parent_dir = parent_dir,
103 .sub_path = sub_path,
104 };
105}
106
107pub const TmpDir = struct {
108 dir: Io.Dir,
109 parent_dir: Io.Dir,
110 sub_path: [sub_path_len]u8,
111
112 const random_bytes_count = 12;
113 const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count);
114
115 pub fn cleanup(self: *TmpDir, io: Io) void {
116 self.dir.close(io);
117 self.parent_dir.deleteTree(io, &self.sub_path) catch {};
118 self.parent_dir.close(io);
119 self.* = undefined;
120 }
121};
test/standalone/posix/relpaths.zig+56-35
...@@ -14,21 +14,21 @@ pub fn main() !void {...@@ -14,21 +14,21 @@ pub fn main() !void {
14 const gpa = debug_allocator.allocator();14 const gpa = debug_allocator.allocator();
15 defer std.debug.assert(debug_allocator.deinit() == .ok);15 defer std.debug.assert(debug_allocator.deinit() == .ok);
1616
17 const io = std.Io.Threaded.global_single_threaded.ioBasic();17 var threaded: std.Io.Threaded = .init(gpa, .{});
18 defer threaded.deinit();
19 const io = threaded.io();
1820
19 // TODO this API isn't supposed to be used outside of unit testing. make it compilation error if used21 var tmp = tmpDir(io, .{});
20 // outside of unit testing.22 defer tmp.cleanup(io);
21 var tmp = std.testing.tmpDir(.{});
22 defer tmp.cleanup();
2323
24 // Want to test relative paths, so cd into the tmpdir for these tests24 // Want to test relative paths, so cd into the tmpdir for these tests
25 try tmp.dir.setAsCwd();25 try std.process.setCurrentDir(io, tmp.dir);
2626
27 try test_symlink(gpa, io, tmp);27 try test_symlink(gpa, io, tmp);
28 try test_link(io, tmp);28 try test_link(io, tmp);
29}29}
3030
31fn test_symlink(gpa: Allocator, io: Io, tmp: std.testing.TmpDir) !void {31fn test_symlink(gpa: Allocator, io: Io, tmp: TmpDir) !void {
32 const target_name = "symlink-target";32 const target_name = "symlink-target";
33 const symlink_name = "symlinker";33 const symlink_name = "symlinker";
3434
...@@ -47,32 +47,15 @@ fn test_symlink(gpa: Allocator, io: Io, tmp: std.testing.TmpDir) !void {...@@ -47,32 +47,15 @@ fn test_symlink(gpa: Allocator, io: Io, tmp: std.testing.TmpDir) !void {
47 else => return err,47 else => return err,
48 };48 };
49 } else {49 } else {
50 try std.posix.symlink(target_name, symlink_name);50 try Io.Dir.cwd().symLink(io, target_name, symlink_name, .{});
51 }51 }
5252
53 var buffer: [std.fs.max_path_bytes]u8 = undefined;53 var buffer: [std.fs.max_path_bytes]u8 = undefined;
54 const given = try std.posix.readlink(symlink_name, buffer[0..]);54 const given = buffer[0..try Io.Dir.cwd().readLink(io, symlink_name, &buffer)];
55 try std.testing.expectEqualStrings(target_name, given);55 try std.testing.expectEqualStrings(target_name, given);
56}56}
5757
58fn getLinkInfo(fd: std.posix.fd_t) !struct { std.posix.ino_t, std.posix.nlink_t } {58fn test_link(io: Io, tmp: TmpDir) !void {
59 if (builtin.target.os.tag == .linux) {
60 const stx = try std.os.linux.wrapped.statx(
61 fd,
62 "",
63 std.posix.AT.EMPTY_PATH,
64 .{ .INO = true, .NLINK = true },
65 );
66 std.debug.assert(stx.mask.INO);
67 std.debug.assert(stx.mask.NLINK);
68 return .{ stx.ino, stx.nlink };
69 }
70
71 const st = try std.posix.fstat(fd);
72 return .{ st.ino, st.nlink };
73}
74
75fn test_link(io: Io, tmp: std.testing.TmpDir) !void {
76 switch (builtin.target.os.tag) {59 switch (builtin.target.os.tag) {
77 .linux, .illumos => {},60 .linux, .illumos => {},
78 else => return,61 else => return,
...@@ -84,7 +67,7 @@ fn test_link(io: Io, tmp: std.testing.TmpDir) !void {...@@ -84,7 +67,7 @@ fn test_link(io: Io, tmp: std.testing.TmpDir) !void {
84 try tmp.dir.writeFile(io, .{ .sub_path = target_name, .data = "example" });67 try tmp.dir.writeFile(io, .{ .sub_path = target_name, .data = "example" });
8568
86 // Test 1: create the relative link from inside tmp69 // Test 1: create the relative link from inside tmp
87 try std.posix.link(target_name, link_name);70 try Io.Dir.hardLink(.cwd(), target_name, .cwd(), link_name, io, .{});
8871
89 // Verify72 // Verify
90 const efd = try tmp.dir.openFile(io, target_name, .{});73 const efd = try tmp.dir.openFile(io, target_name, .{});
...@@ -94,16 +77,54 @@ fn test_link(io: Io, tmp: std.testing.TmpDir) !void {...@@ -94,16 +77,54 @@ fn test_link(io: Io, tmp: std.testing.TmpDir) !void {
94 defer nfd.close(io);77 defer nfd.close(io);
9578
96 {79 {
97 const eino, _ = try getLinkInfo(efd.handle);80 const e_stat = try efd.stat(io);
98 const nino, const nlink = try getLinkInfo(nfd.handle);81 const n_stat = try nfd.stat(io);
99 try std.testing.expectEqual(eino, nino);82 try std.testing.expectEqual(e_stat.inode, n_stat.inode);
100 try std.testing.expectEqual(@as(std.posix.nlink_t, 2), nlink);83 try std.testing.expectEqual(2, n_stat.nlink);
101 }84 }
10285
103 // Test 2: Remove the link and see the stats update86 // Test 2: Remove the link and see the stats update
104 try std.posix.unlink(link_name);87 try Io.Dir.cwd().deleteFile(io, link_name);
105 {88 {
106 _, const elink = try getLinkInfo(efd.handle);89 const e_stat = try efd.stat(io);
107 try std.testing.expectEqual(@as(std.posix.nlink_t, 1), elink);90 try std.testing.expectEqual(1, e_stat.nlink);
108 }91 }
109}92}
93
94pub fn tmpDir(io: Io, opts: Io.Dir.OpenOptions) TmpDir {
95 var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;
96 std.crypto.random.bytes(&random_bytes);
97 var sub_path: [TmpDir.sub_path_len]u8 = undefined;
98 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
99
100 const cwd = Io.Dir.cwd();
101 var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch
102 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir");
103 defer cache_dir.close(io);
104 const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch
105 @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir");
106 const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch
107 @panic("unable to make tmp dir for testing: unable to make and open the tmp dir");
108
109 return .{
110 .dir = dir,
111 .parent_dir = parent_dir,
112 .sub_path = sub_path,
113 };
114}
115
116pub const TmpDir = struct {
117 dir: Io.Dir,
118 parent_dir: Io.Dir,
119 sub_path: [sub_path_len]u8,
120
121 const random_bytes_count = 12;
122 const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count);
123
124 pub fn cleanup(self: *TmpDir, io: Io) void {
125 self.dir.close(io);
126 self.parent_dir.deleteTree(io, &self.sub_path) catch {};
127 self.parent_dir.close(io);
128 self.* = undefined;
129 }
130};
test/standalone/windows_bat_args/fuzz.zig+2-2
...@@ -44,8 +44,8 @@ pub fn main() anyerror!void {...@@ -44,8 +44,8 @@ pub fn main() anyerror!void {
44 var tmp = std.testing.tmpDir(.{});44 var tmp = std.testing.tmpDir(.{});
45 defer tmp.cleanup();45 defer tmp.cleanup();
4646
47 try tmp.dir.setAsCwd();47 try std.process.setCurrentDir(io, tmp.dir);
48 defer tmp.parent_dir.setAsCwd() catch {};48 defer std.process.setCurrentDir(io, tmp.parent_dir) catch {};
4949
50 // `child_exe_path_orig` might be relative; make it relative to our new cwd.50 // `child_exe_path_orig` might be relative; make it relative to our new cwd.
51 const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig });51 const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig });
test/standalone/windows_bat_args/test.zig+2-2
...@@ -18,8 +18,8 @@ pub fn main() anyerror!void {...@@ -18,8 +18,8 @@ pub fn main() anyerror!void {
18 var tmp = std.testing.tmpDir(.{});18 var tmp = std.testing.tmpDir(.{});
19 defer tmp.cleanup();19 defer tmp.cleanup();
2020
21 try tmp.dir.setAsCwd();21 try std.process.setCurrentDir(io, tmp.dir);
22 defer tmp.parent_dir.setAsCwd() catch {};22 defer std.process.setCurrentDir(io, tmp.parent_dir) catch {};
2323
24 // `child_exe_path_orig` might be relative; make it relative to our new cwd.24 // `child_exe_path_orig` might be relative; make it relative to our new cwd.
25 const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig });25 const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig });
test/standalone/windows_spawn/main.zig+3-3
...@@ -127,8 +127,8 @@ pub fn main() anyerror!void {...@@ -127,8 +127,8 @@ pub fn main() anyerror!void {
127 try testExecError(error.FileNotFound, gpa, "goodbye");127 try testExecError(error.FileNotFound, gpa, "goodbye");
128128
129 // Now let's set the tmp dir as the cwd and set the path only include the "something" sub dir129 // Now let's set the tmp dir as the cwd and set the path only include the "something" sub dir
130 try tmp.dir.setAsCwd();130 try std.process.setCurrentDir(io, tmp.dir);
131 defer tmp.parent_dir.setAsCwd() catch {};131 defer std.process.setCurrentDir(io, tmp.parent_dir) catch {};
132 const something_subdir_abs_path = try std.mem.concatWithSentinel(gpa, u16, &.{ tmp_absolute_path_w, utf16Literal("\\something") }, 0);132 const something_subdir_abs_path = try std.mem.concatWithSentinel(gpa, u16, &.{ tmp_absolute_path_w, utf16Literal("\\something") }, 0);
133 defer gpa.free(something_subdir_abs_path);133 defer gpa.free(something_subdir_abs_path);
134134
...@@ -191,7 +191,7 @@ pub fn main() anyerror!void {...@@ -191,7 +191,7 @@ pub fn main() anyerror!void {
191 defer subdir_cwd.close(io);191 defer subdir_cwd.close(io);
192192
193 try renameExe(tmp.dir, "something/goodbye.exe", "hello.exe");193 try renameExe(tmp.dir, "something/goodbye.exe", "hello.exe");
194 try subdir_cwd.setAsCwd();194 try std.process.setCurrentDir(io, subdir_cwd);
195195
196 // clear the PATH again196 // clear the PATH again
197 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(197 std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
tools/fetch_them_macos_headers.zig+5-7
...@@ -4,7 +4,6 @@ const Dir = std.Io.Dir;...@@ -4,7 +4,6 @@ const Dir = std.Io.Dir;
4const mem = std.mem;4const mem = std.mem;
5const process = std.process;5const process = std.process;
6const assert = std.debug.assert;6const assert = std.debug.assert;
7const tmpDir = std.testing.tmpDir;
8const fatal = std.process.fatal;7const fatal = std.process.fatal;
9const info = std.log.info;8const info = std.log.info;
109
...@@ -111,15 +110,14 @@ pub fn main() anyerror!void {...@@ -111,15 +110,14 @@ pub fn main() anyerror!void {
111 const os_ver: OsVer = @enumFromInt(version.major);110 const os_ver: OsVer = @enumFromInt(version.major);
112 info("found SDK deployment target macOS {f} aka '{t}'", .{ version, os_ver });111 info("found SDK deployment target macOS {f} aka '{t}'", .{ version, os_ver });
113112
114 var tmp = tmpDir(.{});113 const tmp_dir: Io.Dir = .cwd();
115 defer tmp.cleanup();
116114
117 for (&[_]Arch{ .aarch64, .x86_64 }) |arch| {115 for (&[_]Arch{ .aarch64, .x86_64 }) |arch| {
118 const target: Target = .{116 const target: Target = .{
119 .arch = arch,117 .arch = arch,
120 .os_ver = os_ver,118 .os_ver = os_ver,
121 };119 };
122 try fetchTarget(allocator, io, argv.items, sysroot_path, target, version, tmp);120 try fetchTarget(allocator, io, argv.items, sysroot_path, target, version, tmp_dir);
123 }121 }
124}122}
125123
...@@ -130,11 +128,11 @@ fn fetchTarget(...@@ -130,11 +128,11 @@ fn fetchTarget(
130 sysroot: []const u8,128 sysroot: []const u8,
131 target: Target,129 target: Target,
132 ver: Version,130 ver: Version,
133 tmp: std.testing.TmpDir,131 tmp_dir: Io.Dir,
134) !void {132) !void {
135 const tmp_filename = "macos-headers";133 const tmp_filename = "macos-headers";
136 const headers_list_filename = "macos-headers.o.d";134 const headers_list_filename = "macos-headers.o.d";
137 const tmp_path = try tmp.dir.realPathFileAlloc(io, ".", arena);135 const tmp_path = try tmp_dir.realPathFileAlloc(io, ".", arena);
138 const tmp_file_path = try Dir.path.join(arena, &[_][]const u8{ tmp_path, tmp_filename });136 const tmp_file_path = try Dir.path.join(arena, &[_][]const u8{ tmp_path, tmp_filename });
139 const headers_list_path = try Dir.path.join(arena, &[_][]const u8{ tmp_path, headers_list_filename });137 const headers_list_path = try Dir.path.join(arena, &[_][]const u8{ tmp_path, headers_list_filename });
140138
...@@ -173,7 +171,7 @@ fn fetchTarget(...@@ -173,7 +171,7 @@ fn fetchTarget(
173 }171 }
174172
175 // Read in the contents of `macos-headers.o.d`173 // Read in the contents of `macos-headers.o.d`
176 const headers_list_file = try tmp.dir.openFile(io, headers_list_filename, .{});174 const headers_list_file = try tmp_dir.openFile(io, headers_list_filename, .{});
177 defer headers_list_file.close(io);175 defer headers_list_file.close(io);
178176
179 var headers_dir = Dir.cwd().openDir(io, headers_source_prefix, .{}) catch |err| switch (err) {177 var headers_dir = Dir.cwd().openDir(io, headers_source_prefix, .{}) catch |err| switch (err) {