diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index 67f0c0d724b8..e7b8d83ad723 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -2026,8 +2026,14 @@ pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ReadLinkError![]u return self.readLinkWasi(sub_path, buffer); } if (native_os == .windows) { - const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path); - return self.readLinkW(sub_path_w.span(), buffer); + var sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path); + const result_w = try self.readLinkW2(sub_path_w.span(), &sub_path_w.data); + + const len = std.unicode.calcWtf8Len(result_w); + if (len > buffer.len) return error.NameTooLong; + + const end_index = std.unicode.wtf16LeToWtf8(buffer, result_w); + return buffer[0..end_index]; } const sub_path_c = try posix.toPosixPath(sub_path); return self.readLinkZ(&sub_path_c, buffer); @@ -2041,18 +2047,35 @@ pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 { /// Same as `readLink`, except the `sub_path_c` parameter is null-terminated. pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 { if (native_os == .windows) { - const sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c); - return self.readLinkW(sub_path_w.span(), buffer); + var sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c); + const result_w = try self.readLinkW2(sub_path_w.span(), &sub_path_w.data); + + const len = std.unicode.calcWtf8Len(result_w); + if (len > buffer.len) return error.NameTooLong; + + const end_index = std.unicode.wtf16LeToWtf8(buffer, result_w); + return buffer[0..end_index]; } return posix.readlinkatZ(self.fd, sub_path_c, buffer); } -/// Windows-only. Same as `readLink` except the pathname parameter -/// is WTF16 LE encoded. +/// Deprecated; use `readLinkW2`. +/// +/// Windows-only. Same as `readLink` except the path parameter +/// is WTF-16 LE encoded, NT-prefixed. pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 { return windows.ReadLink(self.fd, sub_path_w, buffer); } +/// Windows-only. Same as `readLink` except the path parameter +/// is WTF-16 LE encoded, NT-prefixed. +/// +/// `sub_path_w` will never be accessed after `buffer` has been written to, so it +/// is safe to reuse a single buffer for both. +pub fn readLinkW2(self: Dir, sub_path_w: []const u16, buffer: []u16) ![]u16 { + return windows.ReadLink2(self.fd, sub_path_w, buffer); +} + /// Read all of file contents using a preallocated buffer. /// The returned slice has the same pointer as `buffer`. If the length matches `buffer.len` /// the situation is ambiguous. It could either mean that the entire file was read, and diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 0949d71a2da3..01d32a5c3107 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -189,10 +189,16 @@ test "Dir.readLink" { // test 1: symlink to a file try setupSymlink(ctx.dir, file_target_path, "symlink1", .{}); try testReadLink(ctx.dir, canonical_file_target_path, "symlink1"); + if (builtin.os.tag == .windows) { + try testReadLinkW(testing.allocator, ctx.dir, canonical_file_target_path, "symlink1"); + } // test 2: symlink to a directory (can be different on Windows) try setupSymlink(ctx.dir, dir_target_path, "symlink2", .{ .is_directory = true }); try testReadLink(ctx.dir, canonical_dir_target_path, "symlink2"); + if (builtin.os.tag == .windows) { + try testReadLinkW(testing.allocator, ctx.dir, canonical_dir_target_path, "symlink2"); + } // test 3: relative path symlink const parent_file = ".." ++ fs.path.sep_str ++ "target.txt"; @@ -201,6 +207,9 @@ test "Dir.readLink" { defer subdir.close(); try setupSymlink(subdir, canonical_parent_file, "relative-link.txt", .{}); try testReadLink(subdir, canonical_parent_file, "relative-link.txt"); + if (builtin.os.tag == .windows) { + try testReadLinkW(testing.allocator, subdir, canonical_parent_file, "relative-link.txt"); + } } }.impl); } @@ -211,6 +220,25 @@ fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !vo try testing.expectEqualStrings(target_path, actual); } +fn testReadLinkW(allocator: mem.Allocator, dir: Dir, target_path: []const u8, symlink_path: []const u8) !void { + const target_path_w = try std.unicode.wtf8ToWtf16LeAlloc(allocator, target_path); + defer allocator.free(target_path_w); + // Calling the W functions directly requires the path to be NT-prefixed + const symlink_path_w = try std.os.windows.sliceToPrefixedFileW(dir.fd, symlink_path); + { + const wtf8_buffer = try allocator.alloc(u8, target_path.len); + defer allocator.free(wtf8_buffer); + const actual = try dir.readLinkW(symlink_path_w.span(), wtf8_buffer); + try testing.expectEqualStrings(target_path, actual); + } + { + const wtf16_buffer = try allocator.alloc(u16, target_path_w.len); + defer allocator.free(wtf16_buffer); + const actual = try dir.readLinkW2(symlink_path_w.span(), wtf16_buffer); + try testing.expectEqualSlices(u16, target_path_w, actual); + } +} + fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void { var buffer: [fs.max_path_bytes]u8 = undefined; const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]); diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 932e401758b2..cbf159497d79 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -887,61 +887,76 @@ pub const ReadLinkError = error{ AccessDenied, Unexpected, NameTooLong, + BadPathName, + AntivirusInterference, UnsupportedReparsePointType, }; +/// Deprecated; use `ReadLink2`. pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLinkError![]u8 { - // Here, we use `NtCreateFile` to shave off one syscall if we were to use `OpenFile` wrapper. - // With the latter, we'd need to call `NtCreateFile` twice, once for file symlink, and if that - // failed, again for dir symlink. Omitting any mention of file/dir flags makes it possible - // to open the symlink there and then. - const path_len_bytes = math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; - var nt_name = UNICODE_STRING{ - .Length = path_len_bytes, - .MaximumLength = path_len_bytes, - .Buffer = @constCast(sub_path_w.ptr), + const result_handle = OpenFile(sub_path_w, .{ + .access_mask = FILE_READ_ATTRIBUTES | SYNCHRONIZE, + .dir = dir, + .creation = FILE_OPEN, + .follow_symlinks = false, + .filter = .any, + }) catch |err| switch (err) { + error.IsDir, error.NotDir => return error.Unexpected, // filter = .any + error.PathAlreadyExists => return error.Unexpected, // FILE_OPEN + error.WouldBlock => return error.Unexpected, + error.NoDevice => return error.FileNotFound, + error.PipeBusy => return error.AccessDenied, + else => |e| return e, }; - var attr = OBJECT_ATTRIBUTES{ - .Length = @sizeOf(OBJECT_ATTRIBUTES), - .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else dir, - .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. - .ObjectName = &nt_name, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, + defer CloseHandle(result_handle); + + var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(REPARSE_DATA_BUFFER)) = undefined; + _ = DeviceIoControl(result_handle, FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]) catch |err| switch (err) { + error.AccessDenied => return error.Unexpected, + error.UnrecognizedVolume => return error.Unexpected, + else => |e| return e, }; - var result_handle: HANDLE = undefined; - var io: IO_STATUS_BLOCK = undefined; - const rc = ntdll.NtCreateFile( - &result_handle, - FILE_READ_ATTRIBUTES | SYNCHRONIZE, - &attr, - &io, - null, - FILE_ATTRIBUTE_NORMAL, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - FILE_OPEN, - FILE_OPEN_REPARSE_POINT | FILE_SYNCHRONOUS_IO_NONALERT, - null, - 0, - ); - switch (rc) { - .SUCCESS => {}, - .OBJECT_NAME_INVALID => unreachable, - .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, - .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, - .NO_MEDIA_IN_DEVICE => return error.FileNotFound, - .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found - .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't - .INVALID_PARAMETER => unreachable, - .SHARING_VIOLATION => return error.AccessDenied, - .ACCESS_DENIED => return error.AccessDenied, - .PIPE_BUSY => return error.AccessDenied, - .OBJECT_PATH_SYNTAX_BAD => unreachable, - .OBJECT_NAME_COLLISION => unreachable, - .FILE_IS_A_DIRECTORY => unreachable, - else => return unexpectedStatus(rc), + const reparse_struct: *const REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf[0])); + switch (reparse_struct.ReparseTag) { + IO_REPARSE_TAG_SYMLINK => { + const buf: *const SYMBOLIC_LINK_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); + const offset = buf.SubstituteNameOffset >> 1; + const len = buf.SubstituteNameLength >> 1; + const path_buf = @as([*]const u16, &buf.PathBuffer); + const is_relative = buf.Flags & SYMLINK_FLAG_RELATIVE != 0; + return parseReadLinkPath(path_buf[offset..][0..len], is_relative, out_buffer); + }, + IO_REPARSE_TAG_MOUNT_POINT => { + const buf: *const MOUNT_POINT_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); + const offset = buf.SubstituteNameOffset >> 1; + const len = buf.SubstituteNameLength >> 1; + const path_buf = @as([*]const u16, &buf.PathBuffer); + return parseReadLinkPath(path_buf[offset..][0..len], false, out_buffer); + }, + else => { + return error.UnsupportedReparsePointType; + }, } +} + +/// `sub_path_w` will never be accessed after `out_buffer` has been written to, so it +/// is safe to reuse a single buffer for both. +pub fn ReadLink2(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u16) ReadLinkError![]u16 { + const result_handle = OpenFile(sub_path_w, .{ + .access_mask = FILE_READ_ATTRIBUTES | SYNCHRONIZE, + .dir = dir, + .creation = FILE_OPEN, + .follow_symlinks = false, + .filter = .any, + }) catch |err| switch (err) { + error.IsDir, error.NotDir => return error.Unexpected, // filter = .any + error.PathAlreadyExists => return error.Unexpected, // FILE_OPEN + error.WouldBlock => return error.Unexpected, + error.NoDevice => return error.FileNotFound, + error.PipeBusy => return error.AccessDenied, + else => |e| return e, + }; defer CloseHandle(result_handle); var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(REPARSE_DATA_BUFFER)) = undefined; @@ -959,25 +974,26 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin const len = buf.SubstituteNameLength >> 1; const path_buf = @as([*]const u16, &buf.PathBuffer); const is_relative = buf.Flags & SYMLINK_FLAG_RELATIVE != 0; - return parseReadlinkPath(path_buf[offset..][0..len], is_relative, out_buffer); + return parseReadLinkPath2(path_buf[offset..][0..len], is_relative, out_buffer); }, IO_REPARSE_TAG_MOUNT_POINT => { const buf: *const MOUNT_POINT_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); const offset = buf.SubstituteNameOffset >> 1; const len = buf.SubstituteNameLength >> 1; const path_buf = @as([*]const u16, &buf.PathBuffer); - return parseReadlinkPath(path_buf[offset..][0..len], false, out_buffer); + return parseReadLinkPath2(path_buf[offset..][0..len], false, out_buffer); }, - else => |value| { - std.debug.print("unsupported symlink type: {}", .{value}); + else => { return error.UnsupportedReparsePointType; }, } } -/// Asserts that there is enough space is `out_buffer`. +/// Deprecated, should be deleted when ReadLink2 is renamed to ReadLink +/// +/// Asserts that there is enough space in `out_buffer`. /// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/). -fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 { +fn parseReadLinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 { const win32_namespace_path = path: { if (is_relative) break :path path; const win32_path = ntToWin32Namespace(path) catch |err| switch (err) { @@ -990,6 +1006,20 @@ fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u return out_buffer[0..out_len]; } +fn parseReadLinkPath2(path: []const u16, is_relative: bool, out_buffer: []u16) error{NameTooLong}![]u16 { + path: { + if (is_relative) break :path; + return ntToWin32Namespace2(path, out_buffer) catch |err| switch (err) { + error.NameTooLong => |e| return e, + error.NotNtPath => break :path, + }; + } + if (out_buffer.len < path.len) return error.NameTooLong; + const dest = out_buffer[0..path.len]; + @memcpy(dest, path); + return dest; +} + pub const DeleteFileError = error{ FileNotFound, AccessDenied, @@ -2720,6 +2750,8 @@ test getUnprefixedPathType { try std.testing.expectEqual(UnprefixedPathType.drive_absolute, getUnprefixedPathType(u8, "x:/a/b/c")); } +/// Deprecated; use `ntToWin32Namespace2`. +/// /// Similar to `RtlNtPathNameToDosPathName` but does not do any heap allocation. /// The possible transformations are: /// \??\C:\Some\Path -> C:\Some\Path @@ -2731,9 +2763,28 @@ test getUnprefixedPathType { /// /// `path` should be encoded as WTF-16LE. pub fn ntToWin32Namespace(path: []const u16) !PathSpace { + var path_space: PathSpace = undefined; + const win32_path = try ntToWin32Namespace2(path, &path_space.data); + path_space.len = win32_path.len; + path_space.data[win32_path.len] = 0; + return path_space; +} + +/// Similar to `RtlNtPathNameToDosPathName` but does not do any heap allocation. +/// The possible transformations are: +/// \??\C:\Some\Path -> C:\Some\Path +/// \??\UNC\server\share\foo -> \\server\share\foo +/// If the path does not have the NT namespace prefix, then `error.NotNtPath` is returned. +/// +/// Functionality is based on the ReactOS test cases found here: +/// https://github.com/reactos/reactos/blob/master/modules/rostests/apitests/ntdll/RtlNtPathNameToDosPathName.c +/// +/// `path` should be encoded as WTF-16LE. +/// +/// Supports in-place modification (`path` and `out` may refer to the same slice). +pub fn ntToWin32Namespace2(path: []const u16, out: []u16) error{ NameTooLong, NotNtPath }![]u16 { if (path.len > PATH_MAX_WIDE) return error.NameTooLong; - var path_space: PathSpace = undefined; const namespace_prefix = getNamespacePrefix(u16, path); switch (namespace_prefix) { .nt => { @@ -2741,23 +2792,19 @@ pub fn ntToWin32Namespace(path: []const u16) !PathSpace { var after_prefix = path[4..]; // after the `\??\` // The prefix \??\UNC\ means this is a UNC path, in which case the // `\??\UNC\` should be replaced by `\\` (two backslashes) - // TODO: the "UNC" should technically be matched case-insensitively, but - // it's unlikely to matter since most/all paths passed into this - // function will have come from the OS meaning it should have - // the 'canonical' uppercase UNC. const is_unc = after_prefix.len >= 4 and - std.mem.eql(u16, after_prefix[0..3], std.unicode.utf8ToUtf16LeStringLiteral("UNC")) and + eqlIgnoreCaseWTF16(after_prefix[0..3], std.unicode.utf8ToUtf16LeStringLiteral("UNC")) and std.fs.path.PathType.windows.isSep(u16, std.mem.littleToNative(u16, after_prefix[3])); + const win32_len = path.len - @as(usize, if (is_unc) 6 else 4); + if (out.len < win32_len) return error.NameTooLong; if (is_unc) { - path_space.data[0] = comptime std.mem.nativeToLittle(u16, '\\'); + out[0] = comptime std.mem.nativeToLittle(u16, '\\'); dest_index += 1; // We want to include the last `\` of `\??\UNC\` after_prefix = path[7..]; } - @memcpy(path_space.data[dest_index..][0..after_prefix.len], after_prefix); - path_space.len = dest_index + after_prefix.len; - path_space.data[path_space.len] = 0; - return path_space; + @memmove(out[dest_index..][0..after_prefix.len], after_prefix); + return out[0..win32_len]; }, else => return error.NotNtPath, } @@ -2787,6 +2834,19 @@ fn testNtToWin32Namespace(expected: []const u16, path: []const u16) !void { try std.testing.expectEqualSlices(u16, expected, converted.span()); } +test ntToWin32Namespace2 { + const L = std.unicode.utf8ToUtf16LeStringLiteral; + + var mutable_unc_path_buf = L("\\??\\UNC\\path1\\path2").*; + try std.testing.expectEqualSlices(u16, L("\\\\path1\\path2"), try ntToWin32Namespace2(&mutable_unc_path_buf, &mutable_unc_path_buf)); + + var mutable_path_buf = L("\\??\\C:\\test\\").*; + try std.testing.expectEqualSlices(u16, L("C:\\test\\"), try ntToWin32Namespace2(&mutable_path_buf, &mutable_path_buf)); + + var too_small_buf: [6]u16 = undefined; + try std.testing.expectError(error.NameTooLong, ntToWin32Namespace2(L("\\??\\C:\\test"), &too_small_buf)); +} + fn getFullPathNameW(path: [*:0]const u16, out: []u16) !usize { const result = kernel32.GetFullPathNameW(path, @as(u32, @intCast(out.len)), out.ptr, null); if (result == 0) { diff --git a/lib/std/posix.zig b/lib/std/posix.zig index d6a5c27c7c0f..77d5029fe469 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -3323,6 +3323,12 @@ pub const ReadLinkError = error{ UnsupportedReparsePointType, /// On Windows, `\\server` or `\\server\share` was not found. NetworkNotFound, + /// On Windows, antivirus software is enabled by default. It can be + /// disabled, but Windows Update sometimes ignores the user's preference + /// and re-enables it. When enabled, antivirus software on Windows + /// intercepts file system operations and makes them significantly slower + /// in addition to possibly failing with this error code. + AntivirusInterference, } || UnexpectedError; /// Read value of a symbolic link. @@ -3337,26 +3343,51 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { if (native_os == .wasi and !builtin.link_libc) { return readlinkat(AT.FDCWD, file_path, out_buffer); } else if (native_os == .windows) { - const file_path_w = try windows.sliceToPrefixedFileW(null, file_path); - return readlinkW(file_path_w.span(), out_buffer); + var file_path_w = try windows.sliceToPrefixedFileW(null, file_path); + const result_w = try readlinkW2(file_path_w.span(), &file_path_w.data); + + const len = std.unicode.calcWtf8Len(result_w); + if (len > out_buffer.len) return error.NameTooLong; + + const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w); + return out_buffer[0..end_index]; } else { const file_path_c = try toPosixPath(file_path); return readlinkZ(&file_path_c, out_buffer); } } -/// Windows-only. Same as `readlink` except `file_path` is WTF16 LE encoded. +/// Deprecated; use `readlinkW2` +/// +/// Windows-only. Same as `readlink` except `file_path` is WTF-16 LE encoded, NT-prefixed. /// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// See also `readlinkZ`. pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 { return windows.ReadLink(fs.cwd().fd, file_path, out_buffer); } +/// Windows-only. Same as `readlink` except `file_path` is WTF-16 LE encoded, NT-prefixed. +/// The result is encoded as WTF-16 LE. +/// +/// `file_path` will never be accessed after `out_buffer` has been written to, so it +/// is safe to reuse a single buffer for both. +/// +/// See also `readlinkZ`. +pub fn readlinkW2(file_path: []const u16, out_buffer: []u16) ReadLinkError![]u16 { + return windows.ReadLink2(fs.cwd().fd, file_path, out_buffer); +} + /// Same as `readlink` except `file_path` is null-terminated. pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { if (native_os == .windows) { - const file_path_w = try windows.cStrToPrefixedFileW(null, file_path); - return readlinkW(file_path_w.span(), out_buffer); + var file_path_w = try windows.cStrToPrefixedFileW(null, file_path); + const result_w = try readlinkW2(file_path_w.span(), &file_path_w.data); + + const len = std.unicode.calcWtf8Len(result_w); + if (len > out_buffer.len) return error.NameTooLong; + + const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w); + return out_buffer[0..end_index]; } else if (native_os == .wasi and !builtin.link_libc) { return readlink(mem.sliceTo(file_path, 0), out_buffer); } @@ -3394,8 +3425,14 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink return readlinkatWasi(dirfd, file_path, out_buffer); } if (native_os == .windows) { - const file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path); - return readlinkatW(dirfd, file_path_w.span(), out_buffer); + var file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path); + const result_w = try readlinkatW2(dirfd, file_path_w.span(), &file_path_w.data); + + const len = std.unicode.calcWtf8Len(result_w); + if (len > out_buffer.len) return error.NameTooLong; + + const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w); + return out_buffer[0..end_index]; } const file_path_c = try toPosixPath(file_path); return readlinkatZ(dirfd, &file_path_c, out_buffer); @@ -3422,6 +3459,8 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read } } +/// Deprecated; use `readlinkatW2`. +/// /// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 LE encoded. /// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// See also `readlinkat`. @@ -3429,12 +3468,29 @@ pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLi return windows.ReadLink(dirfd, file_path, out_buffer); } +/// Windows-only. Same as `readlinkat` except `file_path` WTF16 LE encoded, NT-prefixed. +/// The result is encoded as WTF-16 LE. +/// +/// `file_path` will never be accessed after `out_buffer` has been written to, so it +/// is safe to reuse a single buffer for both. +/// +/// See also `readlinkat`. +pub fn readlinkatW2(dirfd: fd_t, file_path: []const u16, out_buffer: []u16) ReadLinkError![]u16 { + return windows.ReadLink2(dirfd, file_path, out_buffer); +} + /// Same as `readlinkat` except `file_path` is null-terminated. /// See also `readlinkat`. pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { if (native_os == .windows) { - const file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path); - return readlinkatW(dirfd, file_path_w.span(), out_buffer); + var file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path); + const result_w = try readlinkatW2(dirfd, file_path_w.span(), &file_path_w.data); + + const len = std.unicode.calcWtf8Len(result_w); + if (len > out_buffer.len) return error.NameTooLong; + + const end_index = std.unicode.wtf16LeToWtf8(out_buffer, result_w); + return out_buffer[0..end_index]; } else if (native_os == .wasi and !builtin.link_libc) { return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer); } diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index e237cecd5904..0b2225f22092 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -755,6 +755,7 @@ pub fn abiAndDynamicLinkerFromFile( error.BadPathName => unreachable, // Windows only error.UnsupportedReparsePointType => unreachable, // Windows only error.NetworkNotFound => unreachable, // Windows only + error.AntivirusInterference => unreachable, // Windows only error.AccessDenied, error.PermissionDenied,