diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 67d1ec849c..425e220b1c 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -12,6 +12,8 @@ const Allocator = std.mem.Allocator; handle: Handle, +pub const Handle = std.posix.fd_t; + pub const path = std.fs.path; /// The maximum length of a file path that the operating system will accept. @@ -396,8 +398,6 @@ pub fn walk(dir: Dir, allocator: Allocator) Allocator.Error!Walker { return .{ .inner = try walkSelectively(dir, allocator) }; } -pub const Handle = std.posix.fd_t; - pub const PathNameError = error{ /// Returned when an insufficient buffer is provided that cannot fit the /// path name. @@ -1698,7 +1698,7 @@ pub fn copyFile( options: CopyFileOptions, ) CopyFileError!void { const file = try source_dir.openFile(io, source_path, .{}); - var file_reader: File.Reader = .init(.{ .handle = file.handle }, io, &.{}); + var file_reader: File.Reader = .init(file, io, &.{}); defer file_reader.file.close(io); const permissions = options.permissions orelse blk: { diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index cc1a58a15e..6323a39454 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -11,13 +11,14 @@ const Dir = std.Io.Dir; handle: Handle, +pub const Handle = std.posix.fd_t; + pub const Reader = @import("File/Reader.zig"); pub const Writer = @import("File/Writer.zig"); pub const Atomic = @import("File/Atomic.zig"); /// Memory intended to remain consistent with file contents. pub const MemoryMap = @import("File/MemoryMap.zig"); -pub const Handle = std.posix.fd_t; pub const INode = std.posix.ino_t; pub const NLink = std.posix.nlink_t; pub const Uid = std.posix.uid_t; @@ -73,15 +74,36 @@ pub const Stat = struct { }; pub fn stdout() File { - return .{ .handle = if (is_windows) std.os.windows.peb().ProcessParameters.hStdOutput else std.posix.STDOUT_FILENO }; + return switch (native_os) { + .windows => .{ + .handle = std.os.windows.peb().ProcessParameters.hStdOutput, + }, + else => .{ + .handle = std.posix.STDOUT_FILENO, + }, + }; } pub fn stderr() File { - return .{ .handle = if (is_windows) std.os.windows.peb().ProcessParameters.hStdError else std.posix.STDERR_FILENO }; + return switch (native_os) { + .windows => .{ + .handle = std.os.windows.peb().ProcessParameters.hStdError, + }, + else => .{ + .handle = std.posix.STDERR_FILENO, + }, + }; } pub fn stdin() File { - return .{ .handle = if (is_windows) std.os.windows.peb().ProcessParameters.hStdInput else std.posix.STDIN_FILENO }; + return switch (native_os) { + .windows => .{ + .handle = std.os.windows.peb().ProcessParameters.hStdInput, + }, + else => .{ + .handle = std.posix.STDIN_FILENO, + }, + }; } pub const StatError = error{ diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig index 52bbe83513..68a68e28ec 100644 --- a/lib/std/Io/File/Writer.zig +++ b/lib/std/Io/File/Writer.zig @@ -101,7 +101,7 @@ pub fn moveToReader(w: *Writer) File.Reader { defer w.* = undefined; return .{ .io = w.io, - .file = .{ .handle = w.file.handle }, + .file = w.file, .mode = w.mode, .pos = w.pos, .interface = File.Reader.initInterface(w.interface.buffer), diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 849a2d4375..42816b5276 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -571,7 +571,7 @@ const Future = struct { num_completed: *std.atomic.Value(u32), thread: ?*Thread, ) void { - var need_signal: bool = thread != null and thread.?.cancelAwaitable(.fromFuture(future)); + var need_signal: bool = if (thread) |th| th.cancelAwaitable(.fromFuture(future)) else false; var timeout_ns: u64 = 1 << 10; while (true) { need_signal = need_signal and thread.?.signalCanceledSyscall(t, .fromFuture(future)); @@ -628,7 +628,7 @@ const Thread = struct { const Handle = Handle: { if (std.Thread.use_pthreads) break :Handle std.c.pthread_t; - if (builtin.target.os.tag == .windows) break :Handle windows.HANDLE; + if (is_windows) break :Handle windows.HANDLE; break :Handle void; }; @@ -1364,7 +1364,7 @@ fn worker(t: *Threaded) void { .id = std.Thread.getCurrentId(), .handle = handle: { if (std.Thread.use_pthreads) break :handle std.c.pthread_self(); - if (builtin.target.os.tag == .windows) break :handle undefined; // populated below + if (is_windows) break :handle undefined; // populated below }, .status = .init(.{ .cancelation = .none, @@ -1376,7 +1376,7 @@ fn worker(t: *Threaded) void { }; Thread.current = &thread; - if (builtin.target.os.tag == .windows) { + if (is_windows) { assert(windows.ntdll.NtOpenThread( &thread.handle, .{ @@ -1397,7 +1397,7 @@ fn worker(t: *Threaded) void { &windows.teb().ClientId, ) == .SUCCESS); } - defer if (builtin.target.os.tag == .windows) { + defer if (is_windows) { windows.CloseHandle(thread.handle); }; @@ -3430,53 +3430,133 @@ fn dirCreateFileWindows( sub_path: []const u8, flags: File.CreateFlags, ) File.OpenError!File { - const w = windows; const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; - const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, sub_path); + if (std.mem.eql(u8, sub_path, ".")) return error.IsDir; + if (std.mem.eql(u8, sub_path, "..")) return error.IsDir; + + const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path); const sub_path_w = sub_path_w_array.span(); + const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; - const handle = handle: { - const syscall: Syscall = try .start(); - while (true) { - if (w.OpenFile(sub_path_w, .{ - .dir = dir.handle, - .access_mask = .{ - .STANDARD = .{ .SYNCHRONIZE = true }, - .GENERIC = .{ - .WRITE = true, - .READ = flags.read, - }, - }, - .creation = if (flags.exclusive) - .CREATE - else if (flags.truncate) - .OVERWRITE_IF - else - .OPEN_IF, - })) |handle| { - syscall.finish(); - break :handle handle; - } else |err| switch (err) { - error.OperationCanceled => { - try syscall.checkCancel(); - continue; - }, - else => |e| return syscall.fail(e), - } - } + var nt_name: windows.UNICODE_STRING = .{ + .Length = path_len_bytes, + .MaximumLength = path_len_bytes, + .Buffer = @constCast(sub_path_w.ptr), }; - errdefer w.CloseHandle(handle); + const attr: windows.OBJECT_ATTRIBUTES = .{ + .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), + .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, + .Attributes = .{ + .INHERIT = false, + }, + .ObjectName = &nt_name, + .SecurityDescriptor = null, + .SecurityQualityOfService = null, + }; + const create_disposition: windows.FILE.CREATE_DISPOSITION = if (flags.exclusive) + .CREATE + else if (flags.truncate) + .OVERWRITE_IF + else + .OPEN_IF; + + const access_mask: windows.ACCESS_MASK = .{ + .STANDARD = .{ .SYNCHRONIZE = true }, + .GENERIC = .{ + .WRITE = true, + .READ = flags.read, + }, + }; + + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + + // There are multiple kernel bugs being worked around with retries. + const max_attempts = 13; + var attempt: u5 = 0; + + var handle: windows.HANDLE = undefined; + var syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtCreateFile( + &handle, + access_mask, + &attr, + &io_status_block, + null, + .{ .NORMAL = true }, + .VALID_FLAGS, // share access + create_disposition, + .{ + .NON_DIRECTORY_FILE = true, + .IO = .SYNCHRONOUS_NONALERT, + }, + null, + 0, + )) { + .SUCCESS => { + syscall.finish(); + break; + }, + .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.SharingViolation; + try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); + 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. Here, we simulate the kernel bug being + // fixed by sleeping and retrying until the error goes away. + syscall.finish(); + if (max_attempts - attempt == 0) return error.SharingViolation; + try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); + 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 => |err| return syscall.ntstatusBug(err), + .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err), + .INVALID_HANDLE => |err| return syscall.ntstatusBug(err), + else => |err| return syscall.unexpectedNtstatus(err), + }; + errdefer windows.CloseHandle(handle); - var io_status_block: w.IO_STATUS_BLOCK = undefined; const exclusive = switch (flags.lock) { .none => return .{ .handle = handle }, .shared => false, .exclusive => true, }; - const syscall: Syscall = try .start(); - while (true) switch (w.ntdll.NtLockFile( + + syscall = try .start(); + while (true) switch (windows.ntdll.NtLockFile( handle, null, null, @@ -3968,7 +4048,10 @@ pub fn dirOpenFileWtf16( var attr: w.OBJECT_ATTRIBUTES = .{ .Length = @sizeOf(w.OBJECT_ATTRIBUTES), .RootDirectory = dir_handle, - .Attributes = .{}, + .Attributes = .{ + // TODO should we set INHERIT=false? + //.INHERIT = false, + }, .ObjectName = &nt_name, .SecurityDescriptor = null, .SecurityQualityOfService = null, @@ -7923,15 +8006,14 @@ fn fileClose(userdata: ?*anyopaque, files: []const File) void { for (files) |file| posix.close(file.handle); } -const fileReadStreaming = switch (native_os) { - .windows => fileReadStreamingWindows, - else => fileReadStreamingPosix, -}; - -fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize { +fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; + if (is_windows) return fileReadStreamingWindows(file, data); + return fileReadStreamingPosix(file, data); +} +fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usize { var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; var i: usize = 0; for (data) |buf| { @@ -8013,10 +8095,7 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8) } } -fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize { - const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - +fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize { const DWORD = windows.DWORD; var index: usize = 0; while (index < data.len and data[index].len == 0) index += 1; @@ -8059,10 +8138,7 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u } } -fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { - const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - +fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { if (!have_preadv) @compileError("TODO implement fileReadPositionalPosix for cursed operating systems that don't support preadv (it's only Haiku)"); var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; @@ -8144,15 +8220,14 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8 } } -const fileReadPositional = switch (native_os) { - .windows => fileReadPositionalWindows, - else => fileReadPositionalPosix, -}; - -fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { +fn fileReadPositional(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; + if (is_windows) return fileReadPositionalWindows(file, data, offset); + return fileReadPositionalPosix(file, data, offset); +} +fn fileReadPositionalWindows(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { var index: usize = 0; while (index < data.len and data[index].len == 0) index += 1; if (index == data.len) return 0; @@ -8244,7 +8319,7 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi } } - if (native_os == .windows) { + if (is_windows) { const syscall: Syscall = try .start(); while (true) { if (windows.kernel32.SetFilePointerEx(fd, offset, null, windows.FILE_CURRENT) != 0) { @@ -8329,7 +8404,7 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi _ = t; const fd = file.handle; - if (native_os == .windows) { + if (is_windows) { // "The starting point is zero or the beginning of the file. If [FILE_BEGIN] // is specified, then the liDistanceToMove parameter is interpreted as an unsigned value." // https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilepointerex @@ -11628,7 +11703,7 @@ fn netWriteWindows( splat: usize, ) net.Stream.Writer.Error!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); - comptime assert(native_os == .windows); + comptime assert(is_windows); var iovecs: [max_iovecs_len]ws2_32.WSABUF = undefined; var len: u32 = 0; @@ -11896,7 +11971,7 @@ fn netInterfaceNameResolve( } } - if (native_os == .windows) { + if (is_windows) { try Thread.checkCancel(); @panic("TODO implement netInterfaceNameResolve for Windows"); } @@ -11930,7 +12005,7 @@ fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interfa @panic("TODO implement netInterfaceName for linux"); } - if (native_os == .windows) { + if (is_windows) { @panic("TODO implement netInterfaceName for windows"); } @@ -15247,11 +15322,13 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File { const int = try t.environ.zig_progress_handle; - return .{ .handle = switch (@typeInfo(Io.File.Handle)) { - .int => int, - .pointer => @ptrFromInt(int), - else => return error.UnsupportedOperation, - } }; + return .{ + .handle = switch (@typeInfo(Io.File.Handle)) { + .int => int, + .pointer => @ptrFromInt(int), + else => return error.UnsupportedOperation, + }, + }; } pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 { @@ -15562,13 +15639,13 @@ test { _ = @import("Threaded/test.zig"); } -const use_parking_futex = switch (builtin.target.os.tag) { +const use_parking_futex = switch (native_os) { .windows => true, // RtlWaitOnAddress is a userland implementation anyway .netbsd => true, // NetBSD has `futex(2)`, but it's historically been quite buggy. TODO: evaluate whether it's okay to use now. .illumos => true, // Illumos has no futex mechanism else => false, }; -const use_parking_sleep = switch (builtin.target.os.tag) { +const use_parking_sleep = switch (native_os) { // On Windows, we can implement sleep either with `NtDelayExecution` (which is how `SleepEx` in // kernel32 works) or `NtWaitForAlertByThreadId` (thread parking). We're already using the // latter for futex, so we may as well use it for sleeping too, to maximise code reuse. I'm @@ -15926,7 +16003,7 @@ const parking_sleep = struct { /// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation. fn park(opt_deadline: ?std.Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{Timeout}!void { comptime assert(use_parking_futex or use_parking_sleep); - switch (builtin.target.os.tag) { + switch (native_os) { .windows => { var timeout_buf: windows.LARGE_INTEGER = undefined; const raw_timeout: ?*windows.LARGE_INTEGER = if (opt_deadline) |deadline| timeout: { @@ -15980,7 +16057,7 @@ fn park(opt_deadline: ?std.Io.Clock.Timestamp, addr_hint: ?*const anyopaque) err } } -const UnparkTid = switch (builtin.target.os.tag) { +const UnparkTid = switch (native_os) { // `NtAlertMultipleThreadByThreadId` is weird and wants 64-bit thread handles? .windows => usize, else => std.Thread.Id, @@ -15988,7 +16065,7 @@ const UnparkTid = switch (builtin.target.os.tag) { /// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation. fn unpark(tids: []const UnparkTid, addr_hint: ?*const anyopaque) void { comptime assert(use_parking_futex or use_parking_sleep); - switch (builtin.target.os.tag) { + switch (native_os) { .windows => { // TODO: this condition is currently disabled because mingw-w64 does not contain this // symbol. Once it's added, enable this check to use the new bulk API where possible. diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 75ef13ca91..5ccc46778b 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -977,7 +977,9 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff 0.., ) |main_parent, *main_storage, main_index| { if (main_parent == .unused) continue; - const file: Io.File = .{ .handle = main_storage.getIpcFd() orelse continue }; + const file: Io.File = .{ + .handle = main_storage.getIpcFd() orelse continue, + }; const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata); var bytes_read: usize = 0; while (true) { diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index c94596de8e..262eaef049 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -392,6 +392,8 @@ pub const FILE = struct { Characteristics: ULONG, }; + pub const USE_FILE_POINTER_POSITION = -2; + // ref: um/WinBase.h pub const ATTRIBUTE_TAG_INFO = extern struct { @@ -466,9 +468,11 @@ pub const FILE = struct { pub const CREATE_DISPOSITION = enum(ULONG) { /// If the file already exists, replace it with the given file. If it does not, create the given file. SUPERSEDE = 0x00000000, - /// If the file already exists, open it instead of creating a new file. If it does not, fail the request and do not create a new file. + /// If the file already exists, open it instead of creating a new file. + /// If it does not, fail the request and do not create a new file. OPEN = 0x00000001, - /// If the file already exists, fail the request and do not create or open the given file. If it does not, create the given file. + /// If the file already exists, fail the request and do not create or + /// open the given file. If it does not, create the given file. CREATE = 0x00000002, /// If the file already exists, open it. If it does not, create the given file. OPEN_IF = 0x00000003, @@ -482,75 +486,122 @@ pub const FILE = struct { /// Define the create/open option flags pub const MODE = packed struct(ULONG) { - /// The file being created or opened is a directory file. With this flag, the CreateDisposition parameter must be set to `.CREATE`, `.FILE_OPEN`, or `.OPEN_IF`. - /// With this flag, other compatible CreateOptions flags include only the following: `SYNCHRONOUS_IO`, `WRITE_THROUGH`, `OPEN_FOR_BACKUP_INTENT`, and `OPEN_BY_FILE_ID`. + /// The file being created or opened is a directory file. With this + /// flag, the CreateDisposition parameter must be set to `.CREATE`, + /// `.FILE_OPEN`, or `.OPEN_IF`. With this flag, other compatible + /// CreateOptions flags include only the following: `SYNCHRONOUS_IO`, + /// `WRITE_THROUGH`, `OPEN_FOR_BACKUP_INTENT`, and `OPEN_BY_FILE_ID`. DIRECTORY_FILE: bool = false, - /// Applications that write data to the file must actually transfer the data into the file before any requested write operation is considered complete. - /// This flag is automatically set if the CreateOptions flag `NO_INTERMEDIATE_BUFFERING` is set. + /// Applications that write data to the file must actually transfer the + /// data into the file before any requested write operation is + /// considered complete. This flag is automatically set if the + /// CreateOptions flag `NO_INTERMEDIATE_BUFFERING` is set. WRITE_THROUGH: bool = false, /// All accesses to the file are sequential. SEQUENTIAL_ONLY: bool = false, - /// The file cannot be cached or buffered in a driver's internal buffers. This flag is incompatible with the DesiredAccess `FILE_APPEND_DATA` flag. + /// The file cannot be cached or buffered in a driver's internal + /// buffers. This flag is incompatible with the DesiredAccess + /// `FILE_APPEND_DATA` flag. NO_INTERMEDIATE_BUFFERING: bool = false, IO: enum(u2) { /// All operations on the file are performed asynchronously. ASYNCHRONOUS = 0b00, - /// All operations on the file are performed synchronously. Any wait on behalf of the caller is subject to premature termination from alerts. - /// This flag also causes the I/O system to maintain the file position context. If this flag is set, the DesiredAccess `SYNCHRONIZE` flag also must be set. + /// All operations on the file are performed synchronously. Any + /// wait on behalf of the caller is subject to premature + /// termination from alerts. This flag also causes the I/O system + /// to maintain the file position context. If this flag is set, the + /// DesiredAccess `SYNCHRONIZE` flag also must be set. SYNCHRONOUS_ALERT = 0b01, - /// All operations on the file are performed synchronously. Waits in the system to synchronize I/O queuing and completion are not subject to alerts. - /// This flag also causes the I/O system to maintain the file position context. If this flag is set, the DesiredAccess `SYNCHRONIZE` flag also must be set. + /// All operations on the file are performed synchronously. Waits + /// in the system to synchronize I/O queuing and completion are not + /// subject to alerts. This flag also causes the I/O system to + /// maintain the file position context. If this flag is set, the + /// DesiredAccess `SYNCHRONIZE` flag also must be set. SYNCHRONOUS_NONALERT = 0b10, _, pub const VALID_FLAGS: @This() = @enumFromInt(0b11); } = .ASYNCHRONOUS, - /// The file being opened must not be a directory file or this call fails. The file object being opened can represent a data file, a logical, virtual, or physical - /// device, or a volume. + /// The file being opened must not be a directory file or this call + /// fails. The file object being opened can represent a data file, a + /// logical, virtual, or physical device, or a volume. NON_DIRECTORY_FILE: bool = false, - /// Create a tree connection for this file in order to open it over the network. This flag is not used by device and intermediate drivers. + /// Create a tree connection for this file in order to open it over the + /// network. This flag is not used by device and intermediate drivers. CREATE_TREE_CONNECTION: bool = false, - /// Complete this operation immediately with an alternate success code of `STATUS_OPLOCK_BREAK_IN_PROGRESS` if the target file is oplocked, rather than blocking - /// the caller's thread. If the file is oplocked, another caller already has access to the file. This flag is not used by device and intermediate drivers. + /// Complete this operation immediately with an alternate success code + /// of `STATUS_OPLOCK_BREAK_IN_PROGRESS` if the target file is + /// oplocked, rather than blocking the caller's thread. If the file is + /// oplocked, another caller already has access to the file. This flag + /// is not used by device and intermediate drivers. COMPLETE_IF_OPLOCKED: bool = false, - /// If the extended attributes on an existing file being opened indicate that the caller must understand EAs to properly interpret the file, fail this request - /// because the caller does not understand how to deal with EAs. This flag is irrelevant for device and intermediate drivers. + /// If the extended attributes on an existing file being opened + /// indicate that the caller must understand EAs to properly interpret + /// the file, fail this request because the caller does not understand + /// how to deal with EAs. This flag is irrelevant for device and + /// intermediate drivers. NO_EA_KNOWLEDGE: bool = false, OPEN_REMOTE_INSTANCE: bool = false, - /// Accesses to the file can be random, so no sequential read-ahead operations should be performed on the file by FSDs or the system. + /// Accesses to the file can be random, so no sequential read-ahead + /// operations should be performed on the file by FSDs or the system. RANDOM_ACCESS: bool = false, - /// Delete the file when the last handle to it is passed to `NtClose`. If this flag is set, the `DELETE` flag must be set in the DesiredAccess parameter. + /// Delete the file when the last handle to it is passed to `NtClose`. + /// If this flag is set, the `DELETE` flag must be set in the + /// DesiredAccess parameter. DELETE_ON_CLOSE: bool = false, - /// The file name that is specified by the `ObjectAttributes` parameter includes the 8-byte file reference number for the file. This number is assigned by and - /// specific to the particular file system. If the file is a reparse point, the file name will also include the name of a device. Note that the FAT file system - /// does not support this flag. This flag is not used by device and intermediate drivers. + /// The file name that is specified by the `ObjectAttributes` parameter + /// includes the 8-byte file reference number for the file. This number + /// is assigned by and specific to the particular file system. If the + /// file is a reparse point, the file name will also include the name + /// of a device. Note that the FAT file system does not support this + /// flag. This flag is not used by device and intermediate drivers. OPEN_BY_FILE_ID: bool = false, - /// The file is being opened for backup intent. Therefore, the system should check for certain access rights and grant the caller the appropriate access to the - /// file before checking the DesiredAccess parameter against the file's security descriptor. This flag not used by device and intermediate drivers. + /// The file is being opened for backup intent. Therefore, the system + /// should check for certain access rights and grant the caller the + /// appropriate access to the file before checking the DesiredAccess + /// parameter against the file's security descriptor. This flag not + /// used by device and intermediate drivers. OPEN_FOR_BACKUP_INTENT: bool = false, - /// Suppress inheritance of `FILE_ATTRIBUTE.COMPRESSED` from the parent directory. This allows creation of a non-compressed file in a directory that is marked - /// compressed. + /// Suppress inheritance of `FILE_ATTRIBUTE.COMPRESSED` from the parent + /// directory. This allows creation of a non-compressed file in a + /// directory that is marked compressed. NO_COMPRESSION: bool = false, - /// The file is being opened and an opportunistic lock on the file is being requested as a single atomic operation. The file system checks for oplocks before it - /// performs the create operation and will fail the create with a return code of STATUS_CANNOT_BREAK_OPLOCK if the result would be to break an existing oplock. - /// For more information, see the Remarks section. + /// The file is being opened and an opportunistic lock on the file is + /// being requested as a single atomic operation. The file system + /// checks for oplocks before it performs the create operation and will + /// fail the create with a return code of STATUS_CANNOT_BREAK_OPLOCK if + /// the result would be to break an existing oplock. For more + /// information, see the Remarks section. /// - /// Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP: This flag is not supported. + /// Windows Server 2008, Windows Vista, Windows Server 2003 and Windows + /// XP: This flag is not supported. /// - /// This flag is supported on the following file systems: NTFS, FAT, and exFAT. + /// This flag is supported on the following file systems: NTFS, FAT, + /// and exFAT. OPEN_REQUIRING_OPLOCK: bool = false, Reserved17: u3 = 0, - /// This flag allows an application to request a filter opportunistic lock to prevent other applications from getting share violations. If there are already open - /// handles, the create request will fail with STATUS_OPLOCK_NOT_GRANTED. For more information, see the Remarks section. + /// This flag allows an application to request a filter opportunistic + /// lock to prevent other applications from getting share violations. + /// If there are already open handles, the create request will fail + /// with STATUS_OPLOCK_NOT_GRANTED. For more information, see the + /// Remarks section. RESERVE_OPFILTER: bool = false, - /// Open a file with a reparse point and bypass normal reparse point processing for the file. For more information, see the Remarks section. + /// Open a file with a reparse point and bypass normal reparse point + /// processing for the file. For more information, see the Remarks + /// section. OPEN_REPARSE_POINT: bool = false, - /// Instructs any filters that perform offline storage or virtualization to not recall the contents of the file as a result of this open. + /// Instructs any filters that perform offline storage or + /// virtualization to not recall the contents of the file as a result + /// of this open. OPEN_NO_RECALL: bool = false, - /// This flag instructs the file system to capture the user associated with the calling thread. Any subsequent calls to `FltQueryVolumeInformation` or - /// `ZwQueryVolumeInformationFile` using the returned handle will assume the captured user, rather than the calling user at the time, for purposes of computing - /// the free space available to the caller. This applies to the following FsInformationClass values: `FileFsSizeInformation`, `FileFsFullSizeInformation`, and - /// `FileFsFullSizeInformationEx`. + /// This flag instructs the file system to capture the user associated + /// with the calling thread. Any subsequent calls to + /// `FltQueryVolumeInformation` or `ZwQueryVolumeInformationFile` using + /// the returned handle will assume the captured user, rather than the + /// calling user at the time, for purposes of computing the free space + /// available to the caller. This applies to the following + /// FsInformationClass values: `FileFsSizeInformation`, + /// `FileFsFullSizeInformation`, and `FileFsFullSizeInformationEx`. OPEN_FOR_FREE_SPACE_QUERY: bool = false, Reserved24: u8 = 0, @@ -597,7 +648,8 @@ pub const FILE = struct { // ref: km/ntifs.h pub const INFORMATION = extern struct { - /// The set of flags that specify the mode in which the file can be accessed. These flags are a subset of `MODE`. + /// The set of flags that specify the mode in which the file can be + /// accessed. These flags are a subset of `MODE`. Mode: MODE, }; }; diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index 1cc17c0be6..978987e787 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -567,9 +567,8 @@ pub extern "ntdll" fn NtWaitForAlertByThreadId( Address: ?*const anyopaque, Timeout: ?*const LARGE_INTEGER, ) callconv(.winapi) NTSTATUS; -pub extern "ntdll" fn NtAlertThreadByThreadId( - ThreadId: DWORD, -) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn NtAlertThreadByThreadId(ThreadId: DWORD) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn NtAlertThread(ThreadHandle: HANDLE) callconv(.winapi) NTSTATUS; pub extern "ntdll" fn NtAlertMultipleThreadByThreadId( ThreadIds: [*]const ULONG_PTR, ThreadCount: ULONG, @@ -589,3 +588,16 @@ pub extern "ntdll" fn NtCancelSynchronousIoFile( RequestToCancel: ?*IO_STATUS_BLOCK, IoStatusBlock: *IO_STATUS_BLOCK, ) callconv(.winapi) NTSTATUS; + +pub extern "ntdll" fn NtDelayExecution( + Alertable: BOOLEAN, + DelayInterval: *const LARGE_INTEGER, +) callconv(.winapi) NTSTATUS; + +pub extern "ntdll" fn NtCancelIoFileEx( + FileHandle: HANDLE, + /// Documentation has this as IO_STATUS_BLOCK but it's actually the APC + /// context parameter. + IoRequestToCancel: ?*anyopaque, + IoStatusBlock: *IO_STATUS_BLOCK, +) callconv(.winapi) NTSTATUS; diff --git a/src/link/Lld.zig b/src/link/Lld.zig index dd84da15d4..a94b1111cd 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -278,7 +278,7 @@ pub fn flush( }; result catch |err| switch (err) { error.OutOfMemory, error.LinkFailure => |e| return e, - else => |e| return lld.base.comp.link_diags.fail("failed to link with LLD: {s}", .{@errorName(e)}), + else => |e| return lld.base.comp.link_diags.fail("failed to link with LLD: {t}", .{e}), }; } @@ -1630,7 +1630,11 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi }) catch |err| break :term err; var stderr_reader = child.stderr.?.readerStreaming(io, &.{}); - stderr = try stderr_reader.interface.allocRemaining(gpa, .unlimited); + stderr = stderr_reader.interface.allocRemaining(gpa, .unlimited) catch |err| switch (err) { + error.StreamTooLong => unreachable, // unlimited + error.OutOfMemory => |e| return e, + error.ReadFailed => return stderr_reader.err.?, + }; break :term child.wait(io); }) catch |first_err| term: { const err = switch (first_err) { @@ -1682,7 +1686,11 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi break :term rsp_child.wait(io) catch |err| break :err err; } else { var stderr_reader = rsp_child.stderr.?.readerStreaming(io, &.{}); - stderr = try stderr_reader.interface.allocRemaining(gpa, .unlimited); + stderr = stderr_reader.interface.allocRemaining(gpa, .unlimited) catch |err| switch (err) { + error.StreamTooLong => unreachable, // unlimited + error.OutOfMemory => |e| return e, + error.ReadFailed => return stderr_reader.err.?, + }; break :term rsp_child.wait(io) catch |err| break :err err; } },