std: move os.windows.OpenFile into Io.Threaded

it needs cancelation integration
This commit is contained in:
Andrew Kelley 2026-02-03 21:27:27 -08:00
parent 2a193a3987
commit 3a5fff45ec
2 changed files with 228 additions and 234 deletions

View file

@ -3260,30 +3260,23 @@ fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, pe
const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path);
_ = permissions; // TODO use this value
const syscall: Syscall = try .start();
const sub_dir_handle = while (true) {
break windows.OpenFile(sub_path_w.span(), .{
.dir = dir.handle,
.access_mask = .{
.GENERIC = .{ .READ = true },
.STANDARD = .{ .SYNCHRONIZE = true },
},
.creation = .CREATE,
.filter = .dir_only,
}) catch |err| switch (err) {
error.IsDir => return syscall.fail(error.Unexpected),
error.PipeBusy => return syscall.fail(error.Unexpected),
error.NoDevice => return syscall.fail(error.Unexpected),
error.WouldBlock => return syscall.fail(error.Unexpected),
error.AntivirusInterference => return syscall.fail(error.Unexpected),
error.OperationCanceled => {
try syscall.checkCancel();
continue;
},
else => |e| return syscall.fail(e),
};
const sub_dir_handle = OpenFile(sub_path_w.span(), .{
.dir = dir.handle,
.access_mask = .{
.GENERIC = .{ .READ = true },
.STANDARD = .{ .SYNCHRONIZE = true },
},
.creation = .CREATE,
.filter = .dir_only,
}) catch |err| switch (err) {
error.IsDir => return error.Unexpected,
error.PipeBusy => return error.Unexpected,
error.FileBusy => return error.Unexpected,
error.NoDevice => return error.Unexpected,
error.WouldBlock => return error.Unexpected,
error.AntivirusInterference => return error.Unexpected,
else => |e| return e,
};
syscall.finish();
windows.CloseHandle(sub_dir_handle);
}
@ -5987,27 +5980,19 @@ fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8,
var path_name_w = try sliceToPrefixedFileW(dir.handle, sub_path);
const h_file = handle: {
const syscall: Syscall = try .start();
while (true) {
if (windows.OpenFile(path_name_w.span(), .{
.dir = dir.handle,
.access_mask = .{
.GENERIC = .{ .READ = true },
.STANDARD = .{ .SYNCHRONIZE = true },
},
.creation = .OPEN,
.filter = .any,
})) |handle| {
syscall.finish();
break :handle handle;
} else |err| switch (err) {
error.WouldBlock => unreachable,
error.OperationCanceled => {
try syscall.checkCancel();
continue;
},
else => |e| return syscall.fail(e),
}
if (OpenFile(path_name_w.span(), .{
.dir = dir.handle,
.access_mask = .{
.GENERIC = .{ .READ = true },
.STANDARD = .{ .SYNCHRONIZE = true },
},
.creation = .OPEN,
.filter = .any,
})) |handle| {
break :handle handle;
} else |err| switch (err) {
error.WouldBlock => unreachable,
else => |e| return e,
}
};
defer windows.CloseHandle(h_file);
@ -6116,7 +6101,7 @@ pub fn GetFinalPathNameByHandle(
// Source: https://stackoverflow.com/questions/3012828/using-ioctl-mountmgr-query-points
// This is the NT namespaced version of \\.\MountPointManager
const mgmt_path_u16 = std.unicode.utf8ToUtf16LeStringLiteral("\\??\\MountPointManager");
const mgmt_handle = windows.OpenFile(mgmt_path_u16, .{
const mgmt_handle = OpenFile(mgmt_path_u16, .{
.access_mask = .{ .STANDARD = .{ .SYNCHRONIZE = true } },
.creation = .OPEN,
}) catch |err| switch (err) {
@ -6125,12 +6110,12 @@ pub fn GetFinalPathNameByHandle(
error.NoDevice => return error.Unexpected,
error.AccessDenied => return error.Unexpected,
error.PipeBusy => return error.Unexpected,
error.FileBusy => return error.Unexpected,
error.PathAlreadyExists => return error.Unexpected,
error.WouldBlock => return error.Unexpected,
error.NetworkNotFound => return error.Unexpected,
error.AntivirusInterference => return error.Unexpected,
error.BadPathName => return error.Unexpected,
error.OperationCanceled => @panic("TODO: better integrate cancelation"),
else => |e| return e,
};
defer windows.CloseHandle(mgmt_handle);
@ -7309,31 +7294,23 @@ fn dirRenameWindowsInner(
const new_path_w = new_path_w_buf.span();
const src_fd = src_fd: {
const syscall: Syscall = try .start();
while (true) {
if (w.OpenFile(old_path_w, .{
.dir = old_dir.handle,
.access_mask = .{
.GENERIC = .{ .WRITE = true },
.STANDARD = .{
.RIGHTS = .{ .DELETE = true },
.SYNCHRONIZE = true,
},
if (OpenFile(old_path_w, .{
.dir = old_dir.handle,
.access_mask = .{
.GENERIC = .{ .WRITE = true },
.STANDARD = .{
.RIGHTS = .{ .DELETE = true },
.SYNCHRONIZE = true,
},
.creation = .OPEN,
.filter = .any, // This function is supposed to rename both files and directories.
.follow_symlinks = false,
})) |handle| {
syscall.finish();
break :src_fd handle;
} else |err| switch (err) {
error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.
error.OperationCanceled => {
try syscall.checkCancel();
continue;
},
else => |e| return e,
}
},
.creation = .OPEN,
.filter = .any, // This function is supposed to rename both files and directories.
.follow_symlinks = false,
})) |handle| {
break :src_fd handle;
} else |err| switch (err) {
error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.
else => |e| return e,
}
};
defer w.CloseHandle(src_fd);
@ -7662,32 +7639,25 @@ fn dirSymLinkWindows(
};
const symlink_handle = handle: {
const syscall: Syscall = try .start();
while (true) {
if (w.OpenFile(sym_link_path_w.span(), .{
.access_mask = .{
.GENERIC = .{ .READ = true, .WRITE = true },
.STANDARD = .{ .SYNCHRONIZE = true },
},
.dir = dir.handle,
.creation = .CREATE,
.filter = if (flags.is_directory) .dir_only else .non_directory_only,
})) |handle| {
syscall.finish();
break :handle handle;
} else |err| switch (err) {
error.IsDir => return syscall.fail(error.PathAlreadyExists),
error.NotDir => return syscall.fail(error.Unexpected),
error.WouldBlock => return syscall.fail(error.Unexpected),
error.PipeBusy => return syscall.fail(error.Unexpected),
error.NoDevice => return syscall.fail(error.Unexpected),
error.AntivirusInterference => return syscall.fail(error.Unexpected),
error.OperationCanceled => {
try syscall.checkCancel();
continue;
},
else => |e| return e,
}
if (OpenFile(sym_link_path_w.span(), .{
.access_mask = .{
.GENERIC = .{ .READ = true, .WRITE = true },
.STANDARD = .{ .SYNCHRONIZE = true },
},
.dir = dir.handle,
.creation = .CREATE,
.filter = if (flags.is_directory) .dir_only else .non_directory_only,
})) |handle| {
break :handle handle;
} else |err| switch (err) {
error.IsDir => return error.PathAlreadyExists,
error.NotDir => return error.Unexpected,
error.WouldBlock => return error.Unexpected,
error.PipeBusy => return error.Unexpected,
error.FileBusy => return error.Unexpected,
error.NoDevice => return error.Unexpected,
error.AntivirusInterference => return error.Unexpected,
else => |e| return e,
}
};
defer w.CloseHandle(symlink_handle);
@ -10341,27 +10311,20 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.Execut
var path_name_w_buf = try wToPrefixedFileW(null, image_path_name);
const h_file = handle: {
const syscall: Syscall = try .start();
while (true) {
if (w.OpenFile(path_name_w_buf.span(), .{
.dir = null,
.access_mask = .{
.GENERIC = .{ .READ = true },
.STANDARD = .{ .SYNCHRONIZE = true },
},
.creation = .OPEN,
.filter = .any,
})) |handle| {
syscall.finish();
break :handle handle;
} else |err| switch (err) {
error.WouldBlock => unreachable,
error.OperationCanceled => {
try syscall.checkCancel();
continue;
},
else => |e| return e,
}
if (OpenFile(path_name_w_buf.span(), .{
.dir = null,
.access_mask = .{
.GENERIC = .{ .READ = true },
.STANDARD = .{ .SYNCHRONIZE = true },
},
.creation = .OPEN,
.filter = .any,
})) |handle| {
break :handle handle;
} else |err| switch (err) {
error.WouldBlock => unreachable,
error.FileBusy => unreachable,
else => |e| return e,
}
};
defer w.CloseHandle(h_file);
@ -19055,3 +19018,151 @@ pub fn mutexUnlock(m: *Io.Mutex) void {
},
}
}
const OpenError = error{
IsDir,
NotDir,
FileNotFound,
NoDevice,
AccessDenied,
PipeBusy,
PathAlreadyExists,
WouldBlock,
NetworkNotFound,
AntivirusInterference,
FileBusy,
} || Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
const OpenFileOptions = struct {
access_mask: windows.ACCESS_MASK,
dir: ?windows.HANDLE = null,
sa: ?*windows.SECURITY_ATTRIBUTES = null,
share_access: windows.FILE.SHARE = .VALID_FLAGS,
creation: windows.FILE.CREATE_DISPOSITION,
filter: Filter = .non_directory_only,
/// If false, tries to open path as a reparse point without dereferencing it.
/// Defaults to true.
follow_symlinks: bool = true,
pub const Filter = enum {
/// Causes `OpenFile` to return `error.IsDir` if the opened handle would be a directory.
non_directory_only,
/// Causes `OpenFile` to return `error.NotDir` if the opened handle is not a directory.
dir_only,
/// `OpenFile` does not discriminate between opening files and directories.
any,
};
};
/// TODO: inline this logic everywhere and delete this function
fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!windows.HANDLE {
if (std.mem.eql(u16, sub_path_w, &[_]u16{'.'}) and options.filter == .non_directory_only) {
return error.IsDir;
}
if (std.mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and options.filter == .non_directory_only) {
return error.IsDir;
}
var result: windows.HANDLE = undefined;
const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong;
var nt_name: windows.UNICODE_STRING = .{
.Length = path_len_bytes,
.MaximumLength = path_len_bytes,
.Buffer = @constCast(sub_path_w.ptr),
};
const attr: windows.OBJECT_ATTRIBUTES = .{
.RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir,
.Attributes = .{ .INHERIT = if (options.sa) |sa| sa.bInheritHandle != windows.FALSE else false },
.ObjectName = &nt_name,
.SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null,
};
var iosb: windows.IO_STATUS_BLOCK = undefined;
// There are multiple kernel bugs being worked around with retries.
const max_attempts = 13;
var attempt: u5 = 0;
var syscall: Syscall = try .start();
while (true) {
switch (windows.ntdll.NtCreateFile(
&result,
options.access_mask,
&attr,
&iosb,
null,
.{ .NORMAL = true },
options.share_access,
options.creation,
.{
.DIRECTORY_FILE = options.filter == .dir_only,
.NON_DIRECTORY_FILE = options.filter == .non_directory_only,
.IO = if (options.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS,
.OPEN_REPARSE_POINT = !options.follow_symlinks,
},
null,
0,
)) {
.SUCCESS => {
syscall.finish();
return result;
},
.CANCELLED => {
try syscall.checkCancel();
continue;
},
.SHARING_VIOLATION => {
// This occurs if the file attempting to be opened is a running
// executable. However, there's a kernel bug: the error may be
// incorrectly returned for an indeterminate amount of time
// after an executable file is closed. Here we work around the
// kernel bug with retry attempts.
syscall.finish();
if (max_attempts - attempt == 0) return error.FileBusy;
try parking_sleep.sleep(.{ .duration = .{
.raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1),
.clock = .awake,
} });
attempt += 1;
syscall = try .start();
continue;
},
.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.
syscall.finish();
if (max_attempts - attempt == 0) return error.FileBusy;
try parking_sleep.sleep(.{ .duration = .{
.raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1),
.clock = .awake,
} });
attempt += 1;
syscall = try .start();
continue;
},
.OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
.OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
.OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
.BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found
.BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't
.NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
.ACCESS_DENIED => return syscall.fail(error.AccessDenied),
.PIPE_BUSY => return syscall.fail(error.PipeBusy),
.PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice),
.OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists),
.FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
.NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
.USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
.VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference),
.INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
.OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status),
.INVALID_HANDLE => |status| return syscall.ntstatusBug(status),
else => |status| return syscall.unexpectedNtstatus(status),
}
}
}

View file

@ -2359,123 +2359,6 @@ pub const OBJECT_ATTRIBUTES = extern struct {
// ref none
pub const OpenError = error{
IsDir,
NotDir,
FileNotFound,
NoDevice,
AccessDenied,
PipeBusy,
PathAlreadyExists,
Unexpected,
NameTooLong,
WouldBlock,
NetworkNotFound,
AntivirusInterference,
BadPathName,
OperationCanceled,
};
pub const OpenFileOptions = struct {
access_mask: ACCESS_MASK,
dir: ?HANDLE = null,
sa: ?*SECURITY_ATTRIBUTES = null,
share_access: FILE.SHARE = .VALID_FLAGS,
creation: FILE.CREATE_DISPOSITION,
filter: Filter = .non_directory_only,
/// If false, tries to open path as a reparse point without dereferencing it.
/// Defaults to true.
follow_symlinks: bool = true,
pub const Filter = enum {
/// Causes `OpenFile` to return `error.IsDir` if the opened handle would be a directory.
non_directory_only,
/// Causes `OpenFile` to return `error.NotDir` if the opened handle is not a directory.
dir_only,
/// `OpenFile` does not discriminate between opening files and directories.
any,
};
};
pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HANDLE {
if (mem.eql(u16, sub_path_w, &[_]u16{'.'}) and options.filter == .non_directory_only) {
return error.IsDir;
}
if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and options.filter == .non_directory_only) {
return error.IsDir;
}
var result: HANDLE = undefined;
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 attr: OBJECT_ATTRIBUTES = .{
.RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir,
.Attributes = .{ .INHERIT = if (options.sa) |sa| sa.bInheritHandle != FALSE else false },
.ObjectName = &nt_name,
.SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null,
};
var io: IO_STATUS_BLOCK = undefined;
while (true) {
const rc = ntdll.NtCreateFile(
&result,
options.access_mask,
&attr,
&io,
null,
.{ .NORMAL = true },
options.share_access,
options.creation,
.{
.DIRECTORY_FILE = options.filter == .dir_only,
.NON_DIRECTORY_FILE = options.filter == .non_directory_only,
.IO = if (options.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS,
.OPEN_REPARSE_POINT = !options.follow_symlinks,
},
null,
0,
);
switch (rc) {
.SUCCESS => return 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 => unreachable,
.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 => unreachable,
.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 => unreachable,
.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.
const delay_one_ms: LARGE_INTEGER = -(std.time.ns_per_ms / 100);
_ = ntdll.NtDelayExecution(TRUE, &delay_one_ms);
continue;
},
.VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
.CANCELLED => return error.OperationCanceled,
else => return unexpectedStatus(rc),
}
}
}
pub fn GetCurrentProcess() HANDLE {
const process_pseudo_handle: usize = @bitCast(@as(isize, -1));
return @ptrFromInt(process_pseudo_handle);