authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-19 21:46:57-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:50-07:00
log1c67607397be4efa962d64a1d80b7fd91fe161eb
treec053ecd7ab948c4bddf3587df1f7a95262a2043c
parentb561d5f3feda6b1251fc2b8ab2430e78f546577c

std.Io.Threaded: implement dirOpenFile for Windows


4 files changed, 55 insertions(+), 105 deletions(-)

lib/std/Io/Dir.zig+9
...@@ -97,6 +97,15 @@ pub fn close(dir: Dir, io: Io) void {...@@ -97,6 +97,15 @@ pub fn close(dir: Dir, io: Io) void {
97 return io.vtable.dirClose(io.userdata, dir);97 return io.vtable.dirClose(io.userdata, dir);
98}98}
9999
100/// Opens a file for reading or writing, without attempting to create a new file.
101///
102/// To create a new file, see `createFile`.
103///
104/// Allocates a resource to be released with `File.close`.
105///
106/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
107/// On WASI, `sub_path` should be encoded as valid UTF-8.
108/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
100pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {109pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
101 return io.vtable.dirOpenFile(io.userdata, dir, sub_path, flags);110 return io.vtable.dirOpenFile(io.userdata, dir, sub_path, flags);
102}111}
lib/std/Io/Threaded.zig+45-1
...@@ -192,7 +192,7 @@ pub fn io(t: *Threaded) Io {...@@ -192,7 +192,7 @@ pub fn io(t: *Threaded) Io {
192 else => dirCreateFilePosix,192 else => dirCreateFilePosix,
193 },193 },
194 .dirOpenFile = switch (builtin.os.tag) {194 .dirOpenFile = switch (builtin.os.tag) {
195 .windows => @panic("TODO"),195 .windows => dirOpenFileWindows,
196 .wasi => dirOpenFileWasi,196 .wasi => dirOpenFileWasi,
197 else => dirOpenFilePosix,197 else => dirOpenFilePosix,
198 },198 },
...@@ -1731,6 +1731,50 @@ fn dirOpenFilePosix(...@@ -1731,6 +1731,50 @@ fn dirOpenFilePosix(
1731 return .{ .handle = fd };1731 return .{ .handle = fd };
1732}1732}
17331733
1734fn dirOpenFileWindows(
1735 userdata: ?*anyopaque,
1736 dir: Io.Dir,
1737 sub_path: []const u8,
1738 flags: Io.File.OpenFlags,
1739) Io.File.OpenError!Io.File {
1740 const t: *Threaded = @ptrCast(@alignCast(userdata));
1741 try t.checkCancel();
1742
1743 const w = windows;
1744 const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, sub_path);
1745 const sub_path_w = sub_path_w_array.span();
1746
1747 const handle = try w.OpenFile(sub_path_w, .{
1748 .dir = dir.handle,
1749 .access_mask = w.SYNCHRONIZE |
1750 (if (flags.isRead()) @as(u32, w.GENERIC_READ) else 0) |
1751 (if (flags.isWrite()) @as(u32, w.GENERIC_WRITE) else 0),
1752 .creation = w.FILE_OPEN,
1753 });
1754 errdefer w.CloseHandle(handle);
1755 var io_status_block: w.IO_STATUS_BLOCK = undefined;
1756 const range_off: w.LARGE_INTEGER = 0;
1757 const range_len: w.LARGE_INTEGER = 1;
1758 const exclusive = switch (flags.lock) {
1759 .none => return .{ .handle = handle },
1760 .shared => false,
1761 .exclusive => true,
1762 };
1763 try w.LockFile(
1764 handle,
1765 null,
1766 null,
1767 null,
1768 &io_status_block,
1769 &range_off,
1770 &range_len,
1771 null,
1772 @intFromBool(flags.lock_nonblocking),
1773 @intFromBool(exclusive),
1774 );
1775 return .{ .handle = handle };
1776}
1777
1734fn dirOpenFileWasi(1778fn dirOpenFileWasi(
1735 userdata: ?*anyopaque,1779 userdata: ?*anyopaque,
1736 dir: Io.Dir,1780 dir: Io.Dir,
lib/std/fs.zig-56
...@@ -138,12 +138,6 @@ pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {...@@ -138,12 +138,6 @@ pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
138138
139test makeDirAbsoluteZ {}139test makeDirAbsoluteZ {}
140140
141/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 LE-encoded string.
142pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
143 assert(path.isAbsoluteWindowsW(absolute_path_w));
144 return posix.mkdirW(mem.span(absolute_path_w), Dir.default_mode);
145}
146
147/// Same as `Dir.deleteDir` except the path is absolute.141/// Same as `Dir.deleteDir` except the path is absolute.
148/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).142/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
149/// On WASI, `dir_path` should be encoded as valid UTF-8.143/// On WASI, `dir_path` should be encoded as valid UTF-8.
...@@ -159,12 +153,6 @@ pub fn deleteDirAbsoluteZ(dir_path: [*:0]const u8) !void {...@@ -159,12 +153,6 @@ pub fn deleteDirAbsoluteZ(dir_path: [*:0]const u8) !void {
159 return posix.rmdirZ(dir_path);153 return posix.rmdirZ(dir_path);
160}154}
161155
162/// Same as `deleteDirAbsolute` except the path parameter is WTF-16 and target OS is assumed Windows.
163pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void {
164 assert(path.isAbsoluteWindowsW(dir_path));
165 return posix.rmdirW(mem.span(dir_path));
166}
167
168/// Same as `Dir.rename` except the paths are absolute.156/// Same as `Dir.rename` except the paths are absolute.
169/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).157/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
170/// On WASI, both paths should be encoded as valid UTF-8.158/// On WASI, both paths should be encoded as valid UTF-8.
...@@ -182,13 +170,6 @@ pub fn renameAbsoluteZ(old_path: [*:0]const u8, new_path: [*:0]const u8) !void {...@@ -182,13 +170,6 @@ pub fn renameAbsoluteZ(old_path: [*:0]const u8, new_path: [*:0]const u8) !void {
182 return posix.renameZ(old_path, new_path);170 return posix.renameZ(old_path, new_path);
183}171}
184172
185/// Same as `renameAbsolute` except the path parameters are WTF-16 and target OS is assumed Windows.
186pub fn renameAbsoluteW(old_path: [*:0]const u16, new_path: [*:0]const u16) !void {
187 assert(path.isAbsoluteWindowsW(old_path));
188 assert(path.isAbsoluteWindowsW(new_path));
189 return posix.renameW(old_path, new_path);
190}
191
192/// Same as `Dir.rename`, except `new_sub_path` is relative to `new_dir`173/// Same as `Dir.rename`, except `new_sub_path` is relative to `new_dir`
193pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void {174pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void {
194 return posix.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path);175 return posix.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path);
...@@ -199,12 +180,6 @@ pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_su...@@ -199,12 +180,6 @@ pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_su
199 return posix.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);180 return posix.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);
200}181}
201182
202/// Same as `rename` except the parameters are WTF16LE, NT prefixed.
203/// This function is Windows-only.
204pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_path_w: []const u16) !void {
205 return posix.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w, windows.TRUE);
206}
207
208/// Returns a handle to the current working directory. It is not opened with iteration capability.183/// Returns a handle to the current working directory. It is not opened with iteration capability.
209/// Closing the returned `Dir` is checked illegal behavior. Iterating over the result is illegal behavior.184/// Closing the returned `Dir` is checked illegal behavior. Iterating over the result is illegal behavior.
210/// On POSIX targets, this function is comptime-callable.185/// On POSIX targets, this function is comptime-callable.
...@@ -241,12 +216,6 @@ pub fn openDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenOptions)...@@ -241,12 +216,6 @@ pub fn openDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenOptions)
241 assert(path.isAbsoluteZ(absolute_path_c));216 assert(path.isAbsoluteZ(absolute_path_c));
242 return cwd().openDirZ(absolute_path_c, flags);217 return cwd().openDirZ(absolute_path_c, flags);
243}218}
244/// Same as `openDirAbsolute` but the path parameter is null-terminated.
245pub fn openDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenOptions) File.OpenError!Dir {
246 assert(path.isAbsoluteWindowsW(absolute_path_c));
247 return cwd().openDirW(absolute_path_c, flags);
248}
249
250/// Opens a file for reading or writing, without attempting to create a new file, based on an absolute path.219/// Opens a file for reading or writing, without attempting to create a new file, based on an absolute path.
251/// Call `File.close` to release the resource.220/// Call `File.close` to release the resource.
252/// Asserts that the path is absolute. See `Dir.openFile` for a function that221/// Asserts that the path is absolute. See `Dir.openFile` for a function that
...@@ -261,12 +230,6 @@ pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.O...@@ -261,12 +230,6 @@ pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.O
261 return cwd().openFile(absolute_path, flags);230 return cwd().openFile(absolute_path, flags);
262}231}
263232
264/// Same as `openFileAbsolute` but the path parameter is WTF-16-encoded.
265pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File {
266 assert(path.isAbsoluteWindowsWtf16(absolute_path_w));
267 return cwd().openFileW(absolute_path_w, flags);
268}
269
270/// Test accessing `path`.233/// Test accessing `path`.
271/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.234/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
272/// For example, instead of testing if a file exists and then opening it, just235/// For example, instead of testing if a file exists and then opening it, just
...@@ -279,12 +242,6 @@ pub fn accessAbsolute(absolute_path: []const u8, flags: Io.Dir.AccessOptions) Di...@@ -279,12 +242,6 @@ pub fn accessAbsolute(absolute_path: []const u8, flags: Io.Dir.AccessOptions) Di
279 assert(path.isAbsolute(absolute_path));242 assert(path.isAbsolute(absolute_path));
280 try cwd().access(absolute_path, flags);243 try cwd().access(absolute_path, flags);
281}244}
282/// Same as `accessAbsolute` but the path parameter is WTF-16 encoded.
283pub fn accessAbsoluteW(absolute_path: [*:0]const u16, flags: File.OpenFlags) Dir.AccessError!void {
284 assert(path.isAbsoluteWindowsW(absolute_path));
285 try cwd().accessW(absolute_path, flags);
286}
287
288/// Creates, opens, or overwrites a file with write access, based on an absolute path.245/// Creates, opens, or overwrites a file with write access, based on an absolute path.
289/// Call `File.close` to release the resource.246/// Call `File.close` to release the resource.
290/// Asserts that the path is absolute. See `Dir.createFile` for a function that247/// Asserts that the path is absolute. See `Dir.createFile` for a function that
...@@ -311,12 +268,6 @@ pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void {...@@ -311,12 +268,6 @@ pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void {
311 return cwd().deleteFile(absolute_path);268 return cwd().deleteFile(absolute_path);
312}269}
313270
314/// Same as `deleteFileAbsolute` except the parameter is WTF-16 encoded.
315pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) Dir.DeleteFileError!void {
316 assert(path.isAbsoluteWindowsW(absolute_path_w));
317 return cwd().deleteFileW(mem.span(absolute_path_w));
318}
319
320/// Removes a symlink, file, or directory.271/// Removes a symlink, file, or directory.
321/// This is equivalent to `Dir.deleteTree` with the base directory.272/// This is equivalent to `Dir.deleteTree` with the base directory.
322/// Asserts that the path is absolute. See `Dir.deleteTree` for a function that273/// Asserts that the path is absolute. See `Dir.deleteTree` for a function that
...@@ -348,13 +299,6 @@ pub fn readLinkAbsolute(pathname: []const u8, buffer: *[max_path_bytes]u8) ![]u8...@@ -348,13 +299,6 @@ pub fn readLinkAbsolute(pathname: []const u8, buffer: *[max_path_bytes]u8) ![]u8
348 return posix.readlink(pathname, buffer);299 return posix.readlink(pathname, buffer);
349}300}
350301
351/// Windows-only. Same as `readlinkW`, except the path parameter is null-terminated, WTF16
352/// encoded.
353pub fn readlinkAbsoluteW(pathname_w: [*:0]const u16, buffer: *[max_path_bytes]u8) ![]u8 {
354 assert(path.isAbsoluteWindowsW(pathname_w));
355 return posix.readlinkW(mem.span(pathname_w), buffer);
356}
357
358/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.302/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
359/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent303/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
360/// one; the latter case is known as a dangling link.304/// one; the latter case is known as a dangling link.
lib/std/fs/Dir.zig+1-48
...@@ -846,60 +846,13 @@ pub fn close(self: *Dir) void {...@@ -846,60 +846,13 @@ pub fn close(self: *Dir) void {
846 self.* = undefined;846 self.* = undefined;
847}847}
848848
849/// Opens a file for reading or writing, without attempting to create a new file.849/// Deprecated in favor of `Io.Dir.openFile`.
850/// To create a new file, see `createFile`.
851/// Call `File.close` to release the resource.
852/// Asserts that the path parameter has no null bytes.
853/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
854/// On WASI, `sub_path` should be encoded as valid UTF-8.
855/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
856pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {850pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
857 if (native_os == .windows) {
858 const path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
859 return self.openFileW(path_w.span(), flags);
860 }
861 var threaded: Io.Threaded = .init_single_threaded;851 var threaded: Io.Threaded = .init_single_threaded;
862 const io = threaded.io();852 const io = threaded.io();
863 return .adaptFromNewApi(try Io.Dir.openFile(self.adaptToNewApi(), io, sub_path, flags));853 return .adaptFromNewApi(try Io.Dir.openFile(self.adaptToNewApi(), io, sub_path, flags));
864}854}
865855
866/// Same as `openFile` but Windows-only and the path parameter is
867/// [WTF-16](https://wtf-8.codeberg.page/#potentially-ill-formed-utf-16) encoded.
868pub fn openFileW(self: Dir, sub_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File {
869 const w = windows;
870 const file: File = .{
871 .handle = try w.OpenFile(sub_path_w, .{
872 .dir = self.fd,
873 .access_mask = w.SYNCHRONIZE |
874 (if (flags.isRead()) @as(u32, w.GENERIC_READ) else 0) |
875 (if (flags.isWrite()) @as(u32, w.GENERIC_WRITE) else 0),
876 .creation = w.FILE_OPEN,
877 }),
878 };
879 errdefer file.close();
880 var io: w.IO_STATUS_BLOCK = undefined;
881 const range_off: w.LARGE_INTEGER = 0;
882 const range_len: w.LARGE_INTEGER = 1;
883 const exclusive = switch (flags.lock) {
884 .none => return file,
885 .shared => false,
886 .exclusive => true,
887 };
888 try w.LockFile(
889 file.handle,
890 null,
891 null,
892 null,
893 &io,
894 &range_off,
895 &range_len,
896 null,
897 @intFromBool(flags.lock_nonblocking),
898 @intFromBool(exclusive),
899 );
900 return file;
901}
902
903/// Deprecated in favor of `Io.Dir.createFile`.856/// Deprecated in favor of `Io.Dir.createFile`.
904pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {857pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
905 var threaded: Io.Threaded = .init_single_threaded;858 var threaded: Io.Threaded = .init_single_threaded;