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...@@ -1361,8 +1361,14 @@ pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u
1361 return self.readLinkWasi(sub_path, buffer);1361 return self.readLinkWasi(sub_path, buffer);
1362 }1362 }
1363 if (native_os == .windows) {1363 if (native_os == .windows) {
1364 const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);1364 var sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
1365 return self.readLinkW(sub_path_w.span(), buffer);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];
1366 }1372 }
1367 const sub_path_c = try posix.toPosixPath(sub_path);1373 const sub_path_c = try posix.toPosixPath(sub_path);
1368 return self.readLinkZ(&sub_path_c, buffer);1374 return self.readLinkZ(&sub_path_c, buffer);
...@@ -1376,15 +1382,24 @@ pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {...@@ -1376,15 +1382,24 @@ pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
1376/// Same as `readLink`, except the `sub_path_c` parameter is null-terminated.1382/// Same as `readLink`, except the `sub_path_c` parameter is null-terminated.
1377pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {1383pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
1378 if (native_os == .windows) {1384 if (native_os == .windows) {
1379 const sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c);1385 var sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c);
1380 return self.readLinkW(sub_path_w.span(), buffer);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];
1381 }1393 }
1382 return posix.readlinkatZ(self.fd, sub_path_c, buffer);1394 return posix.readlinkatZ(self.fd, sub_path_c, buffer);
1383}1395}
13841396
1385/// Windows-only. Same as `readLink` except the pathname parameter1397/// Windows-only. Same as `readLink` except the path parameter
1386/// is WTF16 LE encoded.1398/// is WTF-16 LE encoded, NT-prefixed.
1387pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {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 {
1388 return windows.ReadLink(self.fd, sub_path_w, buffer);1403 return windows.ReadLink(self.fd, sub_path_w, buffer);
1389}1404}
13901405
lib/std/fs/test.zig+20
...@@ -193,10 +193,16 @@ test "Dir.readLink" {...@@ -193,10 +193,16 @@ test "Dir.readLink" {
193 // test 1: symlink to a file193 // test 1: symlink to a file
194 try setupSymlink(ctx.dir, file_target_path, "symlink1", .{});194 try setupSymlink(ctx.dir, file_target_path, "symlink1", .{});
195 try testReadLink(ctx.dir, canonical_file_target_path, "symlink1");195 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
197 // test 2: symlink to a directory (can be different on Windows)200 // test 2: symlink to a directory (can be different on Windows)
198 try setupSymlink(ctx.dir, dir_target_path, "symlink2", .{ .is_directory = true });201 try setupSymlink(ctx.dir, dir_target_path, "symlink2", .{ .is_directory = true });
199 try testReadLink(ctx.dir, canonical_dir_target_path, "symlink2");202 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
201 // test 3: relative path symlink207 // test 3: relative path symlink
202 const parent_file = ".." ++ fs.path.sep_str ++ "target.txt";208 const parent_file = ".." ++ fs.path.sep_str ++ "target.txt";
...@@ -205,6 +211,9 @@ test "Dir.readLink" {...@@ -205,6 +211,9 @@ test "Dir.readLink" {
205 defer subdir.close();211 defer subdir.close();
206 try setupSymlink(subdir, canonical_parent_file, "relative-link.txt", .{});212 try setupSymlink(subdir, canonical_parent_file, "relative-link.txt", .{});
207 try testReadLink(subdir, canonical_parent_file, "relative-link.txt");213 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 }
208 }217 }
209 }.impl);218 }.impl);
210}219}
...@@ -215,6 +224,17 @@ fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !vo...@@ -215,6 +224,17 @@ fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !vo
215 try testing.expectEqualStrings(target_path, actual);224 try testing.expectEqualStrings(target_path, actual);
216}225}
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
218fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void {238fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void {
219 var buffer: [fs.max_path_bytes]u8 = undefined;239 var buffer: [fs.max_path_bytes]u8 = undefined;
220 const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]);240 const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]);
lib/std/os/windows.zig+47-96
...@@ -889,61 +889,28 @@ pub const ReadLinkError = error{...@@ -889,61 +889,28 @@ pub const ReadLinkError = error{
889 AccessDenied,889 AccessDenied,
890 Unexpected,890 Unexpected,
891 NameTooLong,891 NameTooLong,
892 BadPathName,
893 AntivirusInterference,
892 UnsupportedReparsePointType,894 UnsupportedReparsePointType,
893};895};
894896
895pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLinkError![]u8 {897/// `sub_path_w` will never be accessed after `out_buffer` has been written to, so it
896 // Here, we use `NtCreateFile` to shave off one syscall if we were to use `OpenFile` wrapper.898/// is safe to reuse a single buffer for both.
897 // With the latter, we'd need to call `NtCreateFile` twice, once for file symlink, and if that899pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u16) ReadLinkError![]u16 {
898 // failed, again for dir symlink. Omitting any mention of file/dir flags makes it possible900 const result_handle = OpenFile(sub_path_w, .{
899 // to open the symlink there and then.901 .access_mask = FILE_READ_ATTRIBUTES | SYNCHRONIZE,
900 const path_len_bytes = math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong;902 .dir = dir,
901 var nt_name = UNICODE_STRING{903 .creation = FILE_OPEN,
902 .Length = path_len_bytes,904 .follow_symlinks = false,
903 .MaximumLength = path_len_bytes,905 .filter = .any,
904 .Buffer = @constCast(sub_path_w.ptr),906 }) catch |err| switch (err) {
905 };907 error.IsDir, error.NotDir => return error.Unexpected, // filter = .any
906 var attr = OBJECT_ATTRIBUTES{908 error.PathAlreadyExists => return error.Unexpected, // FILE_OPEN
907 .Length = @sizeOf(OBJECT_ATTRIBUTES),909 error.WouldBlock => return error.Unexpected,
908 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir,910 error.NoDevice => return error.FileNotFound,
909 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.911 error.PipeBusy => return error.AccessDenied,
910 .ObjectName = &nt_name,912 else => |e| return e,
911 .SecurityDescriptor = null,
912 .SecurityQualityOfService = null,
913 };913 };
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 }
947 defer CloseHandle(result_handle);914 defer CloseHandle(result_handle);
948915
949 var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(REPARSE_DATA_BUFFER)) = undefined;916 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...@@ -961,35 +928,33 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin
961 const len = buf.SubstituteNameLength >> 1;928 const len = buf.SubstituteNameLength >> 1;
962 const path_buf = @as([*]const u16, &buf.PathBuffer);929 const path_buf = @as([*]const u16, &buf.PathBuffer);
963 const is_relative = buf.Flags & SYMLINK_FLAG_RELATIVE != 0;930 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);
965 },932 },
966 IO_REPARSE_TAG_MOUNT_POINT => {933 IO_REPARSE_TAG_MOUNT_POINT => {
967 const buf: *const MOUNT_POINT_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0]));934 const buf: *const MOUNT_POINT_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0]));
968 const offset = buf.SubstituteNameOffset >> 1;935 const offset = buf.SubstituteNameOffset >> 1;
969 const len = buf.SubstituteNameLength >> 1;936 const len = buf.SubstituteNameLength >> 1;
970 const path_buf = @as([*]const u16, &buf.PathBuffer);937 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);
972 },939 },
973 else => |value| {940 else => {
974 std.debug.print("unsupported symlink type: {}", .{value});
975 return error.UnsupportedReparsePointType;941 return error.UnsupportedReparsePointType;
976 },942 },
977 }943 }
978}944}
979945
980/// Asserts that there is enough space is `out_buffer`.946fn parseReadLinkPath(path: []const u16, is_relative: bool, out_buffer: []u16) error{NameTooLong}![]u16 {
981/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).947 path: {
982fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 {948 if (is_relative) break :path;
983 const win32_namespace_path = path: {949 return ntToWin32Namespace(path, out_buffer) catch |err| switch (err) {
984 if (is_relative) break :path path;950 error.NameTooLong => |e| return e,
985 const win32_path = ntToWin32Namespace(path) catch |err| switch (err) {951 error.NotNtPath => break :path,
986 error.NameTooLong => unreachable,
987 error.NotNtPath => break :path path,
988 };952 };
989 break :path win32_path.span();953 }
990 };954 if (out_buffer.len < path.len) return error.NameTooLong;
991 const out_len = std.unicode.wtf16LeToWtf8(out_buffer, win32_namespace_path);955 const dest = out_buffer[0..path.len];
992 return out_buffer[0..out_len];956 @memcpy(dest, path);
957 return dest;
993}958}
994959
995pub const DeleteFileError = error{960pub const DeleteFileError = error{
...@@ -2620,10 +2585,11 @@ test getUnprefixedPathType {...@@ -2620,10 +2585,11 @@ test getUnprefixedPathType {
2620/// https://github.com/reactos/reactos/blob/master/modules/rostests/apitests/ntdll/RtlNtPathNameToDosPathName.c2585/// https://github.com/reactos/reactos/blob/master/modules/rostests/apitests/ntdll/RtlNtPathNameToDosPathName.c
2621///2586///
2622/// `path` should be encoded as WTF-16LE.2587/// `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 {
2624 if (path.len > PATH_MAX_WIDE) return error.NameTooLong;2591 if (path.len > PATH_MAX_WIDE) return error.NameTooLong;
26252592
2626 var path_space: PathSpace = undefined;
2627 const namespace_prefix = getNamespacePrefix(u16, path);2593 const namespace_prefix = getNamespacePrefix(u16, path);
2628 switch (namespace_prefix) {2594 switch (namespace_prefix) {
2629 .nt => {2595 .nt => {
...@@ -2631,23 +2597,19 @@ pub fn ntToWin32Namespace(path: []const u16) !PathSpace {...@@ -2631,23 +2597,19 @@ pub fn ntToWin32Namespace(path: []const u16) !PathSpace {
2631 var after_prefix = path[4..]; // after the `\??\`2597 var after_prefix = path[4..]; // after the `\??\`
2632 // The prefix \??\UNC\ means this is a UNC path, in which case the2598 // The prefix \??\UNC\ means this is a UNC path, in which case the
2633 // `\??\UNC\` should be replaced by `\\` (two backslashes)2599 // `\??\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.
2638 const is_unc = after_prefix.len >= 4 and2600 const is_unc = after_prefix.len >= 4 and
2639 std.mem.eql(u16, after_prefix[0..3], std.unicode.utf8ToUtf16LeStringLiteral("UNC")) and2601 eqlIgnoreCaseWTF16(after_prefix[0..3], std.unicode.utf8ToUtf16LeStringLiteral("UNC")) and
2640 std.fs.path.PathType.windows.isSep(u16, std.mem.littleToNative(u16, after_prefix[3]));2602 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;
2641 if (is_unc) {2605 if (is_unc) {
2642 path_space.data[0] = comptime std.mem.nativeToLittle(u16, '\\');2606 out[0] = comptime std.mem.nativeToLittle(u16, '\\');
2643 dest_index += 1;2607 dest_index += 1;
2644 // We want to include the last `\` of `\??\UNC\`2608 // We want to include the last `\` of `\??\UNC\`
2645 after_prefix = path[7..];2609 after_prefix = path[7..];
2646 }2610 }
2647 @memcpy(path_space.data[dest_index..][0..after_prefix.len], after_prefix);2611 @memmove(out[dest_index..][0..after_prefix.len], after_prefix);
2648 path_space.len = dest_index + after_prefix.len;2612 return out[0..win32_len];
2649 path_space.data[path_space.len] = 0;
2650 return path_space;
2651 },2613 },
2652 else => return error.NotNtPath,2614 else => return error.NotNtPath,
2653 }2615 }
...@@ -2656,25 +2618,14 @@ pub fn ntToWin32Namespace(path: []const u16) !PathSpace {...@@ -2656,25 +2618,14 @@ pub fn ntToWin32Namespace(path: []const u16) !PathSpace {
2656test ntToWin32Namespace {2618test ntToWin32Namespace {
2657 const L = std.unicode.utf8ToUtf16LeStringLiteral;2619 const L = std.unicode.utf8ToUtf16LeStringLiteral;
26582620
2659 try testNtToWin32Namespace(L("UNC"), L("\\??\\UNC"));2621 var mutable_unc_path_buf = L("\\??\\UNC\\path1\\path2").*;
2660 try testNtToWin32Namespace(L("\\\\"), L("\\??\\UNC\\"));2622 try std.testing.expectEqualSlices(u16, L("\\\\path1\\path2"), try ntToWin32Namespace(&mutable_unc_path_buf, &mutable_unc_path_buf));
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\\"));
26692623
2670 try std.testing.expectError(error.NotNtPath, ntToWin32Namespace(L("foo")));2624 var mutable_path_buf = L("\\??\\C:\\test\\").*;
2671 try std.testing.expectError(error.NotNtPath, ntToWin32Namespace(L("C:\\test")));2625 try std.testing.expectEqualSlices(u16, L("C:\\test\\"), try ntToWin32Namespace(&mutable_path_buf, &mutable_path_buf));
2672 try std.testing.expectError(error.NotNtPath, ntToWin32Namespace(L("\\\\.\\test")));
2673}
26742626
2675fn testNtToWin32Namespace(expected: []const u16, path: []const u16) !void {2627 var too_small_buf: [6]u16 = undefined;
2676 const converted = try ntToWin32Namespace(path);2628 try std.testing.expectError(error.NameTooLong, ntToWin32Namespace(L("\\??\\C:\\test"), &too_small_buf));
2677 try std.testing.expectEqualSlices(u16, expected, converted.span());
2678}2629}
26792630
2680fn getFullPathNameW(path: [*:0]const u16, out: []u16) !usize {2631fn getFullPathNameW(path: [*:0]const u16, out: []u16) !usize {
lib/std/posix.zig+52-14
...@@ -3001,6 +3001,12 @@ pub const ReadLinkError = error{...@@ -3001,6 +3001,12 @@ pub const ReadLinkError = error{
3001 UnsupportedReparsePointType,3001 UnsupportedReparsePointType,
3002 /// On Windows, `\\server` or `\\server\share` was not found.3002 /// On Windows, `\\server` or `\\server\share` was not found.
3003 NetworkNotFound,3003 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,
3004} || UnexpectedError;3010} || UnexpectedError;
30053011
3006/// Read value of a symbolic link.3012/// Read value of a symbolic link.
...@@ -3015,26 +3021,42 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {...@@ -3015,26 +3021,42 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
3015 if (native_os == .wasi and !builtin.link_libc) {3021 if (native_os == .wasi and !builtin.link_libc) {
3016 return readlinkat(AT.FDCWD, file_path, out_buffer);3022 return readlinkat(AT.FDCWD, file_path, out_buffer);
3017 } else if (native_os == .windows) {3023 } else if (native_os == .windows) {
3018 const file_path_w = try windows.sliceToPrefixedFileW(null, file_path);3024 var file_path_w = try windows.sliceToPrefixedFileW(null, file_path);
3019 return readlinkW(file_path_w.span(), out_buffer);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];
3020 } else {3032 } else {
3021 const file_path_c = try toPosixPath(file_path);3033 const file_path_c = try toPosixPath(file_path);
3022 return readlinkZ(&file_path_c, out_buffer);3034 return readlinkZ(&file_path_c, out_buffer);
3023 }3035 }
3024}3036}
30253037
3026/// Windows-only. Same as `readlink` except `file_path` is WTF16 LE encoded.3038/// Windows-only. Same as `readlink` except `file_path` is WTF-16 LE encoded, NT-prefixed.
3027/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).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///
3028/// See also `readlinkZ`.3044/// 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 {
3030 return windows.ReadLink(fs.cwd().fd, file_path, out_buffer);3046 return windows.ReadLink(fs.cwd().fd, file_path, out_buffer);
3031}3047}
30323048
3033/// Same as `readlink` except `file_path` is null-terminated.3049/// Same as `readlink` except `file_path` is null-terminated.
3034pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {3050pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
3035 if (native_os == .windows) {3051 if (native_os == .windows) {
3036 const file_path_w = try windows.cStrToPrefixedFileW(null, file_path);3052 var file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
3037 return readlinkW(file_path_w.span(), out_buffer);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];
3038 } else if (native_os == .wasi and !builtin.link_libc) {3060 } else if (native_os == .wasi and !builtin.link_libc) {
3039 return readlink(mem.sliceTo(file_path, 0), out_buffer);3061 return readlink(mem.sliceTo(file_path, 0), out_buffer);
3040 }3062 }
...@@ -3069,8 +3091,14 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink...@@ -3069,8 +3091,14 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink
3069 return readlinkatWasi(dirfd, file_path, out_buffer);3091 return readlinkatWasi(dirfd, file_path, out_buffer);
3070 }3092 }
3071 if (native_os == .windows) {3093 if (native_os == .windows) {
3072 const file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path);3094 var file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path);
3073 return readlinkatW(dirfd, file_path_w.span(), out_buffer);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];
3074 }3102 }
3075 const file_path_c = try toPosixPath(file_path);3103 const file_path_c = try toPosixPath(file_path);
3076 return readlinkatZ(dirfd, &file_path_c, out_buffer);3104 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...@@ -3097,10 +3125,14 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read
3097 }3125 }
3098}3126}
30993127
3100/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 LE encoded.3128/// Windows-only. Same as `readlinkat` except `file_path` WTF16 LE encoded, NT-prefixed.
3101/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).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///
3102/// See also `readlinkat`.3134/// 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 {
3104 return windows.ReadLink(dirfd, file_path, out_buffer);3136 return windows.ReadLink(dirfd, file_path, out_buffer);
3105}3137}
31063138
...@@ -3108,8 +3140,14 @@ pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLi...@@ -3108,8 +3140,14 @@ pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLi
3108/// See also `readlinkat`.3140/// See also `readlinkat`.
3109pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {3141pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
3110 if (native_os == .windows) {3142 if (native_os == .windows) {
3111 const file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path);3143 var file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path);
3112 return readlinkatW(dirfd, file_path_w.span(), out_buffer);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];
3113 } else if (native_os == .wasi and !builtin.link_libc) {3151 } else if (native_os == .wasi and !builtin.link_libc) {
3114 return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer);3152 return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer);
3115 }3153 }
lib/std/zig/system.zig+1
...@@ -705,6 +705,7 @@ fn abiAndDynamicLinkerFromFile(...@@ -705,6 +705,7 @@ fn abiAndDynamicLinkerFromFile(
705 error.BadPathName => unreachable, // Windows only705 error.BadPathName => unreachable, // Windows only
706 error.UnsupportedReparsePointType => unreachable, // Windows only706 error.UnsupportedReparsePointType => unreachable, // Windows only
707 error.NetworkNotFound => unreachable, // Windows only707 error.NetworkNotFound => unreachable, // Windows only
708 error.AntivirusInterference => unreachable, // Windows only
708709
709 error.AccessDenied,710 error.AccessDenied,
710 error.PermissionDenied,711 error.PermissionDenied,