authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2025-11-15 23:36:34-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-11-15 23:36:34-08:00
logaa4332fb0e5f46657c8c41a3971150b642be1c19
tree359fb6f779ff642b75d271176323681593e8c273
parentf6fecfdc001fa2fc3ee63ca344e419825a11f094
parent6aa3570cb0e599e89748073612e8d0317100c807
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #25539 from squeek502/windows-readlinkw

windows: Make readLinkW APIs output WTF-16, reduce stack usage of callers

5 files changed, 142 insertions(+), 117 deletions(-)

lib/std/fs/Dir.zig+22-7
......@@ -1361,8 +1361,14 @@ pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u
13611361 return self.readLinkWasi(sub_path, buffer);
13621362 }
13631363 if (native_os == .windows) {
1364 const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
1365 return self.readLinkW(sub_path_w.span(), buffer);
1364 var sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
1365 const result_w = try self.readLinkW(sub_path_w.span(), &sub_path_w.data);
1366
1367 const len = std.unicode.calcWtf8Len(result_w);
1368 if (len > buffer.len) return error.NameTooLong;
1369
1370 const end_index = std.unicode.wtf16LeToWtf8(buffer, result_w);
1371 return buffer[0..end_index];
13661372 }
13671373 const sub_path_c = try posix.toPosixPath(sub_path);
13681374 return self.readLinkZ(&sub_path_c, buffer);
......@@ -1376,15 +1382,24 @@ pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
13761382/// Same as `readLink`, except the `sub_path_c` parameter is null-terminated.
13771383pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
13781384 if (native_os == .windows) {
1379 const sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c);
1380 return self.readLinkW(sub_path_w.span(), buffer);
1385 var sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c);
1386 const result_w = try self.readLinkW(sub_path_w.span(), &sub_path_w.data);
1387
1388 const len = std.unicode.calcWtf8Len(result_w);
1389 if (len > buffer.len) return error.NameTooLong;
1390
1391 const end_index = std.unicode.wtf16LeToWtf8(buffer, result_w);
1392 return buffer[0..end_index];
13811393 }
13821394 return posix.readlinkatZ(self.fd, sub_path_c, buffer);
13831395}
13841396
1385/// Windows-only. Same as `readLink` except the pathname parameter
1386/// is WTF16 LE encoded.
1387pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
1397/// Windows-only. Same as `readLink` except the path parameter
1398/// is WTF-16 LE encoded, NT-prefixed.
1399///
1400/// `sub_path_w` will never be accessed after `buffer` has been written to, so it
1401/// is safe to reuse a single buffer for both.
1402pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u16) ![]u16 {
13881403 return windows.ReadLink(self.fd, sub_path_w, buffer);
13891404}
13901405
lib/std/fs/test.zig+20
......@@ -193,10 +193,16 @@ test "Dir.readLink" {
193193 // test 1: symlink to a file
194194 try setupSymlink(ctx.dir, file_target_path, "symlink1", .{});
195195 try testReadLink(ctx.dir, canonical_file_target_path, "symlink1");
196 if (builtin.os.tag == .windows) {
197 try testReadLinkW(testing.allocator, ctx.dir, canonical_file_target_path, "symlink1");
198 }
196199
197200 // test 2: symlink to a directory (can be different on Windows)
198201 try setupSymlink(ctx.dir, dir_target_path, "symlink2", .{ .is_directory = true });
199202 try testReadLink(ctx.dir, canonical_dir_target_path, "symlink2");
203 if (builtin.os.tag == .windows) {
204 try testReadLinkW(testing.allocator, ctx.dir, canonical_dir_target_path, "symlink2");
205 }
200206
201207 // test 3: relative path symlink
202208 const parent_file = ".." ++ fs.path.sep_str ++ "target.txt";
......@@ -205,6 +211,9 @@ test "Dir.readLink" {
205211 defer subdir.close();
206212 try setupSymlink(subdir, canonical_parent_file, "relative-link.txt", .{});
207213 try testReadLink(subdir, canonical_parent_file, "relative-link.txt");
214 if (builtin.os.tag == .windows) {
215 try testReadLinkW(testing.allocator, subdir, canonical_parent_file, "relative-link.txt");
216 }
208217 }
209218 }.impl);
210219}
......@@ -215,6 +224,17 @@ fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !vo
215224 try testing.expectEqualStrings(target_path, actual);
216225}
217226
227fn testReadLinkW(allocator: mem.Allocator, dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {
228 const target_path_w = try std.unicode.wtf8ToWtf16LeAlloc(allocator, target_path);
229 defer allocator.free(target_path_w);
230 // Calling the W functions directly requires the path to be NT-prefixed
231 const symlink_path_w = try std.os.windows.sliceToPrefixedFileW(dir.fd, symlink_path);
232 const wtf16_buffer = try allocator.alloc(u16, target_path_w.len);
233 defer allocator.free(wtf16_buffer);
234 const actual = try dir.readLinkW(symlink_path_w.span(), wtf16_buffer);
235 try testing.expectEqualSlices(u16, target_path_w, actual);
236}
237
218238fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void {
219239 var buffer: [fs.max_path_bytes]u8 = undefined;
220240 const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]);
lib/std/os/windows.zig+47-96
......@@ -889,61 +889,28 @@ pub const ReadLinkError = error{
889889 AccessDenied,
890890 Unexpected,
891891 NameTooLong,
892 BadPathName,
893 AntivirusInterference,
892894 UnsupportedReparsePointType,
893895};
894896
895pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
896 // Here, we use `NtCreateFile` to shave off one syscall if we were to use `OpenFile` wrapper.
897 // With the latter, we'd need to call `NtCreateFile` twice, once for file symlink, and if that
898 // failed, again for dir symlink. Omitting any mention of file/dir flags makes it possible
899 // to open the symlink there and then.
900 const path_len_bytes = math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong;
901 var nt_name = UNICODE_STRING{
902 .Length = path_len_bytes,
903 .MaximumLength = path_len_bytes,
904 .Buffer = @constCast(sub_path_w.ptr),
905 };
906 var attr = OBJECT_ATTRIBUTES{
907 .Length = @sizeOf(OBJECT_ATTRIBUTES),
908 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir,
909 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
910 .ObjectName = &nt_name,
911 .SecurityDescriptor = null,
912 .SecurityQualityOfService = null,
897/// `sub_path_w` will never be accessed after `out_buffer` has been written to, so it
898/// is safe to reuse a single buffer for both.
899pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u16) ReadLinkError![]u16 {
900 const result_handle = OpenFile(sub_path_w, .{
901 .access_mask = FILE_READ_ATTRIBUTES | SYNCHRONIZE,
902 .dir = dir,
903 .creation = FILE_OPEN,
904 .follow_symlinks = false,
905 .filter = .any,
906 }) catch |err| switch (err) {
907 error.IsDir, error.NotDir => return error.Unexpected, // filter = .any
908 error.PathAlreadyExists => return error.Unexpected, // FILE_OPEN
909 error.WouldBlock => return error.Unexpected,
910 error.NoDevice => return error.FileNotFound,
911 error.PipeBusy => return error.AccessDenied,
912 else => |e| return e,
913913 };
914 var result_handle: HANDLE = undefined;
915 var io: IO_STATUS_BLOCK = undefined;
916
917 const rc = ntdll.NtCreateFile(
918 &result_handle,
919 FILE_READ_ATTRIBUTES | SYNCHRONIZE,
920 &attr,
921 &io,
922 null,
923 FILE_ATTRIBUTE_NORMAL,
924 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
925 FILE_OPEN,
926 FILE_OPEN_REPARSE_POINT | FILE_SYNCHRONOUS_IO_NONALERT,
927 null,
928 0,
929 );
930 switch (rc) {
931 .SUCCESS => {},
932 .OBJECT_NAME_INVALID => unreachable,
933 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
934 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
935 .NO_MEDIA_IN_DEVICE => return error.FileNotFound,
936 .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found
937 .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't
938 .INVALID_PARAMETER => unreachable,
939 .SHARING_VIOLATION => return error.AccessDenied,
940 .ACCESS_DENIED => return error.AccessDenied,
941 .PIPE_BUSY => return error.AccessDenied,
942 .OBJECT_PATH_SYNTAX_BAD => unreachable,
943 .OBJECT_NAME_COLLISION => unreachable,
944 .FILE_IS_A_DIRECTORY => unreachable,
945 else => return unexpectedStatus(rc),
946 }
947914 defer CloseHandle(result_handle);
948915
949916 var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(REPARSE_DATA_BUFFER)) = undefined;
......@@ -961,35 +928,33 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin
961928 const len = buf.SubstituteNameLength >> 1;
962929 const path_buf = @as([*]const u16, &buf.PathBuffer);
963930 const is_relative = buf.Flags & SYMLINK_FLAG_RELATIVE != 0;
964 return parseReadlinkPath(path_buf[offset..][0..len], is_relative, out_buffer);
931 return parseReadLinkPath(path_buf[offset..][0..len], is_relative, out_buffer);
965932 },
966933 IO_REPARSE_TAG_MOUNT_POINT => {
967934 const buf: *const MOUNT_POINT_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0]));
968935 const offset = buf.SubstituteNameOffset >> 1;
969936 const len = buf.SubstituteNameLength >> 1;
970937 const path_buf = @as([*]const u16, &buf.PathBuffer);
971 return parseReadlinkPath(path_buf[offset..][0..len], false, out_buffer);
938 return parseReadLinkPath(path_buf[offset..][0..len], false, out_buffer);
972939 },
973 else => |value| {
974 std.debug.print("unsupported symlink type: {}", .{value});
940 else => {
975941 return error.UnsupportedReparsePointType;
976942 },
977943 }
978944}
979945
980/// Asserts that there is enough space is `out_buffer`.
981/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
982fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 {
983 const win32_namespace_path = path: {
984 if (is_relative) break :path path;
985 const win32_path = ntToWin32Namespace(path) catch |err| switch (err) {
986 error.NameTooLong => unreachable,
987 error.NotNtPath => break :path path,
946fn parseReadLinkPath(path: []const u16, is_relative: bool, out_buffer: []u16) error{NameTooLong}![]u16 {
947 path: {
948 if (is_relative) break :path;
949 return ntToWin32Namespace(path, out_buffer) catch |err| switch (err) {
950 error.NameTooLong => |e| return e,
951 error.NotNtPath => break :path,
988952 };
989 break :path win32_path.span();
990 };
991 const out_len = std.unicode.wtf16LeToWtf8(out_buffer, win32_namespace_path);
992 return out_buffer[0..out_len];
953 }
954 if (out_buffer.len < path.len) return error.NameTooLong;
955 const dest = out_buffer[0..path.len];
956 @memcpy(dest, path);
957 return dest;
993958}
994959
995960pub const DeleteFileError = error{
......@@ -2620,10 +2585,11 @@ test getUnprefixedPathType {
26202585/// https://github.com/reactos/reactos/blob/master/modules/rostests/apitests/ntdll/RtlNtPathNameToDosPathName.c
26212586///
26222587/// `path` should be encoded as WTF-16LE.
2623pub fn ntToWin32Namespace(path: []const u16) !PathSpace {
2588///
2589/// Supports in-place modification (`path` and `out` may refer to the same slice).
2590pub fn ntToWin32Namespace(path: []const u16, out: []u16) error{ NameTooLong, NotNtPath }![]u16 {
26242591 if (path.len > PATH_MAX_WIDE) return error.NameTooLong;
26252592
2626 var path_space: PathSpace = undefined;
26272593 const namespace_prefix = getNamespacePrefix(u16, path);
26282594 switch (namespace_prefix) {
26292595 .nt => {
......@@ -2631,23 +2597,19 @@ pub fn ntToWin32Namespace(path: []const u16) !PathSpace {
26312597 var after_prefix = path[4..]; // after the `\??\`
26322598 // The prefix \??\UNC\ means this is a UNC path, in which case the
26332599 // `\??\UNC\` should be replaced by `\\` (two backslashes)
2634 // TODO: the "UNC" should technically be matched case-insensitively, but
2635 // it's unlikely to matter since most/all paths passed into this
2636 // function will have come from the OS meaning it should have
2637 // the 'canonical' uppercase UNC.
26382600 const is_unc = after_prefix.len >= 4 and
2639 std.mem.eql(u16, after_prefix[0..3], std.unicode.utf8ToUtf16LeStringLiteral("UNC")) and
2601 eqlIgnoreCaseWTF16(after_prefix[0..3], std.unicode.utf8ToUtf16LeStringLiteral("UNC")) and
26402602 std.fs.path.PathType.windows.isSep(u16, std.mem.littleToNative(u16, after_prefix[3]));
2603 const win32_len = path.len - @as(usize, if (is_unc) 6 else 4);
2604 if (out.len < win32_len) return error.NameTooLong;
26412605 if (is_unc) {
2642 path_space.data[0] = comptime std.mem.nativeToLittle(u16, '\\');
2606 out[0] = comptime std.mem.nativeToLittle(u16, '\\');
26432607 dest_index += 1;
26442608 // We want to include the last `\` of `\??\UNC\`
26452609 after_prefix = path[7..];
26462610 }
2647 @memcpy(path_space.data[dest_index..][0..after_prefix.len], after_prefix);
2648 path_space.len = dest_index + after_prefix.len;
2649 path_space.data[path_space.len] = 0;
2650 return path_space;
2611 @memmove(out[dest_index..][0..after_prefix.len], after_prefix);
2612 return out[0..win32_len];
26512613 },
26522614 else => return error.NotNtPath,
26532615 }
......@@ -2656,25 +2618,14 @@ pub fn ntToWin32Namespace(path: []const u16) !PathSpace {
26562618test ntToWin32Namespace {
26572619 const L = std.unicode.utf8ToUtf16LeStringLiteral;
26582620
2659 try testNtToWin32Namespace(L("UNC"), L("\\??\\UNC"));
2660 try testNtToWin32Namespace(L("\\\\"), L("\\??\\UNC\\"));
2661 try testNtToWin32Namespace(L("\\\\path1"), L("\\??\\UNC\\path1"));
2662 try testNtToWin32Namespace(L("\\\\path1\\path2"), L("\\??\\UNC\\path1\\path2"));
2663
2664 try testNtToWin32Namespace(L(""), L("\\??\\"));
2665 try testNtToWin32Namespace(L("C:"), L("\\??\\C:"));
2666 try testNtToWin32Namespace(L("C:\\"), L("\\??\\C:\\"));
2667 try testNtToWin32Namespace(L("C:\\test"), L("\\??\\C:\\test"));
2668 try testNtToWin32Namespace(L("C:\\test\\"), L("\\??\\C:\\test\\"));
2621 var mutable_unc_path_buf = L("\\??\\UNC\\path1\\path2").*;
2622 try std.testing.expectEqualSlices(u16, L("\\\\path1\\path2"), try ntToWin32Namespace(&mutable_unc_path_buf, &mutable_unc_path_buf));
26692623
2670 try std.testing.expectError(error.NotNtPath, ntToWin32Namespace(L("foo")));
2671 try std.testing.expectError(error.NotNtPath, ntToWin32Namespace(L("C:\\test")));
2672 try std.testing.expectError(error.NotNtPath, ntToWin32Namespace(L("\\\\.\\test")));
2673}
2624 var mutable_path_buf = L("\\??\\C:\\test\\").*;
2625 try std.testing.expectEqualSlices(u16, L("C:\\test\\"), try ntToWin32Namespace(&mutable_path_buf, &mutable_path_buf));
26742626
2675fn testNtToWin32Namespace(expected: []const u16, path: []const u16) !void {
2676 const converted = try ntToWin32Namespace(path);
2677 try std.testing.expectEqualSlices(u16, expected, converted.span());
2627 var too_small_buf: [6]u16 = undefined;
2628 try std.testing.expectError(error.NameTooLong, ntToWin32Namespace(L("\\??\\C:\\test"), &too_small_buf));
26782629}
26792630
26802631fn getFullPathNameW(path: [*:0]const u16, out: []u16) !usize {
lib/std/posix.zig+52-14
......@@ -3001,6 +3001,12 @@ pub const ReadLinkError = error{
30013001 UnsupportedReparsePointType,
30023002 /// On Windows, `\\server` or `\\server\share` was not found.
30033003 NetworkNotFound,
3004 /// On Windows, antivirus software is enabled by default. It can be
3005 /// disabled, but Windows Update sometimes ignores the user's preference
3006 /// and re-enables it. When enabled, antivirus software on Windows
3007 /// intercepts file system operations and makes them significantly slower
3008 /// in addition to possibly failing with this error code.
3009 AntivirusInterference,
30043010} || UnexpectedError;
30053011
30063012/// Read value of a symbolic link.
......@@ -3015,26 +3021,42 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
30153021 if (native_os == .wasi and !builtin.link_libc) {
30163022 return readlinkat(AT.FDCWD, file_path, out_buffer);
30173023 } else if (native_os == .windows) {
3018 const file_path_w = try windows.sliceToPrefixedFileW(null, file_path);
3019 return readlinkW(file_path_w.span(), out_buffer);
3024 var file_path_w = try windows.sliceToPrefixedFileW(null, file_path);
3025 const result_w = try readlinkW(file_path_w.span(), &file_path_w.data);
3026
3027 const len = std.unicode.calcWtf8Len(result_w);
3028 if (len > out_buffer.len) return error.NameTooLong;
3029
3030 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w);
3031 return out_buffer[0..end_index];
30203032 } else {
30213033 const file_path_c = try toPosixPath(file_path);
30223034 return readlinkZ(&file_path_c, out_buffer);
30233035 }
30243036}
30253037
3026/// Windows-only. Same as `readlink` except `file_path` is WTF16 LE encoded.
3027/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
3038/// Windows-only. Same as `readlink` except `file_path` is WTF-16 LE encoded, NT-prefixed.
3039/// The result is encoded as WTF-16 LE.
3040///
3041/// `file_path` will never be accessed after `out_buffer` has been written to, so it
3042/// is safe to reuse a single buffer for both.
3043///
30283044/// See also `readlinkZ`.
3029pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
3045pub fn readlinkW(file_path: []const u16, out_buffer: []u16) ReadLinkError![]u16 {
30303046 return windows.ReadLink(fs.cwd().fd, file_path, out_buffer);
30313047}
30323048
30333049/// Same as `readlink` except `file_path` is null-terminated.
30343050pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
30353051 if (native_os == .windows) {
3036 const file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
3037 return readlinkW(file_path_w.span(), out_buffer);
3052 var file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
3053 const result_w = try readlinkW(file_path_w.span(), &file_path_w.data);
3054
3055 const len = std.unicode.calcWtf8Len(result_w);
3056 if (len > out_buffer.len) return error.NameTooLong;
3057
3058 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w);
3059 return out_buffer[0..end_index];
30383060 } else if (native_os == .wasi and !builtin.link_libc) {
30393061 return readlink(mem.sliceTo(file_path, 0), out_buffer);
30403062 }
......@@ -3069,8 +3091,14 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink
30693091 return readlinkatWasi(dirfd, file_path, out_buffer);
30703092 }
30713093 if (native_os == .windows) {
3072 const file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path);
3073 return readlinkatW(dirfd, file_path_w.span(), out_buffer);
3094 var file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path);
3095 const result_w = try readlinkatW(dirfd, file_path_w.span(), &file_path_w.data);
3096
3097 const len = std.unicode.calcWtf8Len(result_w);
3098 if (len > out_buffer.len) return error.NameTooLong;
3099
3100 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w);
3101 return out_buffer[0..end_index];
30743102 }
30753103 const file_path_c = try toPosixPath(file_path);
30763104 return readlinkatZ(dirfd, &file_path_c, out_buffer);
......@@ -3097,10 +3125,14 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read
30973125 }
30983126}
30993127
3100/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 LE encoded.
3101/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
3128/// Windows-only. Same as `readlinkat` except `file_path` WTF16 LE encoded, NT-prefixed.
3129/// The result is encoded as WTF-16 LE.
3130///
3131/// `file_path` will never be accessed after `out_buffer` has been written to, so it
3132/// is safe to reuse a single buffer for both.
3133///
31023134/// See also `readlinkat`.
3103pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
3135pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u16) ReadLinkError![]u16 {
31043136 return windows.ReadLink(dirfd, file_path, out_buffer);
31053137}
31063138
......@@ -3108,8 +3140,14 @@ pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLi
31083140/// See also `readlinkat`.
31093141pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
31103142 if (native_os == .windows) {
3111 const file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path);
3112 return readlinkatW(dirfd, file_path_w.span(), out_buffer);
3143 var file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path);
3144 const result_w = try readlinkatW(dirfd, file_path_w.span(), &file_path_w.data);
3145
3146 const len = std.unicode.calcWtf8Len(result_w);
3147 if (len > out_buffer.len) return error.NameTooLong;
3148
3149 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w);
3150 return out_buffer[0..end_index];
31133151 } else if (native_os == .wasi and !builtin.link_libc) {
31143152 return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer);
31153153 }
lib/std/zig/system.zig+1
......@@ -705,6 +705,7 @@ fn abiAndDynamicLinkerFromFile(
705705 error.BadPathName => unreachable, // Windows only
706706 error.UnsupportedReparsePointType => unreachable, // Windows only
707707 error.NetworkNotFound => unreachable, // Windows only
708 error.AntivirusInterference => unreachable, // Windows only
708709
709710 error.AccessDenied,
710711 error.PermissionDenied,