| author | |
| committer | |
| log | aa4332fb0e5f46657c8c41a3971150b642be1c19 |
| tree | 359fb6f779ff642b75d271176323681593e8c273 |
| parent | f6fecfdc001fa2fc3ee63ca344e419825a11f094 |
| parent | 6aa3570cb0e599e89748073612e8d0317100c807 |
| signature |
windows: Make readLinkW APIs output WTF-16, reduce stack usage of callers5 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. |
| 1377 | pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 { | 1383 | pub 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 | } |
| 1384 | 1396 | ||
| 1385 | /// Windows-only. Same as `readLink` except the pathname parameter | 1397 | /// Windows-only. Same as `readLink` except the path parameter |
| 1386 | /// is WTF16 LE encoded. | 1398 | /// is WTF-16 LE encoded, NT-prefixed. |
| 1387 | pub 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. | ||
| 1402 | pub 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 | } |
| 1390 | 1405 |
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 file | 193 | // 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 | } | ||
| 196 | 199 | ||
| 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 | } | ||
| 200 | 206 | ||
| 201 | // test 3: relative path symlink | 207 | // 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 | } |
| 217 | 226 | ||
| 227 | fn 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 | |||
| 218 | fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void { | 238 | fn 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 | }; |
| 894 | 896 | ||
| 895 | pub 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 that | 899 | pub 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 possible | 900 | 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); |
| 948 | 915 | ||
| 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 | } |
| 979 | 945 | ||
| 980 | /// Asserts that there is enough space is `out_buffer`. | 946 | fn 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: { |
| 982 | fn 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 | } |
| 994 | 959 | ||
| 995 | pub const DeleteFileError = error{ | 960 | pub 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.c | 2585 | /// 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. |
| 2623 | pub fn ntToWin32Namespace(path: []const u16) !PathSpace { | 2588 | /// |
| 2589 | /// Supports in-place modification (`path` and `out` may refer to the same slice). | ||
| 2590 | pub 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; |
| 2625 | 2592 | ||
| 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 the | 2598 | // 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 and | 2600 | 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 |
| 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 { |
| 2656 | test ntToWin32Namespace { | 2618 | test ntToWin32Namespace { |
| 2657 | const L = std.unicode.utf8ToUtf16LeStringLiteral; | 2619 | const L = std.unicode.utf8ToUtf16LeStringLiteral; |
| 2658 | 2620 | ||
| 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\\")); | ||
| 2669 | 2623 | ||
| 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 | } | ||
| 2674 | 2626 | ||
| 2675 | fn 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 | } |
| 2679 | 2630 | ||
| 2680 | fn getFullPathNameW(path: [*:0]const u16, out: []u16) !usize { | 2631 | fn 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; |
| 3005 | 3011 | ||
| 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 | } |
| 3025 | 3037 | ||
| 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`. |
| 3029 | pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 { | 3045 | pub 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 | } |
| 3032 | 3048 | ||
| 3033 | /// Same as `readlink` except `file_path` is null-terminated. | 3049 | /// Same as `readlink` except `file_path` is null-terminated. |
| 3034 | pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { | 3050 | pub 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 | } |
| 3099 | 3127 | ||
| 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`. |
| 3103 | pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 { | 3135 | pub 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 | } |
| 3106 | 3138 | ||
| ... | @@ -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`. |
| 3109 | pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { | 3141 | pub 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 only | 705 | error.BadPathName => unreachable, // Windows only |
| 706 | error.UnsupportedReparsePointType => unreachable, // Windows only | 706 | error.UnsupportedReparsePointType => unreachable, // Windows only |
| 707 | error.NetworkNotFound => unreachable, // Windows only | 707 | error.NetworkNotFound => unreachable, // Windows only |
| 708 | error.AntivirusInterference => unreachable, // Windows only | ||
| 708 | 709 | ||
| 709 | error.AccessDenied, | 710 | error.AccessDenied, |
| 710 | error.PermissionDenied, | 711 | error.PermissionDenied, |