Commit 6ccb53bff1

Andrew Kelley <andrew@ziglang.org>
2025-10-27 18:07:29
std.Io.Threaded: fix openSelfExe for Windows
missing a call to wToPrefixedFileW
1 parent 441d0c4
Changed files (3)
lib/std/Io/Threaded.zig
@@ -2044,26 +2044,97 @@ fn dirOpenFileWindows(
     const t: *Threaded = @ptrCast(@alignCast(userdata));
     const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
     const sub_path_w = sub_path_w_array.span();
-    return dirOpenFileWindowsInner(t, dir, sub_path_w, flags);
+    const dir_handle = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle;
+    return dirOpenFileWindowsInner(t, dir_handle, sub_path_w, flags);
 }
 
 fn dirOpenFileWindowsInner(
     t: *Threaded,
-    dir: Io.Dir,
+    dir_handle: ?windows.HANDLE,
     sub_path_w: [:0]const u16,
     flags: Io.File.OpenFlags,
 ) Io.File.OpenError!Io.File {
-    try t.checkCancel();
+    if (std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir;
+    if (std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir;
+    const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong;
+
     const w = windows;
-    const handle = try w.OpenFile(sub_path_w, .{
-        .dir = dir.handle,
-        .access_mask = w.SYNCHRONIZE |
-            (if (flags.isRead()) @as(u32, w.GENERIC_READ) else 0) |
-            (if (flags.isWrite()) @as(u32, w.GENERIC_WRITE) else 0),
-        .creation = w.FILE_OPEN,
-    });
-    errdefer w.CloseHandle(handle);
+
+    var nt_name: w.UNICODE_STRING = .{
+        .Length = path_len_bytes,
+        .MaximumLength = path_len_bytes,
+        .Buffer = @constCast(sub_path_w.ptr),
+    };
+    var attr: w.OBJECT_ATTRIBUTES = .{
+        .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
+        .RootDirectory = dir_handle,
+        .Attributes = 0,
+        .ObjectName = &nt_name,
+        .SecurityDescriptor = null,
+        .SecurityQualityOfService = null,
+    };
     var io_status_block: w.IO_STATUS_BLOCK = undefined;
+    const blocking_flag: w.ULONG = w.FILE_SYNCHRONOUS_IO_NONALERT;
+    const file_or_dir_flag: w.ULONG = w.FILE_NON_DIRECTORY_FILE;
+    // If we're not following symlinks, we need to ensure we don't pass in any
+    // synchronization flags such as FILE_SYNCHRONOUS_IO_NONALERT.
+    const create_file_flags: w.ULONG = file_or_dir_flag |
+        if (flags.follow_symlinks) blocking_flag else w.FILE_OPEN_REPARSE_POINT;
+
+    const handle = while (true) {
+        try t.checkCancel();
+
+        var result: w.HANDLE = undefined;
+        const rc = w.ntdll.NtCreateFile(
+            &result,
+            w.SYNCHRONIZE |
+                (if (flags.isRead()) @as(u32, w.GENERIC_READ) else 0) |
+                (if (flags.isWrite()) @as(u32, w.GENERIC_WRITE) else 0),
+            &attr,
+            &io_status_block,
+            null,
+            w.FILE_ATTRIBUTE_NORMAL,
+            w.FILE_SHARE_WRITE | w.FILE_SHARE_READ | w.FILE_SHARE_DELETE,
+            w.FILE_OPEN,
+            create_file_flags,
+            null,
+            0,
+        );
+        switch (rc) {
+            .SUCCESS => break result,
+            .OBJECT_NAME_INVALID => return error.BadPathName,
+            .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
+            .OBJECT_PATH_NOT_FOUND => 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
+            .NO_MEDIA_IN_DEVICE => return error.NoDevice,
+            .INVALID_PARAMETER => |err| return w.statusBug(err),
+            .SHARING_VIOLATION => return error.AccessDenied,
+            .ACCESS_DENIED => return error.AccessDenied,
+            .PIPE_BUSY => return error.PipeBusy,
+            .PIPE_NOT_AVAILABLE => return error.NoDevice,
+            .OBJECT_PATH_SYNTAX_BAD => |err| return w.statusBug(err),
+            .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
+            .FILE_IS_A_DIRECTORY => return error.IsDir,
+            .NOT_A_DIRECTORY => return error.NotDir,
+            .USER_MAPPED_FILE => return error.AccessDenied,
+            .INVALID_HANDLE => |err| return w.statusBug(err),
+            .DELETE_PENDING => {
+                // This error means that there *was* a file in this location on
+                // the file system, but it was deleted. However, the OS is not
+                // finished with the deletion operation, and so this CreateFile
+                // call has failed. There is not really a sane way to handle
+                // this other than retrying the creation after the OS finishes
+                // the deletion.
+                _ = w.kernel32.SleepEx(1, w.FALSE);
+                continue;
+            },
+            .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
+            else => return w.unexpectedStatus(rc),
+        }
+    };
+    errdefer w.CloseHandle(handle);
+
     const range_off: w.LARGE_INTEGER = 0;
     const range_len: w.LARGE_INTEGER = 1;
     const exclusive = switch (flags.lock) {
@@ -2691,20 +2762,19 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr
 
 fn openSelfExe(userdata: ?*anyopaque, flags: Io.File.OpenFlags) Io.File.OpenSelfExeError!Io.File {
     const t: *Threaded = @ptrCast(@alignCast(userdata));
-    if (native_os == .linux or native_os == .serenity) {
-        return dirOpenFilePosix(t, .{ .handle = posix.AT.FDCWD }, "/proc/self/exe", flags);
-    }
-    if (is_windows) {
-        // If ImagePathName is a symlink, then it will contain the path of the symlink,
-        // not the path that the symlink points to. However, because we are opening
-        // the file, we can let the openFileW call follow the symlink for us.
-        const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName;
-        const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
-        const cwd_handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle;
-
-        return dirOpenFileWindowsInner(t, .{ .handle = cwd_handle }, image_path_name, flags);
+    switch (native_os) {
+        .linux, .serenity => return dirOpenFilePosix(t, .{ .handle = posix.AT.FDCWD }, "/proc/self/exe", flags),
+        .windows => {
+            // If ImagePathName is a symlink, then it will contain the path of the symlink,
+            // not the path that the symlink points to. However, because we are opening
+            // the file, we can let the openFileW call follow the symlink for us.
+            const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName;
+            const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
+            const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name);
+            return dirOpenFileWindowsInner(t, null, prefixed_path_w.span(), flags);
+        },
+        else => @panic("TODO implement openSelfExe"),
     }
-    @panic("TODO implement openSelfExe");
 }
 
 fn fileWritePositional(
@@ -2823,7 +2893,8 @@ fn sleepWindows(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
             break :ms std.math.maxInt(windows.DWORD);
         break :ms std.math.lossyCast(windows.DWORD, d.raw.toMilliseconds());
     };
-    windows.kernel32.Sleep(ms);
+    // TODO: alertable true with checkCancel in a loop plus deadline
+    _ = windows.kernel32.SleepEx(ms, windows.FALSE);
 }
 
 fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
lib/std/os/windows/kernel32.zig
@@ -326,10 +326,11 @@ pub extern "kernel32" fn ExitProcess(
     exit_code: UINT,
 ) callconv(.winapi) noreturn;
 
-// TODO: SleepEx with bAlertable=false.
-pub extern "kernel32" fn Sleep(
+// TODO: implement via ntdll instead
+pub extern "kernel32" fn SleepEx(
     dwMilliseconds: DWORD,
-) callconv(.winapi) void;
+    bAlertable: BOOL,
+) callconv(.winapi) DWORD;
 
 // TODO: Wrapper around NtQueryInformationProcess with `PROCESS_BASIC_INFORMATION`.
 pub extern "kernel32" fn GetExitCodeProcess(
lib/std/os/windows.zig
@@ -148,7 +148,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
                 // call has failed. There is not really a sane way to handle
                 // this other than retrying the creation after the OS finishes
                 // the deletion.
-                kernel32.Sleep(1);
+                _ = kernel32.SleepEx(1, TRUE);
                 continue;
             },
             .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,