authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2025-10-10 23:04:40-07:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2025-11-15 18:16:03-08:00
log6aa3570cb0e599e89748073612e8d0317100c807
treef33c2d40c295b6e0a67c31a5b7b7d22f8cf49202
parent06a7597ea8ac8d66f20f75580f8e0a6964e1861e

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

- Affects the following functions: + `std.fs.Dir.readLinkW` + `std.os.windows.ReadLink` + `std.os.windows.ntToWin32Namespace` + `std.posix.readlinkW` + `std.posix.readlinkatW` Each of these functions (except `ntToWin32Namespace`) took WTF-16 as input and would output WTF-8, which makes optimal buffer re-use difficult at callsites and could force unnecessary WTF-16 <-> WTF-8 conversion during an intermediate step. The functions have been updated to output WTF-16, and also allow for the path and the output to re-use the same buffer (i.e. in-place modification), which can reduce the stack usage at callsites. For example, all of `std.fs.Dir.readLink`/`readLinkZ`/`std.posix.readlink`/`readlinkZ`/`readlinkat`/`readlinkatZ` have had their stack usage reduced by one PathSpace struct (64 KiB) when targeting Windows. The new `ntToWin32Namespace` takes an output buffer and returns a slice from that instead of returning a PathSpace, which is necessary to make the above possible.

4 files changed, 119 insertions(+), 65 deletions(-)

lib/std/fs/Dir.zig+22-7
...@@ -1354,8 +1354,14 @@ pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u...@@ -1354,8 +1354,14 @@ pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u
1354 return self.readLinkWasi(sub_path, buffer);1354 return self.readLinkWasi(sub_path, buffer);
1355 }1355 }
1356 if (native_os == .windows) {1356 if (native_os == .windows) {
1357 const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);1357 var sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
1358 return self.readLinkW(sub_path_w.span(), buffer);1358 const result_w = try self.readLinkW(sub_path_w.span(), &sub_path_w.data);
1359
1360 const len = std.unicode.calcWtf8Len(result_w);
1361 if (len > buffer.len) return error.NameTooLong;
1362
1363 const end_index = std.unicode.wtf16LeToWtf8(buffer, result_w);
1364 return buffer[0..end_index];
1359 }1365 }
1360 const sub_path_c = try posix.toPosixPath(sub_path);1366 const sub_path_c = try posix.toPosixPath(sub_path);
1361 return self.readLinkZ(&sub_path_c, buffer);1367 return self.readLinkZ(&sub_path_c, buffer);
...@@ -1369,15 +1375,24 @@ pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {...@@ -1369,15 +1375,24 @@ pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
1369/// Same as `readLink`, except the `sub_path_c` parameter is null-terminated.1375/// Same as `readLink`, except the `sub_path_c` parameter is null-terminated.
1370pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {1376pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
1371 if (native_os == .windows) {1377 if (native_os == .windows) {
1372 const sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c);1378 var sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c);
1373 return self.readLinkW(sub_path_w.span(), buffer);1379 const result_w = try self.readLinkW(sub_path_w.span(), &sub_path_w.data);
1380
1381 const len = std.unicode.calcWtf8Len(result_w);
1382 if (len > buffer.len) return error.NameTooLong;
1383
1384 const end_index = std.unicode.wtf16LeToWtf8(buffer, result_w);
1385 return buffer[0..end_index];
1374 }1386 }
1375 return posix.readlinkatZ(self.fd, sub_path_c, buffer);1387 return posix.readlinkatZ(self.fd, sub_path_c, buffer);
1376}1388}
13771389
1378/// Windows-only. Same as `readLink` except the pathname parameter1390/// Windows-only. Same as `readLink` except the path parameter
1379/// is WTF16 LE encoded.1391/// is WTF-16 LE encoded, NT-prefixed.
1380pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {1392///
1393/// `sub_path_w` will never be accessed after `buffer` has been written to, so it
1394/// is safe to reuse a single buffer for both.
1395pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u16) ![]u16 {
1381 return windows.ReadLink(self.fd, sub_path_w, buffer);1396 return windows.ReadLink(self.fd, sub_path_w, buffer);
1382}1397}
13831398
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+31-44
...@@ -894,7 +894,9 @@ pub const ReadLinkError = error{...@@ -894,7 +894,9 @@ pub const ReadLinkError = error{
894 UnsupportedReparsePointType,894 UnsupportedReparsePointType,
895};895};
896896
897pub 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
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 {
898 const result_handle = OpenFile(sub_path_w, .{900 const result_handle = OpenFile(sub_path_w, .{
899 .access_mask = FILE_READ_ATTRIBUTES | SYNCHRONIZE,901 .access_mask = FILE_READ_ATTRIBUTES | SYNCHRONIZE,
900 .dir = dir,902 .dir = dir,
...@@ -926,14 +928,14 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin...@@ -926,14 +928,14 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin
926 const len = buf.SubstituteNameLength >> 1;928 const len = buf.SubstituteNameLength >> 1;
927 const path_buf = @as([*]const u16, &buf.PathBuffer);929 const path_buf = @as([*]const u16, &buf.PathBuffer);
928 const is_relative = buf.Flags & SYMLINK_FLAG_RELATIVE != 0;930 const is_relative = buf.Flags & SYMLINK_FLAG_RELATIVE != 0;
929 return parseReadlinkPath(path_buf[offset..][0..len], is_relative, out_buffer);931 return parseReadLinkPath(path_buf[offset..][0..len], is_relative, out_buffer);
930 },932 },
931 IO_REPARSE_TAG_MOUNT_POINT => {933 IO_REPARSE_TAG_MOUNT_POINT => {
932 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]));
933 const offset = buf.SubstituteNameOffset >> 1;935 const offset = buf.SubstituteNameOffset >> 1;
934 const len = buf.SubstituteNameLength >> 1;936 const len = buf.SubstituteNameLength >> 1;
935 const path_buf = @as([*]const u16, &buf.PathBuffer);937 const path_buf = @as([*]const u16, &buf.PathBuffer);
936 return parseReadlinkPath(path_buf[offset..][0..len], false, out_buffer);938 return parseReadLinkPath(path_buf[offset..][0..len], false, out_buffer);
937 },939 },
938 else => {940 else => {
939 return error.UnsupportedReparsePointType;941 return error.UnsupportedReparsePointType;
...@@ -941,19 +943,18 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin...@@ -941,19 +943,18 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin
941 }943 }
942}944}
943945
944/// Asserts that there is enough space is `out_buffer`.946fn parseReadLinkPath(path: []const u16, is_relative: bool, out_buffer: []u16) error{NameTooLong}![]u16 {
945/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).947 path: {
946fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 {948 if (is_relative) break :path;
947 const win32_namespace_path = path: {949 return ntToWin32Namespace(path, out_buffer) catch |err| switch (err) {
948 if (is_relative) break :path path;950 error.NameTooLong => |e| return e,
949 const win32_path = ntToWin32Namespace(path) catch |err| switch (err) {951 error.NotNtPath => break :path,
950 error.NameTooLong => unreachable,
951 error.NotNtPath => break :path path,
952 };952 };
953 break :path win32_path.span();953 }
954 };954 if (out_buffer.len < path.len) return error.NameTooLong;
955 const out_len = std.unicode.wtf16LeToWtf8(out_buffer, win32_namespace_path);955 const dest = out_buffer[0..path.len];
956 return out_buffer[0..out_len];956 @memcpy(dest, path);
957 return dest;
957}958}
958959
959pub const DeleteFileError = error{960pub const DeleteFileError = error{
...@@ -2584,10 +2585,11 @@ test getUnprefixedPathType {...@@ -2584,10 +2585,11 @@ test getUnprefixedPathType {
2584/// 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
2585///2586///
2586/// `path` should be encoded as WTF-16LE.2587/// `path` should be encoded as WTF-16LE.
2587pub 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 {
2588 if (path.len > PATH_MAX_WIDE) return error.NameTooLong;2591 if (path.len > PATH_MAX_WIDE) return error.NameTooLong;
25892592
2590 var path_space: PathSpace = undefined;
2591 const namespace_prefix = getNamespacePrefix(u16, path);2593 const namespace_prefix = getNamespacePrefix(u16, path);
2592 switch (namespace_prefix) {2594 switch (namespace_prefix) {
2593 .nt => {2595 .nt => {
...@@ -2595,23 +2597,19 @@ pub fn ntToWin32Namespace(path: []const u16) !PathSpace {...@@ -2595,23 +2597,19 @@ pub fn ntToWin32Namespace(path: []const u16) !PathSpace {
2595 var after_prefix = path[4..]; // after the `\??\`2597 var after_prefix = path[4..]; // after the `\??\`
2596 // 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
2597 // `\??\UNC\` should be replaced by `\\` (two backslashes)2599 // `\??\UNC\` should be replaced by `\\` (two backslashes)
2598 // TODO: the "UNC" should technically be matched case-insensitively, but
2599 // it's unlikely to matter since most/all paths passed into this
2600 // function will have come from the OS meaning it should have
2601 // the 'canonical' uppercase UNC.
2602 const is_unc = after_prefix.len >= 4 and2600 const is_unc = after_prefix.len >= 4 and
2603 std.mem.eql(u16, after_prefix[0..3], std.unicode.utf8ToUtf16LeStringLiteral("UNC")) and2601 eqlIgnoreCaseWTF16(after_prefix[0..3], std.unicode.utf8ToUtf16LeStringLiteral("UNC")) and
2604 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;
2605 if (is_unc) {2605 if (is_unc) {
2606 path_space.data[0] = comptime std.mem.nativeToLittle(u16, '\\');2606 out[0] = comptime std.mem.nativeToLittle(u16, '\\');
2607 dest_index += 1;2607 dest_index += 1;
2608 // We want to include the last `\` of `\??\UNC\`2608 // We want to include the last `\` of `\??\UNC\`
2609 after_prefix = path[7..];2609 after_prefix = path[7..];
2610 }2610 }
2611 @memcpy(path_space.data[dest_index..][0..after_prefix.len], after_prefix);2611 @memmove(out[dest_index..][0..after_prefix.len], after_prefix);
2612 path_space.len = dest_index + after_prefix.len;2612 return out[0..win32_len];
2613 path_space.data[path_space.len] = 0;
2614 return path_space;
2615 },2613 },
2616 else => return error.NotNtPath,2614 else => return error.NotNtPath,
2617 }2615 }
...@@ -2620,25 +2618,14 @@ pub fn ntToWin32Namespace(path: []const u16) !PathSpace {...@@ -2620,25 +2618,14 @@ pub fn ntToWin32Namespace(path: []const u16) !PathSpace {
2620test ntToWin32Namespace {2618test ntToWin32Namespace {
2621 const L = std.unicode.utf8ToUtf16LeStringLiteral;2619 const L = std.unicode.utf8ToUtf16LeStringLiteral;
26222620
2623 try testNtToWin32Namespace(L("UNC"), L("\\??\\UNC"));2621 var mutable_unc_path_buf = L("\\??\\UNC\\path1\\path2").*;
2624 try testNtToWin32Namespace(L("\\\\"), L("\\??\\UNC\\"));2622 try std.testing.expectEqualSlices(u16, L("\\\\path1\\path2"), try ntToWin32Namespace(&mutable_unc_path_buf, &mutable_unc_path_buf));
2625 try testNtToWin32Namespace(L("\\\\path1"), L("\\??\\UNC\\path1"));
2626 try testNtToWin32Namespace(L("\\\\path1\\path2"), L("\\??\\UNC\\path1\\path2"));
26272623
2628 try testNtToWin32Namespace(L(""), L("\\??\\"));2624 var mutable_path_buf = L("\\??\\C:\\test\\").*;
2629 try testNtToWin32Namespace(L("C:"), L("\\??\\C:"));2625 try std.testing.expectEqualSlices(u16, L("C:\\test\\"), try ntToWin32Namespace(&mutable_path_buf, &mutable_path_buf));
2630 try testNtToWin32Namespace(L("C:\\"), L("\\??\\C:\\"));
2631 try testNtToWin32Namespace(L("C:\\test"), L("\\??\\C:\\test"));
2632 try testNtToWin32Namespace(L("C:\\test\\"), L("\\??\\C:\\test\\"));
2633
2634 try std.testing.expectError(error.NotNtPath, ntToWin32Namespace(L("foo")));
2635 try std.testing.expectError(error.NotNtPath, ntToWin32Namespace(L("C:\\test")));
2636 try std.testing.expectError(error.NotNtPath, ntToWin32Namespace(L("\\\\.\\test")));
2637}
26382626
2639fn testNtToWin32Namespace(expected: []const u16, path: []const u16) !void {2627 var too_small_buf: [6]u16 = undefined;
2640 const converted = try ntToWin32Namespace(path);2628 try std.testing.expectError(error.NameTooLong, ntToWin32Namespace(L("\\??\\C:\\test"), &too_small_buf));
2641 try std.testing.expectEqualSlices(u16, expected, converted.span());
2642}2629}
26432630
2644fn getFullPathNameW(path: [*:0]const u16, out: []u16) !usize {2631fn getFullPathNameW(path: [*:0]const u16, out: []u16) !usize {
lib/std/posix.zig+46-14
...@@ -3021,26 +3021,42 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {...@@ -3021,26 +3021,42 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
3021 if (native_os == .wasi and !builtin.link_libc) {3021 if (native_os == .wasi and !builtin.link_libc) {
3022 return readlinkat(AT.FDCWD, file_path, out_buffer);3022 return readlinkat(AT.FDCWD, file_path, out_buffer);
3023 } else if (native_os == .windows) {3023 } else if (native_os == .windows) {
3024 const file_path_w = try windows.sliceToPrefixedFileW(null, file_path);3024 var file_path_w = try windows.sliceToPrefixedFileW(null, file_path);
3025 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];
3026 } else {3032 } else {
3027 const file_path_c = try toPosixPath(file_path);3033 const file_path_c = try toPosixPath(file_path);
3028 return readlinkZ(&file_path_c, out_buffer);3034 return readlinkZ(&file_path_c, out_buffer);
3029 }3035 }
3030}3036}
30313037
3032/// 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.
3033/// 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///
3034/// See also `readlinkZ`.3044/// See also `readlinkZ`.
3035pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {3045pub fn readlinkW(file_path: []const u16, out_buffer: []u16) ReadLinkError![]u16 {
3036 return windows.ReadLink(fs.cwd().fd, file_path, out_buffer);3046 return windows.ReadLink(fs.cwd().fd, file_path, out_buffer);
3037}3047}
30383048
3039/// Same as `readlink` except `file_path` is null-terminated.3049/// Same as `readlink` except `file_path` is null-terminated.
3040pub 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 {
3041 if (native_os == .windows) {3051 if (native_os == .windows) {
3042 const file_path_w = try windows.cStrToPrefixedFileW(null, file_path);3052 var file_path_w = try windows.cStrToPrefixedFileW(null, file_path);
3043 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];
3044 } else if (native_os == .wasi and !builtin.link_libc) {3060 } else if (native_os == .wasi and !builtin.link_libc) {
3045 return readlink(mem.sliceTo(file_path, 0), out_buffer);3061 return readlink(mem.sliceTo(file_path, 0), out_buffer);
3046 }3062 }
...@@ -3075,8 +3091,14 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink...@@ -3075,8 +3091,14 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink
3075 return readlinkatWasi(dirfd, file_path, out_buffer);3091 return readlinkatWasi(dirfd, file_path, out_buffer);
3076 }3092 }
3077 if (native_os == .windows) {3093 if (native_os == .windows) {
3078 const file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path);3094 var file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path);
3079 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];
3080 }3102 }
3081 const file_path_c = try toPosixPath(file_path);3103 const file_path_c = try toPosixPath(file_path);
3082 return readlinkatZ(dirfd, &file_path_c, out_buffer);3104 return readlinkatZ(dirfd, &file_path_c, out_buffer);
...@@ -3103,10 +3125,14 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read...@@ -3103,10 +3125,14 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read
3103 }3125 }
3104}3126}
31053127
3106/// 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.
3107/// 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///
3108/// See also `readlinkat`.3134/// See also `readlinkat`.
3109pub 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 {
3110 return windows.ReadLink(dirfd, file_path, out_buffer);3136 return windows.ReadLink(dirfd, file_path, out_buffer);
3111}3137}
31123138
...@@ -3114,8 +3140,14 @@ pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLi...@@ -3114,8 +3140,14 @@ pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLi
3114/// See also `readlinkat`.3140/// See also `readlinkat`.
3115pub 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 {
3116 if (native_os == .windows) {3142 if (native_os == .windows) {
3117 const file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path);3143 var file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path);
3118 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];
3119 } else if (native_os == .wasi and !builtin.link_libc) {3151 } else if (native_os == .wasi and !builtin.link_libc) {
3120 return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer);3152 return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer);
3121 }3153 }