Commit 66bbe4ec4c

Jakub Konka <kubkon@jakubkonka.com>
2020-07-30 17:50:49
Refactor internal Win routines to reuse OpenFile
This covers mainly `ReadLink` and `CreateSymolicLink` functions.
1 parent a89d5cf
Changed files (4)
lib/std/os/test.zig
@@ -27,7 +27,7 @@ test "symlink with relative paths" {
     try cwd.writeFile("file.txt", "nonsense");
 
     if (builtin.os.tag == .windows) {
-        try os.windows.CreateSymbolicLink(cwd.fd, "symlinked", "file.txt", false);
+        try os.windows.CreateSymbolicLink(cwd.fd, &[_]u16{ 's', 'y', 'm', 'l', 'i', 'n', 'k', 'e', 'd' }, &[_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, false);
     } else {
         try os.symlink("file.txt", "symlinked");
     }
@@ -85,7 +85,7 @@ test "readlinkat" {
 
     // create a symbolic link
     if (builtin.os.tag == .windows) {
-        try os.windows.CreateSymbolicLink(tmp.dir.fd, "link", "file.txt", false);
+        try os.windows.CreateSymbolicLink(tmp.dir.fd, &[_]u16{ 'l', 'i', 'n', 'k' }, &[_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, false);
     } else {
         try os.symlinkat("file.txt", tmp.dir.fd, "link");
     }
lib/std/os/windows.zig
@@ -69,16 +69,21 @@ pub const OpenFileOptions = struct {
     share_access_nonblocking: bool = false,
     creation: ULONG,
     io_mode: std.io.ModeOverride,
-    expect_dir: bool = false,
+    /// If true, tries to open path as a directory.
+    /// Defaults to false.
+    open_dir: bool = false,
+    /// If false, tries to open path as a reparse point without dereferencing it.
+    /// Defaults to true.
+    follow_symlinks: bool = true, 
 };
 
 /// TODO when share_access_nonblocking is false, this implementation uses
 /// untinterruptible sleep() to block. This is not the final iteration of the API.
 pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HANDLE {
-    if (mem.eql(u16, sub_path_w, &[_]u16{'.'}) and !options.expect_dir) {
+    if (mem.eql(u16, sub_path_w, &[_]u16{'.'}) and !options.open_dir and options.follow_symlinks) {
         return error.IsDir;
     }
-    if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and !options.expect_dir) {
+    if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and !options.open_dir and options.follow_symlinks) {
         return error.IsDir;
     }
 
@@ -105,8 +110,8 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
     var delay: usize = 1;
     while (true) {
         const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0;
-        const file_or_dir_flag: ULONG = if (options.expect_dir) FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT else FILE_NON_DIRECTORY_FILE;
-        const flags: ULONG = file_or_dir_flag | blocking_flag;
+        const file_or_dir_flag: ULONG = if (options.open_dir) FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT else FILE_NON_DIRECTORY_FILE;
+        const flags: ULONG = if (options.follow_symlinks) file_or_dir_flag | blocking_flag else FILE_OPEN_REPARSE_POINT;
         const rc = ntdll.NtCreateFile(
             &result,
             options.access_mask,
@@ -143,7 +148,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
             .PIPE_BUSY => return error.PipeBusy,
             .OBJECT_PATH_SYNTAX_BAD => unreachable,
             .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
-            .FILE_IS_A_DIRECTORY => if (options.expect_dir) unreachable else return error.IsDir,
+            .FILE_IS_A_DIRECTORY => if (options.open_dir) unreachable else return error.IsDir,
             else => return unexpectedStatus(rc),
         }
     }
@@ -598,27 +603,14 @@ pub const CreateSymbolicLinkError = error{
     PathAlreadyExists,
     FileNotFound,
     NameTooLong,
-    InvalidUtf8,
-    BadPathName,
     NoDevice,
     Unexpected,
 };
 
 pub fn CreateSymbolicLink(
     dir: ?HANDLE,
-    sym_link_path: []const u8,
-    target_path: []const u8,
-    is_directory: bool,
-) CreateSymbolicLinkError!void {
-    const sym_link_path_w = try sliceToPrefixedFileW(sym_link_path);
-    const target_path_w = try sliceToPrefixedFileW(target_path);
-    return CreateSymbolicLinkW(dir, sym_link_path_w.span(), target_path_w.span(), is_directory);
-}
-
-pub fn CreateSymbolicLinkW(
-    dir: ?HANDLE,
-    sym_link_path: [:0]const u16,
-    target_path: [:0]const u16,
+    sym_link_path: []const u16,
+    target_path: []const u16,
     is_directory: bool,
 ) CreateSymbolicLinkError!void {
     const SYMLINK_DATA = extern struct {
@@ -632,70 +624,18 @@ pub fn CreateSymbolicLinkW(
         Flags: ULONG,
     };
 
-    var symlink_handle: HANDLE = undefined;
-    if (is_directory) {
-        const sym_link_len_bytes = math.cast(u16, sym_link_path.len * 2) catch |err| switch (err) {
-            error.Overflow => return error.NameTooLong,
-        };
-        var nt_name = UNICODE_STRING{
-            .Length = sym_link_len_bytes,
-            .MaximumLength = sym_link_len_bytes,
-            .Buffer = @intToPtr([*]u16, @ptrToInt(sym_link_path.ptr)),
-        };
-
-        if (sym_link_path[0] == '.' and sym_link_path[1] == 0) {
-            // Windows does not recognize this, but it does work with empty string.
-            nt_name.Length = 0;
-        }
-
-        var attr = OBJECT_ATTRIBUTES{
-            .Length = @sizeOf(OBJECT_ATTRIBUTES),
-            .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sym_link_path)) null else dir,
-            .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
-            .ObjectName = &nt_name,
-            .SecurityDescriptor = null,
-            .SecurityQualityOfService = null,
-        };
-
-        var io: IO_STATUS_BLOCK = undefined;
-        const rc = ntdll.NtCreateFile(
-            &symlink_handle,
-            GENERIC_READ | SYNCHRONIZE | FILE_WRITE_ATTRIBUTES,
-            &attr,
-            &io,
-            null,
-            FILE_ATTRIBUTE_NORMAL,
-            FILE_SHARE_READ,
-            FILE_CREATE,
-            FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT,
-            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.NoDevice,
-            .INVALID_PARAMETER => unreachable,
-            .ACCESS_DENIED => return error.AccessDenied,
-            .OBJECT_PATH_SYNTAX_BAD => unreachable,
-            .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
-            else => return unexpectedStatus(rc),
-        }
-    } else {
-        symlink_handle = OpenFile(sym_link_path, .{
-            .access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
-            .dir = dir,
-            .creation = FILE_CREATE,
-            .io_mode = .blocking,
-        }) catch |err| switch (err) {
-            error.WouldBlock => unreachable,
-            error.IsDir => return error.PathAlreadyExists,
-            error.PipeBusy => unreachable,
-            else => |e| return e,
-        };
-    }
+    const symlink_handle = OpenFile(sym_link_path, .{
+        .access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
+        .dir = dir,
+        .creation = FILE_CREATE,
+        .io_mode = .blocking,
+        .open_dir = is_directory,
+    }) catch |err| switch (err) {
+        error.IsDir => return error.PathAlreadyExists,
+        error.WouldBlock => unreachable,
+        error.PipeBusy => unreachable,
+        else => |e| return e,
+    };
     defer CloseHandle(symlink_handle);
 
     // prepare reparse data buffer
@@ -726,72 +666,24 @@ pub const ReadLinkError = error{
     Unexpected,
     NameTooLong,
     UnsupportedReparsePointType,
-    InvalidUtf8,
-    BadPathName,
 };
 
-pub fn ReadLink(
-    dir: ?HANDLE,
-    sub_path: []const u8,
-    out_buffer: []u8,
-) ReadLinkError![]u8 {
-    const sub_path_w = try sliceToPrefixedFileW(sub_path);
-    return ReadLinkW(dir, sub_path_w.span().ptr, out_buffer);
-}
-
-pub fn ReadLinkW(dir: ?HANDLE, sub_path_w: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
-    const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) {
-        error.Overflow => return error.NameTooLong,
-    };
-    var nt_name = UNICODE_STRING{
-        .Length = path_len_bytes,
-        .MaximumLength = path_len_bytes,
-        .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
+pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
+    const result_handle = OpenFile(sub_path_w, .{
+        .dir = dir,
+        .access_mask = FILE_READ_ATTRIBUTES,
+        .share_access = FILE_SHARE_READ,
+        .creation = FILE_OPEN,
+        .io_mode = .blocking,
+        .follow_symlinks = false,
+    }) catch |err| switch (err) {
+        error.WouldBlock => unreachable,
+        error.PipeBusy => unreachable,
+        error.IsDir => unreachable,
+        error.NoDevice => unreachable,
+        error.PathAlreadyExists => unreachable,
+        else => |e| return e,
     };
-
-    if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
-        // Windows does not recognize this, but it does work with empty string.
-        nt_name.Length = 0;
-    }
-
-    var attr = OBJECT_ATTRIBUTES{
-        .Length = @sizeOf(OBJECT_ATTRIBUTES),
-        .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir,
-        .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
-        .ObjectName = &nt_name,
-        .SecurityDescriptor = null,
-        .SecurityQualityOfService = null,
-    };
-    var io: IO_STATUS_BLOCK = undefined;
-    var result_handle: HANDLE = undefined;
-    const rc = ntdll.NtCreateFile(
-        &result_handle,
-        FILE_READ_ATTRIBUTES,
-        &attr,
-        &io,
-        null,
-        FILE_ATTRIBUTE_NORMAL,
-        FILE_SHARE_READ,
-        FILE_OPEN,
-        FILE_OPEN_REPARSE_POINT,
-        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,
-        .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),
-    }
     defer CloseHandle(result_handle);
 
     var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
lib/std/fs.zig
@@ -1261,11 +1261,11 @@ pub const Dir = struct {
     /// are null-terminated, WTF16 encoded.
     pub fn symLinkW(
         self: Dir,
-        target_path_w: [:0]const u16,
-        sym_link_path_w: [:0]const u16,
+        target_path_w: []const u16,
+        sym_link_path_w: []const u16,
         flags: SymLinkFlags,
     ) !void {
-        return os.windows.CreateSymbolicLinkW(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
+        return os.windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
     }
 
     /// Read value of a symbolic link.
@@ -1276,7 +1276,8 @@ pub const Dir = struct {
             return self.readLinkWasi(sub_path, buffer);
         }
         if (builtin.os.tag == .windows) {
-            return os.windows.ReadLink(self.fd, sub_path, buffer);
+            const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
+            return self.readLinkW(sub_path_w.span(), buffer);
         }
         const sub_path_c = try os.toPosixPath(sub_path);
         return self.readLinkZ(&sub_path_c, buffer);
@@ -1293,15 +1294,15 @@ pub const Dir = struct {
     pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
         if (builtin.os.tag == .windows) {
             const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
-            return self.readLinkW(sub_path_w, buffer);
+            return self.readLinkW(sub_path_w.span(), buffer);
         }
         return os.readlinkatZ(self.fd, sub_path_c, buffer);
     }
 
     /// Windows-only. Same as `readLink` except the pathname parameter
     /// is null-terminated, WTF16 encoded.
-    pub fn readLinkW(self: Dir, sub_path_w: [*:0]const u16, buffer: []u8) ![]u8 {
-        return os.windows.ReadLinkW(self.fd, sub_path_w, buffer);
+    pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
+        return os.windows.ReadLink(self.fd, sub_path_w, buffer);
     }
 
     /// On success, caller owns returned buffer.
@@ -1811,7 +1812,9 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags
     assert(path.isAbsolute(target_path));
     assert(path.isAbsolute(sym_link_path));
     if (builtin.os.tag == .windows) {
-        return os.windows.CreateSymbolicLink(null, sym_link_path, target_path, flags.is_directory);
+        const target_path_w = try os.windows.sliceToPrefixedFileW(target_path);
+        const sym_link_path_w = try os.windows.sliceToPrefixedFileW(sym_link_path);
+        return os.windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
     }
     return os.symlink(target_path, sym_link_path);
 }
@@ -1820,10 +1823,10 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags
 /// Note that this function will by default try creating a symbolic link to a file. If you would
 /// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.
 /// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.
-pub fn symLinkAbsoluteW(target_path_w: [:0]const u16, sym_link_path_w: [:0]const u16, flags: SymLinkFlags) !void {
-    assert(path.isAbsoluteWindowsW(target_path_w));
-    assert(path.isAbsoluteWindowsW(sym_link_path_w));
-    return os.windows.CreateSymbolicLinkW(null, sym_link_path_w, target_path_w, flags.is_directory);
+pub fn symLinkAbsoluteW(target_path_w: []const u16, sym_link_path_w: []const u16, flags: SymLinkFlags) !void {
+    assert(path.isAbsoluteWindowsWTF16(target_path_w));
+    assert(path.isAbsoluteWindowsWTF16(sym_link_path_w));
+    return os.windows.CreateSymbolicLink(null, sym_link_path_w, target_path_w, flags.is_directory);
 }
 
 /// Same as `symLinkAbsolute` except the parameters are null-terminated pointers.
@@ -1834,7 +1837,7 @@ pub fn symLinkAbsoluteZ(target_path_c: [*:0]const u8, sym_link_path_c: [*:0]cons
     if (builtin.os.tag == .windows) {
         const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c);
         const sym_link_path_w = try os.windows.cStrToWin32PrefixedFileW(sym_link_path_c);
-        return os.windows.CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, flags.is_directory);
+        return os.windows.CreateSymbolicLink(sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
     }
     return os.symlinkZ(target_path_c, sym_link_path_c);
 }
lib/std/os.zig
@@ -2087,7 +2087,7 @@ pub fn renameatW(
 pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
     if (builtin.os.tag == .windows) {
         const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path);
-        return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode);
+        return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
     } else if (builtin.os.tag == .wasi) {
         return mkdiratWasi(dir_fd, sub_dir_path, mode);
     } else {
@@ -2145,13 +2145,13 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr
     }
 }
 
-pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirError!void {
-    const sub_dir_handle = windows.OpenFile(std.mem.spanZ(sub_path_w), .{
+pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void {
+    const sub_dir_handle = windows.OpenFile(sub_path_w, .{
         .dir = dir_fd,
         .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
         .creation = windows.FILE_CREATE,
         .io_mode = .blocking,
-        .expect_dir = true,
+        .open_dir = true,
     }) catch |err| switch (err) {
         error.IsDir => unreachable,
         error.PipeBusy => unreachable,
@@ -2187,7 +2187,7 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
         @compileError("mkdir is not supported in WASI; use mkdirat instead");
     } else if (builtin.os.tag == .windows) {
         const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
-        return mkdirW(dir_path_w.span().ptr, mode);
+        return mkdirW(dir_path_w.span(), mode);
     } else {
         const dir_path_c = try toPosixPath(dir_path);
         return mkdirZ(&dir_path_c, mode);
@@ -2198,7 +2198,7 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
 pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
     if (builtin.os.tag == .windows) {
         const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
-        return mkdirW(dir_path_w.span().ptr, mode);
+        return mkdirW(dir_path_w.span(), mode);
     }
     switch (errno(system.mkdir(dir_path, mode))) {
         0 => return,
@@ -2220,13 +2220,13 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
 }
 
 /// Windows-only. Same as `mkdir` but the parameters is null-terminated, WTF16 encoded.
-pub fn mkdirW(dir_path_w: [*:0]const u16, mode: u32) MakeDirError!void {
-    const sub_dir_handle = windows.OpenFile(std.mem.spanZ(dir_path_w), .{
+pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void {
+    const sub_dir_handle = windows.OpenFile(dir_path_w, .{
         .dir = std.fs.cwd().fd,
         .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
         .creation = windows.FILE_CREATE,
         .io_mode = .blocking,
-        .expect_dir = true,
+        .open_dir = true,
     }) catch |err| switch (err) {
         error.IsDir => unreachable,
         error.PipeBusy => unreachable,
@@ -2379,7 +2379,8 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
     if (builtin.os.tag == .wasi) {
         @compileError("readlink is not supported in WASI; use readlinkat instead");
     } else if (builtin.os.tag == .windows) {
-        return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer);
+        const file_path_w = try windows.sliceToPrefixedFileW(file_path);
+        return readlinkW(file_path_w.span(), out_buffer);
     } else {
         const file_path_c = try toPosixPath(file_path);
         return readlinkZ(&file_path_c, out_buffer);
@@ -2390,15 +2391,15 @@ pub const readlinkC = @compileError("deprecated: renamed to readlinkZ");
 
 /// Windows-only. Same as `readlink` except `file_path` is null-terminated, WTF16 encoded.
 /// See also `readlinkZ`.
-pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
-    return windows.ReadLinkW(std.fs.cwd().fd, file_path, out_buffer);
+pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
+    return windows.ReadLink(std.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 (builtin.os.tag == .windows) {
         const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path);
-        return readlinkW(file_path_w.span().ptr, out_buffer);
+        return readlinkW(file_path_w.span(), out_buffer);
     }
     const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
     switch (errno(rc)) {
@@ -2424,7 +2425,8 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink
         return readlinkatWasi(dirfd, file_path, out_buffer);
     }
     if (builtin.os.tag == .windows) {
-        return windows.ReadLink(dirfd, file_path, out_buffer);
+        const file_path_w = try windows.sliceToPrefixedFileW(file_path);
+        return readlinkatW(dirfd, file_path_w.span(), out_buffer);
     }
     const file_path_c = try toPosixPath(file_path);
     return readlinkatZ(dirfd, &file_path_c, out_buffer);
@@ -2454,8 +2456,8 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read
 
 /// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded.
 /// See also `readlinkat`.
-pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
-    return windows.ReadLinkW(dirfd, file_path, out_buffer);
+pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
+    return windows.ReadLink(dirfd, file_path, out_buffer);
 }
 
 /// Same as `readlinkat` except `file_path` is null-terminated.
@@ -2463,7 +2465,7 @@ pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) Rea
 pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
     if (builtin.os.tag == .windows) {
         const file_path_w = try windows.cStrToPrefixedFileW(file_path);
-        return readlinkatW(dirfd, file_path_w.span().ptr, out_buffer);
+        return readlinkatW(dirfd, file_path_w.span(), out_buffer);
     }
     const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);
     switch (errno(rc)) {
@@ -3984,7 +3986,7 @@ pub const RealPathError = error{
 pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
     if (builtin.os.tag == .windows) {
         const pathname_w = try windows.sliceToPrefixedFileW(pathname);
-        return realpathW(pathname_w.span().ptr, out_buffer);
+        return realpathW(pathname_w.span(), out_buffer);
     }
     if (builtin.os.tag == .wasi) {
         @compileError("Use std.fs.wasi.PreopenList to obtain valid Dir handles instead of using absolute paths");
@@ -3999,7 +4001,7 @@ pub const realpathC = @compileError("deprecated: renamed realpathZ");
 pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
     if (builtin.os.tag == .windows) {
         const pathname_w = try windows.cStrToPrefixedFileW(pathname);
-        return realpathW(pathname_w.span().ptr, out_buffer);
+        return realpathW(pathname_w.span(), out_buffer);
     }
     if (builtin.os.tag == .linux and !builtin.link_libc) {
         const fd = openZ(pathname, linux.O_PATH | linux.O_NONBLOCK | linux.O_CLOEXEC, 0) catch |err| switch (err) {
@@ -4037,7 +4039,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
 
 /// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded.
 /// TODO use ntdll for better semantics
-pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
+pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
     const w = windows;
 
     const dir = std.fs.cwd().fd;
@@ -4045,20 +4047,20 @@ pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) Real
     const share_access = w.FILE_SHARE_READ;
     const creation = w.FILE_OPEN;
     const h_file = blk: {
-        const res = w.OpenFile(std.mem.spanZ(pathname), .{
+        const res = w.OpenFile(pathname, .{
             .dir = dir,
             .access_mask = access_mask,
             .share_access = share_access,
             .creation = creation,
             .io_mode = .blocking,
         }) catch |err| switch (err) {
-            error.IsDir => break :blk w.OpenFile(std.mem.spanZ(pathname), .{
+            error.IsDir => break :blk w.OpenFile(pathname, .{
                 .dir = dir,
                 .access_mask = access_mask,
                 .share_access = share_access,
                 .creation = creation,
                 .io_mode = .blocking,
-                .expect_dir = true,
+                .open_dir = true,
             }) catch |er| switch (er) {
                 error.WouldBlock => unreachable,
                 else => |e2| return e2,