| ... | ... | @@ -3674,10 +3674,147 @@ fn dirReadHaiku(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir |
| 3674 | 3674 | } |
| 3675 | 3675 | |
| 3676 | 3676 | fn 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; |
| 3681 | 3818 | } |
| 3682 | 3819 | |
| 3683 | 3820 | fn dirReadWasi(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize { |