diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index a9ed7b2390..57c1dfb945 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -49,8 +49,6 @@ pub const ChildProcess = struct { /// Set to change the current working directory when spawning the child process. /// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/5190 /// Once that is done, `cwd` will be deprecated in favor of this field. - /// The directory handle must be opened with the ability to be passed - /// to a child process (no `O_CLOEXEC` flag on POSIX). cwd_dir: ?fs.Dir = null, err_pipe: if (builtin.os.tag == .windows) void else [2]os.fd_t, @@ -443,26 +441,17 @@ pub const ChildProcess = struct { // we are the parent const pid = @intCast(i32, pid_result); if (self.stdin_behavior == StdIo.Pipe) { - self.stdin = File{ - .handle = stdin_pipe[1], - .io_mode = std.io.mode, - }; + self.stdin = File{ .handle = stdin_pipe[1] }; } else { self.stdin = null; } if (self.stdout_behavior == StdIo.Pipe) { - self.stdout = File{ - .handle = stdout_pipe[0], - .io_mode = std.io.mode, - }; + self.stdout = File{ .handle = stdout_pipe[0] }; } else { self.stdout = null; } if (self.stderr_behavior == StdIo.Pipe) { - self.stderr = File{ - .handle = stderr_pipe[0], - .io_mode = std.io.mode, - }; + self.stderr = File{ .handle = stderr_pipe[0] }; } else { self.stderr = null; } @@ -686,26 +675,17 @@ pub const ChildProcess = struct { }; if (g_hChildStd_IN_Wr) |h| { - self.stdin = File{ - .handle = h, - .io_mode = io.mode, - }; + self.stdin = File{ .handle = h }; } else { self.stdin = null; } if (g_hChildStd_OUT_Rd) |h| { - self.stdout = File{ - .handle = h, - .io_mode = io.mode, - }; + self.stdout = File{ .handle = h }; } else { self.stdout = null; } if (g_hChildStd_ERR_Rd) |h| { - self.stderr = File{ - .handle = h, - .io_mode = io.mode, - }; + self.stderr = File{ .handle = h }; } else { self.stderr = null; } @@ -845,8 +825,8 @@ const ErrInt = std.meta.Int(false, @sizeOf(anyerror) * 8); fn writeIntFd(fd: i32, value: ErrInt) !void { const file = File{ .handle = fd, - .io_mode = .blocking, - .async_block_allowed = File.async_block_allowed_yes, + .capable_io_mode = .blocking, + .intended_io_mode = .blocking, }; file.outStream().writeIntNative(u64, @intCast(u64, value)) catch return error.SystemResources; } @@ -854,8 +834,8 @@ fn writeIntFd(fd: i32, value: ErrInt) !void { fn readIntFd(fd: i32) !ErrInt { const file = File{ .handle = fd, - .io_mode = .blocking, - .async_block_allowed = File.async_block_allowed_yes, + .capable_io_mode = .blocking, + .intended_io_mode = .blocking, }; return @intCast(ErrInt, file.inStream().readIntNative(u64) catch return error.SystemResources); } diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 4459907576..df84a8bbcb 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -112,39 +112,43 @@ pub fn detectTTYConfig() TTY.Config { /// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned. /// TODO multithreaded awareness pub fn dumpCurrentStackTrace(start_addr: ?usize) void { - const stderr = getStderrStream(); - if (builtin.strip_debug_info) { - noasync stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; - return; + noasync { + const stderr = getStderrStream(); + if (builtin.strip_debug_info) { + stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; + return; + } + const debug_info = getSelfDebugInfo() catch |err| { + stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return; + return; + }; + writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(), start_addr) catch |err| { + stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return; + return; + }; } - const debug_info = getSelfDebugInfo() catch |err| { - noasync stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return; - return; - }; - writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(), start_addr) catch |err| { - noasync stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return; - return; - }; } /// Tries to print the stack trace starting from the supplied base pointer to stderr, /// unbuffered, and ignores any error returned. /// TODO multithreaded awareness pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void { - const stderr = getStderrStream(); - if (builtin.strip_debug_info) { - noasync stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; - return; - } - const debug_info = getSelfDebugInfo() catch |err| { - noasync stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return; - return; - }; - const tty_config = detectTTYConfig(); - printSourceAtAddress(debug_info, stderr, ip, tty_config) catch return; - var it = StackIterator.init(null, bp); - while (it.next()) |return_address| { - printSourceAtAddress(debug_info, stderr, return_address - 1, tty_config) catch return; + noasync { + const stderr = getStderrStream(); + if (builtin.strip_debug_info) { + stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; + return; + } + const debug_info = getSelfDebugInfo() catch |err| { + stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return; + return; + }; + const tty_config = detectTTYConfig(); + printSourceAtAddress(debug_info, stderr, ip, tty_config) catch return; + var it = StackIterator.init(null, bp); + while (it.next()) |return_address| { + printSourceAtAddress(debug_info, stderr, return_address - 1, tty_config) catch return; + } } } @@ -199,19 +203,21 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace /// Tries to print a stack trace to stderr, unbuffered, and ignores any error returned. /// TODO multithreaded awareness pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void { - const stderr = getStderrStream(); - if (builtin.strip_debug_info) { - noasync stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; - return; + noasync { + const stderr = getStderrStream(); + if (builtin.strip_debug_info) { + stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; + return; + } + const debug_info = getSelfDebugInfo() catch |err| { + stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return; + return; + }; + writeStackTrace(stack_trace, stderr, getDebugInfoAllocator(), debug_info, detectTTYConfig()) catch |err| { + stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return; + return; + }; } - const debug_info = getSelfDebugInfo() catch |err| { - noasync stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return; - return; - }; - writeStackTrace(stack_trace, stderr, getDebugInfoAllocator(), debug_info, detectTTYConfig()) catch |err| { - noasync stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return; - return; - }; } /// This function invokes undefined behavior when `ok` is `false`. @@ -255,7 +261,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c resetSegfaultHandler(); } - switch (panic_stage) { + noasync switch (panic_stage) { 0 => { panic_stage = 1; @@ -267,7 +273,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c defer held.release(); const stderr = getStderrStream(); - noasync stderr.print(format ++ "\n", args) catch os.abort(); + stderr.print(format ++ "\n", args) catch os.abort(); if (trace) |t| { dumpStackTrace(t.*); } @@ -292,12 +298,12 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c // we're still holding the mutex but that's fine as we're going to // call abort() const stderr = getStderrStream(); - noasync stderr.print("Panicked during a panic. Aborting.\n", .{}) catch os.abort(); + stderr.print("Panicked during a panic. Aborting.\n", .{}) catch os.abort(); }, else => { // Panicked while printing "Panicked during a panic." }, - } + }; os.abort(); } @@ -666,158 +672,160 @@ pub fn openSelfDebugInfo(allocator: *mem.Allocator) anyerror!DebugInfo { /// TODO resources https://github.com/ziglang/zig/issues/4353 fn openCoffDebugInfo(allocator: *mem.Allocator, coff_file_path: [:0]const u16) !ModuleDebugInfo { - const coff_file = try std.fs.openFileAbsoluteW(coff_file_path.ptr, .{}); - errdefer coff_file.close(); + noasync { + const coff_file = try std.fs.openFileAbsoluteW(coff_file_path, .{ .intended_io_mode = .blocking }); + errdefer coff_file.close(); - const coff_obj = try allocator.create(coff.Coff); - coff_obj.* = coff.Coff.init(allocator, coff_file); + const coff_obj = try allocator.create(coff.Coff); + coff_obj.* = coff.Coff.init(allocator, coff_file); - var di = ModuleDebugInfo{ - .base_address = undefined, - .coff = coff_obj, - .pdb = undefined, - .sect_contribs = undefined, - .modules = undefined, - }; - - try di.coff.loadHeader(); - - var path_buf: [windows.MAX_PATH]u8 = undefined; - const len = try di.coff.getPdbPath(path_buf[0..]); - const raw_path = path_buf[0..len]; - - const path = try fs.path.resolve(allocator, &[_][]const u8{raw_path}); - - try di.pdb.openFile(di.coff, path); - - var pdb_stream = di.pdb.getStream(pdb.StreamType.Pdb) orelse return error.InvalidDebugInfo; - const version = try pdb_stream.inStream().readIntLittle(u32); - const signature = try pdb_stream.inStream().readIntLittle(u32); - const age = try pdb_stream.inStream().readIntLittle(u32); - var guid: [16]u8 = undefined; - try pdb_stream.inStream().readNoEof(&guid); - if (version != 20000404) // VC70, only value observed by LLVM team - return error.UnknownPDBVersion; - if (!mem.eql(u8, &di.coff.guid, &guid) or di.coff.age != age) - return error.PDBMismatch; - // We validated the executable and pdb match. - - const string_table_index = str_tab_index: { - const name_bytes_len = try pdb_stream.inStream().readIntLittle(u32); - const name_bytes = try allocator.alloc(u8, name_bytes_len); - try pdb_stream.inStream().readNoEof(name_bytes); - - const HashTableHeader = packed struct { - Size: u32, - Capacity: u32, - - fn maxLoad(cap: u32) u32 { - return cap * 2 / 3 + 1; - } + var di = ModuleDebugInfo{ + .base_address = undefined, + .coff = coff_obj, + .pdb = undefined, + .sect_contribs = undefined, + .modules = undefined, }; - const hash_tbl_hdr = try pdb_stream.inStream().readStruct(HashTableHeader); - if (hash_tbl_hdr.Capacity == 0) - return error.InvalidDebugInfo; - if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity)) - return error.InvalidDebugInfo; + try di.coff.loadHeader(); - const present = try readSparseBitVector(&pdb_stream.inStream(), allocator); - if (present.len != hash_tbl_hdr.Size) - return error.InvalidDebugInfo; - const deleted = try readSparseBitVector(&pdb_stream.inStream(), allocator); + var path_buf: [windows.MAX_PATH]u8 = undefined; + const len = try di.coff.getPdbPath(path_buf[0..]); + const raw_path = path_buf[0..len]; - const Bucket = struct { - first: u32, - second: u32, - }; - const bucket_list = try allocator.alloc(Bucket, present.len); - for (present) |_| { - const name_offset = try pdb_stream.inStream().readIntLittle(u32); - const name_index = try pdb_stream.inStream().readIntLittle(u32); - const name = mem.spanZ(@ptrCast([*:0]u8, name_bytes.ptr + name_offset)); - if (mem.eql(u8, name, "/names")) { - break :str_tab_index name_index; + const path = try fs.path.resolve(allocator, &[_][]const u8{raw_path}); + + try di.pdb.openFile(di.coff, path); + + var pdb_stream = di.pdb.getStream(pdb.StreamType.Pdb) orelse return error.InvalidDebugInfo; + const version = try pdb_stream.inStream().readIntLittle(u32); + const signature = try pdb_stream.inStream().readIntLittle(u32); + const age = try pdb_stream.inStream().readIntLittle(u32); + var guid: [16]u8 = undefined; + try pdb_stream.inStream().readNoEof(&guid); + if (version != 20000404) // VC70, only value observed by LLVM team + return error.UnknownPDBVersion; + if (!mem.eql(u8, &di.coff.guid, &guid) or di.coff.age != age) + return error.PDBMismatch; + // We validated the executable and pdb match. + + const string_table_index = str_tab_index: { + const name_bytes_len = try pdb_stream.inStream().readIntLittle(u32); + const name_bytes = try allocator.alloc(u8, name_bytes_len); + try pdb_stream.inStream().readNoEof(name_bytes); + + const HashTableHeader = packed struct { + Size: u32, + Capacity: u32, + + fn maxLoad(cap: u32) u32 { + return cap * 2 / 3 + 1; + } + }; + const hash_tbl_hdr = try pdb_stream.inStream().readStruct(HashTableHeader); + if (hash_tbl_hdr.Capacity == 0) + return error.InvalidDebugInfo; + + if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity)) + return error.InvalidDebugInfo; + + const present = try readSparseBitVector(&pdb_stream.inStream(), allocator); + if (present.len != hash_tbl_hdr.Size) + return error.InvalidDebugInfo; + const deleted = try readSparseBitVector(&pdb_stream.inStream(), allocator); + + const Bucket = struct { + first: u32, + second: u32, + }; + const bucket_list = try allocator.alloc(Bucket, present.len); + for (present) |_| { + const name_offset = try pdb_stream.inStream().readIntLittle(u32); + const name_index = try pdb_stream.inStream().readIntLittle(u32); + const name = mem.spanZ(@ptrCast([*:0]u8, name_bytes.ptr + name_offset)); + if (mem.eql(u8, name, "/names")) { + break :str_tab_index name_index; + } } - } - return error.MissingDebugInfo; - }; + return error.MissingDebugInfo; + }; - di.pdb.string_table = di.pdb.getStreamById(string_table_index) orelse return error.MissingDebugInfo; - di.pdb.dbi = di.pdb.getStream(pdb.StreamType.Dbi) orelse return error.MissingDebugInfo; + di.pdb.string_table = di.pdb.getStreamById(string_table_index) orelse return error.MissingDebugInfo; + di.pdb.dbi = di.pdb.getStream(pdb.StreamType.Dbi) orelse return error.MissingDebugInfo; - const dbi = di.pdb.dbi; + const dbi = di.pdb.dbi; - // Dbi Header - const dbi_stream_header = try dbi.inStream().readStruct(pdb.DbiStreamHeader); - if (dbi_stream_header.VersionHeader != 19990903) // V70, only value observed by LLVM team - return error.UnknownPDBVersion; - if (dbi_stream_header.Age != age) - return error.UnmatchingPDB; + // Dbi Header + const dbi_stream_header = try dbi.inStream().readStruct(pdb.DbiStreamHeader); + if (dbi_stream_header.VersionHeader != 19990903) // V70, only value observed by LLVM team + return error.UnknownPDBVersion; + if (dbi_stream_header.Age != age) + return error.UnmatchingPDB; - const mod_info_size = dbi_stream_header.ModInfoSize; - const section_contrib_size = dbi_stream_header.SectionContributionSize; + const mod_info_size = dbi_stream_header.ModInfoSize; + const section_contrib_size = dbi_stream_header.SectionContributionSize; - var modules = ArrayList(Module).init(allocator); + var modules = ArrayList(Module).init(allocator); - // Module Info Substream - var mod_info_offset: usize = 0; - while (mod_info_offset != mod_info_size) { - const mod_info = try dbi.inStream().readStruct(pdb.ModInfo); - var this_record_len: usize = @sizeOf(pdb.ModInfo); + // Module Info Substream + var mod_info_offset: usize = 0; + while (mod_info_offset != mod_info_size) { + const mod_info = try dbi.inStream().readStruct(pdb.ModInfo); + var this_record_len: usize = @sizeOf(pdb.ModInfo); - const module_name = try dbi.readNullTermString(allocator); - this_record_len += module_name.len + 1; + const module_name = try dbi.readNullTermString(allocator); + this_record_len += module_name.len + 1; - const obj_file_name = try dbi.readNullTermString(allocator); - this_record_len += obj_file_name.len + 1; + const obj_file_name = try dbi.readNullTermString(allocator); + this_record_len += obj_file_name.len + 1; - if (this_record_len % 4 != 0) { - const round_to_next_4 = (this_record_len | 0x3) + 1; - const march_forward_bytes = round_to_next_4 - this_record_len; - try dbi.seekBy(@intCast(isize, march_forward_bytes)); - this_record_len += march_forward_bytes; + if (this_record_len % 4 != 0) { + const round_to_next_4 = (this_record_len | 0x3) + 1; + const march_forward_bytes = round_to_next_4 - this_record_len; + try dbi.seekBy(@intCast(isize, march_forward_bytes)); + this_record_len += march_forward_bytes; + } + + try modules.append(Module{ + .mod_info = mod_info, + .module_name = module_name, + .obj_file_name = obj_file_name, + + .populated = false, + .symbols = undefined, + .subsect_info = undefined, + .checksum_offset = null, + }); + + mod_info_offset += this_record_len; + if (mod_info_offset > mod_info_size) + return error.InvalidDebugInfo; } - try modules.append(Module{ - .mod_info = mod_info, - .module_name = module_name, - .obj_file_name = obj_file_name, + di.modules = modules.toOwnedSlice(); - .populated = false, - .symbols = undefined, - .subsect_info = undefined, - .checksum_offset = null, - }); + // Section Contribution Substream + var sect_contribs = ArrayList(pdb.SectionContribEntry).init(allocator); + var sect_cont_offset: usize = 0; + if (section_contrib_size != 0) { + const ver = @intToEnum(pdb.SectionContrSubstreamVersion, try dbi.inStream().readIntLittle(u32)); + if (ver != pdb.SectionContrSubstreamVersion.Ver60) + return error.InvalidDebugInfo; + sect_cont_offset += @sizeOf(u32); + } + while (sect_cont_offset != section_contrib_size) { + const entry = try sect_contribs.addOne(); + entry.* = try dbi.inStream().readStruct(pdb.SectionContribEntry); + sect_cont_offset += @sizeOf(pdb.SectionContribEntry); - mod_info_offset += this_record_len; - if (mod_info_offset > mod_info_size) - return error.InvalidDebugInfo; + if (sect_cont_offset > section_contrib_size) + return error.InvalidDebugInfo; + } + + di.sect_contribs = sect_contribs.toOwnedSlice(); + + return di; } - - di.modules = modules.toOwnedSlice(); - - // Section Contribution Substream - var sect_contribs = ArrayList(pdb.SectionContribEntry).init(allocator); - var sect_cont_offset: usize = 0; - if (section_contrib_size != 0) { - const ver = @intToEnum(pdb.SectionContrSubstreamVersion, try dbi.inStream().readIntLittle(u32)); - if (ver != pdb.SectionContrSubstreamVersion.Ver60) - return error.InvalidDebugInfo; - sect_cont_offset += @sizeOf(u32); - } - while (sect_cont_offset != section_contrib_size) { - const entry = try sect_contribs.addOne(); - entry.* = try dbi.inStream().readStruct(pdb.SectionContribEntry); - sect_cont_offset += @sizeOf(pdb.SectionContribEntry); - - if (sect_cont_offset > section_contrib_size) - return error.InvalidDebugInfo; - } - - di.sect_contribs = sect_contribs.toOwnedSlice(); - - return di; } fn readSparseBitVector(stream: var, allocator: *mem.Allocator) ![]usize { @@ -1001,7 +1009,7 @@ fn openMachODebugInfo(allocator: *mem.Allocator, macho_file_path: []const u8) !M fn printLineFromFileAnyOs(out_stream: var, line_info: LineInfo) !void { // Need this to always block even in async I/O mode, because this could potentially // be called from e.g. the event loop code crashing. - var f = try fs.cwd().openFile(line_info.file_name, .{ .always_blocking = true }); + var f = try fs.cwd().openFile(line_info.file_name, .{ .intended_io_mode = .blocking }); defer f.close(); // TODO fstat and make sure that the file has the correct size @@ -1049,7 +1057,7 @@ const MachoSymbol = struct { fn mapWholeFile(path: []const u8) ![]align(mem.page_size) const u8 { noasync { - const file = try fs.cwd().openFile(path, .{ .always_blocking = true }); + const file = try fs.cwd().openFile(path, .{ .intended_io_mode = .blocking }); defer file.close(); const file_len = try math.cast(usize, try file.getEndPos()); @@ -1410,59 +1418,61 @@ pub const ModuleDebugInfo = switch (builtin.os.tag) { } fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo { - // Translate the VA into an address into this object - const relocated_address = address - self.base_address; - assert(relocated_address >= 0x100000000); + noasync { + // Translate the VA into an address into this object + const relocated_address = address - self.base_address; + assert(relocated_address >= 0x100000000); - // Find the .o file where this symbol is defined - const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse - return SymbolInfo{}; + // Find the .o file where this symbol is defined + const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse + return SymbolInfo{}; - // Take the symbol name from the N_FUN STAB entry, we're going to - // use it if we fail to find the DWARF infos - const stab_symbol = mem.spanZ(self.strings[symbol.nlist.n_strx..]); + // Take the symbol name from the N_FUN STAB entry, we're going to + // use it if we fail to find the DWARF infos + const stab_symbol = mem.spanZ(self.strings[symbol.nlist.n_strx..]); - if (symbol.ofile == null) - return SymbolInfo{ .symbol_name = stab_symbol }; - - const o_file_path = mem.spanZ(self.strings[symbol.ofile.?.n_strx..]); - - // Check if its debug infos are already in the cache - var o_file_di = self.ofiles.getValue(o_file_path) orelse - (self.loadOFile(o_file_path) catch |err| switch (err) { - error.FileNotFound, - error.MissingDebugInfo, - error.InvalidDebugInfo, - => { + if (symbol.ofile == null) return SymbolInfo{ .symbol_name = stab_symbol }; - }, - else => return err, - }); - // Translate again the address, this time into an address inside the - // .o file - const relocated_address_o = relocated_address - symbol.reloc; + const o_file_path = mem.spanZ(self.strings[symbol.ofile.?.n_strx..]); - if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| { - return SymbolInfo{ - .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???", - .compile_unit_name = compile_unit.die.getAttrString(&o_file_di, DW.AT_name) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => "???", - else => return err, + // Check if its debug infos are already in the cache + var o_file_di = self.ofiles.getValue(o_file_path) orelse + (self.loadOFile(o_file_path) catch |err| switch (err) { + error.FileNotFound, + error.MissingDebugInfo, + error.InvalidDebugInfo, + => { + return SymbolInfo{ .symbol_name = stab_symbol }; }, - .line_info = o_file_di.getLineNumberInfo(compile_unit.*, relocated_address_o) catch |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => null, - else => return err, + else => return err, + }); + + // Translate again the address, this time into an address inside the + // .o file + const relocated_address_o = relocated_address - symbol.reloc; + + if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| { + return SymbolInfo{ + .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???", + .compile_unit_name = compile_unit.die.getAttrString(&o_file_di, DW.AT_name) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => "???", + else => return err, + }, + .line_info = o_file_di.getLineNumberInfo(compile_unit.*, relocated_address_o) catch |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => null, + else => return err, + }, + }; + } else |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => { + return SymbolInfo{ .symbol_name = stab_symbol }; }, - }; - } else |err| switch (err) { - error.MissingDebugInfo, error.InvalidDebugInfo => { - return SymbolInfo{ .symbol_name = stab_symbol }; - }, - else => return err, + else => return err, + } + + unreachable; } - - unreachable; } }, .uefi, .windows => struct { diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index 110d476b10..70e26cc71c 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -328,14 +328,14 @@ pub const WindowsDynLib = struct { pub fn open(path: []const u8) !WindowsDynLib { const path_w = try windows.sliceToPrefixedFileW(path); - return openW(&path_w); + return openW(path_w.span().ptr); } pub const openC = @compileError("deprecated: renamed to openZ"); pub fn openZ(path_c: [*:0]const u8) !WindowsDynLib { const path_w = try windows.cStrToPrefixedFileW(path_c); - return openW(&path_w); + return openW(path_w.span().ptr); } pub fn openW(path_w: [*:0]const u16) !WindowsDynLib { diff --git a/lib/std/event/loop.zig b/lib/std/event/loop.zig index fc122a1862..6fa65f90dd 100644 --- a/lib/std/event/loop.zig +++ b/lib/std/event/loop.zig @@ -4,19 +4,27 @@ const root = @import("root"); const assert = std.debug.assert; const testing = std.testing; const mem = std.mem; -const AtomicRmwOp = builtin.AtomicRmwOp; -const AtomicOrder = builtin.AtomicOrder; const os = std.os; const windows = os.windows; const maxInt = std.math.maxInt; const Thread = std.Thread; +const is_windows = std.Target.current.os.tag == .windows; + pub const Loop = struct { next_tick_queue: std.atomic.Queue(anyframe), os_data: OsData, final_resume_node: ResumeNode, pending_event_count: usize, extra_threads: []*Thread, + /// TODO change this to a pool of configurable number of threads + /// and rename it to be not file-system-specific. it will become + /// a thread pool for turning non-CPU-bound blocking things into + /// async things. A fallback for any missing OS-specific API. + fs_thread: *Thread, + fs_queue: std.atomic.Queue(Request), + fs_end_request: Request.Node, + fs_thread_wakeup: std.ResetEvent, /// For resources that have the same lifetime as the `Loop`. /// This is only used by `Loop` for the thread pool and associated resources. @@ -143,7 +151,12 @@ pub const Loop = struct { .handle = undefined, .overlapped = ResumeNode.overlapped_init, }, + .fs_end_request = .{ .data = .{ .msg = .end, .finish = .NoAction } }, + .fs_queue = std.atomic.Queue(Request).init(), + .fs_thread = undefined, + .fs_thread_wakeup = std.ResetEvent.init(), }; + errdefer self.fs_thread_wakeup.deinit(); errdefer self.arena.deinit(); // We need at least one of these in case the fs thread wants to use onNextTick @@ -158,10 +171,19 @@ pub const Loop = struct { try self.initOsData(extra_thread_count); errdefer self.deinitOsData(); + + if (!builtin.single_threaded) { + self.fs_thread = try Thread.spawn(self, posixFsRun); + } + errdefer if (!builtin.single_threaded) { + self.posixFsRequest(&self.fs_end_request); + self.fs_thread.wait(); + }; } pub fn deinit(self: *Loop) void { self.deinitOsData(); + self.fs_thread_wakeup.deinit(); self.arena.deinit(); self.* = undefined; } @@ -173,21 +195,10 @@ pub const Loop = struct { const wakeup_bytes = [_]u8{0x1} ** 8; fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void { - switch (builtin.os.tag) { + noasync switch (builtin.os.tag) { .linux => { - self.os_data.fs_queue = std.atomic.Queue(Request).init(); - self.os_data.fs_queue_item = 0; - // we need another thread for the file system because Linux does not have an async - // file system I/O API. - self.os_data.fs_end_request = Request.Node{ - .data = Request{ - .msg = .end, - .finish = .NoAction, - }, - }; - errdefer { - while (self.available_eventfd_resume_nodes.pop()) |node| noasync os.close(node.data.eventfd); + while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd); } for (self.eventfd_resume_nodes) |*eventfd_node| { eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{ @@ -206,10 +217,10 @@ pub const Loop = struct { } self.os_data.epollfd = try os.epoll_create1(os.EPOLL_CLOEXEC); - errdefer noasync os.close(self.os_data.epollfd); + errdefer os.close(self.os_data.epollfd); self.os_data.final_eventfd = try os.eventfd(0, os.EFD_CLOEXEC | os.EFD_NONBLOCK); - errdefer noasync os.close(self.os_data.final_eventfd); + errdefer os.close(self.os_data.final_eventfd); self.os_data.final_eventfd_event = os.epoll_event{ .events = os.EPOLLIN, @@ -222,12 +233,6 @@ pub const Loop = struct { &self.os_data.final_eventfd_event, ); - self.os_data.fs_thread = try Thread.spawn(self, posixFsRun); - errdefer { - self.posixFsRequest(&self.os_data.fs_end_request); - self.os_data.fs_thread.wait(); - } - if (builtin.single_threaded) { assert(extra_thread_count == 0); return; @@ -236,7 +241,7 @@ pub const Loop = struct { var extra_thread_index: usize = 0; errdefer { // writing 8 bytes to an eventfd cannot fail - const amt = noasync os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable; + const amt = os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable; assert(amt == wakeup_bytes.len); while (extra_thread_index != 0) { extra_thread_index -= 1; @@ -249,22 +254,7 @@ pub const Loop = struct { }, .macosx, .freebsd, .netbsd, .dragonfly => { self.os_data.kqfd = try os.kqueue(); - errdefer noasync os.close(self.os_data.kqfd); - - self.os_data.fs_kqfd = try os.kqueue(); - errdefer noasync os.close(self.os_data.fs_kqfd); - - self.os_data.fs_queue = std.atomic.Queue(Request).init(); - // we need another thread for the file system because Darwin does not have an async - // file system I/O API. - self.os_data.fs_end_request = Request.Node{ - .prev = undefined, - .next = undefined, - .data = Request{ - .msg = .end, - .finish = .NoAction, - }, - }; + errdefer os.close(self.os_data.kqfd); const empty_kevs = &[0]os.Kevent{}; @@ -310,30 +300,6 @@ pub const Loop = struct { self.os_data.final_kevent.flags = os.EV_ENABLE; self.os_data.final_kevent.fflags = os.NOTE_TRIGGER; - self.os_data.fs_kevent_wake = os.Kevent{ - .ident = 0, - .filter = os.EVFILT_USER, - .flags = os.EV_ADD | os.EV_ENABLE, - .fflags = os.NOTE_TRIGGER, - .data = 0, - .udata = undefined, - }; - - self.os_data.fs_kevent_wait = os.Kevent{ - .ident = 0, - .filter = os.EVFILT_USER, - .flags = os.EV_ADD | os.EV_CLEAR, - .fflags = 0, - .data = 0, - .udata = undefined, - }; - - self.os_data.fs_thread = try Thread.spawn(self, posixFsRun); - errdefer { - self.posixFsRequest(&self.os_data.fs_end_request); - self.os_data.fs_thread.wait(); - } - if (builtin.single_threaded) { assert(extra_thread_count == 0); return; @@ -401,25 +367,24 @@ pub const Loop = struct { } }, else => {}, - } + }; } fn deinitOsData(self: *Loop) void { - switch (builtin.os.tag) { + noasync switch (builtin.os.tag) { .linux => { - noasync os.close(self.os_data.final_eventfd); - while (self.available_eventfd_resume_nodes.pop()) |node| noasync os.close(node.data.eventfd); - noasync os.close(self.os_data.epollfd); + os.close(self.os_data.final_eventfd); + while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd); + os.close(self.os_data.epollfd); }, .macosx, .freebsd, .netbsd, .dragonfly => { - noasync os.close(self.os_data.kqfd); - noasync os.close(self.os_data.fs_kqfd); + os.close(self.os_data.kqfd); }, .windows => { windows.CloseHandle(self.os_data.io_port); }, else => {}, - } + }; } /// resume_node must live longer than the anyframe that it holds a reference to. @@ -657,7 +622,7 @@ pub const Loop = struct { .freebsd, .netbsd, .dragonfly, - => self.os_data.fs_thread.wait(), + => self.fs_thread.wait(), else => {}, } @@ -694,23 +659,25 @@ pub const Loop = struct { /// call finishOneEvent when done pub fn beginOneEvent(self: *Loop) void { - _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); + _ = @atomicRmw(usize, &self.pending_event_count, .Add, 1, .SeqCst); } pub fn finishOneEvent(self: *Loop) void { - const prev = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst); - if (prev == 1) { + noasync { + const prev = @atomicRmw(usize, &self.pending_event_count, .Sub, 1, .SeqCst); + if (prev != 1) return; + // cause all the threads to stop + self.posixFsRequest(&self.fs_end_request); + switch (builtin.os.tag) { .linux => { - self.posixFsRequest(&self.os_data.fs_end_request); // writing 8 bytes to an eventfd cannot fail - const amt = noasync os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable; + const amt = os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable; assert(amt == wakeup_bytes.len); return; }, .macosx, .freebsd, .netbsd, .dragonfly => { - self.posixFsRequest(&self.os_data.fs_end_request); const final_kevent = @as(*const [1]os.Kevent, &self.os_data.final_kevent); const empty_kevs = &[0]os.Kevent{}; // cannot fail because we already added it and this just enables it @@ -1063,73 +1030,55 @@ pub const Loop = struct { fn posixFsRequest(self: *Loop, request_node: *Request.Node) void { self.beginOneEvent(); // finished in posixFsRun after processing the msg - self.os_data.fs_queue.put(request_node); - switch (builtin.os.tag) { - .macosx, .freebsd, .netbsd, .dragonfly => { - const fs_kevs = @as(*const [1]os.Kevent, &self.os_data.fs_kevent_wake); - const empty_kevs = &[0]os.Kevent{}; - _ = os.kevent(self.os_data.fs_kqfd, fs_kevs, empty_kevs, null) catch unreachable; - }, - .linux => { - @atomicStore(i32, &self.os_data.fs_queue_item, 1, AtomicOrder.SeqCst); - const rc = os.linux.futex_wake(&self.os_data.fs_queue_item, os.linux.FUTEX_WAKE, 1); - switch (os.linux.getErrno(rc)) { - 0 => {}, - os.EINVAL => unreachable, - else => unreachable, - } - }, - else => @compileError("Unsupported OS"), - } + self.fs_queue.put(request_node); + self.fs_thread_wakeup.set(); } fn posixFsCancel(self: *Loop, request_node: *Request.Node) void { - if (self.os_data.fs_queue.remove(request_node)) { + if (self.fs_queue.remove(request_node)) { self.finishOneEvent(); } } - // TODO make this whole function noasync - // https://github.com/ziglang/zig/issues/3157 fn posixFsRun(self: *Loop) void { - while (true) { - if (builtin.os.tag == .linux) { - @atomicStore(i32, &self.os_data.fs_queue_item, 0, .SeqCst); - } - while (self.os_data.fs_queue.get()) |node| { + noasync while (true) { + self.fs_thread_wakeup.reset(); + while (self.fs_queue.get()) |node| { switch (node.data.msg) { .end => return, .read => |*msg| { - msg.result = noasync os.read(msg.fd, msg.buf); + msg.result = os.read(msg.fd, msg.buf); }, .readv => |*msg| { - msg.result = noasync os.readv(msg.fd, msg.iov); + msg.result = os.readv(msg.fd, msg.iov); }, .write => |*msg| { - msg.result = noasync os.write(msg.fd, msg.bytes); + msg.result = os.write(msg.fd, msg.bytes); }, .writev => |*msg| { - msg.result = noasync os.writev(msg.fd, msg.iov); + msg.result = os.writev(msg.fd, msg.iov); }, .pwritev => |*msg| { - msg.result = noasync os.pwritev(msg.fd, msg.iov, msg.offset); + msg.result = os.pwritev(msg.fd, msg.iov, msg.offset); }, .pread => |*msg| { - msg.result = noasync os.pread(msg.fd, msg.buf, msg.offset); + msg.result = os.pread(msg.fd, msg.buf, msg.offset); }, .preadv => |*msg| { - msg.result = noasync os.preadv(msg.fd, msg.iov, msg.offset); + msg.result = os.preadv(msg.fd, msg.iov, msg.offset); }, .open => |*msg| { - msg.result = noasync os.openZ(msg.path, msg.flags, msg.mode); + if (is_windows) unreachable; // TODO + msg.result = os.openZ(msg.path, msg.flags, msg.mode); }, .openat => |*msg| { - msg.result = noasync os.openatZ(msg.fd, msg.path, msg.flags, msg.mode); + if (is_windows) unreachable; // TODO + msg.result = os.openatZ(msg.fd, msg.path, msg.flags, msg.mode); }, .faccessat => |*msg| { - msg.result = noasync os.faccessatZ(msg.dirfd, msg.path, msg.mode, msg.flags); + msg.result = os.faccessatZ(msg.dirfd, msg.path, msg.mode, msg.flags); }, - .close => |*msg| noasync os.close(msg.fd), + .close => |*msg| os.close(msg.fd), } switch (node.data.finish) { .TickNode => |*tick_node| self.onNextTick(tick_node), @@ -1137,22 +1086,8 @@ pub const Loop = struct { } self.finishOneEvent(); } - switch (builtin.os.tag) { - .linux => { - const rc = os.linux.futex_wait(&self.os_data.fs_queue_item, os.linux.FUTEX_WAIT, 0, null); - switch (os.linux.getErrno(rc)) { - 0, os.EINTR, os.EAGAIN => continue, - else => unreachable, - } - }, - .macosx, .freebsd, .netbsd, .dragonfly => { - const fs_kevs = @as(*const [1]os.Kevent, &self.os_data.fs_kevent_wait); - var out_kevs: [1]os.Kevent = undefined; - _ = os.kevent(self.os_data.fs_kqfd, fs_kevs, out_kevs[0..], null) catch unreachable; - }, - else => @compileError("Unsupported OS"), - } - } + self.fs_thread_wakeup.wait(); + }; } const OsData = switch (builtin.os.tag) { @@ -1168,22 +1103,12 @@ pub const Loop = struct { const KEventData = struct { kqfd: i32, final_kevent: os.Kevent, - fs_kevent_wake: os.Kevent, - fs_kevent_wait: os.Kevent, - fs_thread: *Thread, - fs_kqfd: i32, - fs_queue: std.atomic.Queue(Request), - fs_end_request: Request.Node, }; const LinuxOsData = struct { epollfd: i32, final_eventfd: i32, final_eventfd_event: os.linux.epoll_event, - fs_thread: *Thread, - fs_queue_item: i32, - fs_queue: std.atomic.Queue(Request), - fs_end_request: Request.Node, }; pub const Request = struct { @@ -1324,11 +1249,11 @@ test "std.event.Loop - basic" { loop.run(); } -async fn testEventLoop() i32 { +fn testEventLoop() i32 { return 1234; } -async fn testEventLoop2(h: anyframe->i32, did_it: *bool) void { +fn testEventLoop2(h: anyframe->i32, did_it: *bool) void { const value = await h; testing.expect(value == 1234); did_it.* = true; diff --git a/lib/std/fs.zig b/lib/std/fs.zig index df8bfe97d7..063b2f3fdc 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -8,6 +8,8 @@ const Allocator = std.mem.Allocator; const assert = std.debug.assert; const math = std.math; +const is_darwin = std.Target.current.os.tag.isDarwin(); + pub const path = @import("fs/path.zig"); pub const File = @import("fs/file.zig").File; @@ -197,7 +199,7 @@ pub const AtomicFile = struct { if (std.Target.current.os.tag == .windows) { const dest_path_w = try os.windows.sliceToPrefixedFileW(self.dest_basename); const tmp_path_w = try os.windows.cStrToPrefixedFileW(&self.tmp_path_buf); - try os.renameatW(self.dir.fd, &tmp_path_w, self.dir.fd, &dest_path_w, os.windows.TRUE); + try os.renameatW(self.dir.fd, tmp_path_w.span(), self.dir.fd, dest_path_w.span(), os.windows.TRUE); self.file_exists = false; } else { const dest_path_c = try os.toPosixPath(self.dest_basename); @@ -580,7 +582,7 @@ pub const Dir = struct { pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File { if (builtin.os.tag == .windows) { const path_w = try os.windows.sliceToPrefixedFileW(sub_path); - return self.openFileW(&path_w, flags); + return self.openFileW(path_w.span(), flags); } const path_c = try os.toPosixPath(sub_path); return self.openFileZ(&path_c, flags); @@ -592,13 +594,16 @@ pub const Dir = struct { pub fn openFileZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File { if (builtin.os.tag == .windows) { const path_w = try os.windows.cStrToPrefixedFileW(sub_path); - return self.openFileW(&path_w, flags); + return self.openFileW(path_w.span(), flags); } // Use the O_ locking flags if the os supports them // (Or if it's darwin, as darwin's `open` doesn't support the O_SYNC flag) - const has_flock_open_flags = @hasDecl(os, "O_EXLOCK") and !builtin.os.tag.isDarwin(); - const nonblocking_lock_flag = if (has_flock_open_flags and flags.lock_nonblocking) (os.O_NONBLOCK | os.O_SYNC) else @as(u32, 0); + const has_flock_open_flags = @hasDecl(os, "O_EXLOCK") and !is_darwin; + const nonblocking_lock_flag = if (has_flock_open_flags and flags.lock_nonblocking) + os.O_NONBLOCK | os.O_SYNC + else + @as(u32, 0); const lock_flag: u32 = if (has_flock_open_flags) switch (flags.lock) { .None => @as(u32, 0), .Shared => os.O_SHLOCK | nonblocking_lock_flag, @@ -606,14 +611,13 @@ pub const Dir = struct { } else 0; const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0; - const O_CLOEXEC: u32 = if (flags.share_with_child_process) 0 else os.O_CLOEXEC; - const os_flags = lock_flag | O_LARGEFILE | O_CLOEXEC | if (flags.write and flags.read) + const os_flags = lock_flag | O_LARGEFILE | os.O_CLOEXEC | if (flags.write and flags.read) @as(u32, os.O_RDWR) else if (flags.write) @as(u32, os.O_WRONLY) else @as(u32, os.O_RDONLY); - const fd = if (need_async_thread and !flags.always_blocking) + const fd = if (flags.intended_io_mode != .blocking) try std.event.Loop.instance.?.openatZ(self.fd, sub_path, os_flags, 0) else try os.openatZ(self.fd, sub_path, os_flags, 0); @@ -630,31 +634,32 @@ pub const Dir = struct { return File{ .handle = fd, - .io_mode = .blocking, - .async_block_allowed = if (flags.always_blocking) - File.async_block_allowed_yes - else - File.async_block_allowed_no, + .capable_io_mode = .blocking, + .intended_io_mode = flags.intended_io_mode, }; } /// Same as `openFile` but Windows-only and the path parameter is /// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. - pub fn openFileW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) File.OpenError!File { + pub fn openFileW(self: Dir, sub_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File { const w = os.windows; - const access_mask = w.SYNCHRONIZE | - (if (flags.read) @as(u32, w.GENERIC_READ) else 0) | - (if (flags.write) @as(u32, w.GENERIC_WRITE) else 0); - - const share_access = switch (flags.lock) { - .None => @as(?w.ULONG, null), - .Shared => w.FILE_SHARE_READ | w.FILE_SHARE_DELETE, - .Exclusive => w.FILE_SHARE_DELETE, - }; - return @as(File, .{ - .handle = try os.windows.OpenFileW(self.fd, sub_path_w, null, access_mask, share_access, flags.lock_nonblocking, w.FILE_OPEN), - .io_mode = .blocking, + .handle = try os.windows.OpenFile(sub_path_w, .{ + .dir = self.fd, + .access_mask = w.SYNCHRONIZE | + (if (flags.read) @as(u32, w.GENERIC_READ) else 0) | + (if (flags.write) @as(u32, w.GENERIC_WRITE) else 0), + .share_access = switch (flags.lock) { + .None => w.FILE_SHARE_WRITE | w.FILE_SHARE_READ | w.FILE_SHARE_DELETE, + .Shared => w.FILE_SHARE_READ | w.FILE_SHARE_DELETE, + .Exclusive => w.FILE_SHARE_DELETE, + }, + .share_access_nonblocking = flags.lock_nonblocking, + .creation = w.FILE_OPEN, + .io_mode = flags.intended_io_mode, + }), + .capable_io_mode = std.io.default_mode, + .intended_io_mode = flags.intended_io_mode, }); } @@ -664,7 +669,7 @@ pub const Dir = struct { pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File { if (builtin.os.tag == .windows) { const path_w = try os.windows.sliceToPrefixedFileW(sub_path); - return self.createFileW(&path_w, flags); + return self.createFileW(path_w.span(), flags); } const path_c = try os.toPosixPath(sub_path); return self.createFileZ(&path_c, flags); @@ -676,13 +681,16 @@ pub const Dir = struct { pub fn createFileZ(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File { if (builtin.os.tag == .windows) { const path_w = try os.windows.cStrToPrefixedFileW(sub_path_c); - return self.createFileW(&path_w, flags); + return self.createFileW(path_w.span(), flags); } // Use the O_ locking flags if the os supports them // (Or if it's darwin, as darwin's `open` doesn't support the O_SYNC flag) - const has_flock_open_flags = @hasDecl(os, "O_EXLOCK") and !builtin.os.tag.isDarwin(); - const nonblocking_lock_flag = if (has_flock_open_flags and flags.lock_nonblocking) (os.O_NONBLOCK | os.O_SYNC) else @as(u32, 0); + const has_flock_open_flags = @hasDecl(os, "O_EXLOCK") and !is_darwin; + const nonblocking_lock_flag: u32 = if (has_flock_open_flags and flags.lock_nonblocking) + os.O_NONBLOCK | os.O_SYNC + else + 0; const lock_flag: u32 = if (has_flock_open_flags) switch (flags.lock) { .None => @as(u32, 0), .Shared => os.O_SHLOCK, @@ -690,12 +698,11 @@ pub const Dir = struct { } else 0; const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0; - const O_CLOEXEC: u32 = if (flags.share_with_child_process) 0 else os.O_CLOEXEC; - const os_flags = lock_flag | O_LARGEFILE | os.O_CREAT | O_CLOEXEC | + const os_flags = lock_flag | O_LARGEFILE | os.O_CREAT | os.O_CLOEXEC | (if (flags.truncate) @as(u32, os.O_TRUNC) else 0) | (if (flags.read) @as(u32, os.O_RDWR) else os.O_WRONLY) | (if (flags.exclusive) @as(u32, os.O_EXCL) else 0); - const fd = if (need_async_thread) + const fd = if (flags.intended_io_mode != .blocking) try std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, flags.mode) else try os.openatZ(self.fd, sub_path_c, os_flags, flags.mode); @@ -710,31 +717,38 @@ pub const Dir = struct { }); } - return File{ .handle = fd, .io_mode = .blocking }; + return File{ + .handle = fd, + .capable_io_mode = .blocking, + .intended_io_mode = flags.intended_io_mode, + }; } /// Same as `createFile` but Windows-only and the path parameter is /// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. - pub fn createFileW(self: Dir, sub_path_w: [*:0]const u16, flags: File.CreateFlags) File.OpenError!File { + pub fn createFileW(self: Dir, sub_path_w: []const u16, flags: File.CreateFlags) File.OpenError!File { const w = os.windows; - const access_mask = w.SYNCHRONIZE | w.GENERIC_WRITE | - (if (flags.read) @as(u32, w.GENERIC_READ) else 0); - const creation = if (flags.exclusive) - @as(u32, w.FILE_CREATE) - else if (flags.truncate) - @as(u32, w.FILE_OVERWRITE_IF) - else - @as(u32, w.FILE_OPEN_IF); - - const share_access = switch (flags.lock) { - .None => @as(?w.ULONG, null), - .Shared => w.FILE_SHARE_READ | w.FILE_SHARE_DELETE, - .Exclusive => w.FILE_SHARE_DELETE, - }; - + const read_flag = if (flags.read) @as(u32, w.GENERIC_READ) else 0; return @as(File, .{ - .handle = try os.windows.OpenFileW(self.fd, sub_path_w, null, access_mask, share_access, flags.lock_nonblocking, creation), - .io_mode = .blocking, + .handle = try os.windows.OpenFile(sub_path_w, .{ + .dir = self.fd, + .access_mask = w.SYNCHRONIZE | w.GENERIC_WRITE | read_flag, + .share_access = switch (flags.lock) { + .None => w.FILE_SHARE_WRITE | w.FILE_SHARE_READ | w.FILE_SHARE_DELETE, + .Shared => w.FILE_SHARE_READ | w.FILE_SHARE_DELETE, + .Exclusive => w.FILE_SHARE_DELETE, + }, + .share_access_nonblocking = flags.lock_nonblocking, + .creation = if (flags.exclusive) + @as(u32, w.FILE_CREATE) + else if (flags.truncate) + @as(u32, w.FILE_OVERWRITE_IF) + else + @as(u32, w.FILE_OPEN_IF), + .io_mode = flags.intended_io_mode, + }), + .capable_io_mode = std.io.default_mode, + .intended_io_mode = flags.intended_io_mode, }); } @@ -818,11 +832,6 @@ pub const Dir = struct { /// `true` means the opened directory can be scanned for the files and sub-directories /// of the result. It means the `iterate` function can be called. iterate: bool = false, - - /// `true` means the opened directory can be passed to a child process. - /// `false` means the directory handle is considered to be closed when a child - /// process is spawned. This corresponds to the inverse of `O_CLOEXEC` on POSIX. - share_with_child_process: bool = false, }; /// Opens a directory at the given path. The directory is a system resource that remains @@ -832,7 +841,7 @@ pub const Dir = struct { pub fn openDir(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir { if (builtin.os.tag == .windows) { const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); - return self.openDirW(&sub_path_w, args); + return self.openDirW(sub_path_w.span().ptr, args); } else { const sub_path_c = try os.toPosixPath(sub_path); return self.openDirZ(&sub_path_c, args); @@ -845,14 +854,12 @@ pub const Dir = struct { pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) OpenError!Dir { if (builtin.os.tag == .windows) { const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c); - return self.openDirW(&sub_path_w, args); + return self.openDirW(sub_path_w.span().ptr, args); } else if (!args.iterate) { const O_PATH = if (@hasDecl(os, "O_PATH")) os.O_PATH else 0; - const O_CLOEXEC: u32 = if (args.share_with_child_process) 0 else os.O_CLOEXEC; - return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | O_CLOEXEC | O_PATH); + return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC | O_PATH); } else { - const O_CLOEXEC: u32 = if (args.share_with_child_process) 0 else os.O_CLOEXEC; - return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | O_CLOEXEC); + return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC); } } @@ -989,7 +996,7 @@ pub const Dir = struct { pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void { if (builtin.os.tag == .windows) { const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); - return self.deleteDirW(&sub_path_w); + return self.deleteDirW(sub_path_w.span().ptr); } const sub_path_c = try os.toPosixPath(sub_path); return self.deleteDirZ(&sub_path_c); @@ -1248,7 +1255,7 @@ pub const Dir = struct { pub fn access(self: Dir, sub_path: []const u8, flags: File.OpenFlags) AccessError!void { if (builtin.os.tag == .windows) { const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); - return self.accessW(&sub_path_w, flags); + return self.accessW(sub_path_w.span().ptr, flags); } const path_c = try os.toPosixPath(sub_path); return self.accessZ(&path_c, flags); @@ -1258,7 +1265,7 @@ pub const Dir = struct { pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) AccessError!void { if (builtin.os.tag == .windows) { const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path); - return self.accessW(&sub_path_w, flags); + return self.accessW(sub_path_w.span().ptr, flags); } const os_mode = if (flags.write and flags.read) @as(u32, os.R_OK | os.W_OK) @@ -1266,7 +1273,7 @@ pub const Dir = struct { @as(u32, os.W_OK) else @as(u32, os.F_OK); - const result = if (need_async_thread) + const result = if (need_async_thread and flags.intended_io_mode != .blocking) std.event.Loop.instance.?.faccessatZ(self.fd, sub_path, os_mode, 0) else os.faccessatZ(self.fd, sub_path, os_mode, 0); @@ -1408,8 +1415,8 @@ pub fn openFileAbsoluteZ(absolute_path_c: [*:0]const u8, flags: File.OpenFlags) } /// Same as `openFileAbsolute` but the path parameter is WTF-16 encoded. -pub fn openFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.OpenFlags) File.OpenError!File { - assert(path.isAbsoluteWindowsW(absolute_path_w)); +pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File { + assert(path.isAbsoluteWindowsWTF16(absolute_path_w)); return cwd().openFileW(absolute_path_w, flags); } @@ -1598,7 +1605,7 @@ pub fn openSelfExe() OpenSelfExeError!File { if (builtin.os.tag == .windows) { const wide_slice = selfExePathW(); const prefixed_path_w = try os.windows.wToPrefixedFileW(wide_slice); - return cwd().openFileW(&prefixed_path_w, .{}); + return cwd().openFileW(prefixed_path_w.span(), .{}); } var buf: [MAX_PATH_BYTES]u8 = undefined; const self_exe_path = try selfExePath(&buf); @@ -1626,7 +1633,7 @@ pub fn selfExePathAlloc(allocator: *Allocator) ![]u8 { /// been deleted, the file path looks something like `/a/b/c/exe (deleted)`. /// TODO make the return type of this a null terminated pointer pub fn selfExePath(out_buffer: *[MAX_PATH_BYTES]u8) SelfExePathError![]u8 { - if (comptime std.Target.current.isDarwin()) { + if (is_darwin) { var u32_len: u32 = out_buffer.len; const rc = std.c._NSGetExecutablePath(out_buffer, &u32_len); if (rc != 0) return error.NameTooLong; diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index aec8468bc0..b7c575a04a 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -8,7 +8,7 @@ const assert = std.debug.assert; const windows = os.windows; const Os = builtin.Os; const maxInt = std.math.maxInt; -const need_async_thread = std.fs.need_async_thread; +const is_windows = std.Target.current.os.tag == .windows; pub const File = struct { /// The OS-specific file descriptor or file handle. @@ -17,15 +17,14 @@ pub const File = struct { /// On some systems, such as Linux, file system file descriptors are incapable of non-blocking I/O. /// This forces us to perform asynchronous I/O on a dedicated thread, to achieve non-blocking /// file-system I/O. To do this, `File` must be aware of whether it is a file system file descriptor, - /// or, more specifically, whether the I/O is blocking. - io_mode: io.Mode, + /// or, more specifically, whether the I/O is always blocking. + capable_io_mode: io.ModeOverride = io.default_mode, - /// Even when 'std.io.mode' is async, it is still sometimes desirable to perform blocking I/O, although - /// not by default. For example, when printing a stack trace to stderr. - async_block_allowed: @TypeOf(async_block_allowed_no) = async_block_allowed_no, - - pub const async_block_allowed_yes = if (io.is_async) true else {}; - pub const async_block_allowed_no = if (io.is_async) false else {}; + /// Furthermore, even when `std.io.mode` is async, it is still sometimes desirable to perform blocking I/O, + /// although not by default. For example, when printing a stack trace to stderr. + /// This field tracks both by acting as an overriding I/O mode. When not building in async I/O mode, + /// the type only has the `.blocking` tag, making it a zero-bit type. + intended_io_mode: io.ModeOverride = io.default_mode, pub const Mode = os.mode_t; @@ -36,9 +35,7 @@ pub const File = struct { pub const OpenError = windows.CreateFileError || os.OpenError || os.FlockError; - pub const Lock = enum { - None, Shared, Exclusive - }; + pub const Lock = enum { None, Shared, Exclusive }; /// TODO https://github.com/ziglang/zig/issues/3802 pub const OpenFlags = struct { @@ -63,17 +60,15 @@ pub const File = struct { /// Sets whether or not to wait until the file is locked to return. If set to true, /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file /// is available to proceed. + /// In async I/O mode, non-blocking at the OS level is + /// determined by `intended_io_mode`, and `true` means `error.WouldBlock` is returned, + /// and `false` means `error.WouldBlock` is handled by the event loop. lock_nonblocking: bool = false, - /// This prevents `O_NONBLOCK` from being passed even if `std.io.is_async`. - /// It allows the use of `noasync` when calling functions related to opening - /// the file, reading, and writing. - always_blocking: bool = false, - - /// `true` means the opened directory can be passed to a child process. - /// `false` means the directory handle is considered to be closed when a child - /// process is spawned. This corresponds to the inverse of `O_CLOEXEC` on POSIX. - share_with_child_process: bool = false, + /// Setting this to `.blocking` prevents `O_NONBLOCK` from being passed even + /// if `std.io.is_async`. It allows the use of `noasync` when calling functions + /// related to opening the file, reading, writing, and locking. + intended_io_mode: io.ModeOverride = io.default_mode, }; /// TODO https://github.com/ziglang/zig/issues/3802 @@ -107,22 +102,27 @@ pub const File = struct { /// Sets whether or not to wait until the file is locked to return. If set to true, /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file /// is available to proceed. + /// In async I/O mode, non-blocking at the OS level is + /// determined by `intended_io_mode`, and `true` means `error.WouldBlock` is returned, + /// and `false` means `error.WouldBlock` is handled by the event loop. lock_nonblocking: bool = false, /// For POSIX systems this is the file system mode the file will /// be created with. mode: Mode = default_mode, - /// `true` means the opened directory can be passed to a child process. - /// `false` means the directory handle is considered to be closed when a child - /// process is spawned. This corresponds to the inverse of `O_CLOEXEC` on POSIX. - share_with_child_process: bool = false, + /// Setting this to `.blocking` prevents `O_NONBLOCK` from being passed even + /// if `std.io.is_async`. It allows the use of `noasync` when calling functions + /// related to opening the file, reading, writing, and locking. + intended_io_mode: io.ModeOverride = io.default_mode, }; /// Upon success, the stream is in an uninitialized state. To continue using it, /// you must use the open() function. pub fn close(self: File) void { - if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { + if (is_windows) { + windows.CloseHandle(self.handle); + } else if (self.capable_io_mode != self.intended_io_mode) { std.event.Loop.instance.?.close(self.handle); } else { os.close(self.handle); @@ -305,7 +305,9 @@ pub const File = struct { pub const PReadError = os.PReadError; pub fn read(self: File, buffer: []u8) ReadError!usize { - if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { + if (is_windows) { + return windows.ReadFile(self.handle, buffer, null, self.intended_io_mode); + } else if (self.capable_io_mode != self.intended_io_mode) { return std.event.Loop.instance.?.read(self.handle, buffer); } else { return os.read(self.handle, buffer); @@ -325,7 +327,9 @@ pub const File = struct { } pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize { - if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { + if (is_windows) { + return windows.ReadFile(self.handle, buffer, offset, self.intended_io_mode); + } else if (self.capable_io_mode != self.intended_io_mode) { return std.event.Loop.instance.?.pread(self.handle, buffer, offset); } else { return os.pread(self.handle, buffer, offset); @@ -345,7 +349,12 @@ pub const File = struct { } pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize { - if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { + if (is_windows) { + // TODO improve this to use ReadFileScatter + if (iovecs.len == 0) return @as(usize, 0); + const first = iovecs[0]; + return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], null, self.intended_io_mode); + } else if (self.capable_io_mode != self.intended_io_mode) { return std.event.Loop.instance.?.readv(self.handle, iovecs); } else { return os.readv(self.handle, iovecs); @@ -379,7 +388,12 @@ pub const File = struct { } pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) PReadError!usize { - if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { + if (is_windows) { + // TODO improve this to use ReadFileScatter + if (iovecs.len == 0) return @as(usize, 0); + const first = iovecs[0]; + return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], offset, self.intended_io_mode); + } else if (self.capable_io_mode != self.intended_io_mode) { return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset); } else { return os.preadv(self.handle, iovecs, offset); @@ -416,7 +430,9 @@ pub const File = struct { pub const PWriteError = os.PWriteError; pub fn write(self: File, bytes: []const u8) WriteError!usize { - if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { + if (is_windows) { + return windows.WriteFile(self.handle, bytes, null, self.intended_io_mode); + } else if (self.capable_io_mode != self.intended_io_mode) { return std.event.Loop.instance.?.write(self.handle, bytes); } else { return os.write(self.handle, bytes); @@ -431,7 +447,9 @@ pub const File = struct { } pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize { - if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { + if (is_windows) { + return windows.WriteFile(self.handle, bytes, offset, self.intended_io_mode); + } else if (self.capable_io_mode != self.intended_io_mode) { return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset); } else { return os.pwrite(self.handle, bytes, offset); @@ -446,7 +464,12 @@ pub const File = struct { } pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!usize { - if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { + if (is_windows) { + // TODO improve this to use WriteFileScatter + if (iovecs.len == 0) return @as(usize, 0); + const first = iovecs[0]; + return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], null, self.intended_io_mode); + } else if (self.capable_io_mode != self.intended_io_mode) { return std.event.Loop.instance.?.writev(self.handle, iovecs); } else { return os.writev(self.handle, iovecs); @@ -472,7 +495,12 @@ pub const File = struct { } pub fn pwritev(self: File, iovecs: []os.iovec_const, offset: usize) PWriteError!usize { - if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { + if (is_windows) { + // TODO improve this to use WriteFileScatter + if (iovecs.len == 0) return @as(usize, 0); + const first = iovecs[0]; + return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], offset, self.intended_io_mode); + } else if (self.capable_io_mode != self.intended_io_mode) { return std.event.Loop.instance.?.pwritev(self.handle, iovecs, offset); } else { return os.pwritev(self.handle, iovecs, offset); diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig index ba375645d5..e040330dd0 100644 --- a/lib/std/fs/path.zig +++ b/lib/std/fs/path.zig @@ -177,6 +177,10 @@ pub fn isAbsoluteWindowsW(path_w: [*:0]const u16) bool { return isAbsoluteWindowsImpl(u16, mem.spanZ(path_w)); } +pub fn isAbsoluteWindowsWTF16(path: []const u16) bool { + return isAbsoluteWindowsImpl(u16, path); +} + pub const isAbsoluteWindowsC = @compileError("deprecated: renamed to isAbsoluteWindowsZ"); pub fn isAbsoluteWindowsZ(path_c: [*:0]const u8) bool { diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 2d0141a5d9..7d238c7e92 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -27,7 +27,12 @@ test "open file with exclusive nonblocking lock twice" { } test "open file with lock twice, make sure it wasn't open at the same time" { - if (builtin.single_threaded) return; + if (builtin.single_threaded) return error.SkipZigTest; + + if (std.io.is_async) { + // This test starts its own threads and is not compatible with async I/O. + return error.SkipZigTest; + } const filename = "file_lock_test.txt"; @@ -58,6 +63,11 @@ test "open file with lock twice, make sure it wasn't open at the same time" { test "create file, lock and read from multiple process at once" { if (builtin.single_threaded) return error.SkipZigTest; + if (std.io.is_async) { + // This test starts its own threads and is not compatible with async I/O. + return error.SkipZigTest; + } + if (true) { // https://github.com/ziglang/zig/issues/5006 return error.SkipZigTest; diff --git a/lib/std/io.zig b/lib/std/io.zig index b498f2a299..f6c3325716 100644 --- a/lib/std/io.zig +++ b/lib/std/io.zig @@ -30,6 +30,11 @@ else Mode.blocking; pub const is_async = mode != .blocking; +/// This is an enum value to use for I/O mode at runtime, since it takes up zero bytes at runtime, +/// and makes expressions comptime-known when `is_async` is `false`. +pub const ModeOverride = if (is_async) Mode else enum { blocking }; +pub const default_mode: ModeOverride = if (is_async) Mode.evented else .blocking; + fn getStdOutHandle() os.fd_t { if (builtin.os.tag == .windows) { return os.windows.peb().ProcessParameters.hStdOutput; @@ -42,10 +47,13 @@ fn getStdOutHandle() os.fd_t { return os.STDOUT_FILENO; } +/// TODO: async stdout on windows without a dedicated thread. +/// https://github.com/ziglang/zig/pull/4816#issuecomment-604521023 pub fn getStdOut() File { return File{ .handle = getStdOutHandle(), - .io_mode = .blocking, + .capable_io_mode = .blocking, + .intended_io_mode = default_mode, }; } @@ -61,11 +69,13 @@ fn getStdErrHandle() os.fd_t { return os.STDERR_FILENO; } +/// This returns a `File` that is configured to block with every write, in order +/// to facilitate better debugging. This can be changed by modifying the `intended_io_mode` field. pub fn getStdErr() File { return File{ .handle = getStdErrHandle(), - .io_mode = .blocking, - .async_block_allowed = File.async_block_allowed_yes, + .capable_io_mode = .blocking, + .intended_io_mode = .blocking, }; } @@ -81,10 +91,13 @@ fn getStdInHandle() os.fd_t { return os.STDIN_FILENO; } +/// TODO: async stdin on windows without a dedicated thread. +/// https://github.com/ziglang/zig/pull/4816#issuecomment-604521023 pub fn getStdIn() File { return File{ .handle = getStdInHandle(), - .io_mode = .blocking, + .capable_io_mode = .blocking, + .intended_io_mode = default_mode, }; } diff --git a/lib/std/net.zig b/lib/std/net.zig index 9e57aa500b..1c7355bcb2 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -412,7 +412,7 @@ pub fn tcpConnectToAddress(address: Address) !fs.File { errdefer os.close(sockfd); try os.connect(sockfd, &address.any, address.getOsSockLen()); - return fs.File{ .handle = sockfd, .io_mode = std.io.mode }; + return fs.File{ .handle = sockfd }; } /// Call `AddressList.deinit` on the result. @@ -1381,10 +1381,7 @@ pub const StreamServer = struct { var adr_len: os.socklen_t = @sizeOf(Address); if (os.accept4(self.sockfd.?, &accepted_addr.any, &adr_len, accept_flags)) |fd| { return Connection{ - .file = fs.File{ - .handle = fd, - .io_mode = std.io.mode, - }, + .file = fs.File{ .handle = fd }, .address = accepted_addr, }; } else |err| switch (err) { diff --git a/lib/std/os.zig b/lib/std/os.zig index 5a502035d5..90c74738b8 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -177,8 +177,8 @@ fn getRandomBytesDevURandom(buf: []u8) !void { const file = std.fs.File{ .handle = fd, - .io_mode = .blocking, - .async_block_allowed = std.fs.File.async_block_allowed_yes, + .capable_io_mode = .blocking, + .intended_io_mode = .blocking, }; const stream = file.inStream(); stream.readNoEof(buf) catch return error.Unexpected; @@ -309,7 +309,7 @@ pub const ReadError = error{ /// For POSIX the limit is `math.maxInt(isize)`. pub fn read(fd: fd_t, buf: []u8) ReadError!usize { if (builtin.os.tag == .windows) { - return windows.ReadFile(fd, buf, null); + return windows.ReadFile(fd, buf, null, std.io.default_mode); } if (builtin.os.tag == .wasi and !builtin.link_libc) { @@ -369,7 +369,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { /// On these systems, the read races with concurrent writes to the same file descriptor. pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { if (std.Target.current.os.tag == .windows) { - // TODO does Windows have a way to read an io vector? + // TODO improve this to use ReadFileScatter if (iov.len == 0) return @as(usize, 0); const first = iov[0]; return read(fd, first.iov_base[0..first.iov_len]); @@ -412,7 +412,7 @@ pub const PReadError = ReadError || error{Unseekable}; /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { if (builtin.os.tag == .windows) { - return windows.ReadFile(fd, buf, offset); + return windows.ReadFile(fd, buf, offset, std.io.default_mode); } while (true) { @@ -588,7 +588,7 @@ pub const WriteError = error{ /// The corresponding POSIX limit is `math.maxInt(isize)`. pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { if (builtin.os.tag == .windows) { - return windows.WriteFile(fd, bytes, null); + return windows.WriteFile(fd, bytes, null, std.io.default_mode); } if (builtin.os.tag == .wasi and !builtin.link_libc) { @@ -655,7 +655,7 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { /// If `iov.len` is larger than will fit in a `u31`, a partial write will occur. pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize { if (std.Target.current.os.tag == .windows) { - // TODO does Windows have a way to write an io vector? + // TODO improve this to use WriteFileScatter if (iov.len == 0) return @as(usize, 0); const first = iov[0]; return write(fd, first.iov_base[0..first.iov_len]); @@ -713,7 +713,7 @@ pub const PWriteError = WriteError || error{Unseekable}; /// The corresponding POSIX limit is `math.maxInt(isize)`. pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { if (std.Target.current.os.tag == .windows) { - return windows.WriteFile(fd, bytes, offset); + return windows.WriteFile(fd, bytes, offset, std.io.default_mode); } // Prevent EINVAL. @@ -858,8 +858,11 @@ pub const OpenError = error{ /// Open and possibly create a file. Keeps trying if it gets interrupted. /// See also `openC`. -/// TODO support windows pub fn open(file_path: []const u8, flags: u32, perm: mode_t) OpenError!fd_t { + if (std.Target.current.os.tag == .windows) { + const file_path_w = try windows.sliceToPrefixedFileW(file_path); + return openW(file_path_w.span(), flags, perm); + } const file_path_c = try toPosixPath(file_path); return openZ(&file_path_c, flags, perm); } @@ -868,8 +871,11 @@ pub const openC = @compileError("deprecated: renamed to openZ"); /// Open and possibly create a file. Keeps trying if it gets interrupted. /// See also `open`. -/// TODO support windows pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t { + if (std.Target.current.os.tag == .windows) { + const file_path_w = try windows.cStrToPrefixedFileW(file_path); + return openW(file_path_w.span(), flags, perm); + } while (true) { const rc = system.open(file_path, flags, perm); switch (errno(rc)) { @@ -899,6 +905,13 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t } } +/// Windows-only. The path parameter is +/// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. +/// Translates the POSIX open API call to a Windows API call. +pub fn openW(file_path_w: []const u16, flags: u32, perm: usize) OpenError!fd_t { + @compileError("TODO implement openW for windows"); +} + /// Open and possibly create a file. Keeps trying if it gets interrupted. /// `file_path` is relative to the open directory handle `dir_fd`. /// See also `openatC`. @@ -1308,7 +1321,7 @@ pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError! if (builtin.os.tag == .windows) { const target_path_w = try windows.sliceToPrefixedFileW(target_path); const sym_link_path_w = try windows.sliceToPrefixedFileW(sym_link_path); - return windows.CreateSymbolicLinkW(&sym_link_path_w, &target_path_w, 0); + return windows.CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, 0); } else { const target_path_c = try toPosixPath(target_path); const sym_link_path_c = try toPosixPath(sym_link_path); @@ -1324,7 +1337,7 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin if (builtin.os.tag == .windows) { const target_path_w = try windows.cStrToPrefixedFileW(target_path); const sym_link_path_w = try windows.cStrToPrefixedFileW(sym_link_path); - return windows.CreateSymbolicLinkW(&sym_link_path_w, &target_path_w, 0); + return windows.CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, 0); } switch (errno(system.symlink(target_path, sym_link_path))) { 0 => return, @@ -1400,7 +1413,7 @@ pub const UnlinkError = error{ pub fn unlink(file_path: []const u8) UnlinkError!void { if (builtin.os.tag == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); - return windows.DeleteFileW(&file_path_w); + return windows.DeleteFileW(file_path_w.span().ptr); } else { const file_path_c = try toPosixPath(file_path); return unlinkZ(&file_path_c); @@ -1413,7 +1426,7 @@ pub const unlinkC = @compileError("deprecated: renamed to unlinkZ"); pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { if (builtin.os.tag == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(file_path); - return windows.DeleteFileW(&file_path_w); + return windows.DeleteFileW(file_path_w.span().ptr); } switch (errno(system.unlink(file_path))) { 0 => return, @@ -1444,7 +1457,7 @@ pub const UnlinkatError = UnlinkError || error{ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { if (builtin.os.tag == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); - return unlinkatW(dirfd, &file_path_w, flags); + return unlinkatW(dirfd, file_path_w.span().ptr, flags); } const file_path_c = try toPosixPath(file_path); return unlinkatZ(dirfd, &file_path_c, flags); @@ -1456,7 +1469,7 @@ pub const unlinkatC = @compileError("deprecated: renamed to unlinkatZ"); pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void { if (builtin.os.tag == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(file_path_c); - return unlinkatW(dirfd, &file_path_w, flags); + return unlinkatW(dirfd, file_path_w.span().ptr, flags); } switch (errno(system.unlinkat(dirfd, file_path_c, flags))) { 0 => return, @@ -1571,7 +1584,7 @@ pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { if (builtin.os.tag == .windows) { const old_path_w = try windows.sliceToPrefixedFileW(old_path); const new_path_w = try windows.sliceToPrefixedFileW(new_path); - return renameW(&old_path_w, &new_path_w); + return renameW(old_path_w.span().ptr, new_path_w.span().ptr); } else { const old_path_c = try toPosixPath(old_path); const new_path_c = try toPosixPath(new_path); @@ -1586,7 +1599,7 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi if (builtin.os.tag == .windows) { const old_path_w = try windows.cStrToPrefixedFileW(old_path); const new_path_w = try windows.cStrToPrefixedFileW(new_path); - return renameW(&old_path_w, &new_path_w); + return renameW(old_path_w.span().ptr, new_path_w.span().ptr); } switch (errno(system.rename(old_path, new_path))) { 0 => return, @@ -1629,7 +1642,7 @@ pub fn renameat( if (builtin.os.tag == .windows) { const old_path_w = try windows.sliceToPrefixedFileW(old_path); const new_path_w = try windows.sliceToPrefixedFileW(new_path); - return renameatW(old_dir_fd, &old_path_w, new_dir_fd, &new_path_w, windows.TRUE); + return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE); } else { const old_path_c = try toPosixPath(old_path); const new_path_c = try toPosixPath(new_path); @@ -1647,7 +1660,7 @@ pub fn renameatZ( if (builtin.os.tag == .windows) { const old_path_w = try windows.cStrToPrefixedFileW(old_path); const new_path_w = try windows.cStrToPrefixedFileW(new_path); - return renameatW(old_dir_fd, &old_path_w, new_dir_fd, &new_path_w, windows.TRUE); + return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE); } switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) { @@ -1674,38 +1687,40 @@ pub fn renameatZ( } } -/// Same as `renameat` except the parameters are null-terminated UTF16LE encoded byte arrays. -/// Assumes target is Windows. -/// TODO these args can actually be slices when using ntdll. audit the rest of the W functions too. +/// Same as `renameat` but Windows-only and the path parameters are +/// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. pub fn renameatW( old_dir_fd: fd_t, - old_path: [*:0]const u16, + old_path_w: []const u16, new_dir_fd: fd_t, - new_path_w: [*:0]const u16, + new_path_w: []const u16, ReplaceIfExists: windows.BOOLEAN, ) RenameError!void { - const access_mask = windows.SYNCHRONIZE | windows.GENERIC_WRITE | windows.DELETE; - const src_fd = windows.OpenFileW(old_dir_fd, old_path, null, access_mask, null, false, windows.FILE_OPEN) catch |err| switch (err) { - error.WouldBlock => unreachable, + const src_fd = windows.OpenFile(old_path_w, .{ + .dir = old_dir_fd, + .access_mask = windows.SYNCHRONIZE | windows.GENERIC_WRITE | windows.DELETE, + .creation = windows.FILE_OPEN, + .io_mode = .blocking, + }) catch |err| switch (err) { + error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`. else => |e| return e, }; defer windows.CloseHandle(src_fd); const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION) + (MAX_PATH_BYTES - 1); var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION)) = undefined; - const new_path = mem.span(new_path_w); - const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION) - 1 + new_path.len * 2; + const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION) - 1 + new_path_w.len * 2; if (struct_len > struct_buf_len) return error.NameTooLong; const rename_info = @ptrCast(*windows.FILE_RENAME_INFORMATION, &rename_info_buf); rename_info.* = .{ .ReplaceIfExists = ReplaceIfExists, - .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(new_path_w)) null else new_dir_fd, - .FileNameLength = @intCast(u32, new_path.len * 2), // already checked error.NameTooLong + .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(new_path_w)) null else new_dir_fd, + .FileNameLength = @intCast(u32, new_path_w.len * 2), // already checked error.NameTooLong .FileName = undefined, }; - std.mem.copy(u16, @as([*]u16, &rename_info.FileName)[0..new_path.len], new_path); + std.mem.copy(u16, @as([*]u16, &rename_info.FileName)[0..new_path_w.len], new_path_w); var io_status_block: windows.IO_STATUS_BLOCK = undefined; @@ -1749,7 +1764,7 @@ pub const MakeDirError = error{ 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, mode); + return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode); } else { const sub_dir_path_c = try toPosixPath(sub_dir_path); return mkdiratZ(dir_fd, &sub_dir_path_c, mode); @@ -1761,7 +1776,7 @@ pub const mkdiratC = @compileError("deprecated: renamed to mkdiratZ"); pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void { if (builtin.os.tag == .windows) { const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path); - return mkdiratW(dir_fd, &sub_dir_path_w, mode); + return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode); } switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) { 0 => return, @@ -1805,7 +1820,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); - const sub_dir_handle = try windows.CreateDirectoryW(null, &dir_path_w, null); + const sub_dir_handle = try windows.CreateDirectoryW(null, dir_path_w.span().ptr, null); windows.CloseHandle(sub_dir_handle); return; } @@ -1846,7 +1861,7 @@ pub const DeleteDirError = error{ pub fn rmdir(dir_path: []const u8) DeleteDirError!void { if (builtin.os.tag == .windows) { const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); - return windows.RemoveDirectoryW(&dir_path_w); + return windows.RemoveDirectoryW(dir_path_w.span().ptr); } else { const dir_path_c = try toPosixPath(dir_path); return rmdirZ(&dir_path_c); @@ -1859,7 +1874,7 @@ pub const rmdirC = @compileError("deprecated: renamed to rmdirZ"); pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { if (builtin.os.tag == .windows) { const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); - return windows.RemoveDirectoryW(&dir_path_w); + return windows.RemoveDirectoryW(dir_path_w.span().ptr); } switch (errno(system.rmdir(dir_path))) { 0 => return, @@ -2869,7 +2884,7 @@ pub const AccessError = error{ pub fn access(path: []const u8, mode: u32) AccessError!void { if (builtin.os.tag == .windows) { const path_w = try windows.sliceToPrefixedFileW(path); - _ = try windows.GetFileAttributesW(&path_w); + _ = try windows.GetFileAttributesW(path_w.span().ptr); return; } const path_c = try toPosixPath(path); @@ -2882,7 +2897,7 @@ pub const accessC = @compileError("Deprecated in favor of `accessZ`"); pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void { if (builtin.os.tag == .windows) { const path_w = try windows.cStrToPrefixedFileW(path); - _ = try windows.GetFileAttributesW(&path_w); + _ = try windows.GetFileAttributesW(path_w.span().ptr); return; } switch (errno(system.access(path, mode))) { @@ -2923,7 +2938,7 @@ pub fn accessW(path: [*:0]const u16, mode: u32) windows.GetFileAttributesError!v pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void { if (builtin.os.tag == .windows) { const path_w = try windows.sliceToPrefixedFileW(path); - return faccessatW(dirfd, &path_w, mode, flags); + return faccessatW(dirfd, path_w.span().ptr, mode, flags); } const path_c = try toPosixPath(path); return faccessatZ(dirfd, &path_c, mode, flags); @@ -2933,7 +2948,7 @@ pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessErr pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) AccessError!void { if (builtin.os.tag == .windows) { const path_w = try windows.cStrToPrefixedFileW(path); - return faccessatW(dirfd, &path_w, mode, flags); + return faccessatW(dirfd, path_w.span().ptr, mode, flags); } switch (errno(system.faccessat(dirfd, path, mode, flags))) { 0 => return, @@ -3288,7 +3303,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, out_buffer); + return realpathW(pathname_w.span().ptr, out_buffer); } const pathname_c = try toPosixPath(pathname); return realpathZ(&pathname_c, out_buffer); @@ -3300,7 +3315,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, out_buffer); + return realpathW(pathname_w.span().ptr, 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) { diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 7118fabf7d..ff10c08865 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -59,7 +59,7 @@ pub fn CreateFile( hTemplateFile: ?HANDLE, ) CreateFileError!HANDLE { const file_path_w = try sliceToPrefixedFileW(file_path); - return CreateFileW(&file_path_w, desired_access, share_mode, lpSecurityAttributes, creation_disposition, flags_and_attrs, hTemplateFile); + return CreateFileW(file_path_w.span().ptr, desired_access, share_mode, lpSecurityAttributes, creation_disposition, flags_and_attrs, hTemplateFile); } pub fn CreateFileW( @@ -103,57 +103,59 @@ pub const OpenError = error{ WouldBlock, }; -/// TODO rename to CreateFileW -/// TODO actually we don't need the path parameter to be null terminated -pub fn OpenFileW( - dir: ?HANDLE, - sub_path_w: [*:0]const u16, - sa: ?*SECURITY_ATTRIBUTES, +pub const OpenFileOptions = struct { access_mask: ACCESS_MASK, - share_access_opt: ?ULONG, - share_access_nonblocking: bool, + dir: ?HANDLE = null, + sa: ?*SECURITY_ATTRIBUTES = null, + share_access: ULONG = FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, + share_access_nonblocking: bool = false, creation: ULONG, -) OpenError!HANDLE { - if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { + io_mode: std.io.ModeOverride, +}; + +/// 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{'.'})) { return error.IsDir; } - if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) { + if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' })) { return error.IsDir; } var result: HANDLE = undefined; - const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) { + const path_len_bytes = math.cast(u16, sub_path_w.len * 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)), + .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w.ptr)), }; var attr = OBJECT_ATTRIBUTES{ .Length = @sizeOf(OBJECT_ATTRIBUTES), - .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir, + .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else options.dir, .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. .ObjectName = &nt_name, - .SecurityDescriptor = if (sa) |ptr| ptr.lpSecurityDescriptor else null, + .SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null, .SecurityQualityOfService = null, }; var io: IO_STATUS_BLOCK = undefined; - const share_access = share_access_opt orelse (FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE); var delay: usize = 1; while (true) { + const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0; const rc = ntdll.NtCreateFile( &result, - access_mask, + options.access_mask, &attr, &io, null, FILE_ATTRIBUTE_NORMAL, - share_access, - creation, - FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, + options.share_access, + options.creation, + FILE_NON_DIRECTORY_FILE | blocking_flag, null, 0, ); @@ -165,14 +167,16 @@ pub fn OpenFileW( .NO_MEDIA_IN_DEVICE => return error.NoDevice, .INVALID_PARAMETER => unreachable, .SHARING_VIOLATION => { - if (share_access_nonblocking) { + if (options.share_access_nonblocking) { return error.WouldBlock; } + // TODO sleep in a way that is interruptable + // TODO integrate with async I/O std.time.sleep(delay); if (delay < 1 * std.time.ns_per_s) { delay *= 2; } - continue; // TODO: don't loop for async + continue; }, .ACCESS_DENIED => return error.AccessDenied, .PIPE_BUSY => return error.PipeBusy, @@ -195,7 +199,7 @@ pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) C pub fn CreateEventEx(attributes: ?*SECURITY_ATTRIBUTES, name: []const u8, flags: DWORD, desired_access: DWORD) !HANDLE { const nameW = try sliceToPrefixedFileW(name); - return CreateEventExW(attributes, &nameW, flags, desired_access); + return CreateEventExW(attributes, nameW.span().ptr, flags, desired_access); } pub fn CreateEventExW(attributes: ?*SECURITY_ATTRIBUTES, nameW: [*:0]const u16, flags: DWORD, desired_access: DWORD) !HANDLE { @@ -328,42 +332,6 @@ pub fn WaitForMultipleObjectsEx(handles: []const HANDLE, waitAll: bool, millisec } } -pub const FindFirstFileError = error{ - FileNotFound, - InvalidUtf8, - BadPathName, - NameTooLong, - Unexpected, -}; - -pub fn FindFirstFile(dir_path: []const u8, find_file_data: *WIN32_FIND_DATAW) FindFirstFileError!HANDLE { - const dir_path_w = try sliceToPrefixedSuffixedFileW(dir_path, [_]u16{ '\\', '*' }); - const handle = kernel32.FindFirstFileW(&dir_path_w, find_file_data); - - if (handle == INVALID_HANDLE_VALUE) { - switch (kernel32.GetLastError()) { - .FILE_NOT_FOUND => return error.FileNotFound, - .PATH_NOT_FOUND => return error.FileNotFound, - else => |err| return unexpectedError(err), - } - } - - return handle; -} - -pub const FindNextFileError = error{Unexpected}; - -/// Returns `true` if there was another file, `false` otherwise. -pub fn FindNextFile(handle: HANDLE, find_file_data: *WIN32_FIND_DATAW) FindNextFileError!bool { - if (kernel32.FindNextFileW(handle, find_file_data) == 0) { - switch (kernel32.GetLastError()) { - .NO_MORE_FILES => return false, - else => |err| return unexpectedError(err), - } - } - return true; -} - pub const CreateIoCompletionPortError = error{Unexpected}; pub fn CreateIoCompletionPort( @@ -447,10 +415,11 @@ pub const ReadFileError = error{ /// If buffer's length exceeds what a Windows DWORD integer can hold, it will be broken into /// multiple non-atomic reads. -pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usize { - if (std.event.Loop.instance) |loop| { - // TODO support async ReadFile with no offset - const off = offset.?; +pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64, io_mode: std.io.ModeOverride) ReadFileError!usize { + if (io_mode != .blocking) { + const loop = std.event.Loop.instance.?; + // TODO make getting the file position non-blocking + const off = if (offset) |o| o else try SetFilePointerEx_CURRENT_get(in_hFile); var resume_node = std.event.Loop.ResumeNode.Basic{ .base = .{ .id = .Basic, @@ -465,22 +434,27 @@ pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usiz }, }; // TODO only call create io completion port once per fd - _ = windows.CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined) catch undefined; + _ = CreateIoCompletionPort(in_hFile, loop.os_data.io_port, undefined, undefined) catch undefined; loop.beginOneEvent(); suspend { // TODO handle buffer bigger than DWORD can hold - _ = windows.kernel32.ReadFile(fd, buffer.ptr, @intCast(windows.DWORD, buffer.len), null, &resume_node.base.overlapped); + _ = kernel32.ReadFile(in_hFile, buffer.ptr, @intCast(DWORD, buffer.len), null, &resume_node.base.overlapped); } - var bytes_transferred: windows.DWORD = undefined; - if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) { - switch (windows.kernel32.GetLastError()) { + var bytes_transferred: DWORD = undefined; + if (kernel32.GetOverlappedResult(in_hFile, &resume_node.base.overlapped, &bytes_transferred, FALSE) == 0) { + switch (kernel32.GetLastError()) { .IO_PENDING => unreachable, .OPERATION_ABORTED => return error.OperationAborted, .BROKEN_PIPE => return error.BrokenPipe, .HANDLE_EOF => return @as(usize, bytes_transferred), - else => |err| return windows.unexpectedError(err), + else => |err| return unexpectedError(err), } } + if (offset == null) { + // TODO make setting the file position non-blocking + const new_off = off + bytes_transferred; + try SetFilePointerEx_CURRENT(in_hFile, @bitCast(i64, new_off)); + } return @as(usize, bytes_transferred); } else { var index: usize = 0; @@ -520,10 +494,16 @@ pub const WriteFileError = error{ Unexpected, }; -pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError!usize { - if (std.event.Loop.instance) |loop| { - // TODO support async WriteFile with no offset - const off = offset.?; +pub fn WriteFile( + handle: HANDLE, + bytes: []const u8, + offset: ?u64, + io_mode: std.io.ModeOverride, +) WriteFileError!usize { + if (std.event.Loop.instance != null and io_mode != .blocking) { + const loop = std.event.Loop.instance.?; + // TODO make getting the file position non-blocking + const off = if (offset) |o| o else try SetFilePointerEx_CURRENT_get(handle); var resume_node = std.event.Loop.ResumeNode.Basic{ .base = .{ .id = .Basic, @@ -538,14 +518,14 @@ pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError }, }; // TODO only call create io completion port once per fd - _ = CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined); + _ = CreateIoCompletionPort(handle, loop.os_data.io_port, undefined, undefined) catch undefined; loop.beginOneEvent(); suspend { - const adjusted_len = math.cast(windows.DWORD, bytes.len) catch maxInt(windows.DWORD); - _ = kernel32.WriteFile(fd, bytes.ptr, adjusted_len, null, &resume_node.base.overlapped); + const adjusted_len = math.cast(DWORD, bytes.len) catch maxInt(DWORD); + _ = kernel32.WriteFile(handle, bytes.ptr, adjusted_len, null, &resume_node.base.overlapped); } - var bytes_transferred: windows.DWORD = undefined; - if (kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, FALSE) == 0) { + var bytes_transferred: DWORD = undefined; + if (kernel32.GetOverlappedResult(handle, &resume_node.base.overlapped, &bytes_transferred, FALSE) == 0) { switch (kernel32.GetLastError()) { .IO_PENDING => unreachable, .INVALID_USER_BUFFER => return error.SystemResources, @@ -553,9 +533,14 @@ pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError .OPERATION_ABORTED => return error.OperationAborted, .NOT_ENOUGH_QUOTA => return error.SystemResources, .BROKEN_PIPE => return error.BrokenPipe, - else => |err| return windows.unexpectedError(err), + else => |err| return unexpectedError(err), } } + if (offset == null) { + // TODO make setting the file position non-blocking + const new_off = off + bytes_transferred; + try SetFilePointerEx_CURRENT(handle, @bitCast(i64, new_off)); + } return bytes_transferred; } else { var bytes_written: DWORD = undefined; @@ -623,7 +608,7 @@ pub fn CreateSymbolicLink( ) CreateSymbolicLinkError!void { const sym_link_path_w = try sliceToPrefixedFileW(sym_link_path); const target_path_w = try sliceToPrefixedFileW(target_path); - return CreateSymbolicLinkW(&sym_link_path_w, &target_path_w, flags); + return CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, flags); } pub fn CreateSymbolicLinkW( @@ -648,7 +633,7 @@ pub const DeleteFileError = error{ pub fn DeleteFile(filename: []const u8) DeleteFileError!void { const filename_w = try sliceToPrefixedFileW(filename); - return DeleteFileW(&filename_w); + return DeleteFileW(filename_w.span().ptr); } pub fn DeleteFileW(filename: [*:0]const u16) DeleteFileError!void { @@ -670,7 +655,7 @@ pub const MoveFileError = error{Unexpected}; pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void { const old_path_w = try sliceToPrefixedFileW(old_path); const new_path_w = try sliceToPrefixedFileW(new_path); - return MoveFileExW(&old_path_w, &new_path_w, flags); + return MoveFileExW(old_path_w.span().ptr, new_path_w.span().ptr, flags); } pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void { @@ -695,7 +680,7 @@ pub const CreateDirectoryError = error{ /// Returns an open directory handle which the caller is responsible for closing with `CloseHandle`. pub fn CreateDirectory(dir: ?HANDLE, pathname: []const u8, sa: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!HANDLE { const pathname_w = try sliceToPrefixedFileW(pathname); - return CreateDirectoryW(dir, &pathname_w, sa); + return CreateDirectoryW(dir, pathname_w.span().ptr, sa); } /// Same as `CreateDirectory` except takes a WTF-16 encoded path. @@ -763,7 +748,7 @@ pub const RemoveDirectoryError = error{ pub fn RemoveDirectory(dir_path: []const u8) RemoveDirectoryError!void { const dir_path_w = try sliceToPrefixedFileW(dir_path); - return RemoveDirectoryW(&dir_path_w); + return RemoveDirectoryW(dir_path_w.span().ptr); } pub fn RemoveDirectoryW(dir_path_w: [*:0]const u16) RemoveDirectoryError!void { @@ -892,7 +877,7 @@ pub const GetFileAttributesError = error{ pub fn GetFileAttributes(filename: []const u8) GetFileAttributesError!DWORD { const filename_w = try sliceToPrefixedFileW(filename); - return GetFileAttributesW(&filename_w); + return GetFileAttributesW(filename_w.span().ptr); } pub fn GetFileAttributesW(lpFileName: [*:0]const u16) GetFileAttributesError!DWORD { @@ -1232,34 +1217,22 @@ pub fn nanoSecondsToFileTime(ns: i64) FILETIME { }; } -pub fn cStrToPrefixedFileW(s: [*:0]const u8) ![PATH_MAX_WIDE:0]u16 { +pub const PathSpace = struct { + data: [PATH_MAX_WIDE:0]u16, + len: usize, + + pub fn span(self: PathSpace) [:0]const u16 { + return self.data[0..self.len :0]; + } +}; + +pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace { return sliceToPrefixedFileW(mem.spanZ(s)); } -pub fn sliceToPrefixedFileW(s: []const u8) ![PATH_MAX_WIDE:0]u16 { - return sliceToPrefixedSuffixedFileW(s, &[_]u16{}); -} - -/// Assumes an absolute path. -pub fn wToPrefixedFileW(s: []const u16) ![PATH_MAX_WIDE:0]u16 { +pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace { // TODO https://github.com/ziglang/zig/issues/2765 - var result: [PATH_MAX_WIDE:0]u16 = undefined; - - const start_index = if (mem.startsWith(u16, s, &[_]u16{ '\\', '?' })) 0 else blk: { - const prefix = [_]u16{ '\\', '?', '?', '\\' }; - mem.copy(u16, result[0..], &prefix); - break :blk prefix.len; - }; - const end_index = start_index + s.len; - if (end_index + 1 > result.len) return error.NameTooLong; - mem.copy(u16, result[start_index..], s); - result[end_index] = 0; - return result; -} - -pub fn sliceToPrefixedSuffixedFileW(s: []const u8, comptime suffix: []const u16) ![PATH_MAX_WIDE + suffix.len:0]u16 { - // TODO https://github.com/ziglang/zig/issues/2765 - var result: [PATH_MAX_WIDE + suffix.len:0]u16 = undefined; + var path_space: PathSpace = undefined; for (s) |byte| { switch (byte) { '*', '?', '"', '<', '>', '|' => return error.BadPathName, @@ -1268,25 +1241,46 @@ pub fn sliceToPrefixedSuffixedFileW(s: []const u8, comptime suffix: []const u16) } const start_index = if (mem.startsWith(u8, s, "\\?") or !std.fs.path.isAbsolute(s)) 0 else blk: { const prefix = [_]u16{ '\\', '?', '?', '\\' }; - mem.copy(u16, result[0..], &prefix); + mem.copy(u16, path_space.data[0..], &prefix); break :blk prefix.len; }; - const end_index = start_index + try std.unicode.utf8ToUtf16Le(result[start_index..], s); - if (end_index + suffix.len > result.len) return error.NameTooLong; + path_space.len = start_index + try std.unicode.utf8ToUtf16Le(path_space.data[start_index..], s); + if (path_space.len > path_space.data.len) return error.NameTooLong; // > File I/O functions in the Windows API convert "/" to "\" as part of // > converting the name to an NT-style name, except when using the "\\?\" // > prefix as detailed in the following sections. // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation // Because we want the larger maximum path length for absolute paths, we // convert forward slashes to backward slashes here. - for (result[0..end_index]) |*elem| { + for (path_space.data[0..path_space.len]) |*elem| { if (elem.* == '/') { elem.* = '\\'; } } - mem.copy(u16, result[end_index..], suffix); - result[end_index + suffix.len] = 0; - return result; + path_space.data[path_space.len] = 0; + return path_space; +} + +/// Assumes an absolute path. +pub fn wToPrefixedFileW(s: []const u16) !PathSpace { + // TODO https://github.com/ziglang/zig/issues/2765 + var path_space: PathSpace = undefined; + + const start_index = if (mem.startsWith(u16, s, &[_]u16{ '\\', '?' })) 0 else blk: { + const prefix = [_]u16{ '\\', '?', '?', '\\' }; + mem.copy(u16, path_space.data[0..], &prefix); + break :blk prefix.len; + }; + path_space.len = start_index + s.len; + if (path_space.len > path_space.data.len) return error.NameTooLong; + mem.copy(u16, path_space.data[start_index..], s); + for (path_space.data[0..path_space.len]) |*elem| { + if (elem.* == '/') { + elem.* = '\\'; + } + } + path_space.data[path_space.len] = 0; + return path_space; } inline fn MAKELANGID(p: c_ushort, s: c_ushort) LANGID { diff --git a/lib/std/pdb.zig b/lib/std/pdb.zig index 422a51a800..75589b71ff 100644 --- a/lib/std/pdb.zig +++ b/lib/std/pdb.zig @@ -470,7 +470,7 @@ pub const Pdb = struct { msf: Msf, pub fn openFile(self: *Pdb, coff_ptr: *coff.Coff, file_name: []u8) !void { - self.in_file = try fs.cwd().openFile(file_name, .{}); + self.in_file = try fs.cwd().openFile(file_name, .{ .intended_io_mode = .blocking }); self.allocator = coff_ptr.allocator; self.coff = coff_ptr; diff --git a/lib/std/reset_event.zig b/lib/std/reset_event.zig index c28db809ca..860f494f3f 100644 --- a/lib/std/reset_event.zig +++ b/lib/std/reset_event.zig @@ -52,7 +52,7 @@ pub const ResetEvent = struct { /// Wait for the event to be set by blocking the current thread. /// A timeout in nanoseconds can be provided as a hint for how - /// long the thread should block on the unset event before throwind error.TimedOut. + /// long the thread should block on the unset event before throwing error.TimedOut. pub fn timedWait(self: *ResetEvent, timeout_ns: u64) !void { return self.os_event.wait(timeout_ns); } diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 5868f9383d..8186f1f4d8 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -96,7 +96,7 @@ pub const TestContext = struct { case: ZIRCompareOutputCase, target: std.Target, ) !void { - var tmp = std.testing.tmpDir(.{ .share_with_child_process = true }); + var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var prg_node = root_node.start(case.name, 4);