authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-03 21:27:27-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-04 16:30:17-08:00
log3a5fff45ec04f4b713acae51a38510c316e42445
tree4d9f21b7faa4cdffc537cc56e4bc3f9969413368
parent2a193a39871ad098931cecc1154cc42278c28a2b

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

it needs cancelation integration

2 files changed, 228 insertions(+), 234 deletions(-)

lib/std/Io/Threaded.zig+228-117
......@@ -3260,30 +3260,23 @@ fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, pe
32603260 const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path);
32613261 _ = permissions; // TODO use this value
32623262
3263 const syscall: Syscall = try .start();
3264 const sub_dir_handle = while (true) {
3265 break windows.OpenFile(sub_path_w.span(), .{
3266 .dir = dir.handle,
3267 .access_mask = .{
3268 .GENERIC = .{ .READ = true },
3269 .STANDARD = .{ .SYNCHRONIZE = true },
3270 },
3271 .creation = .CREATE,
3272 .filter = .dir_only,
3273 }) catch |err| switch (err) {
3274 error.IsDir => return syscall.fail(error.Unexpected),
3275 error.PipeBusy => return syscall.fail(error.Unexpected),
3276 error.NoDevice => return syscall.fail(error.Unexpected),
3277 error.WouldBlock => return syscall.fail(error.Unexpected),
3278 error.AntivirusInterference => return syscall.fail(error.Unexpected),
3279 error.OperationCanceled => {
3280 try syscall.checkCancel();
3281 continue;
3282 },
3283 else => |e| return syscall.fail(e),
3284 };
3263 const sub_dir_handle = OpenFile(sub_path_w.span(), .{
3264 .dir = dir.handle,
3265 .access_mask = .{
3266 .GENERIC = .{ .READ = true },
3267 .STANDARD = .{ .SYNCHRONIZE = true },
3268 },
3269 .creation = .CREATE,
3270 .filter = .dir_only,
3271 }) catch |err| switch (err) {
3272 error.IsDir => return error.Unexpected,
3273 error.PipeBusy => return error.Unexpected,
3274 error.FileBusy => return error.Unexpected,
3275 error.NoDevice => return error.Unexpected,
3276 error.WouldBlock => return error.Unexpected,
3277 error.AntivirusInterference => return error.Unexpected,
3278 else => |e| return e,
32853279 };
3286 syscall.finish();
32873280 windows.CloseHandle(sub_dir_handle);
32883281}
32893282
......@@ -5987,27 +5980,19 @@ fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8,
59875980 var path_name_w = try sliceToPrefixedFileW(dir.handle, sub_path);
59885981
59895982 const h_file = handle: {
5990 const syscall: Syscall = try .start();
5991 while (true) {
5992 if (windows.OpenFile(path_name_w.span(), .{
5993 .dir = dir.handle,
5994 .access_mask = .{
5995 .GENERIC = .{ .READ = true },
5996 .STANDARD = .{ .SYNCHRONIZE = true },
5997 },
5998 .creation = .OPEN,
5999 .filter = .any,
6000 })) |handle| {
6001 syscall.finish();
6002 break :handle handle;
6003 } else |err| switch (err) {
6004 error.WouldBlock => unreachable,
6005 error.OperationCanceled => {
6006 try syscall.checkCancel();
6007 continue;
6008 },
6009 else => |e| return syscall.fail(e),
6010 }
5983 if (OpenFile(path_name_w.span(), .{
5984 .dir = dir.handle,
5985 .access_mask = .{
5986 .GENERIC = .{ .READ = true },
5987 .STANDARD = .{ .SYNCHRONIZE = true },
5988 },
5989 .creation = .OPEN,
5990 .filter = .any,
5991 })) |handle| {
5992 break :handle handle;
5993 } else |err| switch (err) {
5994 error.WouldBlock => unreachable,
5995 else => |e| return e,
60115996 }
60125997 };
60135998 defer windows.CloseHandle(h_file);
......@@ -6116,7 +6101,7 @@ pub fn GetFinalPathNameByHandle(
61166101 // Source: https://stackoverflow.com/questions/3012828/using-ioctl-mountmgr-query-points
61176102 // This is the NT namespaced version of \\.\MountPointManager
61186103 const mgmt_path_u16 = std.unicode.utf8ToUtf16LeStringLiteral("\\??\\MountPointManager");
6119 const mgmt_handle = windows.OpenFile(mgmt_path_u16, .{
6104 const mgmt_handle = OpenFile(mgmt_path_u16, .{
61206105 .access_mask = .{ .STANDARD = .{ .SYNCHRONIZE = true } },
61216106 .creation = .OPEN,
61226107 }) catch |err| switch (err) {
......@@ -6125,12 +6110,12 @@ pub fn GetFinalPathNameByHandle(
61256110 error.NoDevice => return error.Unexpected,
61266111 error.AccessDenied => return error.Unexpected,
61276112 error.PipeBusy => return error.Unexpected,
6113 error.FileBusy => return error.Unexpected,
61286114 error.PathAlreadyExists => return error.Unexpected,
61296115 error.WouldBlock => return error.Unexpected,
61306116 error.NetworkNotFound => return error.Unexpected,
61316117 error.AntivirusInterference => return error.Unexpected,
61326118 error.BadPathName => return error.Unexpected,
6133 error.OperationCanceled => @panic("TODO: better integrate cancelation"),
61346119 else => |e| return e,
61356120 };
61366121 defer windows.CloseHandle(mgmt_handle);
......@@ -7309,31 +7294,23 @@ fn dirRenameWindowsInner(
73097294 const new_path_w = new_path_w_buf.span();
73107295
73117296 const src_fd = src_fd: {
7312 const syscall: Syscall = try .start();
7313 while (true) {
7314 if (w.OpenFile(old_path_w, .{
7315 .dir = old_dir.handle,
7316 .access_mask = .{
7317 .GENERIC = .{ .WRITE = true },
7318 .STANDARD = .{
7319 .RIGHTS = .{ .DELETE = true },
7320 .SYNCHRONIZE = true,
7321 },
7322 },
7323 .creation = .OPEN,
7324 .filter = .any, // This function is supposed to rename both files and directories.
7325 .follow_symlinks = false,
7326 })) |handle| {
7327 syscall.finish();
7328 break :src_fd handle;
7329 } else |err| switch (err) {
7330 error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.
7331 error.OperationCanceled => {
7332 try syscall.checkCancel();
7333 continue;
7297 if (OpenFile(old_path_w, .{
7298 .dir = old_dir.handle,
7299 .access_mask = .{
7300 .GENERIC = .{ .WRITE = true },
7301 .STANDARD = .{
7302 .RIGHTS = .{ .DELETE = true },
7303 .SYNCHRONIZE = true,
73347304 },
7335 else => |e| return e,
7336 }
7305 },
7306 .creation = .OPEN,
7307 .filter = .any, // This function is supposed to rename both files and directories.
7308 .follow_symlinks = false,
7309 })) |handle| {
7310 break :src_fd handle;
7311 } else |err| switch (err) {
7312 error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.
7313 else => |e| return e,
73377314 }
73387315 };
73397316 defer w.CloseHandle(src_fd);
......@@ -7662,32 +7639,25 @@ fn dirSymLinkWindows(
76627639 };
76637640
76647641 const symlink_handle = handle: {
7665 const syscall: Syscall = try .start();
7666 while (true) {
7667 if (w.OpenFile(sym_link_path_w.span(), .{
7668 .access_mask = .{
7669 .GENERIC = .{ .READ = true, .WRITE = true },
7670 .STANDARD = .{ .SYNCHRONIZE = true },
7671 },
7672 .dir = dir.handle,
7673 .creation = .CREATE,
7674 .filter = if (flags.is_directory) .dir_only else .non_directory_only,
7675 })) |handle| {
7676 syscall.finish();
7677 break :handle handle;
7678 } else |err| switch (err) {
7679 error.IsDir => return syscall.fail(error.PathAlreadyExists),
7680 error.NotDir => return syscall.fail(error.Unexpected),
7681 error.WouldBlock => return syscall.fail(error.Unexpected),
7682 error.PipeBusy => return syscall.fail(error.Unexpected),
7683 error.NoDevice => return syscall.fail(error.Unexpected),
7684 error.AntivirusInterference => return syscall.fail(error.Unexpected),
7685 error.OperationCanceled => {
7686 try syscall.checkCancel();
7687 continue;
7688 },
7689 else => |e| return e,
7690 }
7642 if (OpenFile(sym_link_path_w.span(), .{
7643 .access_mask = .{
7644 .GENERIC = .{ .READ = true, .WRITE = true },
7645 .STANDARD = .{ .SYNCHRONIZE = true },
7646 },
7647 .dir = dir.handle,
7648 .creation = .CREATE,
7649 .filter = if (flags.is_directory) .dir_only else .non_directory_only,
7650 })) |handle| {
7651 break :handle handle;
7652 } else |err| switch (err) {
7653 error.IsDir => return error.PathAlreadyExists,
7654 error.NotDir => return error.Unexpected,
7655 error.WouldBlock => return error.Unexpected,
7656 error.PipeBusy => return error.Unexpected,
7657 error.FileBusy => return error.Unexpected,
7658 error.NoDevice => return error.Unexpected,
7659 error.AntivirusInterference => return error.Unexpected,
7660 else => |e| return e,
76917661 }
76927662 };
76937663 defer w.CloseHandle(symlink_handle);
......@@ -10341,27 +10311,20 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.Execut
1034110311 var path_name_w_buf = try wToPrefixedFileW(null, image_path_name);
1034210312
1034310313 const h_file = handle: {
10344 const syscall: Syscall = try .start();
10345 while (true) {
10346 if (w.OpenFile(path_name_w_buf.span(), .{
10347 .dir = null,
10348 .access_mask = .{
10349 .GENERIC = .{ .READ = true },
10350 .STANDARD = .{ .SYNCHRONIZE = true },
10351 },
10352 .creation = .OPEN,
10353 .filter = .any,
10354 })) |handle| {
10355 syscall.finish();
10356 break :handle handle;
10357 } else |err| switch (err) {
10358 error.WouldBlock => unreachable,
10359 error.OperationCanceled => {
10360 try syscall.checkCancel();
10361 continue;
10362 },
10363 else => |e| return e,
10364 }
10314 if (OpenFile(path_name_w_buf.span(), .{
10315 .dir = null,
10316 .access_mask = .{
10317 .GENERIC = .{ .READ = true },
10318 .STANDARD = .{ .SYNCHRONIZE = true },
10319 },
10320 .creation = .OPEN,
10321 .filter = .any,
10322 })) |handle| {
10323 break :handle handle;
10324 } else |err| switch (err) {
10325 error.WouldBlock => unreachable,
10326 error.FileBusy => unreachable,
10327 else => |e| return e,
1036510328 }
1036610329 };
1036710330 defer w.CloseHandle(h_file);
......@@ -19055,3 +19018,151 @@ pub fn mutexUnlock(m: *Io.Mutex) void {
1905519018 },
1905619019 }
1905719020}
19021
19022const OpenError = error{
19023 IsDir,
19024 NotDir,
19025 FileNotFound,
19026 NoDevice,
19027 AccessDenied,
19028 PipeBusy,
19029 PathAlreadyExists,
19030 WouldBlock,
19031 NetworkNotFound,
19032 AntivirusInterference,
19033 FileBusy,
19034} || Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
19035
19036const OpenFileOptions = struct {
19037 access_mask: windows.ACCESS_MASK,
19038 dir: ?windows.HANDLE = null,
19039 sa: ?*windows.SECURITY_ATTRIBUTES = null,
19040 share_access: windows.FILE.SHARE = .VALID_FLAGS,
19041 creation: windows.FILE.CREATE_DISPOSITION,
19042 filter: Filter = .non_directory_only,
19043 /// If false, tries to open path as a reparse point without dereferencing it.
19044 /// Defaults to true.
19045 follow_symlinks: bool = true,
19046
19047 pub const Filter = enum {
19048 /// Causes `OpenFile` to return `error.IsDir` if the opened handle would be a directory.
19049 non_directory_only,
19050 /// Causes `OpenFile` to return `error.NotDir` if the opened handle is not a directory.
19051 dir_only,
19052 /// `OpenFile` does not discriminate between opening files and directories.
19053 any,
19054 };
19055};
19056
19057/// TODO: inline this logic everywhere and delete this function
19058fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!windows.HANDLE {
19059 if (std.mem.eql(u16, sub_path_w, &[_]u16{'.'}) and options.filter == .non_directory_only) {
19060 return error.IsDir;
19061 }
19062 if (std.mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and options.filter == .non_directory_only) {
19063 return error.IsDir;
19064 }
19065
19066 var result: windows.HANDLE = undefined;
19067
19068 const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong;
19069 var nt_name: windows.UNICODE_STRING = .{
19070 .Length = path_len_bytes,
19071 .MaximumLength = path_len_bytes,
19072 .Buffer = @constCast(sub_path_w.ptr),
19073 };
19074 const attr: windows.OBJECT_ATTRIBUTES = .{
19075 .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir,
19076 .Attributes = .{ .INHERIT = if (options.sa) |sa| sa.bInheritHandle != windows.FALSE else false },
19077 .ObjectName = &nt_name,
19078 .SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null,
19079 };
19080
19081 var iosb: windows.IO_STATUS_BLOCK = undefined;
19082
19083 // There are multiple kernel bugs being worked around with retries.
19084 const max_attempts = 13;
19085 var attempt: u5 = 0;
19086
19087 var syscall: Syscall = try .start();
19088 while (true) {
19089 switch (windows.ntdll.NtCreateFile(
19090 &result,
19091 options.access_mask,
19092 &attr,
19093 &iosb,
19094 null,
19095 .{ .NORMAL = true },
19096 options.share_access,
19097 options.creation,
19098 .{
19099 .DIRECTORY_FILE = options.filter == .dir_only,
19100 .NON_DIRECTORY_FILE = options.filter == .non_directory_only,
19101 .IO = if (options.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS,
19102 .OPEN_REPARSE_POINT = !options.follow_symlinks,
19103 },
19104 null,
19105 0,
19106 )) {
19107 .SUCCESS => {
19108 syscall.finish();
19109 return result;
19110 },
19111 .CANCELLED => {
19112 try syscall.checkCancel();
19113 continue;
19114 },
19115 .SHARING_VIOLATION => {
19116 // This occurs if the file attempting to be opened is a running
19117 // executable. However, there's a kernel bug: the error may be
19118 // incorrectly returned for an indeterminate amount of time
19119 // after an executable file is closed. Here we work around the
19120 // kernel bug with retry attempts.
19121 syscall.finish();
19122 if (max_attempts - attempt == 0) return error.FileBusy;
19123 try parking_sleep.sleep(.{ .duration = .{
19124 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1),
19125 .clock = .awake,
19126 } });
19127 attempt += 1;
19128 syscall = try .start();
19129 continue;
19130 },
19131 .DELETE_PENDING => {
19132 // This error means that there *was* a file in this location on
19133 // the file system, but it was deleted. However, the OS is not
19134 // finished with the deletion operation, and so this CreateFile
19135 // call has failed. There is not really a sane way to handle
19136 // this other than retrying the creation after the OS finishes
19137 // the deletion.
19138 syscall.finish();
19139 if (max_attempts - attempt == 0) return error.FileBusy;
19140 try parking_sleep.sleep(.{ .duration = .{
19141 .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1),
19142 .clock = .awake,
19143 } });
19144 attempt += 1;
19145 syscall = try .start();
19146 continue;
19147 },
19148 .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName),
19149 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound),
19150 .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound),
19151 .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found
19152 .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't
19153 .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice),
19154 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
19155 .PIPE_BUSY => return syscall.fail(error.PipeBusy),
19156 .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice),
19157 .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists),
19158 .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir),
19159 .NOT_A_DIRECTORY => return syscall.fail(error.NotDir),
19160 .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied),
19161 .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference),
19162 .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status),
19163 .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status),
19164 .INVALID_HANDLE => |status| return syscall.ntstatusBug(status),
19165 else => |status| return syscall.unexpectedNtstatus(status),
19166 }
19167 }
19168}
lib/std/os/windows.zig-117
......@@ -2359,123 +2359,6 @@ pub const OBJECT_ATTRIBUTES = extern struct {
23592359
23602360// ref none
23612361
2362pub const OpenError = error{
2363 IsDir,
2364 NotDir,
2365 FileNotFound,
2366 NoDevice,
2367 AccessDenied,
2368 PipeBusy,
2369 PathAlreadyExists,
2370 Unexpected,
2371 NameTooLong,
2372 WouldBlock,
2373 NetworkNotFound,
2374 AntivirusInterference,
2375 BadPathName,
2376 OperationCanceled,
2377};
2378
2379pub const OpenFileOptions = struct {
2380 access_mask: ACCESS_MASK,
2381 dir: ?HANDLE = null,
2382 sa: ?*SECURITY_ATTRIBUTES = null,
2383 share_access: FILE.SHARE = .VALID_FLAGS,
2384 creation: FILE.CREATE_DISPOSITION,
2385 filter: Filter = .non_directory_only,
2386 /// If false, tries to open path as a reparse point without dereferencing it.
2387 /// Defaults to true.
2388 follow_symlinks: bool = true,
2389
2390 pub const Filter = enum {
2391 /// Causes `OpenFile` to return `error.IsDir` if the opened handle would be a directory.
2392 non_directory_only,
2393 /// Causes `OpenFile` to return `error.NotDir` if the opened handle is not a directory.
2394 dir_only,
2395 /// `OpenFile` does not discriminate between opening files and directories.
2396 any,
2397 };
2398};
2399
2400pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HANDLE {
2401 if (mem.eql(u16, sub_path_w, &[_]u16{'.'}) and options.filter == .non_directory_only) {
2402 return error.IsDir;
2403 }
2404 if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and options.filter == .non_directory_only) {
2405 return error.IsDir;
2406 }
2407
2408 var result: HANDLE = undefined;
2409
2410 const path_len_bytes = math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong;
2411 var nt_name: UNICODE_STRING = .{
2412 .Length = path_len_bytes,
2413 .MaximumLength = path_len_bytes,
2414 .Buffer = @constCast(sub_path_w.ptr),
2415 };
2416 const attr: OBJECT_ATTRIBUTES = .{
2417 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir,
2418 .Attributes = .{ .INHERIT = if (options.sa) |sa| sa.bInheritHandle != FALSE else false },
2419 .ObjectName = &nt_name,
2420 .SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null,
2421 };
2422 var io: IO_STATUS_BLOCK = undefined;
2423 while (true) {
2424 const rc = ntdll.NtCreateFile(
2425 &result,
2426 options.access_mask,
2427 &attr,
2428 &io,
2429 null,
2430 .{ .NORMAL = true },
2431 options.share_access,
2432 options.creation,
2433 .{
2434 .DIRECTORY_FILE = options.filter == .dir_only,
2435 .NON_DIRECTORY_FILE = options.filter == .non_directory_only,
2436 .IO = if (options.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS,
2437 .OPEN_REPARSE_POINT = !options.follow_symlinks,
2438 },
2439 null,
2440 0,
2441 );
2442 switch (rc) {
2443 .SUCCESS => return result,
2444 .OBJECT_NAME_INVALID => return error.BadPathName,
2445 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
2446 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
2447 .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found
2448 .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't
2449 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
2450 .INVALID_PARAMETER => unreachable,
2451 .SHARING_VIOLATION => return error.AccessDenied,
2452 .ACCESS_DENIED => return error.AccessDenied,
2453 .PIPE_BUSY => return error.PipeBusy,
2454 .PIPE_NOT_AVAILABLE => return error.NoDevice,
2455 .OBJECT_PATH_SYNTAX_BAD => unreachable,
2456 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
2457 .FILE_IS_A_DIRECTORY => return error.IsDir,
2458 .NOT_A_DIRECTORY => return error.NotDir,
2459 .USER_MAPPED_FILE => return error.AccessDenied,
2460 .INVALID_HANDLE => unreachable,
2461 .DELETE_PENDING => {
2462 // This error means that there *was* a file in this location on
2463 // the file system, but it was deleted. However, the OS is not
2464 // finished with the deletion operation, and so this CreateFile
2465 // call has failed. There is not really a sane way to handle
2466 // this other than retrying the creation after the OS finishes
2467 // the deletion.
2468 const delay_one_ms: LARGE_INTEGER = -(std.time.ns_per_ms / 100);
2469 _ = ntdll.NtDelayExecution(TRUE, &delay_one_ms);
2470 continue;
2471 },
2472 .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
2473 .CANCELLED => return error.OperationCanceled,
2474 else => return unexpectedStatus(rc),
2475 }
2476 }
2477}
2478
24792362pub fn GetCurrentProcess() HANDLE {
24802363 const process_pseudo_handle: usize = @bitCast(@as(isize, -1));
24812364 return @ptrFromInt(process_pseudo_handle);