authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2025-12-18 02:00:34-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-23 22:15:10-08:00
loga2416c685a83788780fec1c379008a2d795f7bd2
tree82daf738420120d11f36608336ca91d034894922
parentd72983da44b7ebe967868ba5236a2c4b05e84510

Implement dirReadWindows


2 files changed, 145 insertions(+), 5 deletions(-)

lib/std/Io/Dir.zig+4-1
......@@ -101,7 +101,10 @@ pub const Reader = struct {
101101 /// Fill position of `buffer`.
102102 end: usize,
103103
104 pub const min_buffer_len = 32;
104 pub const min_buffer_len = switch (native_os) {
105 .windows => std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)),
106 else => 32, // TODO: what is this based on?
107 };
105108
106109 pub const State = enum {
107110 /// Indicates the next call to `read` should rewind and start over the
lib/std/Io/Threaded.zig+141-4
......@@ -3674,10 +3674,147 @@ fn dirReadHaiku(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir
36743674}
36753675
36763676fn dirReadWindows(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
3677 _ = userdata;
3678 _ = dr;
3679 _ = buffer;
3680 @panic("TODO");
3677 const t: *Threaded = @ptrCast(@alignCast(userdata));
3678 const current_thread = Thread.getCurrent(t);
3679 const w = windows;
3680
3681 // We want to be able to use the `dr.buffer` for both the NtQueryDirectoryFile call (which
3682 // returns WTF-16 names) *and* as a buffer for storing those WTF-16 names as WTF-8 to be able
3683 // to return them in `Dir.Entry.name`. However, the problem that needs to be overcome in order to do
3684 // that is that each WTF-16 code unit can be encoded as a maximum of 3 WTF-8 bytes, which means
3685 // that it's not guaranteed that the memory used for the WTF-16 name will be sufficient
3686 // for the WTF-8 encoding of the same name (for example, € is encoded as one WTF-16 code unit,
3687 // [2 bytes] but encoded in WTF-8 as 3 bytes).
3688 //
3689 // The approach taken here is to "reserve" enough space in the `dr.buffer` to ensure that
3690 // at least one entry with the maximum possible WTF-8 name length can be stored without clobbering
3691 // any entries that follow it. That is, we determine how much space is needed to allow that,
3692 // and then only provide the remaining portion of `dr.buffer` to the NtQueryDirectoryFile
3693 // call. The WTF-16 names can then be safely converted using the full `dr.buffer` slice, making
3694 // sure that each name can only potentially overwrite the data of its own entry.
3695 //
3696 // The worst case, where an entry's name is both the maximum length of a component and
3697 // made up entirely of code points that are encoded as one WTF-16 code unit/three WTF-8 bytes,
3698 // would therefore look like the diagram below, and only one entry would be able to be returned:
3699 //
3700 // | reserved | remaining unreserved buffer |
3701 // | entry 1 | entry 2 | ... |
3702 // | wtf-8 name of entry 1 |
3703 //
3704 // However, in the average case we will be able to store more than one WTF-8 name at a time in the
3705 // available buffer and therefore we will be able to populate more than one `Dir.Entry` at a time.
3706 // That might look something like this (where name 1, name 2, etc are the converted WTF-8 names):
3707 //
3708 // | reserved | remaining unreserved buffer |
3709 // | entry 1 | entry 2 | ... |
3710 // | name 1 | name 2 | name 3 | name 4 | ... |
3711 //
3712 // Note: More than the minimum amount of space could be reserved to make the "worst case"
3713 // less likely, but since the worst-case also requires a maximum length component to matter,
3714 // it's unlikely for it to become a problem in normal scenarios even if all names on the filesystem
3715 // are made up of non-ASCII characters that have the "one WTF-16 code unit <-> three WTF-8 bytes"
3716 // property (e.g. code points >= U+0800 and <= U+FFFF), as it's unlikely for a significant
3717 // number of components to be maximum length.
3718
3719 // We need `3 * NAME_MAX` bytes to store a max-length component as WTF-8 safely.
3720 // Because needing to store a max-length component depends on a `FileName` *with* the maximum
3721 // component length, we know that the corresponding populated `FILE_BOTH_DIR_INFORMATION` will
3722 // be of size `@sizeOf(w.FILE_BOTH_DIR_INFORMATION) + 2 * NAME_MAX` bytes, so we only need to
3723 // reserve enough to get us to up to having `3 * NAME_MAX` bytes available when taking into account
3724 // that we have the ability to write over top of the reserved memory + the full footprint of that
3725 // particular `FILE_BOTH_DIR_INFORMATION`.
3726 const reserve_needed = w.NAME_MAX - @sizeOf(w.FILE_BOTH_DIR_INFORMATION);
3727 const unreserved_start = std.mem.alignForward(usize, reserve_needed, @alignOf(usize));
3728 const unreserved_buffer = dr.buffer[unreserved_start..];
3729 // This is enforced by `Dir.Reader`
3730 assert(unreserved_buffer.len >= @sizeOf(w.FILE_BOTH_DIR_INFORMATION) + w.NAME_MAX * 2);
3731
3732 var name_index: usize = 0;
3733 var buffer_index: usize = 0;
3734 while (buffer.len - buffer_index != 0) {
3735 if (dr.end - dr.index == 0) {
3736 // Refill the buffer, unless we've already created references to
3737 // buffered data.
3738 if (buffer_index != 0) break;
3739
3740 try current_thread.checkCancel();
3741 var io_status_block: w.IO_STATUS_BLOCK = undefined;
3742 const rc = w.ntdll.NtQueryDirectoryFile(
3743 dr.dir.handle,
3744 null,
3745 null,
3746 null,
3747 &io_status_block,
3748 unreserved_buffer.ptr,
3749 unreserved_buffer.len,
3750 .BothDirectory,
3751 w.FALSE,
3752 null,
3753 @intFromBool(dr.state == .reset),
3754 );
3755 dr.state = .reading;
3756 if (io_status_block.Information == 0) {
3757 dr.state = .finished;
3758 return 0;
3759 }
3760 dr.index = 0;
3761 dr.end = io_status_block.Information;
3762 switch (rc) {
3763 .SUCCESS => {},
3764 .ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability
3765 else => return w.unexpectedStatus(rc),
3766 }
3767 }
3768
3769 // While the official API docs guarantee FILE_BOTH_DIR_INFORMATION to be aligned properly
3770 // this may not always be the case (e.g. due to faulty VM/sandboxing tools)
3771 const dir_info: *align(2) w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&unreserved_buffer[dr.index]));
3772 const backtrack_index = dr.index;
3773 if (dir_info.NextEntryOffset != 0) {
3774 dr.index += dir_info.NextEntryOffset;
3775 } else {
3776 dr.index = dr.end;
3777 }
3778
3779 const name_wtf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];
3780
3781 if (std.mem.eql(u16, name_wtf16le, &[_]u16{'.'}) or std.mem.eql(u16, name_wtf16le, &[_]u16{ '.', '.' })) {
3782 continue;
3783 }
3784
3785 // Read any relevant information from the `dir_info` now since it's possible the WTF-8
3786 // name will overwrite it.
3787 const kind: File.Kind = blk: {
3788 const attrs = dir_info.FileAttributes;
3789 if (attrs.REPARSE_POINT) break :blk .sym_link;
3790 if (attrs.DIRECTORY) break :blk .directory;
3791 break :blk .file;
3792 };
3793 const inode: File.INode = dir_info.FileIndex;
3794
3795 // If there's no more space for WTF-8 names without bleeding over into
3796 // the remaining unprocessed entries, then backtrack and return what we have so far.
3797 if (name_index + std.unicode.calcWtf8Len(name_wtf16le) > unreserved_start + dr.index) {
3798 // We should always be able to fit at least one entry into the buffer no matter what
3799 std.debug.assert(buffer_index != 0);
3800 dr.index = backtrack_index;
3801 break;
3802 }
3803
3804 const name_buf = dr.buffer[name_index..];
3805 const name_wtf8_len = std.unicode.wtf16LeToWtf8(name_buf, name_wtf16le);
3806 const name_wtf8 = name_buf[0..name_wtf8_len];
3807 name_index += name_wtf8_len;
3808
3809 buffer[buffer_index] = .{
3810 .name = name_wtf8,
3811 .kind = kind,
3812 .inode = inode,
3813 };
3814 buffer_index += 1;
3815 }
3816
3817 return buffer_index;
36813818}
36823819
36833820fn dirReadWasi(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {