authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 13:51:37-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-10-29 13:51:37-07:00
loga072d821be9e4bae68c7c14e9438f3750d2c0c89
treeb8a6bd999084f7a9b6aee42e9ed6599a8f749e53
parentb2bc44e0d5e5edde083ec281aa0575b16478d881
parent16185f66f1e500d61d43550e7c847a36ad1032df
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #25592 from ziglang/init-std.Io

std: Introduce `Io` Interface

143 files changed, 17689 insertions(+), 9743 deletions(-)

CMakeLists.txt-1
......@@ -413,7 +413,6 @@ set(ZIG_STAGE2_SOURCES
413413 lib/std/Thread/Futex.zig
414414 lib/std/Thread/Mutex.zig
415415 lib/std/Thread/Pool.zig
416 lib/std/Thread/ResetEvent.zig
417416 lib/std/Thread/WaitGroup.zig
418417 lib/std/array_hash_map.zig
419418 lib/std/array_list.zig
README.md+4-12
......@@ -76,23 +76,15 @@ This produces a `zig2` executable in the current working directory. This is a
7676[without LLVM extensions](https://github.com/ziglang/zig/issues/16270), and is
7777therefore lacking these features:
7878- Release mode optimizations
79- [aarch64 machine code backend](https://github.com/ziglang/zig/issues/21172)
80- [@cImport](https://github.com/ziglang/zig/issues/20630)
81- [zig translate-c](https://github.com/ziglang/zig/issues/20875)
82- [Ability to compile assembly files](https://github.com/ziglang/zig/issues/21169)
8379- [Some ELF linking features](https://github.com/ziglang/zig/issues/17749)
84- [Most COFF/PE linking features](https://github.com/ziglang/zig/issues/17751)
80- [Some COFF/PE linking features](https://github.com/ziglang/zig/issues/17751)
8581- [Some WebAssembly linking features](https://github.com/ziglang/zig/issues/17750)
86- [Ability to create import libs from def files](https://github.com/ziglang/zig/issues/17807)
8782- [Ability to create static archives from object files](https://github.com/ziglang/zig/issues/9828)
83- [Ability to compile assembly files](https://github.com/ziglang/zig/issues/21169)
8884- Ability to compile C, C++, Objective-C, and Objective-C++ files
8985
90However, a compiler built this way does provide a C backend, which may be
91useful for creating system packages of Zig projects using the system C
92toolchain. **In this case, LLVM is not needed!**
93
94Furthermore, a compiler built this way provides an LLVM backend that produces
95bitcode files, which may be compiled into object files via a system Clang
86Even when built this way, Zig provides an LLVM backend that produces bitcode
87files, which may be optimized and compiled into object files via a system Clang
9688package. This can be used to produce system packages of Zig applications
9789without the Zig package dependency on LLVM.
9890
ci/x86_64-windows-debug.ps1+1-1
......@@ -95,7 +95,7 @@ Enter-VsDevShell -VsInstallPath "C:\Program Files (x86)\Microsoft Visual Studio\
9595CheckLastExitCode
9696
9797Write-Output "Build and run behavior tests with msvc..."
98& cl.exe -I..\lib test-x86_64-windows-msvc.c compiler_rt-x86_64-windows-msvc.c /W3 /Z7 -link -nologo -debug -subsystem:console kernel32.lib ntdll.lib libcmt.lib
98& cl.exe -I..\lib test-x86_64-windows-msvc.c compiler_rt-x86_64-windows-msvc.c /W3 /Z7 -link -nologo -debug -subsystem:console kernel32.lib ntdll.lib libcmt.lib ws2_32.lib
9999CheckLastExitCode
100100
101101& .\test-x86_64-windows-msvc.exe
ci/x86_64-windows-release.ps1+1-1
......@@ -113,7 +113,7 @@ Enter-VsDevShell -VsInstallPath "C:\Program Files (x86)\Microsoft Visual Studio\
113113CheckLastExitCode
114114
115115Write-Output "Build and run behavior tests with msvc..."
116& cl.exe -I..\lib test-x86_64-windows-msvc.c compiler_rt-x86_64-windows-msvc.c /W3 /Z7 -link -nologo -debug -subsystem:console kernel32.lib ntdll.lib libcmt.lib
116& cl.exe -I..\lib test-x86_64-windows-msvc.c compiler_rt-x86_64-windows-msvc.c /W3 /Z7 -link -nologo -debug -subsystem:console kernel32.lib ntdll.lib libcmt.lib ws2_32.lib
117117CheckLastExitCode
118118
119119& .\test-x86_64-windows-msvc.exe
lib/compiler/aro/aro/Compilation.zig+32-28
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23const assert = std.debug.assert;
34const EpochSeconds = std.time.epoch.EpochSeconds;
45const mem = std.mem;
......@@ -113,7 +114,7 @@ pub const Environment = struct {
113114 if (parsed > max_timestamp) return error.InvalidEpoch;
114115 return .{ .provided = parsed };
115116 } else {
116 const timestamp = std.math.cast(u64, std.time.timestamp()) orelse return error.InvalidEpoch;
117 const timestamp = std.math.cast(u64, 0) orelse return error.InvalidEpoch;
117118 return .{ .system = std.math.clamp(timestamp, 0, max_timestamp) };
118119 }
119120 }
......@@ -124,6 +125,7 @@ const Compilation = @This();
124125gpa: Allocator,
125126/// Allocations in this arena live all the way until `Compilation.deinit`.
126127arena: Allocator,
128io: Io,
127129diagnostics: *Diagnostics,
128130
129131code_gen_options: CodeGenOptions = .default,
......@@ -157,10 +159,11 @@ type_store: TypeStore = .{},
157159ms_cwd_source_id: ?Source.Id = null,
158160cwd: std.fs.Dir,
159161
160pub fn init(gpa: Allocator, arena: Allocator, diagnostics: *Diagnostics, cwd: std.fs.Dir) Compilation {
162pub fn init(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics, cwd: std.fs.Dir) Compilation {
161163 return .{
162164 .gpa = gpa,
163165 .arena = arena,
166 .io = io,
164167 .diagnostics = diagnostics,
165168 .cwd = cwd,
166169 };
......@@ -168,10 +171,11 @@ pub fn init(gpa: Allocator, arena: Allocator, diagnostics: *Diagnostics, cwd: st
168171
169172/// Initialize Compilation with default environment,
170173/// pragma handlers and emulation mode set to target.
171pub fn initDefault(gpa: Allocator, arena: Allocator, diagnostics: *Diagnostics, cwd: std.fs.Dir) !Compilation {
174pub fn initDefault(gpa: Allocator, arena: Allocator, io: Io, diagnostics: *Diagnostics, cwd: std.fs.Dir) !Compilation {
172175 var comp: Compilation = .{
173176 .gpa = gpa,
174177 .arena = arena,
178 .io = io,
175179 .diagnostics = diagnostics,
176180 .environment = try Environment.loadAll(gpa),
177181 .cwd = cwd,
......@@ -222,14 +226,14 @@ pub const SystemDefinesMode = enum {
222226 include_system_defines,
223227};
224228
225fn generateSystemDefines(comp: *Compilation, w: *std.Io.Writer) !void {
229fn generateSystemDefines(comp: *Compilation, w: *Io.Writer) !void {
226230 const define = struct {
227 fn define(_w: *std.Io.Writer, name: []const u8) !void {
231 fn define(_w: *Io.Writer, name: []const u8) !void {
228232 try _w.print("#define {s} 1\n", .{name});
229233 }
230234 }.define;
231235 const defineStd = struct {
232 fn defineStd(_w: *std.Io.Writer, name: []const u8, is_gnu: bool) !void {
236 fn defineStd(_w: *Io.Writer, name: []const u8, is_gnu: bool) !void {
233237 if (is_gnu) {
234238 try _w.print("#define {s} 1\n", .{name});
235239 }
......@@ -956,7 +960,7 @@ fn generateSystemDefines(comp: *Compilation, w: *std.Io.Writer) !void {
956960pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode) AddSourceError!Source {
957961 try comp.type_store.initNamedTypes(comp);
958962
959 var allocating: std.Io.Writer.Allocating = try .initCapacity(comp.gpa, 2 << 13);
963 var allocating: Io.Writer.Allocating = try .initCapacity(comp.gpa, 2 << 13);
960964 defer allocating.deinit();
961965
962966 comp.writeBuiltinMacros(system_defines_mode, &allocating.writer) catch |err| switch (err) {
......@@ -970,7 +974,7 @@ pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefi
970974 return comp.addSourceFromOwnedBuffer("<builtin>", contents, .user);
971975}
972976
973fn writeBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode, w: *std.Io.Writer) !void {
977fn writeBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode, w: *Io.Writer) !void {
974978 if (system_defines_mode == .include_system_defines) {
975979 try w.writeAll(
976980 \\#define __VERSION__ "Aro
......@@ -1018,7 +1022,7 @@ fn writeBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode
10181022 }
10191023}
10201024
1021fn generateFloatMacros(w: *std.Io.Writer, prefix: []const u8, semantics: target_util.FPSemantics, ext: []const u8) !void {
1025fn generateFloatMacros(w: *Io.Writer, prefix: []const u8, semantics: target_util.FPSemantics, ext: []const u8) !void {
10221026 const denormMin = semantics.chooseValue(
10231027 []const u8,
10241028 .{
......@@ -1093,7 +1097,7 @@ fn generateFloatMacros(w: *std.Io.Writer, prefix: []const u8, semantics: target_
10931097 try w.print("#define __{s}_MIN__ {s}{s}\n", .{ prefix, min, ext });
10941098}
10951099
1096fn generateTypeMacro(comp: *const Compilation, w: *std.Io.Writer, name: []const u8, qt: QualType) !void {
1100fn generateTypeMacro(comp: *const Compilation, w: *Io.Writer, name: []const u8, qt: QualType) !void {
10971101 try w.print("#define {s} ", .{name});
10981102 try qt.print(comp, w);
10991103 try w.writeByte('\n');
......@@ -1128,7 +1132,7 @@ fn generateFastOrLeastType(
11281132 bits: usize,
11291133 kind: enum { least, fast },
11301134 signedness: std.builtin.Signedness,
1131 w: *std.Io.Writer,
1135 w: *Io.Writer,
11321136) !void {
11331137 const ty = comp.intLeastN(bits, signedness); // defining the fast types as the least types is permitted
11341138
......@@ -1158,7 +1162,7 @@ fn generateFastOrLeastType(
11581162 try comp.generateFmt(prefix, w, ty);
11591163}
11601164
1161fn generateFastAndLeastWidthTypes(comp: *Compilation, w: *std.Io.Writer) !void {
1165fn generateFastAndLeastWidthTypes(comp: *Compilation, w: *Io.Writer) !void {
11621166 const sizes = [_]usize{ 8, 16, 32, 64 };
11631167 for (sizes) |size| {
11641168 try comp.generateFastOrLeastType(size, .least, .signed, w);
......@@ -1168,7 +1172,7 @@ fn generateFastAndLeastWidthTypes(comp: *Compilation, w: *std.Io.Writer) !void {
11681172 }
11691173}
11701174
1171fn generateExactWidthTypes(comp: *Compilation, w: *std.Io.Writer) !void {
1175fn generateExactWidthTypes(comp: *Compilation, w: *Io.Writer) !void {
11721176 try comp.generateExactWidthType(w, .schar);
11731177
11741178 if (QualType.short.sizeof(comp) > QualType.char.sizeof(comp)) {
......@@ -1216,7 +1220,7 @@ fn generateExactWidthTypes(comp: *Compilation, w: *std.Io.Writer) !void {
12161220 }
12171221}
12181222
1219fn generateFmt(comp: *const Compilation, prefix: []const u8, w: *std.Io.Writer, qt: QualType) !void {
1223fn generateFmt(comp: *const Compilation, prefix: []const u8, w: *Io.Writer, qt: QualType) !void {
12201224 const unsigned = qt.signedness(comp) == .unsigned;
12211225 const modifier = qt.formatModifier(comp);
12221226 const formats = if (unsigned) "ouxX" else "di";
......@@ -1225,7 +1229,7 @@ fn generateFmt(comp: *const Compilation, prefix: []const u8, w: *std.Io.Writer,
12251229 }
12261230}
12271231
1228fn generateSuffixMacro(comp: *const Compilation, prefix: []const u8, w: *std.Io.Writer, qt: QualType) !void {
1232fn generateSuffixMacro(comp: *const Compilation, prefix: []const u8, w: *Io.Writer, qt: QualType) !void {
12291233 return w.print("#define {s}_C_SUFFIX__ {s}\n", .{ prefix, qt.intValueSuffix(comp) });
12301234}
12311235
......@@ -1233,7 +1237,7 @@ fn generateSuffixMacro(comp: *const Compilation, prefix: []const u8, w: *std.Io.
12331237/// Name macro (e.g. #define __UINT32_TYPE__ unsigned int)
12341238/// Format strings (e.g. #define __UINT32_FMTu__ "u")
12351239/// Suffix macro (e.g. #define __UINT32_C_SUFFIX__ U)
1236fn generateExactWidthType(comp: *Compilation, w: *std.Io.Writer, original_qt: QualType) !void {
1240fn generateExactWidthType(comp: *Compilation, w: *Io.Writer, original_qt: QualType) !void {
12371241 var qt = original_qt;
12381242 const width = qt.sizeof(comp) * 8;
12391243 const unsigned = qt.signedness(comp) == .unsigned;
......@@ -1266,7 +1270,7 @@ pub fn hasHalfPrecisionFloatABI(comp: *const Compilation) bool {
12661270 return comp.langopts.allow_half_args_and_returns or target_util.hasHalfPrecisionFloatABI(comp.target);
12671271}
12681272
1269fn generateIntMax(comp: *const Compilation, w: *std.Io.Writer, name: []const u8, qt: QualType) !void {
1273fn generateIntMax(comp: *const Compilation, w: *Io.Writer, name: []const u8, qt: QualType) !void {
12701274 const unsigned = qt.signedness(comp) == .unsigned;
12711275 const max: u128 = switch (qt.bitSizeof(comp)) {
12721276 8 => if (unsigned) std.math.maxInt(u8) else std.math.maxInt(i8),
......@@ -1290,7 +1294,7 @@ pub fn wcharMax(comp: *const Compilation) u32 {
12901294 };
12911295}
12921296
1293fn generateExactWidthIntMax(comp: *Compilation, w: *std.Io.Writer, original_qt: QualType) !void {
1297fn generateExactWidthIntMax(comp: *Compilation, w: *Io.Writer, original_qt: QualType) !void {
12941298 var qt = original_qt;
12951299 const bit_count: u8 = @intCast(qt.sizeof(comp) * 8);
12961300 const unsigned = qt.signedness(comp) == .unsigned;
......@@ -1307,16 +1311,16 @@ fn generateExactWidthIntMax(comp: *Compilation, w: *std.Io.Writer, original_qt:
13071311 return comp.generateIntMax(w, name, qt);
13081312}
13091313
1310fn generateIntWidth(comp: *Compilation, w: *std.Io.Writer, name: []const u8, qt: QualType) !void {
1314fn generateIntWidth(comp: *Compilation, w: *Io.Writer, name: []const u8, qt: QualType) !void {
13111315 try w.print("#define __{s}_WIDTH__ {d}\n", .{ name, qt.sizeof(comp) * 8 });
13121316}
13131317
1314fn generateIntMaxAndWidth(comp: *Compilation, w: *std.Io.Writer, name: []const u8, qt: QualType) !void {
1318fn generateIntMaxAndWidth(comp: *Compilation, w: *Io.Writer, name: []const u8, qt: QualType) !void {
13151319 try comp.generateIntMax(w, name, qt);
13161320 try comp.generateIntWidth(w, name, qt);
13171321}
13181322
1319fn generateSizeofType(comp: *Compilation, w: *std.Io.Writer, name: []const u8, qt: QualType) !void {
1323fn generateSizeofType(comp: *Compilation, w: *Io.Writer, name: []const u8, qt: QualType) !void {
13201324 try w.print("#define {s} {d}\n", .{ name, qt.sizeof(comp) });
13211325}
13221326
......@@ -1797,7 +1801,7 @@ pub const IncludeType = enum {
17971801 angle_brackets,
17981802};
17991803
1800fn getPathContents(comp: *Compilation, path: []const u8, limit: std.Io.Limit) ![]u8 {
1804fn getPathContents(comp: *Compilation, path: []const u8, limit: Io.Limit) ![]u8 {
18011805 if (mem.indexOfScalar(u8, path, 0) != null) {
18021806 return error.FileNotFound;
18031807 }
......@@ -1807,11 +1811,12 @@ fn getPathContents(comp: *Compilation, path: []const u8, limit: std.Io.Limit) ![
18071811 return comp.getFileContents(file, limit);
18081812}
18091813
1810fn getFileContents(comp: *Compilation, file: std.fs.File, limit: std.Io.Limit) ![]u8 {
1814fn getFileContents(comp: *Compilation, file: std.fs.File, limit: Io.Limit) ![]u8 {
1815 const io = comp.io;
18111816 var file_buf: [4096]u8 = undefined;
1812 var file_reader = file.reader(&file_buf);
1817 var file_reader = file.reader(io, &file_buf);
18131818
1814 var allocating: std.Io.Writer.Allocating = .init(comp.gpa);
1819 var allocating: Io.Writer.Allocating = .init(comp.gpa);
18151820 defer allocating.deinit();
18161821 if (file_reader.getSize()) |size| {
18171822 const limited_size = limit.minInt64(size);
......@@ -1838,7 +1843,7 @@ pub fn findEmbed(
18381843 includer_token_source: Source.Id,
18391844 /// angle bracket vs quotes
18401845 include_type: IncludeType,
1841 limit: std.Io.Limit,
1846 limit: Io.Limit,
18421847 opt_dep_file: ?*DepFile,
18431848) !?[]u8 {
18441849 if (std.fs.path.isAbsolute(filename)) {
......@@ -2002,8 +2007,7 @@ pub fn locSlice(comp: *const Compilation, loc: Source.Location) []const u8 {
20022007pub fn getSourceMTimeUncached(comp: *const Compilation, source_id: Source.Id) ?u64 {
20032008 const source = comp.getSource(source_id);
20042009 if (comp.cwd.statFile(source.path)) |stat| {
2005 const mtime = @divTrunc(stat.mtime, std.time.ns_per_s);
2006 return std.math.cast(u64, mtime);
2010 return std.math.cast(u64, stat.mtime.toSeconds());
20072011 } else |_| {
20082012 return null;
20092013 }
lib/compiler/aro/aro/Driver.zig+3-3
......@@ -273,6 +273,7 @@ pub fn parseArgs(
273273 macro_buf: *std.ArrayList(u8),
274274 args: []const []const u8,
275275) (Compilation.Error || std.Io.Writer.Error)!bool {
276 const io = d.comp.io;
276277 var i: usize = 1;
277278 var comment_arg: []const u8 = "";
278279 var hosted: ?bool = null;
......@@ -772,7 +773,7 @@ pub fn parseArgs(
772773 opts.arch_os_abi, @errorName(e),
773774 }),
774775 };
775 d.comp.target = std.zig.system.resolveTargetQuery(query) catch |e| {
776 d.comp.target = std.zig.system.resolveTargetQuery(io, query) catch |e| {
776777 return d.fatal("unable to resolve target: {s}", .{errorDescription(e)});
777778 };
778779 }
......@@ -916,8 +917,7 @@ pub fn errorDescription(e: anyerror) []const u8 {
916917 error.NotDir => "is not a directory",
917918 error.NotOpenForReading => "file is not open for reading",
918919 error.NotOpenForWriting => "file is not open for writing",
919 error.InvalidUtf8 => "path is not valid UTF-8",
920 error.InvalidWtf8 => "path is not valid WTF-8",
920 error.BadPathName => "bad path name",
921921 error.FileBusy => "file is busy",
922922 error.NameTooLong => "file name is too long",
923923 error.AccessDenied => "access denied",
lib/compiler/build_runner.zig+23-14
......@@ -1,5 +1,8 @@
1const std = @import("std");
1const runner = @This();
22const builtin = @import("builtin");
3
4const std = @import("std");
5const Io = std.Io;
36const assert = std.debug.assert;
47const fmt = std.fmt;
58const mem = std.mem;
......@@ -11,7 +14,6 @@ const WebServer = std.Build.WebServer;
1114const Allocator = std.mem.Allocator;
1215const fatal = std.process.fatal;
1316const Writer = std.Io.Writer;
14const runner = @This();
1517const tty = std.Io.tty;
1618
1719pub const root = @import("@build");
......@@ -38,6 +40,10 @@ pub fn main() !void {
3840
3941 const args = try process.argsAlloc(arena);
4042
43 var threaded: std.Io.Threaded = .init(gpa);
44 defer threaded.deinit();
45 const io = threaded.io();
46
4147 // skip my own exe name
4248 var arg_idx: usize = 1;
4349
......@@ -68,8 +74,10 @@ pub fn main() !void {
6874 };
6975
7076 var graph: std.Build.Graph = .{
77 .io = io,
7178 .arena = arena,
7279 .cache = .{
80 .io = io,
7381 .gpa = arena,
7482 .manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),
7583 },
......@@ -79,7 +87,7 @@ pub fn main() !void {
7987 .zig_lib_directory = zig_lib_directory,
8088 .host = .{
8189 .query = .{},
82 .result = try std.zig.system.resolveTargetQuery(.{}),
90 .result = try std.zig.system.resolveTargetQuery(io, .{}),
8391 },
8492 .time_report = false,
8593 };
......@@ -116,7 +124,7 @@ pub fn main() !void {
116124 var watch = false;
117125 var fuzz: ?std.Build.Fuzz.Mode = null;
118126 var debounce_interval_ms: u16 = 50;
119 var webui_listen: ?std.net.Address = null;
127 var webui_listen: ?Io.net.IpAddress = null;
120128
121129 if (try std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(arena)) |str| {
122130 if (std.meta.stringToEnum(ErrorStyle, str)) |style| {
......@@ -283,11 +291,11 @@ pub fn main() !void {
283291 });
284292 };
285293 } else if (mem.eql(u8, arg, "--webui")) {
286 webui_listen = std.net.Address.parseIp("::1", 0) catch unreachable;
294 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
287295 } else if (mem.startsWith(u8, arg, "--webui=")) {
288296 const addr_str = arg["--webui=".len..];
289297 if (std.mem.eql(u8, addr_str, "-")) fatal("web interface cannot listen on stdio", .{});
290 webui_listen = std.net.Address.parseIpAndPort(addr_str) catch |err| {
298 webui_listen = Io.net.IpAddress.parseLiteral(addr_str) catch |err| {
291299 fatal("invalid web UI address '{s}': {s}", .{ addr_str, @errorName(err) });
292300 };
293301 } else if (mem.eql(u8, arg, "--debug-log")) {
......@@ -329,14 +337,10 @@ pub fn main() !void {
329337 watch = true;
330338 } else if (mem.eql(u8, arg, "--time-report")) {
331339 graph.time_report = true;
332 if (webui_listen == null) {
333 webui_listen = std.net.Address.parseIp("::1", 0) catch unreachable;
334 }
340 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
335341 } else if (mem.eql(u8, arg, "--fuzz")) {
336342 fuzz = .{ .forever = undefined };
337 if (webui_listen == null) {
338 webui_listen = std.net.Address.parseIp("::1", 0) catch unreachable;
339 }
343 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
340344 } else if (mem.startsWith(u8, arg, "--fuzz=")) {
341345 const value = arg["--fuzz=".len..];
342346 if (value.len == 0) fatal("missing argument to --fuzz", .{});
......@@ -545,13 +549,15 @@ pub fn main() !void {
545549
546550 var w: Watch = w: {
547551 if (!watch) break :w undefined;
548 if (!Watch.have_impl) fatal("--watch not yet implemented for {s}", .{@tagName(builtin.os.tag)});
552 if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{builtin.os.tag});
549553 break :w try .init();
550554 };
551555
552556 try run.thread_pool.init(thread_pool_options);
553557 defer run.thread_pool.deinit();
554558
559 const now = Io.Clock.Timestamp.now(io, .awake) catch |err| fatal("failed to collect timestamp: {t}", .{err});
560
555561 run.web_server = if (webui_listen) |listen_address| ws: {
556562 if (builtin.single_threaded) unreachable; // `fatal` above
557563 break :ws .init(.{
......@@ -563,11 +569,12 @@ pub fn main() !void {
563569 .root_prog_node = main_progress_node,
564570 .watch = watch,
565571 .listen_address = listen_address,
572 .base_timestamp = now,
566573 });
567574 } else null;
568575
569576 if (run.web_server) |*ws| {
570 ws.start() catch |err| fatal("failed to start web server: {s}", .{@errorName(err)});
577 ws.start() catch |err| fatal("failed to start web server: {t}", .{err});
571578 }
572579
573580 rebuild: while (true) : (if (run.error_style.clearOnUpdate()) {
......@@ -750,6 +757,7 @@ fn runStepNames(
750757 fuzz: ?std.Build.Fuzz.Mode,
751758) !void {
752759 const gpa = run.gpa;
760 const io = b.graph.io;
753761 const step_stack = &run.step_stack;
754762 const thread_pool = &run.thread_pool;
755763
......@@ -853,6 +861,7 @@ fn runStepNames(
853861 assert(mode == .limit);
854862 var f = std.Build.Fuzz.init(
855863 gpa,
864 io,
856865 thread_pool,
857866 step_stack.keys(),
858867 parent_prog_node,
lib/compiler/libc.zig+5-1
......@@ -29,6 +29,10 @@ pub fn main() !void {
2929 const arena = arena_instance.allocator();
3030 const gpa = arena;
3131
32 var threaded: std.Io.Threaded = .init(gpa);
33 defer threaded.deinit();
34 const io = threaded.io();
35
3236 const args = try std.process.argsAlloc(arena);
3337 const zig_lib_directory = args[1];
3438
......@@ -66,7 +70,7 @@ pub fn main() !void {
6670 const target_query = std.zig.parseTargetQueryOrReportFatalError(gpa, .{
6771 .arch_os_abi = target_arch_os_abi,
6872 });
69 const target = std.zig.resolveTargetQueryOrFatal(target_query);
73 const target = std.zig.resolveTargetQueryOrFatal(io, target_query);
7074
7175 if (print_includes) {
7276 const libc_installation: ?*LibCInstallation = libc: {
lib/compiler/objcopy.zig+6-3
......@@ -29,7 +29,6 @@ pub fn main() !void {
2929}
3030
3131fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
32 _ = gpa;
3332 var i: usize = 0;
3433 var opt_out_fmt: ?std.Target.ObjectFormat = null;
3534 var opt_input: ?[]const u8 = null;
......@@ -148,12 +147,16 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
148147 const input = opt_input orelse fatal("expected input parameter", .{});
149148 const output = opt_output orelse fatal("expected output parameter", .{});
150149
150 var threaded: std.Io.Threaded = .init(gpa);
151 defer threaded.deinit();
152 const io = threaded.io();
153
151154 const input_file = fs.cwd().openFile(input, .{}) catch |err| fatal("failed to open {s}: {t}", .{ input, err });
152155 defer input_file.close();
153156
154157 const stat = input_file.stat() catch |err| fatal("failed to stat {s}: {t}", .{ input, err });
155158
156 var in: File.Reader = .initSize(input_file, &input_buffer, stat.size);
159 var in: File.Reader = .initSize(input_file.adaptToNewApi(), io, &input_buffer, stat.size);
157160
158161 const elf_hdr = std.elf.Header.read(&in.interface) catch |err| switch (err) {
159162 error.ReadFailed => fatal("unable to read {s}: {t}", .{ input, in.err.? }),
......@@ -218,7 +221,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
218221 try out.end();
219222
220223 if (listen) {
221 var stdin_reader = fs.File.stdin().reader(&stdin_buffer);
224 var stdin_reader = fs.File.stdin().reader(io, &stdin_buffer);
222225 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
223226 var server = try Server.init(.{
224227 .in = &stdin_reader.interface,
lib/compiler/resinator/compile.zig+15-8
......@@ -1,6 +1,12 @@
1const std = @import("std");
21const builtin = @import("builtin");
2const native_endian = builtin.cpu.arch.endian();
3
4const std = @import("std");
5const Io = std.Io;
36const Allocator = std.mem.Allocator;
7const WORD = std.os.windows.WORD;
8const DWORD = std.os.windows.DWORD;
9
410const Node = @import("ast.zig").Node;
511const lex = @import("lex.zig");
612const Parser = @import("parse.zig").Parser;
......@@ -17,8 +23,6 @@ const res = @import("res.zig");
1723const ico = @import("ico.zig");
1824const ani = @import("ani.zig");
1925const bmp = @import("bmp.zig");
20const WORD = std.os.windows.WORD;
21const DWORD = std.os.windows.DWORD;
2226const utils = @import("utils.zig");
2327const NameOrOrdinal = res.NameOrOrdinal;
2428const SupportedCodePage = @import("code_pages.zig").SupportedCodePage;
......@@ -28,7 +32,6 @@ const windows1252 = @import("windows1252.zig");
2832const lang = @import("lang.zig");
2933const code_pages = @import("code_pages.zig");
3034const errors = @import("errors.zig");
31const native_endian = builtin.cpu.arch.endian();
3235
3336pub const CompileOptions = struct {
3437 cwd: std.fs.Dir,
......@@ -77,7 +80,7 @@ pub const Dependencies = struct {
7780 }
7881};
7982
80pub fn compile(allocator: Allocator, source: []const u8, writer: *std.Io.Writer, options: CompileOptions) !void {
83pub fn compile(allocator: Allocator, io: Io, source: []const u8, writer: *std.Io.Writer, options: CompileOptions) !void {
8184 var lexer = lex.Lexer.init(source, .{
8285 .default_code_page = options.default_code_page,
8386 .source_mappings = options.source_mappings,
......@@ -166,10 +169,11 @@ pub fn compile(allocator: Allocator, source: []const u8, writer: *std.Io.Writer,
166169 defer arena_allocator.deinit();
167170 const arena = arena_allocator.allocator();
168171
169 var compiler = Compiler{
172 var compiler: Compiler = .{
170173 .source = source,
171174 .arena = arena,
172175 .allocator = allocator,
176 .io = io,
173177 .cwd = options.cwd,
174178 .diagnostics = options.diagnostics,
175179 .dependencies = options.dependencies,
......@@ -191,6 +195,7 @@ pub const Compiler = struct {
191195 source: []const u8,
192196 arena: Allocator,
193197 allocator: Allocator,
198 io: Io,
194199 cwd: std.fs.Dir,
195200 state: State = .{},
196201 diagnostics: *Diagnostics,
......@@ -409,7 +414,7 @@ pub const Compiler = struct {
409414 }
410415 }
411416
412 var first_error: ?std.fs.File.OpenError = null;
417 var first_error: ?(std.fs.File.OpenError || std.fs.File.StatError) = null;
413418 for (self.search_dirs) |search_dir| {
414419 if (utils.openFileNotDir(search_dir.dir, path, .{})) |file| {
415420 errdefer file.close();
......@@ -496,6 +501,8 @@ pub const Compiler = struct {
496501 }
497502
498503 pub fn writeResourceExternal(self: *Compiler, node: *Node.ResourceExternal, writer: *std.Io.Writer) !void {
504 const io = self.io;
505
499506 // Init header with data size zero for now, will need to fill it in later
500507 var header = try self.resourceHeader(node.id, node.type, .{});
501508 defer header.deinit(self.allocator);
......@@ -582,7 +589,7 @@ pub const Compiler = struct {
582589 };
583590 defer file_handle.close();
584591 var file_buffer: [2048]u8 = undefined;
585 var file_reader = file_handle.reader(&file_buffer);
592 var file_reader = file_handle.reader(io, &file_buffer);
586593
587594 if (maybe_predefined_type) |predefined_type| {
588595 switch (predefined_type) {
lib/compiler/resinator/cvtres.zig+11-4
......@@ -1,5 +1,7 @@
11const std = @import("std");
2const Io = std.Io;
23const Allocator = std.mem.Allocator;
4
35const res = @import("res.zig");
46const NameOrOrdinal = res.NameOrOrdinal;
57const MemoryFlags = res.MemoryFlags;
......@@ -169,8 +171,7 @@ pub fn parseNameOrOrdinal(allocator: Allocator, reader: *std.Io.Reader) !NameOrO
169171
170172pub const CoffOptions = struct {
171173 target: std.coff.IMAGE.FILE.MACHINE = .AMD64,
172 /// If true, zeroes will be written to all timestamp fields
173 reproducible: bool = true,
174 timestamp: i64 = 0,
174175 /// If true, the MEM_WRITE flag will not be set in the .rsrc section header
175176 read_only: bool = false,
176177 /// If non-null, a symbol with this name and storage class EXTERNAL will be added to the symbol table.
......@@ -188,7 +189,13 @@ pub const Diagnostics = union {
188189 overflow_resource: usize,
189190};
190191
191pub fn writeCoff(allocator: Allocator, writer: *std.Io.Writer, resources: []const Resource, options: CoffOptions, diagnostics: ?*Diagnostics) !void {
192pub fn writeCoff(
193 allocator: Allocator,
194 writer: *std.Io.Writer,
195 resources: []const Resource,
196 options: CoffOptions,
197 diagnostics: ?*Diagnostics,
198) !void {
192199 var resource_tree = ResourceTree.init(allocator, options);
193200 defer resource_tree.deinit();
194201
......@@ -215,7 +222,7 @@ pub fn writeCoff(allocator: Allocator, writer: *std.Io.Writer, resources: []cons
215222 const pointer_to_rsrc02_data = pointer_to_relocations + relocations_len;
216223 const pointer_to_symbol_table = pointer_to_rsrc02_data + lengths.rsrc02;
217224
218 const timestamp: i64 = if (options.reproducible) 0 else std.time.timestamp();
225 const timestamp: i64 = options.timestamp;
219226 const size_of_optional_header = 0;
220227 const machine_type: std.coff.IMAGE.FILE.MACHINE = options.target;
221228 const flags = std.coff.Header.Flags{
lib/compiler/resinator/errors.zig+27-9
......@@ -1,5 +1,11 @@
1const builtin = @import("builtin");
2const native_endian = builtin.cpu.arch.endian();
3
14const std = @import("std");
5const Io = std.Io;
26const assert = std.debug.assert;
7const Allocator = std.mem.Allocator;
8
39const Token = @import("lex.zig").Token;
410const SourceMappings = @import("source_mapping.zig").SourceMappings;
511const utils = @import("utils.zig");
......@@ -11,19 +17,19 @@ const parse = @import("parse.zig");
1117const lang = @import("lang.zig");
1218const code_pages = @import("code_pages.zig");
1319const SupportedCodePage = code_pages.SupportedCodePage;
14const builtin = @import("builtin");
15const native_endian = builtin.cpu.arch.endian();
1620
1721pub const Diagnostics = struct {
1822 errors: std.ArrayList(ErrorDetails) = .empty,
1923 /// Append-only, cannot handle removing strings.
2024 /// Expects to own all strings within the list.
2125 strings: std.ArrayList([]const u8) = .empty,
22 allocator: std.mem.Allocator,
26 allocator: Allocator,
27 io: Io,
2328
24 pub fn init(allocator: std.mem.Allocator) Diagnostics {
29 pub fn init(allocator: Allocator, io: Io) Diagnostics {
2530 return .{
2631 .allocator = allocator,
32 .io = io,
2733 };
2834 }
2935
......@@ -62,10 +68,11 @@ pub const Diagnostics = struct {
6268 }
6369
6470 pub fn renderToStdErr(self: *Diagnostics, cwd: std.fs.Dir, source: []const u8, tty_config: std.Io.tty.Config, source_mappings: ?SourceMappings) void {
71 const io = self.io;
6572 const stderr = std.debug.lockStderrWriter(&.{});
6673 defer std.debug.unlockStderrWriter();
6774 for (self.errors.items) |err_details| {
68 renderErrorMessage(stderr, tty_config, cwd, err_details, source, self.strings.items, source_mappings) catch return;
75 renderErrorMessage(io, stderr, tty_config, cwd, err_details, source, self.strings.items, source_mappings) catch return;
6976 }
7077 }
7178
......@@ -167,9 +174,9 @@ pub const ErrorDetails = struct {
167174 filename_string_index: FilenameStringIndex,
168175
169176 pub const FilenameStringIndex = std.meta.Int(.unsigned, 32 - @bitSizeOf(FileOpenErrorEnum));
170 pub const FileOpenErrorEnum = std.meta.FieldEnum(std.fs.File.OpenError);
177 pub const FileOpenErrorEnum = std.meta.FieldEnum(std.fs.File.OpenError || std.fs.File.StatError);
171178
172 pub fn enumFromError(err: std.fs.File.OpenError) FileOpenErrorEnum {
179 pub fn enumFromError(err: (std.fs.File.OpenError || std.fs.File.StatError)) FileOpenErrorEnum {
173180 return switch (err) {
174181 inline else => |e| @field(ErrorDetails.FileOpenError.FileOpenErrorEnum, @errorName(e)),
175182 };
......@@ -894,7 +901,16 @@ fn cellCount(code_page: SupportedCodePage, source: []const u8, start_index: usiz
894901
895902const truncated_str = "<...truncated...>";
896903
897pub fn renderErrorMessage(writer: *std.Io.Writer, tty_config: std.Io.tty.Config, cwd: std.fs.Dir, err_details: ErrorDetails, source: []const u8, strings: []const []const u8, source_mappings: ?SourceMappings) !void {
904pub fn renderErrorMessage(
905 io: Io,
906 writer: *std.Io.Writer,
907 tty_config: std.Io.tty.Config,
908 cwd: std.fs.Dir,
909 err_details: ErrorDetails,
910 source: []const u8,
911 strings: []const []const u8,
912 source_mappings: ?SourceMappings,
913) !void {
898914 if (err_details.type == .hint) return;
899915
900916 const source_line_start = err_details.token.getLineStartForErrorDisplay(source);
......@@ -989,6 +1005,7 @@ pub fn renderErrorMessage(writer: *std.Io.Writer, tty_config: std.Io.tty.Config,
9891005 var initial_lines_err: ?anyerror = null;
9901006 var file_reader_buf: [max_source_line_bytes * 2]u8 = undefined;
9911007 var corresponding_lines: ?CorrespondingLines = CorrespondingLines.init(
1008 io,
9921009 cwd,
9931010 err_details,
9941011 source_line_for_display.line,
......@@ -1084,6 +1101,7 @@ const CorrespondingLines = struct {
10841101 code_page: SupportedCodePage,
10851102
10861103 pub fn init(
1104 io: Io,
10871105 cwd: std.fs.Dir,
10881106 err_details: ErrorDetails,
10891107 line_for_comparison: []const u8,
......@@ -1108,7 +1126,7 @@ const CorrespondingLines = struct {
11081126 .code_page = err_details.code_page,
11091127 .file_reader = undefined,
11101128 };
1111 corresponding_lines.file_reader = corresponding_lines.file.reader(file_reader_buf);
1129 corresponding_lines.file_reader = corresponding_lines.file.reader(io, file_reader_buf);
11121130 errdefer corresponding_lines.deinit();
11131131
11141132 try corresponding_lines.writeLineFromStreamVerbatim(
lib/compiler/resinator/main.zig+90-73
......@@ -1,5 +1,9 @@
1const std = @import("std");
21const builtin = @import("builtin");
2
3const std = @import("std");
4const Io = std.Io;
5const Allocator = std.mem.Allocator;
6
37const removeComments = @import("comments.zig").removeComments;
48const parseAndRemoveLineCommands = @import("source_mapping.zig").parseAndRemoveLineCommands;
59const compile = @import("compile.zig").compile;
......@@ -16,19 +20,18 @@ const aro = @import("aro");
1620const compiler_util = @import("../util.zig");
1721
1822pub fn main() !void {
19 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
20 defer std.debug.assert(gpa.deinit() == .ok);
21 const allocator = gpa.allocator();
23 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
24 defer std.debug.assert(debug_allocator.deinit() == .ok);
25 const gpa = debug_allocator.allocator();
2226
23 var arena_state = std.heap.ArenaAllocator.init(allocator);
27 var arena_state = std.heap.ArenaAllocator.init(gpa);
2428 defer arena_state.deinit();
2529 const arena = arena_state.allocator();
2630
2731 const stderr = std.fs.File.stderr();
2832 const stderr_config = std.Io.tty.detectConfig(stderr);
2933
30 const args = try std.process.argsAlloc(allocator);
31 defer std.process.argsFree(allocator, args);
34 const args = try std.process.argsAlloc(arena);
3235
3336 if (args.len < 2) {
3437 try renderErrorMessage(std.debug.lockStderrWriter(&.{}), stderr_config, .err, "expected zig lib dir as first argument", .{});
......@@ -59,11 +62,11 @@ pub fn main() !void {
5962 };
6063
6164 var options = options: {
62 var cli_diagnostics = cli.Diagnostics.init(allocator);
65 var cli_diagnostics = cli.Diagnostics.init(gpa);
6366 defer cli_diagnostics.deinit();
64 var options = cli.parse(allocator, cli_args, &cli_diagnostics) catch |err| switch (err) {
67 var options = cli.parse(gpa, cli_args, &cli_diagnostics) catch |err| switch (err) {
6568 error.ParseError => {
66 try error_handler.emitCliDiagnostics(allocator, cli_args, &cli_diagnostics);
69 try error_handler.emitCliDiagnostics(gpa, cli_args, &cli_diagnostics);
6770 std.process.exit(1);
6871 },
6972 else => |e| return e,
......@@ -84,6 +87,10 @@ pub fn main() !void {
8487 };
8588 defer options.deinit();
8689
90 var threaded: std.Io.Threaded = .init(gpa);
91 defer threaded.deinit();
92 const io = threaded.io();
93
8794 if (options.print_help_and_exit) {
8895 try cli.writeUsage(stdout, "zig rc");
8996 try stdout.flush();
......@@ -99,12 +106,13 @@ pub fn main() !void {
99106 try stdout.flush();
100107 }
101108
102 var dependencies = Dependencies.init(allocator);
109 var dependencies = Dependencies.init(gpa);
103110 defer dependencies.deinit();
104111 const maybe_dependencies: ?*Dependencies = if (options.depfile_path != null) &dependencies else null;
105112
106113 var include_paths = LazyIncludePaths{
107114 .arena = arena,
115 .io = io,
108116 .auto_includes_option = options.auto_includes,
109117 .zig_lib_dir = zig_lib_dir,
110118 .target_machine_type = options.coff_options.target,
......@@ -112,12 +120,12 @@ pub fn main() !void {
112120
113121 const full_input = full_input: {
114122 if (options.input_format == .rc and options.preprocess != .no) {
115 var preprocessed_buf: std.Io.Writer.Allocating = .init(allocator);
123 var preprocessed_buf: std.Io.Writer.Allocating = .init(gpa);
116124 errdefer preprocessed_buf.deinit();
117125
118126 // We're going to throw away everything except the final preprocessed output anyway,
119127 // so we can use a scoped arena for everything else.
120 var aro_arena_state = std.heap.ArenaAllocator.init(allocator);
128 var aro_arena_state = std.heap.ArenaAllocator.init(gpa);
121129 defer aro_arena_state.deinit();
122130 const aro_arena = aro_arena_state.allocator();
123131
......@@ -129,12 +137,12 @@ pub fn main() !void {
129137 .color = stderr_config,
130138 } } },
131139 true => .{ .output = .{ .to_list = .{
132 .arena = .init(allocator),
140 .arena = .init(gpa),
133141 } } },
134142 };
135143 defer diagnostics.deinit();
136144
137 var comp = aro.Compilation.init(aro_arena, aro_arena, &diagnostics, std.fs.cwd());
145 var comp = aro.Compilation.init(aro_arena, aro_arena, io, &diagnostics, std.fs.cwd());
138146 defer comp.deinit();
139147
140148 var argv: std.ArrayList([]const u8) = .empty;
......@@ -159,20 +167,20 @@ pub fn main() !void {
159167
160168 preprocess.preprocess(&comp, &preprocessed_buf.writer, argv.items, maybe_dependencies) catch |err| switch (err) {
161169 error.GeneratedSourceError => {
162 try error_handler.emitAroDiagnostics(allocator, "failed during preprocessor setup (this is always a bug)", &comp);
170 try error_handler.emitAroDiagnostics(gpa, "failed during preprocessor setup (this is always a bug)", &comp);
163171 std.process.exit(1);
164172 },
165173 // ArgError can occur if e.g. the .rc file is not found
166174 error.ArgError, error.PreprocessError => {
167 try error_handler.emitAroDiagnostics(allocator, "failed during preprocessing", &comp);
175 try error_handler.emitAroDiagnostics(gpa, "failed during preprocessing", &comp);
168176 std.process.exit(1);
169177 },
170178 error.FileTooBig => {
171 try error_handler.emitMessage(allocator, .err, "failed during preprocessing: maximum file size exceeded", .{});
179 try error_handler.emitMessage(gpa, .err, "failed during preprocessing: maximum file size exceeded", .{});
172180 std.process.exit(1);
173181 },
174182 error.WriteFailed => {
175 try error_handler.emitMessage(allocator, .err, "failed during preprocessing: error writing the preprocessed output", .{});
183 try error_handler.emitMessage(gpa, .err, "failed during preprocessing: error writing the preprocessed output", .{});
176184 std.process.exit(1);
177185 },
178186 error.OutOfMemory => |e| return e,
......@@ -182,22 +190,22 @@ pub fn main() !void {
182190 } else {
183191 switch (options.input_source) {
184192 .stdio => |file| {
185 var file_reader = file.reader(&.{});
186 break :full_input file_reader.interface.allocRemaining(allocator, .unlimited) catch |err| {
187 try error_handler.emitMessage(allocator, .err, "unable to read input from stdin: {s}", .{@errorName(err)});
193 var file_reader = file.reader(io, &.{});
194 break :full_input file_reader.interface.allocRemaining(gpa, .unlimited) catch |err| {
195 try error_handler.emitMessage(gpa, .err, "unable to read input from stdin: {s}", .{@errorName(err)});
188196 std.process.exit(1);
189197 };
190198 },
191199 .filename => |input_filename| {
192 break :full_input std.fs.cwd().readFileAlloc(input_filename, allocator, .unlimited) catch |err| {
193 try error_handler.emitMessage(allocator, .err, "unable to read input file path '{s}': {s}", .{ input_filename, @errorName(err) });
200 break :full_input std.fs.cwd().readFileAlloc(input_filename, gpa, .unlimited) catch |err| {
201 try error_handler.emitMessage(gpa, .err, "unable to read input file path '{s}': {s}", .{ input_filename, @errorName(err) });
194202 std.process.exit(1);
195203 };
196204 },
197205 }
198206 }
199207 };
200 defer allocator.free(full_input);
208 defer gpa.free(full_input);
201209
202210 if (options.preprocess == .only) {
203211 switch (options.output_source) {
......@@ -221,55 +229,55 @@ pub fn main() !void {
221229 }
222230 else if (options.input_format == .res)
223231 IoStream.fromIoSource(options.input_source, .input) catch |err| {
224 try error_handler.emitMessage(allocator, .err, "unable to read res file path '{s}': {s}", .{ options.input_source.filename, @errorName(err) });
232 try error_handler.emitMessage(gpa, .err, "unable to read res file path '{s}': {s}", .{ options.input_source.filename, @errorName(err) });
225233 std.process.exit(1);
226234 }
227235 else
228236 IoStream.fromIoSource(options.output_source, .output) catch |err| {
229 try error_handler.emitMessage(allocator, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) });
237 try error_handler.emitMessage(gpa, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) });
230238 std.process.exit(1);
231239 };
232 defer res_stream.deinit(allocator);
240 defer res_stream.deinit(gpa);
233241
234242 const res_data = res_data: {
235243 if (options.input_format != .res) {
236244 // Note: We still want to run this when no-preprocess is set because:
237245 // 1. We want to print accurate line numbers after removing multiline comments
238246 // 2. We want to be able to handle an already-preprocessed input with #line commands in it
239 var mapping_results = parseAndRemoveLineCommands(allocator, full_input, full_input, .{ .initial_filename = options.input_source.filename }) catch |err| switch (err) {
247 var mapping_results = parseAndRemoveLineCommands(gpa, full_input, full_input, .{ .initial_filename = options.input_source.filename }) catch |err| switch (err) {
240248 error.InvalidLineCommand => {
241249 // TODO: Maybe output the invalid line command
242 try error_handler.emitMessage(allocator, .err, "invalid line command in the preprocessed source", .{});
250 try error_handler.emitMessage(gpa, .err, "invalid line command in the preprocessed source", .{});
243251 if (options.preprocess == .no) {
244 try error_handler.emitMessage(allocator, .note, "line commands must be of the format: #line <num> \"<path>\"", .{});
252 try error_handler.emitMessage(gpa, .note, "line commands must be of the format: #line <num> \"<path>\"", .{});
245253 } else {
246 try error_handler.emitMessage(allocator, .note, "this is likely to be a bug, please report it", .{});
254 try error_handler.emitMessage(gpa, .note, "this is likely to be a bug, please report it", .{});
247255 }
248256 std.process.exit(1);
249257 },
250258 error.LineNumberOverflow => {
251259 // TODO: Better error message
252 try error_handler.emitMessage(allocator, .err, "line number count exceeded maximum of {}", .{std.math.maxInt(usize)});
260 try error_handler.emitMessage(gpa, .err, "line number count exceeded maximum of {}", .{std.math.maxInt(usize)});
253261 std.process.exit(1);
254262 },
255263 error.OutOfMemory => |e| return e,
256264 };
257 defer mapping_results.mappings.deinit(allocator);
265 defer mapping_results.mappings.deinit(gpa);
258266
259267 const default_code_page = options.default_code_page orelse .windows1252;
260268 const has_disjoint_code_page = hasDisjointCodePage(mapping_results.result, &mapping_results.mappings, default_code_page);
261269
262270 const final_input = try removeComments(mapping_results.result, mapping_results.result, &mapping_results.mappings);
263271
264 var diagnostics = Diagnostics.init(allocator);
272 var diagnostics = Diagnostics.init(gpa, io);
265273 defer diagnostics.deinit();
266274
267275 var output_buffer: [4096]u8 = undefined;
268 var res_stream_writer = res_stream.source.writer(allocator, &output_buffer);
276 var res_stream_writer = res_stream.source.writer(gpa, &output_buffer);
269277 defer res_stream_writer.deinit(&res_stream.source);
270278 const output_buffered_stream = res_stream_writer.interface();
271279
272 compile(allocator, final_input, output_buffered_stream, .{
280 compile(gpa, io, final_input, output_buffered_stream, .{
273281 .cwd = std.fs.cwd(),
274282 .diagnostics = &diagnostics,
275283 .source_mappings = &mapping_results.mappings,
......@@ -287,7 +295,7 @@ pub fn main() !void {
287295 .warn_instead_of_error_on_invalid_code_page = options.warn_instead_of_error_on_invalid_code_page,
288296 }) catch |err| switch (err) {
289297 error.ParseError, error.CompileError => {
290 try error_handler.emitDiagnostics(allocator, std.fs.cwd(), final_input, &diagnostics, mapping_results.mappings);
298 try error_handler.emitDiagnostics(gpa, std.fs.cwd(), final_input, &diagnostics, mapping_results.mappings);
291299 // Delete the output file on error
292300 res_stream.cleanupAfterError();
293301 std.process.exit(1);
......@@ -305,7 +313,7 @@ pub fn main() !void {
305313 // write the depfile
306314 if (options.depfile_path) |depfile_path| {
307315 var depfile = std.fs.cwd().createFile(depfile_path, .{}) catch |err| {
308 try error_handler.emitMessage(allocator, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) });
316 try error_handler.emitMessage(gpa, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) });
309317 std.process.exit(1);
310318 };
311319 defer depfile.close();
......@@ -332,41 +340,41 @@ pub fn main() !void {
332340
333341 if (options.output_format != .coff) return;
334342
335 break :res_data res_stream.source.readAll(allocator) catch |err| {
336 try error_handler.emitMessage(allocator, .err, "unable to read res from '{s}': {s}", .{ res_stream.name, @errorName(err) });
343 break :res_data res_stream.source.readAll(gpa, io) catch |err| {
344 try error_handler.emitMessage(gpa, .err, "unable to read res from '{s}': {s}", .{ res_stream.name, @errorName(err) });
337345 std.process.exit(1);
338346 };
339347 };
340348 // No need to keep the res_data around after parsing the resources from it
341 defer res_data.deinit(allocator);
349 defer res_data.deinit(gpa);
342350
343351 std.debug.assert(options.output_format == .coff);
344352
345353 // TODO: Maybe use a buffered file reader instead of reading file into memory -> fbs
346354 var res_reader: std.Io.Reader = .fixed(res_data.bytes);
347 break :resources cvtres.parseRes(allocator, &res_reader, .{ .max_size = res_data.bytes.len }) catch |err| {
355 break :resources cvtres.parseRes(gpa, &res_reader, .{ .max_size = res_data.bytes.len }) catch |err| {
348356 // TODO: Better errors
349 try error_handler.emitMessage(allocator, .err, "unable to parse res from '{s}': {s}", .{ res_stream.name, @errorName(err) });
357 try error_handler.emitMessage(gpa, .err, "unable to parse res from '{s}': {s}", .{ res_stream.name, @errorName(err) });
350358 std.process.exit(1);
351359 };
352360 };
353361 defer resources.deinit();
354362
355363 var coff_stream = IoStream.fromIoSource(options.output_source, .output) catch |err| {
356 try error_handler.emitMessage(allocator, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) });
364 try error_handler.emitMessage(gpa, .err, "unable to create output file '{s}': {s}", .{ options.output_source.filename, @errorName(err) });
357365 std.process.exit(1);
358366 };
359 defer coff_stream.deinit(allocator);
367 defer coff_stream.deinit(gpa);
360368
361369 var coff_output_buffer: [4096]u8 = undefined;
362 var coff_output_buffered_stream = coff_stream.source.writer(allocator, &coff_output_buffer);
370 var coff_output_buffered_stream = coff_stream.source.writer(gpa, &coff_output_buffer);
363371
364372 var cvtres_diagnostics: cvtres.Diagnostics = .{ .none = {} };
365 cvtres.writeCoff(allocator, coff_output_buffered_stream.interface(), resources.list.items, options.coff_options, &cvtres_diagnostics) catch |err| {
373 cvtres.writeCoff(gpa, coff_output_buffered_stream.interface(), resources.list.items, options.coff_options, &cvtres_diagnostics) catch |err| {
366374 switch (err) {
367375 error.DuplicateResource => {
368376 const duplicate_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];
369 try error_handler.emitMessage(allocator, .err, "duplicate resource [id: {f}, type: {f}, language: {f}]", .{
377 try error_handler.emitMessage(gpa, .err, "duplicate resource [id: {f}, type: {f}, language: {f}]", .{
370378 duplicate_resource.name_value,
371379 fmtResourceType(duplicate_resource.type_value),
372380 duplicate_resource.language,
......@@ -374,8 +382,8 @@ pub fn main() !void {
374382 },
375383 error.ResourceDataTooLong => {
376384 const overflow_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];
377 try error_handler.emitMessage(allocator, .err, "resource has a data length that is too large to be written into a coff section", .{});
378 try error_handler.emitMessage(allocator, .note, "the resource with the invalid size is [id: {f}, type: {f}, language: {f}]", .{
385 try error_handler.emitMessage(gpa, .err, "resource has a data length that is too large to be written into a coff section", .{});
386 try error_handler.emitMessage(gpa, .note, "the resource with the invalid size is [id: {f}, type: {f}, language: {f}]", .{
379387 overflow_resource.name_value,
380388 fmtResourceType(overflow_resource.type_value),
381389 overflow_resource.language,
......@@ -383,15 +391,15 @@ pub fn main() !void {
383391 },
384392 error.TotalResourceDataTooLong => {
385393 const overflow_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];
386 try error_handler.emitMessage(allocator, .err, "total resource data exceeds the maximum of the coff 'size of raw data' field", .{});
387 try error_handler.emitMessage(allocator, .note, "size overflow occurred when attempting to write this resource: [id: {f}, type: {f}, language: {f}]", .{
394 try error_handler.emitMessage(gpa, .err, "total resource data exceeds the maximum of the coff 'size of raw data' field", .{});
395 try error_handler.emitMessage(gpa, .note, "size overflow occurred when attempting to write this resource: [id: {f}, type: {f}, language: {f}]", .{
388396 overflow_resource.name_value,
389397 fmtResourceType(overflow_resource.type_value),
390398 overflow_resource.language,
391399 });
392400 },
393401 else => {
394 try error_handler.emitMessage(allocator, .err, "unable to write coff output file '{s}': {s}", .{ coff_stream.name, @errorName(err) });
402 try error_handler.emitMessage(gpa, .err, "unable to write coff output file '{s}': {s}", .{ coff_stream.name, @errorName(err) });
395403 },
396404 }
397405 // Delete the output file on error
......@@ -423,7 +431,7 @@ const IoStream = struct {
423431 };
424432 }
425433
426 pub fn deinit(self: *IoStream, allocator: std.mem.Allocator) void {
434 pub fn deinit(self: *IoStream, allocator: Allocator) void {
427435 self.source.deinit(allocator);
428436 }
429437
......@@ -458,7 +466,7 @@ const IoStream = struct {
458466 }
459467 }
460468
461 pub fn deinit(self: *Source, allocator: std.mem.Allocator) void {
469 pub fn deinit(self: *Source, allocator: Allocator) void {
462470 switch (self.*) {
463471 .file => |file| file.close(),
464472 .stdio => {},
......@@ -471,18 +479,18 @@ const IoStream = struct {
471479 bytes: []const u8,
472480 needs_free: bool,
473481
474 pub fn deinit(self: Data, allocator: std.mem.Allocator) void {
482 pub fn deinit(self: Data, allocator: Allocator) void {
475483 if (self.needs_free) {
476484 allocator.free(self.bytes);
477485 }
478486 }
479487 };
480488
481 pub fn readAll(self: Source, allocator: std.mem.Allocator) !Data {
489 pub fn readAll(self: Source, allocator: Allocator, io: Io) !Data {
482490 return switch (self) {
483491 inline .file, .stdio => |file| .{
484492 .bytes = b: {
485 var file_reader = file.reader(&.{});
493 var file_reader = file.reader(io, &.{});
486494 break :b try file_reader.interface.allocRemaining(allocator, .unlimited);
487495 },
488496 .needs_free = true,
......@@ -496,7 +504,7 @@ const IoStream = struct {
496504 file: std.fs.File.Writer,
497505 allocating: std.Io.Writer.Allocating,
498506
499 pub const Error = std.mem.Allocator.Error || std.fs.File.WriteError;
507 pub const Error = Allocator.Error || std.fs.File.WriteError;
500508
501509 pub fn interface(this: *@This()) *std.Io.Writer {
502510 return switch (this.*) {
......@@ -514,7 +522,7 @@ const IoStream = struct {
514522 }
515523 };
516524
517 pub fn writer(source: *Source, allocator: std.mem.Allocator, buffer: []u8) Writer {
525 pub fn writer(source: *Source, allocator: Allocator, buffer: []u8) Writer {
518526 return switch (source.*) {
519527 .file, .stdio => |file| .{ .file = file.writer(buffer) },
520528 .memory => |*list| .{ .allocating = .fromArrayList(allocator, list) },
......@@ -525,17 +533,20 @@ const IoStream = struct {
525533};
526534
527535const LazyIncludePaths = struct {
528 arena: std.mem.Allocator,
536 arena: Allocator,
537 io: Io,
529538 auto_includes_option: cli.Options.AutoIncludes,
530539 zig_lib_dir: []const u8,
531540 target_machine_type: std.coff.IMAGE.FILE.MACHINE,
532541 resolved_include_paths: ?[]const []const u8 = null,
533542
534543 pub fn get(self: *LazyIncludePaths, error_handler: *ErrorHandler) ![]const []const u8 {
544 const io = self.io;
545
535546 if (self.resolved_include_paths) |include_paths|
536547 return include_paths;
537548
538 return getIncludePaths(self.arena, self.auto_includes_option, self.zig_lib_dir, self.target_machine_type) catch |err| switch (err) {
549 return getIncludePaths(self.arena, io, self.auto_includes_option, self.zig_lib_dir, self.target_machine_type) catch |err| switch (err) {
539550 error.OutOfMemory => |e| return e,
540551 else => |e| {
541552 switch (e) {
......@@ -556,7 +567,13 @@ const LazyIncludePaths = struct {
556567 }
557568};
558569
559fn getIncludePaths(arena: std.mem.Allocator, auto_includes_option: cli.Options.AutoIncludes, zig_lib_dir: []const u8, target_machine_type: std.coff.IMAGE.FILE.MACHINE) ![]const []const u8 {
570fn getIncludePaths(
571 arena: Allocator,
572 io: Io,
573 auto_includes_option: cli.Options.AutoIncludes,
574 zig_lib_dir: []const u8,
575 target_machine_type: std.coff.IMAGE.FILE.MACHINE,
576) ![]const []const u8 {
560577 if (auto_includes_option == .none) return &[_][]const u8{};
561578
562579 const includes_arch: std.Target.Cpu.Arch = switch (target_machine_type) {
......@@ -600,7 +617,7 @@ fn getIncludePaths(arena: std.mem.Allocator, auto_includes_option: cli.Options.A
600617 .cpu_arch = includes_arch,
601618 .abi = .msvc,
602619 };
603 const target = std.zig.resolveTargetQueryOrFatal(target_query);
620 const target = std.zig.resolveTargetQueryOrFatal(io, target_query);
604621 const is_native_abi = target_query.isNativeAbi();
605622 const detected_libc = std.zig.LibCDirs.detect(arena, zig_lib_dir, &target, is_native_abi, true, null) catch {
606623 if (includes == .any) {
......@@ -626,7 +643,7 @@ fn getIncludePaths(arena: std.mem.Allocator, auto_includes_option: cli.Options.A
626643 .cpu_arch = includes_arch,
627644 .abi = .gnu,
628645 };
629 const target = std.zig.resolveTargetQueryOrFatal(target_query);
646 const target = std.zig.resolveTargetQueryOrFatal(io, target_query);
630647 const is_native_abi = target_query.isNativeAbi();
631648 const detected_libc = std.zig.LibCDirs.detect(arena, zig_lib_dir, &target, is_native_abi, true, null) catch |err| switch (err) {
632649 error.OutOfMemory => |e| return e,
......@@ -647,7 +664,7 @@ const ErrorHandler = union(enum) {
647664
648665 pub fn emitCliDiagnostics(
649666 self: *ErrorHandler,
650 allocator: std.mem.Allocator,
667 allocator: Allocator,
651668 args: []const []const u8,
652669 diagnostics: *cli.Diagnostics,
653670 ) !void {
......@@ -666,7 +683,7 @@ const ErrorHandler = union(enum) {
666683
667684 pub fn emitAroDiagnostics(
668685 self: *ErrorHandler,
669 allocator: std.mem.Allocator,
686 allocator: Allocator,
670687 fail_msg: []const u8,
671688 comp: *aro.Compilation,
672689 ) !void {
......@@ -692,7 +709,7 @@ const ErrorHandler = union(enum) {
692709
693710 pub fn emitDiagnostics(
694711 self: *ErrorHandler,
695 allocator: std.mem.Allocator,
712 allocator: Allocator,
696713 cwd: std.fs.Dir,
697714 source: []const u8,
698715 diagnostics: *Diagnostics,
......@@ -713,7 +730,7 @@ const ErrorHandler = union(enum) {
713730
714731 pub fn emitMessage(
715732 self: *ErrorHandler,
716 allocator: std.mem.Allocator,
733 allocator: Allocator,
717734 msg_type: @import("utils.zig").ErrorMessageType,
718735 comptime format: []const u8,
719736 args: anytype,
......@@ -738,7 +755,7 @@ const ErrorHandler = union(enum) {
738755};
739756
740757fn cliDiagnosticsToErrorBundle(
741 gpa: std.mem.Allocator,
758 gpa: Allocator,
742759 diagnostics: *cli.Diagnostics,
743760) !ErrorBundle {
744761 @branchHint(.cold);
......@@ -783,7 +800,7 @@ fn cliDiagnosticsToErrorBundle(
783800}
784801
785802fn diagnosticsToErrorBundle(
786 gpa: std.mem.Allocator,
803 gpa: Allocator,
787804 source: []const u8,
788805 diagnostics: *Diagnostics,
789806 mappings: SourceMappings,
......@@ -870,7 +887,7 @@ fn diagnosticsToErrorBundle(
870887 return try bundle.toOwnedBundle("");
871888}
872889
873fn errorStringToErrorBundle(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) !ErrorBundle {
890fn errorStringToErrorBundle(allocator: Allocator, comptime format: []const u8, args: anytype) !ErrorBundle {
874891 @branchHint(.cold);
875892 var bundle: ErrorBundle.Wip = undefined;
876893 try bundle.init(allocator);
lib/compiler/resinator/utils.zig+5-1
......@@ -26,7 +26,11 @@ pub const UncheckedSliceWriter = struct {
2626/// Cross-platform 'std.fs.Dir.openFile' wrapper that will always return IsDir if
2727/// a directory is attempted to be opened.
2828/// TODO: Remove once https://github.com/ziglang/zig/issues/5732 is addressed.
29pub fn openFileNotDir(cwd: std.fs.Dir, path: []const u8, flags: std.fs.File.OpenFlags) std.fs.File.OpenError!std.fs.File {
29pub fn openFileNotDir(
30 cwd: std.fs.Dir,
31 path: []const u8,
32 flags: std.fs.File.OpenFlags,
33) (std.fs.File.OpenError || std.fs.File.StatError)!std.fs.File {
3034 const file = try cwd.openFile(path, flags);
3135 errdefer file.close();
3236 // https://github.com/ziglang/zig/issues/5732
lib/compiler/test_runner.zig+10-13
......@@ -2,6 +2,7 @@
22const builtin = @import("builtin");
33
44const std = @import("std");
5const Io = std.Io;
56const fatal = std.process.fatal;
67const testing = std.testing;
78const assert = std.debug.assert;
......@@ -12,10 +13,11 @@ pub const std_options: std.Options = .{
1213};
1314
1415var log_err_count: usize = 0;
15var fba = std.heap.FixedBufferAllocator.init(&fba_buffer);
16var fba: std.heap.FixedBufferAllocator = .init(&fba_buffer);
1617var fba_buffer: [8192]u8 = undefined;
1718var stdin_buffer: [4096]u8 = undefined;
1819var stdout_buffer: [4096]u8 = undefined;
20var runner_threaded_io: Io.Threaded = .init_single_threaded;
1921
2022/// Keep in sync with logic in `std.Build.addRunArtifact` which decides whether
2123/// the test runner will communicate with the build runner via `std.zig.Server`.
......@@ -63,8 +65,6 @@ pub fn main() void {
6365 fuzz_abi.fuzzer_init(.fromSlice(cache_dir));
6466 }
6567
66 fba.reset();
67
6868 if (listen) {
6969 return mainServer() catch @panic("internal test runner failure");
7070 } else {
......@@ -74,7 +74,7 @@ pub fn main() void {
7474
7575fn mainServer() !void {
7676 @disableInstrumentation();
77 var stdin_reader = std.fs.File.stdin().readerStreaming(&stdin_buffer);
77 var stdin_reader = std.fs.File.stdin().readerStreaming(runner_threaded_io.io(), &stdin_buffer);
7878 var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer);
7979 var server = try std.zig.Server.init(.{
8080 .in = &stdin_reader.interface,
......@@ -131,6 +131,7 @@ fn mainServer() !void {
131131
132132 .run_test => {
133133 testing.allocator_instance = .{};
134 testing.io_instance = .init(testing.allocator);
134135 log_err_count = 0;
135136 const index = try server.receiveBody_u32();
136137 const test_fn = builtin.test_functions[index];
......@@ -152,6 +153,7 @@ fn mainServer() !void {
152153 break :s .fail;
153154 },
154155 };
156 testing.io_instance.deinit();
155157 const leak_count = testing.allocator_instance.detectLeaks();
156158 testing.allocator_instance.deinitWithoutLeakChecks();
157159 try server.serveTestResults(.{
......@@ -228,18 +230,13 @@ fn mainTerminal() void {
228230 });
229231 const have_tty = std.fs.File.stderr().isTty();
230232
231 var async_frame_buffer: []align(builtin.target.stackAlignment()) u8 = undefined;
232 // TODO this is on the next line (using `undefined` above) because otherwise zig incorrectly
233 // ignores the alignment of the slice.
234 async_frame_buffer = &[_]u8{};
235
236233 var leaks: usize = 0;
237234 for (test_fn_list, 0..) |test_fn, i| {
238235 testing.allocator_instance = .{};
236 testing.io_instance = .init(testing.allocator);
239237 defer {
240 if (testing.allocator_instance.deinit() == .leak) {
241 leaks += 1;
242 }
238 testing.io_instance.deinit();
239 if (testing.allocator_instance.deinit() == .leak) leaks += 1;
243240 }
244241 testing.log_level = .warn;
245242
......@@ -326,7 +323,7 @@ pub fn mainSimple() anyerror!void {
326323 .stage2_aarch64, .stage2_riscv64 => true,
327324 else => false,
328325 };
329 // is the backend capable of calling `std.Io.Writer.print`?
326 // is the backend capable of calling `Io.Writer.print`?
330327 const enable_print = switch (builtin.zig_backend) {
331328 .stage2_aarch64, .stage2_riscv64 => true,
332329 else => false,
lib/compiler/translate-c/main.zig+5-1
......@@ -18,6 +18,10 @@ pub fn main() u8 {
1818 defer arena_instance.deinit();
1919 const arena = arena_instance.allocator();
2020
21 var threaded: std.Io.Threaded = .init(gpa);
22 defer threaded.deinit();
23 const io = threaded.io();
24
2125 var args = process.argsAlloc(arena) catch {
2226 std.debug.print("ran out of memory allocating arguments\n", .{});
2327 if (fast_exit) process.exit(1);
......@@ -42,7 +46,7 @@ pub fn main() u8 {
4246 };
4347 defer diagnostics.deinit();
4448
45 var comp = aro.Compilation.initDefault(gpa, arena, &diagnostics, std.fs.cwd()) catch |err| switch (err) {
49 var comp = aro.Compilation.initDefault(gpa, arena, io, &diagnostics, std.fs.cwd()) catch |err| switch (err) {
4650 error.OutOfMemory => {
4751 std.debug.print("ran out of memory initializing C compilation\n", .{});
4852 if (fast_exit) process.exit(1);
lib/std/Build.zig+9-3
......@@ -1,5 +1,7 @@
1const std = @import("std.zig");
21const builtin = @import("builtin");
2
3const std = @import("std.zig");
4const Io = std.Io;
35const fs = std.fs;
46const mem = std.mem;
57const debug = std.debug;
......@@ -110,6 +112,7 @@ pub const ReleaseMode = enum {
110112/// Shared state among all Build instances.
111113/// Settings that are here rather than in Build are not configurable per-package.
112114pub const Graph = struct {
115 io: Io,
113116 arena: Allocator,
114117 system_library_options: std.StringArrayHashMapUnmanaged(SystemLibraryMode) = .empty,
115118 system_package_mode: bool = false,
......@@ -1834,6 +1837,8 @@ pub fn runAllowFail(
18341837 if (!process.can_spawn)
18351838 return error.ExecNotSupported;
18361839
1840 const io = b.graph.io;
1841
18371842 const max_output_size = 400 * 1024;
18381843 var child = std.process.Child.init(argv, b.allocator);
18391844 child.stdin_behavior = .Ignore;
......@@ -1844,7 +1849,7 @@ pub fn runAllowFail(
18441849 try Step.handleVerbose2(b, null, child.env_map, argv);
18451850 try child.spawn();
18461851
1847 var stdout_reader = child.stdout.?.readerStreaming(&.{});
1852 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
18481853 const stdout = stdout_reader.interface.allocRemaining(b.allocator, .limited(max_output_size)) catch {
18491854 return error.ReadFailure;
18501855 };
......@@ -2666,9 +2671,10 @@ pub fn resolveTargetQuery(b: *Build, query: Target.Query) ResolvedTarget {
26662671 // Hot path. This is faster than querying the native CPU and OS again.
26672672 return b.graph.host;
26682673 }
2674 const io = b.graph.io;
26692675 return .{
26702676 .query = query,
2671 .result = std.zig.system.resolveTargetQuery(query) catch
2677 .result = std.zig.system.resolveTargetQuery(io, query) catch
26722678 @panic("unable to resolve target query"),
26732679 };
26742680}
lib/std/Build/Cache.zig+44-28
......@@ -3,8 +3,10 @@
33//! not to withstand attacks using specially-crafted input.
44
55const Cache = @This();
6const std = @import("std");
76const builtin = @import("builtin");
7
8const std = @import("std");
9const Io = std.Io;
810const crypto = std.crypto;
911const fs = std.fs;
1012const assert = std.debug.assert;
......@@ -15,10 +17,11 @@ const Allocator = std.mem.Allocator;
1517const log = std.log.scoped(.cache);
1618
1719gpa: Allocator,
20io: Io,
1821manifest_dir: fs.Dir,
1922hash: HashHelper = .{},
2023/// This value is accessed from multiple threads, protected by mutex.
21recent_problematic_timestamp: i128 = 0,
24recent_problematic_timestamp: Io.Timestamp = .zero,
2225mutex: std.Thread.Mutex = .{},
2326
2427/// A set of strings such as the zig library directory or project source root, which
......@@ -152,7 +155,7 @@ pub const File = struct {
152155 pub const Stat = struct {
153156 inode: fs.File.INode,
154157 size: u64,
155 mtime: i128,
158 mtime: Io.Timestamp,
156159
157160 pub fn fromFs(fs_stat: fs.File.Stat) Stat {
158161 return .{
......@@ -327,7 +330,7 @@ pub const Manifest = struct {
327330 diagnostic: Diagnostic = .none,
328331 /// Keeps track of the last time we performed a file system write to observe
329332 /// what time the file system thinks it is, according to its own granularity.
330 recent_problematic_timestamp: i128 = 0,
333 recent_problematic_timestamp: Io.Timestamp = .zero,
331334
332335 pub const Diagnostic = union(enum) {
333336 none,
......@@ -661,9 +664,10 @@ pub const Manifest = struct {
661664 },
662665 } {
663666 const gpa = self.cache.gpa;
667 const io = self.cache.io;
664668 const input_file_count = self.files.entries.len;
665669 var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded
666 var manifest_reader = self.manifest_file.?.reader(&tiny_buffer); // Reads positionally from zero.
670 var manifest_reader = self.manifest_file.?.reader(io, &tiny_buffer); // Reads positionally from zero.
667671 const limit: std.Io.Limit = .limited(manifest_file_size_max);
668672 const file_contents = manifest_reader.interface.allocRemaining(gpa, limit) catch |err| switch (err) {
669673 error.OutOfMemory => return error.OutOfMemory,
......@@ -724,7 +728,7 @@ pub const Manifest = struct {
724728 file.stat = .{
725729 .size = stat_size,
726730 .inode = stat_inode,
727 .mtime = stat_mtime,
731 .mtime = .{ .nanoseconds = stat_mtime },
728732 };
729733 file.bin_digest = file_bin_digest;
730734 break :f file;
......@@ -743,7 +747,7 @@ pub const Manifest = struct {
743747 .stat = .{
744748 .size = stat_size,
745749 .inode = stat_inode,
746 .mtime = stat_mtime,
750 .mtime = .{ .nanoseconds = stat_mtime },
747751 },
748752 .bin_digest = file_bin_digest,
749753 };
......@@ -776,7 +780,7 @@ pub const Manifest = struct {
776780 return error.CacheCheckFailed;
777781 };
778782 const size_match = actual_stat.size == cache_hash_file.stat.size;
779 const mtime_match = actual_stat.mtime == cache_hash_file.stat.mtime;
783 const mtime_match = actual_stat.mtime.nanoseconds == cache_hash_file.stat.mtime.nanoseconds;
780784 const inode_match = actual_stat.inode == cache_hash_file.stat.inode;
781785
782786 if (!size_match or !mtime_match or !inode_match) {
......@@ -788,7 +792,7 @@ pub const Manifest = struct {
788792
789793 if (self.isProblematicTimestamp(cache_hash_file.stat.mtime)) {
790794 // The actual file has an unreliable timestamp, force it to be hashed
791 cache_hash_file.stat.mtime = 0;
795 cache_hash_file.stat.mtime = .zero;
792796 cache_hash_file.stat.inode = 0;
793797 }
794798
......@@ -844,10 +848,10 @@ pub const Manifest = struct {
844848 }
845849 }
846850
847 fn isProblematicTimestamp(man: *Manifest, file_time: i128) bool {
851 fn isProblematicTimestamp(man: *Manifest, timestamp: Io.Timestamp) bool {
848852 // If the file_time is prior to the most recent problematic timestamp
849853 // then we don't need to access the filesystem.
850 if (file_time < man.recent_problematic_timestamp)
854 if (timestamp.nanoseconds < man.recent_problematic_timestamp.nanoseconds)
851855 return false;
852856
853857 // Next we will check the globally shared Cache timestamp, which is accessed
......@@ -857,7 +861,7 @@ pub const Manifest = struct {
857861
858862 // Save the global one to our local one to avoid locking next time.
859863 man.recent_problematic_timestamp = man.cache.recent_problematic_timestamp;
860 if (file_time < man.recent_problematic_timestamp)
864 if (timestamp.nanoseconds < man.recent_problematic_timestamp.nanoseconds)
861865 return false;
862866
863867 // This flag prevents multiple filesystem writes for the same hit() call.
......@@ -875,7 +879,7 @@ pub const Manifest = struct {
875879 man.cache.recent_problematic_timestamp = man.recent_problematic_timestamp;
876880 }
877881
878 return file_time >= man.recent_problematic_timestamp;
882 return timestamp.nanoseconds >= man.recent_problematic_timestamp.nanoseconds;
879883 }
880884
881885 fn populateFileHash(self: *Manifest, ch_file: *File) !void {
......@@ -900,7 +904,7 @@ pub const Manifest = struct {
900904
901905 if (self.isProblematicTimestamp(ch_file.stat.mtime)) {
902906 // The actual file has an unreliable timestamp, force it to be hashed
903 ch_file.stat.mtime = 0;
907 ch_file.stat.mtime = .zero;
904908 ch_file.stat.inode = 0;
905909 }
906910
......@@ -1036,7 +1040,7 @@ pub const Manifest = struct {
10361040
10371041 if (self.isProblematicTimestamp(new_file.stat.mtime)) {
10381042 // The actual file has an unreliable timestamp, force it to be hashed
1039 new_file.stat.mtime = 0;
1043 new_file.stat.mtime = .zero;
10401044 new_file.stat.inode = 0;
10411045 }
10421046
......@@ -1301,7 +1305,7 @@ fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) fs.File.PReadErro
13011305}
13021306
13031307// Create/Write a file, close it, then grab its stat.mtime timestamp.
1304fn testGetCurrentFileTimestamp(dir: fs.Dir) !i128 {
1308fn testGetCurrentFileTimestamp(dir: fs.Dir) !Io.Timestamp {
13051309 const test_out_file = "test-filetimestamp.tmp";
13061310
13071311 var file = try dir.createFile(test_out_file, .{
......@@ -1317,6 +1321,8 @@ fn testGetCurrentFileTimestamp(dir: fs.Dir) !i128 {
13171321}
13181322
13191323test "cache file and then recall it" {
1324 const io = std.testing.io;
1325
13201326 var tmp = testing.tmpDir(.{});
13211327 defer tmp.cleanup();
13221328
......@@ -1327,15 +1333,16 @@ test "cache file and then recall it" {
13271333
13281334 // Wait for file timestamps to tick
13291335 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
1330 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
1331 std.Thread.sleep(1);
1336 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
1337 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
13321338 }
13331339
13341340 var digest1: HexDigest = undefined;
13351341 var digest2: HexDigest = undefined;
13361342
13371343 {
1338 var cache = Cache{
1344 var cache: Cache = .{
1345 .io = io,
13391346 .gpa = testing.allocator,
13401347 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
13411348 };
......@@ -1378,6 +1385,8 @@ test "cache file and then recall it" {
13781385}
13791386
13801387test "check that changing a file makes cache fail" {
1388 const io = std.testing.io;
1389
13811390 var tmp = testing.tmpDir(.{});
13821391 defer tmp.cleanup();
13831392
......@@ -1390,15 +1399,16 @@ test "check that changing a file makes cache fail" {
13901399
13911400 // Wait for file timestamps to tick
13921401 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
1393 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
1394 std.Thread.sleep(1);
1402 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
1403 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
13951404 }
13961405
13971406 var digest1: HexDigest = undefined;
13981407 var digest2: HexDigest = undefined;
13991408
14001409 {
1401 var cache = Cache{
1410 var cache: Cache = .{
1411 .io = io,
14021412 .gpa = testing.allocator,
14031413 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
14041414 };
......@@ -1447,6 +1457,8 @@ test "check that changing a file makes cache fail" {
14471457}
14481458
14491459test "no file inputs" {
1460 const io = testing.io;
1461
14501462 var tmp = testing.tmpDir(.{});
14511463 defer tmp.cleanup();
14521464
......@@ -1455,7 +1467,8 @@ test "no file inputs" {
14551467 var digest1: HexDigest = undefined;
14561468 var digest2: HexDigest = undefined;
14571469
1458 var cache = Cache{
1470 var cache: Cache = .{
1471 .io = io,
14591472 .gpa = testing.allocator,
14601473 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
14611474 };
......@@ -1490,6 +1503,8 @@ test "no file inputs" {
14901503}
14911504
14921505test "Manifest with files added after initial hash work" {
1506 const io = std.testing.io;
1507
14931508 var tmp = testing.tmpDir(.{});
14941509 defer tmp.cleanup();
14951510
......@@ -1502,8 +1517,8 @@ test "Manifest with files added after initial hash work" {
15021517
15031518 // Wait for file timestamps to tick
15041519 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
1505 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
1506 std.Thread.sleep(1);
1520 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
1521 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
15071522 }
15081523
15091524 var digest1: HexDigest = undefined;
......@@ -1511,7 +1526,8 @@ test "Manifest with files added after initial hash work" {
15111526 var digest3: HexDigest = undefined;
15121527
15131528 {
1514 var cache = Cache{
1529 var cache: Cache = .{
1530 .io = io,
15151531 .gpa = testing.allocator,
15161532 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
15171533 };
......@@ -1552,8 +1568,8 @@ test "Manifest with files added after initial hash work" {
15521568
15531569 // Wait for file timestamps to tick
15541570 const initial_time2 = try testGetCurrentFileTimestamp(tmp.dir);
1555 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time2) {
1556 std.Thread.sleep(1);
1571 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time2.nanoseconds) {
1572 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
15571573 }
15581574
15591575 {
lib/std/Build/Cache/Path.zig+6-4
......@@ -1,5 +1,7 @@
11const Path = @This();
2
23const std = @import("../../std.zig");
4const Io = std.Io;
35const assert = std.debug.assert;
46const fs = std.fs;
57const Allocator = std.mem.Allocator;
......@@ -119,7 +121,7 @@ pub fn atomicFile(
119121 return p.root_dir.handle.atomicFile(joined_path, options);
120122}
121123
122pub fn access(p: Path, sub_path: []const u8, flags: fs.File.OpenFlags) !void {
124pub fn access(p: Path, sub_path: []const u8, flags: Io.Dir.AccessOptions) !void {
123125 var buf: [fs.max_path_bytes]u8 = undefined;
124126 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
125127 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{
......@@ -151,7 +153,7 @@ pub fn fmtEscapeString(path: Path) std.fmt.Alt(Path, formatEscapeString) {
151153 return .{ .data = path };
152154}
153155
154pub fn formatEscapeString(path: Path, writer: *std.Io.Writer) std.Io.Writer.Error!void {
156pub fn formatEscapeString(path: Path, writer: *Io.Writer) Io.Writer.Error!void {
155157 if (path.root_dir.path) |p| {
156158 try std.zig.stringEscape(p, writer);
157159 if (path.sub_path.len > 0) try std.zig.stringEscape(fs.path.sep_str, writer);
......@@ -167,7 +169,7 @@ pub fn fmtEscapeChar(path: Path) std.fmt.Alt(Path, formatEscapeChar) {
167169}
168170
169171/// Deprecated, use double quoted escape to print paths.
170pub fn formatEscapeChar(path: Path, writer: *std.Io.Writer) std.Io.Writer.Error!void {
172pub fn formatEscapeChar(path: Path, writer: *Io.Writer) Io.Writer.Error!void {
171173 if (path.root_dir.path) |p| {
172174 for (p) |byte| try std.zig.charEscape(byte, writer);
173175 if (path.sub_path.len > 0) try writer.writeByte(fs.path.sep);
......@@ -177,7 +179,7 @@ pub fn formatEscapeChar(path: Path, writer: *std.Io.Writer) std.Io.Writer.Error!
177179 }
178180}
179181
180pub fn format(self: Path, writer: *std.Io.Writer) std.Io.Writer.Error!void {
182pub fn format(self: Path, writer: *Io.Writer) Io.Writer.Error!void {
181183 if (std.fs.path.isAbsolute(self.sub_path)) {
182184 try writer.writeAll(self.sub_path);
183185 return;
lib/std/Build/Fuzz.zig+6-1
......@@ -1,4 +1,5 @@
11const std = @import("../std.zig");
2const Io = std.Io;
23const Build = std.Build;
34const Cache = Build.Cache;
45const Step = std.Build.Step;
......@@ -14,6 +15,7 @@ const Fuzz = @This();
1415const build_runner = @import("root");
1516
1617gpa: Allocator,
18io: Io,
1719mode: Mode,
1820
1921/// Allocated into `gpa`.
......@@ -75,6 +77,7 @@ const CoverageMap = struct {
7577
7678pub fn init(
7779 gpa: Allocator,
80 io: Io,
7881 thread_pool: *std.Thread.Pool,
7982 all_steps: []const *Build.Step,
8083 root_prog_node: std.Progress.Node,
......@@ -111,6 +114,7 @@ pub fn init(
111114
112115 return .{
113116 .gpa = gpa,
117 .io = io,
114118 .mode = mode,
115119 .run_steps = run_steps,
116120 .wait_group = .{},
......@@ -484,6 +488,7 @@ fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReporte
484488
485489pub fn waitAndPrintReport(fuzz: *Fuzz) void {
486490 assert(fuzz.mode == .limit);
491 const io = fuzz.io;
487492
488493 fuzz.wait_group.wait();
489494 fuzz.wait_group.reset();
......@@ -506,7 +511,7 @@ pub fn waitAndPrintReport(fuzz: *Fuzz) void {
506511
507512 const fuzz_abi = std.Build.abi.fuzz;
508513 var rbuf: [0x1000]u8 = undefined;
509 var r = coverage_file.reader(&rbuf);
514 var r = coverage_file.reader(io, &rbuf);
510515
511516 var header: fuzz_abi.SeenPcsHeader = undefined;
512517 r.interface.readSliceAll(std.mem.asBytes(&header)) catch |err| {
lib/std/Build/Step.zig+11-8
......@@ -1,9 +1,11 @@
11const Step = @This();
2const builtin = @import("builtin");
3
24const std = @import("../std.zig");
5const Io = std.Io;
36const Build = std.Build;
47const Allocator = std.mem.Allocator;
58const assert = std.debug.assert;
6const builtin = @import("builtin");
79const Cache = Build.Cache;
810const Path = Cache.Path;
911const ArrayList = std.ArrayList;
......@@ -327,7 +329,7 @@ pub fn cast(step: *Step, comptime T: type) ?*T {
327329}
328330
329331/// For debugging purposes, prints identifying information about this Step.
330pub fn dump(step: *Step, w: *std.Io.Writer, tty_config: std.Io.tty.Config) void {
332pub fn dump(step: *Step, w: *Io.Writer, tty_config: Io.tty.Config) void {
331333 if (step.debug_stack_trace.instruction_addresses.len > 0) {
332334 w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {};
333335 std.debug.writeStackTrace(&step.debug_stack_trace, w, tty_config) catch {};
......@@ -382,7 +384,7 @@ pub fn addError(step: *Step, comptime fmt: []const u8, args: anytype) error{OutO
382384
383385pub const ZigProcess = struct {
384386 child: std.process.Child,
385 poller: std.Io.Poller(StreamEnum),
387 poller: Io.Poller(StreamEnum),
386388 progress_ipc_fd: if (std.Progress.have_ipc) ?std.posix.fd_t else void,
387389
388390 pub const StreamEnum = enum { stdout, stderr };
......@@ -458,7 +460,7 @@ pub fn evalZigProcess(
458460 const zp = try gpa.create(ZigProcess);
459461 zp.* = .{
460462 .child = child,
461 .poller = std.Io.poll(gpa, ZigProcess.StreamEnum, .{
463 .poller = Io.poll(gpa, ZigProcess.StreamEnum, .{
462464 .stdout = child.stdout.?,
463465 .stderr = child.stderr.?,
464466 }),
......@@ -505,11 +507,12 @@ pub fn evalZigProcess(
505507}
506508
507509/// Wrapper around `std.fs.Dir.updateFile` that handles verbose and error output.
508pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u8) !std.fs.Dir.PrevStatus {
510pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u8) !Io.Dir.PrevStatus {
509511 const b = s.owner;
512 const io = b.graph.io;
510513 const src_path = src_lazy_path.getPath3(b, s);
511514 try handleVerbose(b, null, &.{ "install", "-C", b.fmt("{f}", .{src_path}), dest_path });
512 return src_path.root_dir.handle.updateFile(src_path.sub_path, std.fs.cwd(), dest_path, .{}) catch |err| {
515 return Io.Dir.updateFile(src_path.root_dir.handle.adaptToNewApi(), io, src_path.sub_path, .cwd(), dest_path, .{}) catch |err| {
513516 return s.fail("unable to update file from '{f}' to '{s}': {s}", .{
514517 src_path, dest_path, @errorName(err),
515518 });
......@@ -738,7 +741,7 @@ pub fn allocPrintCmd2(
738741 argv: []const []const u8,
739742) Allocator.Error![]u8 {
740743 const shell = struct {
741 fn escape(writer: *std.Io.Writer, string: []const u8, is_argv0: bool) !void {
744 fn escape(writer: *Io.Writer, string: []const u8, is_argv0: bool) !void {
742745 for (string) |c| {
743746 if (switch (c) {
744747 else => true,
......@@ -772,7 +775,7 @@ pub fn allocPrintCmd2(
772775 }
773776 };
774777
775 var aw: std.Io.Writer.Allocating = .init(gpa);
778 var aw: Io.Writer.Allocating = .init(gpa);
776779 defer aw.deinit();
777780 const writer = &aw.writer;
778781 if (opt_cwd) |cwd| writer.print("cd {s} && ", .{cwd}) catch return error.OutOfMemory;
lib/std/Build/Step/Compile.zig+2-2
......@@ -1701,7 +1701,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
17011701 // This prevents a warning, that should probably be upgraded to an error in Zig's
17021702 // CLI parsing code, when the linker sees an -L directory that does not exist.
17031703
1704 if (prefix_dir.accessZ("lib", .{})) |_| {
1704 if (prefix_dir.access("lib", .{})) |_| {
17051705 try zig_args.appendSlice(&.{
17061706 "-L", b.pathJoin(&.{ search_prefix, "lib" }),
17071707 });
......@@ -1712,7 +1712,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
17121712 }),
17131713 }
17141714
1715 if (prefix_dir.accessZ("include", .{})) |_| {
1715 if (prefix_dir.access("include", .{})) |_| {
17161716 try zig_args.appendSlice(&.{
17171717 "-I", b.pathJoin(&.{ search_prefix, "include" }),
17181718 });
lib/std/Build/Step/Options.zig+5-1
......@@ -532,12 +532,16 @@ const Arg = struct {
532532test Options {
533533 if (builtin.os.tag == .wasi) return error.SkipZigTest;
534534
535 const io = std.testing.io;
536
535537 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
536538 defer arena.deinit();
537539
538540 var graph: std.Build.Graph = .{
541 .io = io,
539542 .arena = arena.allocator(),
540543 .cache = .{
544 .io = io,
541545 .gpa = arena.allocator(),
542546 .manifest_dir = std.fs.cwd(),
543547 },
......@@ -546,7 +550,7 @@ test Options {
546550 .global_cache_root = .{ .path = "test", .handle = std.fs.cwd() },
547551 .host = .{
548552 .query = .{},
549 .result = try std.zig.system.resolveTargetQuery(.{}),
553 .result = try std.zig.system.resolveTargetQuery(io, .{}),
550554 },
551555 .zig_lib_directory = std.Build.Cache.Directory.cwd(),
552556 .time_report = false,
lib/std/Build/Step/Run.zig+8-5
......@@ -761,6 +761,7 @@ const IndexedOutput = struct {
761761};
762762fn make(step: *Step, options: Step.MakeOptions) !void {
763763 const b = step.owner;
764 const io = b.graph.io;
764765 const arena = b.allocator;
765766 const run: *Run = @fieldParentPtr("step", step);
766767 const has_side_effects = run.hasSideEffects();
......@@ -834,7 +835,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
834835 defer file.close();
835836
836837 var buf: [1024]u8 = undefined;
837 var file_reader = file.reader(&buf);
838 var file_reader = file.reader(io, &buf);
838839 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {
839840 error.ReadFailed => return step.fail(
840841 "failed to read from '{f}': {t}",
......@@ -1067,6 +1068,7 @@ pub fn rerunInFuzzMode(
10671068) !void {
10681069 const step = &run.step;
10691070 const b = step.owner;
1071 const io = b.graph.io;
10701072 const arena = b.allocator;
10711073 var argv_list: std.ArrayList([]const u8) = .empty;
10721074 for (run.argv.items) |arg| {
......@@ -1093,7 +1095,7 @@ pub fn rerunInFuzzMode(
10931095 defer file.close();
10941096
10951097 var buf: [1024]u8 = undefined;
1096 var file_reader = file.reader(&buf);
1098 var file_reader = file.reader(io, &buf);
10971099 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {
10981100 error.ReadFailed => return file_reader.err.?,
10991101 error.WriteFailed => return error.OutOfMemory,
......@@ -2090,6 +2092,7 @@ fn sendRunFuzzTestMessage(
20902092
20912093fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {
20922094 const b = run.step.owner;
2095 const io = b.graph.io;
20932096 const arena = b.allocator;
20942097
20952098 try child.spawn();
......@@ -2113,7 +2116,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {
21132116 defer file.close();
21142117 // TODO https://github.com/ziglang/zig/issues/23955
21152118 var read_buffer: [1024]u8 = undefined;
2116 var file_reader = file.reader(&read_buffer);
2119 var file_reader = file.reader(io, &read_buffer);
21172120 var write_buffer: [1024]u8 = undefined;
21182121 var stdin_writer = child.stdin.?.writer(&write_buffer);
21192122 _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
......@@ -2159,7 +2162,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {
21592162 stdout_bytes = try poller.toOwnedSlice(.stdout);
21602163 stderr_bytes = try poller.toOwnedSlice(.stderr);
21612164 } else {
2162 var stdout_reader = stdout.readerStreaming(&.{});
2165 var stdout_reader = stdout.readerStreaming(io, &.{});
21632166 stdout_bytes = stdout_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) {
21642167 error.OutOfMemory => return error.OutOfMemory,
21652168 error.ReadFailed => return stdout_reader.err.?,
......@@ -2167,7 +2170,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {
21672170 };
21682171 }
21692172 } else if (child.stderr) |stderr| {
2170 var stderr_reader = stderr.readerStreaming(&.{});
2173 var stderr_reader = stderr.readerStreaming(io, &.{});
21712174 stderr_bytes = stderr_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) {
21722175 error.OutOfMemory => return error.OutOfMemory,
21732176 error.ReadFailed => return stderr_reader.err.?,
lib/std/Build/Step/UpdateSourceFiles.zig+13-11
......@@ -3,11 +3,13 @@
33//! not be used during the normal build process, but as a utility run by a
44//! developer with intention to update source files, which will then be
55//! committed to version control.
6const UpdateSourceFiles = @This();
7
68const std = @import("std");
9const Io = std.Io;
710const Step = std.Build.Step;
811const fs = std.fs;
912const ArrayList = std.ArrayList;
10const UpdateSourceFiles = @This();
1113
1214step: Step,
1315output_source_files: std.ArrayListUnmanaged(OutputSourceFile),
......@@ -70,22 +72,21 @@ pub fn addBytesToSource(usf: *UpdateSourceFiles, bytes: []const u8, sub_path: []
7072fn make(step: *Step, options: Step.MakeOptions) !void {
7173 _ = options;
7274 const b = step.owner;
75 const io = b.graph.io;
7376 const usf: *UpdateSourceFiles = @fieldParentPtr("step", step);
7477
7578 var any_miss = false;
7679 for (usf.output_source_files.items) |output_source_file| {
7780 if (fs.path.dirname(output_source_file.sub_path)) |dirname| {
7881 b.build_root.handle.makePath(dirname) catch |err| {
79 return step.fail("unable to make path '{f}{s}': {s}", .{
80 b.build_root, dirname, @errorName(err),
81 });
82 return step.fail("unable to make path '{f}{s}': {t}", .{ b.build_root, dirname, err });
8283 };
8384 }
8485 switch (output_source_file.contents) {
8586 .bytes => |bytes| {
8687 b.build_root.handle.writeFile(.{ .sub_path = output_source_file.sub_path, .data = bytes }) catch |err| {
87 return step.fail("unable to write file '{f}{s}': {s}", .{
88 b.build_root, output_source_file.sub_path, @errorName(err),
88 return step.fail("unable to write file '{f}{s}': {t}", .{
89 b.build_root, output_source_file.sub_path, err,
8990 });
9091 };
9192 any_miss = true;
......@@ -94,15 +95,16 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
9495 if (!step.inputs.populated()) try step.addWatchInput(file_source);
9596
9697 const source_path = file_source.getPath2(b, step);
97 const prev_status = fs.Dir.updateFile(
98 fs.cwd(),
98 const prev_status = Io.Dir.updateFile(
99 .cwd(),
100 io,
99101 source_path,
100 b.build_root.handle,
102 b.build_root.handle.adaptToNewApi(),
101103 output_source_file.sub_path,
102104 .{},
103105 ) catch |err| {
104 return step.fail("unable to update file from '{s}' to '{f}{s}': {s}", .{
105 source_path, b.build_root, output_source_file.sub_path, @errorName(err),
106 return step.fail("unable to update file from '{s}' to '{f}{s}': {t}", .{
107 source_path, b.build_root, output_source_file.sub_path, err,
106108 });
107109 };
108110 any_miss = any_miss or prev_status == .stale;
lib/std/Build/Step/WriteFile.zig+13-23
......@@ -2,6 +2,7 @@
22//! the local cache which has a set of files that have either been generated
33//! during the build, or are copied from the source package.
44const std = @import("std");
5const Io = std.Io;
56const Step = std.Build.Step;
67const fs = std.fs;
78const ArrayList = std.ArrayList;
......@@ -174,6 +175,7 @@ fn maybeUpdateName(write_file: *WriteFile) void {
174175fn make(step: *Step, options: Step.MakeOptions) !void {
175176 _ = options;
176177 const b = step.owner;
178 const io = b.graph.io;
177179 const arena = b.allocator;
178180 const gpa = arena;
179181 const write_file: *WriteFile = @fieldParentPtr("step", step);
......@@ -264,40 +266,27 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
264266 };
265267 defer cache_dir.close();
266268
267 const cwd = fs.cwd();
268
269269 for (write_file.files.items) |file| {
270270 if (fs.path.dirname(file.sub_path)) |dirname| {
271271 cache_dir.makePath(dirname) catch |err| {
272 return step.fail("unable to make path '{f}{s}{c}{s}': {s}", .{
273 b.cache_root, cache_path, fs.path.sep, dirname, @errorName(err),
272 return step.fail("unable to make path '{f}{s}{c}{s}': {t}", .{
273 b.cache_root, cache_path, fs.path.sep, dirname, err,
274274 });
275275 };
276276 }
277277 switch (file.contents) {
278278 .bytes => |bytes| {
279279 cache_dir.writeFile(.{ .sub_path = file.sub_path, .data = bytes }) catch |err| {
280 return step.fail("unable to write file '{f}{s}{c}{s}': {s}", .{
281 b.cache_root, cache_path, fs.path.sep, file.sub_path, @errorName(err),
280 return step.fail("unable to write file '{f}{s}{c}{s}': {t}", .{
281 b.cache_root, cache_path, fs.path.sep, file.sub_path, err,
282282 });
283283 };
284284 },
285285 .copy => |file_source| {
286286 const source_path = file_source.getPath2(b, step);
287 const prev_status = fs.Dir.updateFile(
288 cwd,
289 source_path,
290 cache_dir,
291 file.sub_path,
292 .{},
293 ) catch |err| {
294 return step.fail("unable to update file from '{s}' to '{f}{s}{c}{s}': {s}", .{
295 source_path,
296 b.cache_root,
297 cache_path,
298 fs.path.sep,
299 file.sub_path,
300 @errorName(err),
287 const prev_status = Io.Dir.updateFile(.cwd(), io, source_path, cache_dir.adaptToNewApi(), file.sub_path, .{}) catch |err| {
288 return step.fail("unable to update file from '{s}' to '{f}{s}{c}{s}': {t}", .{
289 source_path, b.cache_root, cache_path, fs.path.sep, file.sub_path, err,
301290 });
302291 };
303292 // At this point we already will mark the step as a cache miss.
......@@ -331,10 +320,11 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
331320 switch (entry.kind) {
332321 .directory => try cache_dir.makePath(dest_path),
333322 .file => {
334 const prev_status = fs.Dir.updateFile(
335 src_entry_path.root_dir.handle,
323 const prev_status = Io.Dir.updateFile(
324 src_entry_path.root_dir.handle.adaptToNewApi(),
325 io,
336326 src_entry_path.sub_path,
337 cache_dir,
327 cache_dir.adaptToNewApi(),
338328 dest_path,
339329 .{},
340330 ) catch |err| {
lib/std/Build/WebServer.zig+47-31
......@@ -2,15 +2,16 @@ gpa: Allocator,
22thread_pool: *std.Thread.Pool,
33graph: *const Build.Graph,
44all_steps: []const *Build.Step,
5listen_address: std.net.Address,
6ttyconf: std.Io.tty.Config,
5listen_address: net.IpAddress,
6ttyconf: Io.tty.Config,
77root_prog_node: std.Progress.Node,
88watch: bool,
99
10tcp_server: ?std.net.Server,
10tcp_server: ?net.Server,
1111serve_thread: ?std.Thread,
1212
13base_timestamp: i128,
13/// Uses `Io.Clock.awake`.
14base_timestamp: Io.Timestamp,
1415/// The "step name" data which trails `abi.Hello`, for the steps in `all_steps`.
1516step_names_trailing: []u8,
1617
......@@ -42,6 +43,8 @@ runner_request: ?RunnerRequest,
4243/// on a fixed interval of this many milliseconds.
4344const default_update_interval_ms = 500;
4445
46pub const base_clock: Io.Clock = .awake;
47
4548/// Thread-safe. Triggers updates to be sent to connected WebSocket clients; see `update_id`.
4649pub fn notifyUpdate(ws: *WebServer) void {
4750 _ = ws.update_id.rmw(.Add, 1, .release);
......@@ -53,15 +56,17 @@ pub const Options = struct {
5356 thread_pool: *std.Thread.Pool,
5457 graph: *const std.Build.Graph,
5558 all_steps: []const *Build.Step,
56 ttyconf: std.Io.tty.Config,
59 ttyconf: Io.tty.Config,
5760 root_prog_node: std.Progress.Node,
5861 watch: bool,
59 listen_address: std.net.Address,
62 listen_address: net.IpAddress,
63 base_timestamp: Io.Clock.Timestamp,
6064};
6165pub fn init(opts: Options) WebServer {
62 // The upcoming `std.Io` interface should allow us to use `Io.async` and `Io.concurrent`
66 // The upcoming `Io` interface should allow us to use `Io.async` and `Io.concurrent`
6367 // instead of threads, so that the web server can function in single-threaded builds.
6468 comptime assert(!builtin.single_threaded);
69 assert(opts.base_timestamp.clock == base_clock);
6570
6671 const all_steps = opts.all_steps;
6772
......@@ -106,7 +111,7 @@ pub fn init(opts: Options) WebServer {
106111 .tcp_server = null,
107112 .serve_thread = null,
108113
109 .base_timestamp = std.time.nanoTimestamp(),
114 .base_timestamp = opts.base_timestamp.raw,
110115 .step_names_trailing = step_names_trailing,
111116
112117 .step_status_bits = step_status_bits,
......@@ -147,32 +152,34 @@ pub fn deinit(ws: *WebServer) void {
147152pub fn start(ws: *WebServer) error{AlreadyReported}!void {
148153 assert(ws.tcp_server == null);
149154 assert(ws.serve_thread == null);
155 const io = ws.graph.io;
150156
151 ws.tcp_server = ws.listen_address.listen(.{ .reuse_address = true }) catch |err| {
157 ws.tcp_server = ws.listen_address.listen(io, .{ .reuse_address = true }) catch |err| {
152158 log.err("failed to listen to port {d}: {s}", .{ ws.listen_address.getPort(), @errorName(err) });
153159 return error.AlreadyReported;
154160 };
155161 ws.serve_thread = std.Thread.spawn(.{}, serve, .{ws}) catch |err| {
156162 log.err("unable to spawn web server thread: {s}", .{@errorName(err)});
157 ws.tcp_server.?.deinit();
163 ws.tcp_server.?.deinit(io);
158164 ws.tcp_server = null;
159165 return error.AlreadyReported;
160166 };
161167
162 log.info("web interface listening at http://{f}/", .{ws.tcp_server.?.listen_address});
168 log.info("web interface listening at http://{f}/", .{ws.tcp_server.?.socket.address});
163169 if (ws.listen_address.getPort() == 0) {
164 log.info("hint: pass '--webui={f}' to use the same port next time", .{ws.tcp_server.?.listen_address});
170 log.info("hint: pass '--webui={f}' to use the same port next time", .{ws.tcp_server.?.socket.address});
165171 }
166172}
167173fn serve(ws: *WebServer) void {
174 const io = ws.graph.io;
168175 while (true) {
169 const connection = ws.tcp_server.?.accept() catch |err| {
176 var stream = ws.tcp_server.?.accept(io) catch |err| {
170177 log.err("failed to accept connection: {s}", .{@errorName(err)});
171178 return;
172179 };
173 _ = std.Thread.spawn(.{}, accept, .{ ws, connection }) catch |err| {
180 _ = std.Thread.spawn(.{}, accept, .{ ws, stream }) catch |err| {
174181 log.err("unable to spawn connection thread: {s}", .{@errorName(err)});
175 connection.stream.close();
182 stream.close(io);
176183 continue;
177184 };
178185 }
......@@ -227,6 +234,7 @@ pub fn finishBuild(ws: *WebServer, opts: struct {
227234
228235 ws.fuzz = Fuzz.init(
229236 ws.gpa,
237 ws.graph.io,
230238 ws.thread_pool,
231239 ws.all_steps,
232240 ws.root_prog_node,
......@@ -241,17 +249,24 @@ pub fn finishBuild(ws: *WebServer, opts: struct {
241249}
242250
243251pub fn now(s: *const WebServer) i64 {
244 return @intCast(std.time.nanoTimestamp() - s.base_timestamp);
252 const io = s.graph.io;
253 const ts = base_clock.now(io) catch s.base_timestamp;
254 return @intCast(s.base_timestamp.durationTo(ts).toNanoseconds());
245255}
246256
247fn accept(ws: *WebServer, connection: std.net.Server.Connection) void {
248 defer connection.stream.close();
249
257fn accept(ws: *WebServer, stream: net.Stream) void {
258 const io = ws.graph.io;
259 defer {
260 // `net.Stream.close` wants to helpfully overwrite `stream` with
261 // `undefined`, but it cannot do so since it is an immutable parameter.
262 var copy = stream;
263 copy.close(io);
264 }
250265 var send_buffer: [4096]u8 = undefined;
251266 var recv_buffer: [4096]u8 = undefined;
252 var connection_reader = connection.stream.reader(&recv_buffer);
253 var connection_writer = connection.stream.writer(&send_buffer);
254 var server: http.Server = .init(connection_reader.interface(), &connection_writer.interface);
267 var connection_reader = stream.reader(io, &recv_buffer);
268 var connection_writer = stream.writer(io, &send_buffer);
269 var server: http.Server = .init(&connection_reader.interface, &connection_writer.interface);
255270
256271 while (true) {
257272 var request = server.receiveHead() catch |err| switch (err) {
......@@ -466,12 +481,9 @@ pub fn serveFile(
466481 },
467482 });
468483}
469pub fn serveTarFile(
470 ws: *WebServer,
471 request: *http.Server.Request,
472 paths: []const Cache.Path,
473) !void {
484pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []const Cache.Path) !void {
474485 const gpa = ws.gpa;
486 const io = ws.graph.io;
475487
476488 var send_buffer: [0x4000]u8 = undefined;
477489 var response = try request.respondStreaming(&send_buffer, .{
......@@ -496,7 +508,7 @@ pub fn serveTarFile(
496508 defer file.close();
497509 const stat = try file.stat();
498510 var read_buffer: [1024]u8 = undefined;
499 var file_reader: std.fs.File.Reader = .initSize(file, &read_buffer, stat.size);
511 var file_reader: Io.File.Reader = .initSize(file.adaptToNewApi(), io, &read_buffer, stat.size);
500512
501513 // TODO: this logic is completely bogus -- obviously so, because `path.root_dir.path` can
502514 // be cwd-relative. This is also related to why linkification doesn't work in the fuzzer UI:
......@@ -508,7 +520,7 @@ pub fn serveTarFile(
508520 if (cached_cwd_path == null) cached_cwd_path = try std.process.getCwdAlloc(gpa);
509521 break :cwd cached_cwd_path.?;
510522 };
511 try archiver.writeFile(path.sub_path, &file_reader, stat.mtime);
523 try archiver.writeFile(path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds()));
512524 }
513525
514526 // intentionally not calling `archiver.finishPedantically`
......@@ -516,6 +528,7 @@ pub fn serveTarFile(
516528}
517529
518530fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.OptimizeMode) !Cache.Path {
531 const io = ws.graph.io;
519532 const root_name = "build-web";
520533 const arch_os_abi = "wasm32-freestanding";
521534 const cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext";
......@@ -565,7 +578,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
565578 child.stderr_behavior = .Pipe;
566579 try child.spawn();
567580
568 var poller = std.Io.poll(gpa, enum { stdout, stderr }, .{
581 var poller = Io.poll(gpa, enum { stdout, stderr }, .{
569582 .stdout = child.stdout.?,
570583 .stderr = child.stderr.?,
571584 });
......@@ -659,7 +672,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
659672 };
660673 const bin_name = try std.zig.binNameAlloc(arena, .{
661674 .root_name = root_name,
662 .target = &(std.zig.system.resolveTargetQuery(std.Build.parseTargetQuery(.{
675 .target = &(std.zig.system.resolveTargetQuery(io, std.Build.parseTargetQuery(.{
663676 .arch_os_abi = arch_os_abi,
664677 .cpu_features = cpu_features,
665678 }) catch unreachable) catch unreachable),
......@@ -841,7 +854,10 @@ const cache_control_header: http.Header = .{
841854};
842855
843856const builtin = @import("builtin");
857
844858const std = @import("std");
859const Io = std.Io;
860const net = std.Io.net;
845861const assert = std.debug.assert;
846862const mem = std.mem;
847863const log = std.log.scoped(.web_server);
lib/std/Io.zig+1096
......@@ -548,8 +548,1104 @@ pub fn PollFiles(comptime StreamEnum: type) type {
548548}
549549
550550test {
551 _ = net;
551552 _ = Reader;
552553 _ = Writer;
553554 _ = tty;
555 _ = Evented;
556 _ = Threaded;
554557 _ = @import("Io/test.zig");
555558}
559
560const Io = @This();
561
562pub const Evented = switch (builtin.os.tag) {
563 .linux => switch (builtin.cpu.arch) {
564 .x86_64, .aarch64 => @import("Io/IoUring.zig"),
565 else => void, // context-switching code not implemented yet
566 },
567 .dragonfly, .freebsd, .netbsd, .openbsd, .macos, .ios, .tvos, .visionos, .watchos => switch (builtin.cpu.arch) {
568 .x86_64, .aarch64 => @import("Io/Kqueue.zig"),
569 else => void, // context-switching code not implemented yet
570 },
571 else => void,
572};
573pub const Threaded = @import("Io/Threaded.zig");
574pub const net = @import("Io/net.zig");
575
576userdata: ?*anyopaque,
577vtable: *const VTable,
578
579pub const VTable = struct {
580 /// If it returns `null` it means `result` has been already populated and
581 /// `await` will be a no-op.
582 ///
583 /// Thread-safe.
584 async: *const fn (
585 /// Corresponds to `Io.userdata`.
586 userdata: ?*anyopaque,
587 /// The pointer of this slice is an "eager" result value.
588 /// The length is the size in bytes of the result type.
589 /// This pointer's lifetime expires directly after the call to this function.
590 result: []u8,
591 result_alignment: std.mem.Alignment,
592 /// Copied and then passed to `start`.
593 context: []const u8,
594 context_alignment: std.mem.Alignment,
595 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
596 ) ?*AnyFuture,
597 /// Thread-safe.
598 concurrent: *const fn (
599 /// Corresponds to `Io.userdata`.
600 userdata: ?*anyopaque,
601 result_len: usize,
602 result_alignment: std.mem.Alignment,
603 /// Copied and then passed to `start`.
604 context: []const u8,
605 context_alignment: std.mem.Alignment,
606 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
607 ) ConcurrentError!*AnyFuture,
608 /// This function is only called when `async` returns a non-null value.
609 ///
610 /// Thread-safe.
611 await: *const fn (
612 /// Corresponds to `Io.userdata`.
613 userdata: ?*anyopaque,
614 /// The same value that was returned from `async`.
615 any_future: *AnyFuture,
616 /// Points to a buffer where the result is written.
617 /// The length is equal to size in bytes of result type.
618 result: []u8,
619 result_alignment: std.mem.Alignment,
620 ) void,
621 /// Equivalent to `await` but initiates cancel request.
622 ///
623 /// This function is only called when `async` returns a non-null value.
624 ///
625 /// Thread-safe.
626 cancel: *const fn (
627 /// Corresponds to `Io.userdata`.
628 userdata: ?*anyopaque,
629 /// The same value that was returned from `async`.
630 any_future: *AnyFuture,
631 /// Points to a buffer where the result is written.
632 /// The length is equal to size in bytes of result type.
633 result: []u8,
634 result_alignment: std.mem.Alignment,
635 ) void,
636 /// Returns whether the current thread of execution is known to have
637 /// been requested to cancel.
638 ///
639 /// Thread-safe.
640 cancelRequested: *const fn (?*anyopaque) bool,
641
642 /// Executes `start` asynchronously in a manner such that it cleans itself
643 /// up. This mode does not support results, await, or cancel.
644 ///
645 /// Thread-safe.
646 groupAsync: *const fn (
647 /// Corresponds to `Io.userdata`.
648 userdata: ?*anyopaque,
649 /// Owner of the spawned async task.
650 group: *Group,
651 /// Copied and then passed to `start`.
652 context: []const u8,
653 context_alignment: std.mem.Alignment,
654 start: *const fn (*Group, context: *const anyopaque) void,
655 ) void,
656 groupWait: *const fn (?*anyopaque, *Group, token: *anyopaque) void,
657 groupCancel: *const fn (?*anyopaque, *Group, token: *anyopaque) void,
658
659 /// Blocks until one of the futures from the list has a result ready, such
660 /// that awaiting it will not block. Returns that index.
661 select: *const fn (?*anyopaque, futures: []const *AnyFuture) Cancelable!usize,
662
663 mutexLock: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) Cancelable!void,
664 mutexLockUncancelable: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) void,
665 mutexUnlock: *const fn (?*anyopaque, prev_state: Mutex.State, mutex: *Mutex) void,
666
667 conditionWait: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) Cancelable!void,
668 conditionWaitUncancelable: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) void,
669 conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,
670
671 dirMake: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void,
672 dirMakePath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.Mode) Dir.MakeError!void,
673 dirMakeOpenPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.MakeOpenPathError!Dir,
674 dirStat: *const fn (?*anyopaque, Dir) Dir.StatError!Dir.Stat,
675 dirStatPath: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.StatPathOptions) Dir.StatPathError!File.Stat,
676 dirAccess: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.AccessOptions) Dir.AccessError!void,
677 dirCreateFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.CreateFlags) File.OpenError!File,
678 dirOpenFile: *const fn (?*anyopaque, Dir, sub_path: []const u8, File.OpenFlags) File.OpenError!File,
679 dirOpenDir: *const fn (?*anyopaque, Dir, sub_path: []const u8, Dir.OpenOptions) Dir.OpenError!Dir,
680 dirClose: *const fn (?*anyopaque, Dir) void,
681 fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat,
682 fileClose: *const fn (?*anyopaque, File) void,
683 fileWriteStreaming: *const fn (?*anyopaque, File, buffer: [][]const u8) File.WriteStreamingError!usize,
684 fileWritePositional: *const fn (?*anyopaque, File, buffer: [][]const u8, offset: u64) File.WritePositionalError!usize,
685 /// Returns 0 on end of stream.
686 fileReadStreaming: *const fn (?*anyopaque, File, data: [][]u8) File.Reader.Error!usize,
687 /// Returns 0 on end of stream.
688 fileReadPositional: *const fn (?*anyopaque, File, data: [][]u8, offset: u64) File.ReadPositionalError!usize,
689 fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void,
690 fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void,
691 openSelfExe: *const fn (?*anyopaque, File.OpenFlags) File.OpenSelfExeError!File,
692
693 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
694 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
695
696 netListenIp: *const fn (?*anyopaque, address: net.IpAddress, net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,
697 netAccept: *const fn (?*anyopaque, server: net.Socket.Handle) net.Server.AcceptError!net.Stream,
698 netBindIp: *const fn (?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket,
699 netConnectIp: *const fn (?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.ConnectOptions) net.IpAddress.ConnectError!net.Stream,
700 netListenUnix: *const fn (?*anyopaque, *const net.UnixAddress, net.UnixAddress.ListenOptions) net.UnixAddress.ListenError!net.Socket.Handle,
701 netConnectUnix: *const fn (?*anyopaque, *const net.UnixAddress) net.UnixAddress.ConnectError!net.Socket.Handle,
702 netSend: *const fn (?*anyopaque, net.Socket.Handle, []net.OutgoingMessage, net.SendFlags) struct { ?net.Socket.SendError, usize },
703 netReceive: *const fn (?*anyopaque, net.Socket.Handle, message_buffer: []net.IncomingMessage, data_buffer: []u8, net.ReceiveFlags, Timeout) struct { ?net.Socket.ReceiveTimeoutError, usize },
704 /// Returns 0 on end of stream.
705 netRead: *const fn (?*anyopaque, src: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize,
706 netWrite: *const fn (?*anyopaque, dest: net.Socket.Handle, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize,
707 netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void,
708 netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,
709 netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,
710 netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) void,
711};
712
713pub const Cancelable = error{
714 /// Caller has requested the async operation to stop.
715 Canceled,
716};
717
718pub const UnexpectedError = error{
719 /// The Operating System returned an undocumented error code.
720 ///
721 /// This error is in theory not possible, but it would be better
722 /// to handle this error than to invoke undefined behavior.
723 ///
724 /// When this error code is observed, it usually means the Zig Standard
725 /// Library needs a small patch to add the error code to the error set for
726 /// the respective function.
727 Unexpected,
728};
729
730pub const Dir = @import("Io/Dir.zig");
731pub const File = @import("Io/File.zig");
732
733pub const Clock = enum {
734 /// A settable system-wide clock that measures real (i.e. wall-clock)
735 /// time. This clock is affected by discontinuous jumps in the system
736 /// time (e.g., if the system administrator manually changes the
737 /// clock), and by frequency adjust‐ ments performed by NTP and similar
738 /// applications.
739 ///
740 /// This clock normally counts the number of seconds since 1970-01-01
741 /// 00:00:00 Coordinated Universal Time (UTC) except that it ignores
742 /// leap seconds; near a leap second it is typically adjusted by NTP to
743 /// stay roughly in sync with UTC.
744 ///
745 /// The epoch is implementation-defined. For example NTFS/Windows uses
746 /// 1601-01-01.
747 real,
748 /// A nonsettable system-wide clock that represents time since some
749 /// unspecified point in the past.
750 ///
751 /// Monotonic: Guarantees that the time returned by consecutive calls
752 /// will not go backwards, but successive calls may return identical
753 /// (not-increased) time values.
754 ///
755 /// Not affected by discontinuous jumps in the system time (e.g., if
756 /// the system administrator manually changes the clock), but may be
757 /// affected by frequency adjustments.
758 ///
759 /// This clock expresses intent to **exclude time that the system is
760 /// suspended**. However, implementations may be unable to satisify
761 /// this, and may include that time.
762 ///
763 /// * On Linux, corresponds `CLOCK_MONOTONIC`.
764 /// * On macOS, corresponds to `CLOCK_UPTIME_RAW`.
765 awake,
766 /// Identical to `awake` except it expresses intent to **include time
767 /// that the system is suspended**, however, due to limitations it may
768 /// behave identically to `awake`.
769 ///
770 /// * On Linux, corresponds `CLOCK_BOOTTIME`.
771 /// * On macOS, corresponds to `CLOCK_MONOTONIC_RAW`.
772 boot,
773 /// Tracks the amount of CPU in user or kernel mode used by the calling
774 /// process.
775 cpu_process,
776 /// Tracks the amount of CPU in user or kernel mode used by the calling
777 /// thread.
778 cpu_thread,
779
780 pub const Error = error{UnsupportedClock} || UnexpectedError;
781
782 /// This function is not cancelable because first of all it does not block,
783 /// but more importantly, the cancelation logic itself may want to check
784 /// the time.
785 pub fn now(clock: Clock, io: Io) Error!Io.Timestamp {
786 return io.vtable.now(io.userdata, clock);
787 }
788
789 pub const Timestamp = struct {
790 raw: Io.Timestamp,
791 clock: Clock,
792
793 /// This function is not cancelable because first of all it does not block,
794 /// but more importantly, the cancelation logic itself may want to check
795 /// the time.
796 pub fn now(io: Io, clock: Clock) Error!Clock.Timestamp {
797 return .{
798 .raw = try io.vtable.now(io.userdata, clock),
799 .clock = clock,
800 };
801 }
802
803 pub fn wait(t: Clock.Timestamp, io: Io) SleepError!void {
804 return io.vtable.sleep(io.userdata, .{ .deadline = t });
805 }
806
807 pub fn durationTo(from: Clock.Timestamp, to: Clock.Timestamp) Clock.Duration {
808 assert(from.clock == to.clock);
809 return .{
810 .raw = from.raw.durationTo(to.raw),
811 .clock = from.clock,
812 };
813 }
814
815 pub fn addDuration(from: Clock.Timestamp, duration: Clock.Duration) Clock.Timestamp {
816 assert(from.clock == duration.clock);
817 return .{
818 .raw = from.raw.addDuration(duration.raw),
819 .clock = from.clock,
820 };
821 }
822
823 pub fn subDuration(from: Clock.Timestamp, duration: Clock.Duration) Clock.Timestamp {
824 assert(from.clock == duration.clock);
825 return .{
826 .raw = from.raw.subDuration(duration.raw),
827 .clock = from.clock,
828 };
829 }
830
831 pub fn fromNow(io: Io, duration: Clock.Duration) Error!Clock.Timestamp {
832 return .{
833 .clock = duration.clock,
834 .raw = (try duration.clock.now(io)).addDuration(duration.raw),
835 };
836 }
837
838 pub fn untilNow(timestamp: Clock.Timestamp, io: Io) Error!Clock.Duration {
839 const now_ts = try Clock.Timestamp.now(io, timestamp.clock);
840 return timestamp.durationTo(now_ts);
841 }
842
843 pub fn durationFromNow(timestamp: Clock.Timestamp, io: Io) Error!Clock.Duration {
844 const now_ts = try timestamp.clock.now(io);
845 return .{
846 .clock = timestamp.clock,
847 .raw = now_ts.durationTo(timestamp.raw),
848 };
849 }
850
851 pub fn toClock(t: Clock.Timestamp, io: Io, clock: Clock) Error!Clock.Timestamp {
852 if (t.clock == clock) return t;
853 const now_old = try t.clock.now(io);
854 const now_new = try clock.now(io);
855 const duration = now_old.durationTo(t);
856 return .{
857 .clock = clock,
858 .raw = now_new.addDuration(duration),
859 };
860 }
861
862 pub fn compare(lhs: Clock.Timestamp, op: std.math.CompareOperator, rhs: Clock.Timestamp) bool {
863 assert(lhs.clock == rhs.clock);
864 return std.math.compare(lhs.raw.nanoseconds, op, rhs.raw.nanoseconds);
865 }
866 };
867
868 pub const Duration = struct {
869 raw: Io.Duration,
870 clock: Clock,
871
872 pub fn sleep(duration: Clock.Duration, io: Io) SleepError!void {
873 return io.vtable.sleep(io.userdata, .{ .duration = duration });
874 }
875 };
876};
877
878pub const Timestamp = struct {
879 nanoseconds: i96,
880
881 pub const zero: Timestamp = .{ .nanoseconds = 0 };
882
883 pub fn durationTo(from: Timestamp, to: Timestamp) Duration {
884 return .{ .nanoseconds = to.nanoseconds - from.nanoseconds };
885 }
886
887 pub fn addDuration(from: Timestamp, duration: Duration) Timestamp {
888 return .{ .nanoseconds = from.nanoseconds + duration.nanoseconds };
889 }
890
891 pub fn subDuration(from: Timestamp, duration: Duration) Timestamp {
892 return .{ .nanoseconds = from.nanoseconds - duration.nanoseconds };
893 }
894
895 pub fn withClock(t: Timestamp, clock: Clock) Clock.Timestamp {
896 return .{ .nanoseconds = t.nanoseconds, .clock = clock };
897 }
898
899 pub fn fromNanoseconds(x: i96) Timestamp {
900 return .{ .nanoseconds = x };
901 }
902
903 pub fn toSeconds(t: Timestamp) i64 {
904 return @intCast(@divTrunc(t.nanoseconds, std.time.ns_per_s));
905 }
906
907 pub fn toNanoseconds(t: Timestamp) i96 {
908 return t.nanoseconds;
909 }
910
911 pub fn formatNumber(t: Timestamp, w: *std.Io.Writer, n: std.fmt.Number) std.Io.Writer.Error!void {
912 return w.printInt(t.nanoseconds, n.mode.base() orelse 10, n.case, .{
913 .precision = n.precision,
914 .width = n.width,
915 .alignment = n.alignment,
916 .fill = n.fill,
917 });
918 }
919};
920
921pub const Duration = struct {
922 nanoseconds: i96,
923
924 pub const zero: Duration = .{ .nanoseconds = 0 };
925 pub const max: Duration = .{ .nanoseconds = std.math.maxInt(i96) };
926
927 pub fn fromNanoseconds(x: i96) Duration {
928 return .{ .nanoseconds = x };
929 }
930
931 pub fn fromMilliseconds(x: i64) Duration {
932 return .{ .nanoseconds = @as(i96, x) * std.time.ns_per_ms };
933 }
934
935 pub fn fromSeconds(x: i64) Duration {
936 return .{ .nanoseconds = @as(i96, x) * std.time.ns_per_s };
937 }
938
939 pub fn toMilliseconds(d: Duration) i64 {
940 return @intCast(@divTrunc(d.nanoseconds, std.time.ns_per_ms));
941 }
942
943 pub fn toSeconds(d: Duration) i64 {
944 return @intCast(@divTrunc(d.nanoseconds, std.time.ns_per_s));
945 }
946
947 pub fn toNanoseconds(d: Duration) i96 {
948 return d.nanoseconds;
949 }
950};
951
952/// Declares under what conditions an operation should return `error.Timeout`.
953pub const Timeout = union(enum) {
954 none,
955 duration: Clock.Duration,
956 deadline: Clock.Timestamp,
957
958 pub const Error = error{ Timeout, UnsupportedClock };
959
960 pub fn toDeadline(t: Timeout, io: Io) Clock.Error!?Clock.Timestamp {
961 return switch (t) {
962 .none => null,
963 .duration => |d| try .fromNow(io, d),
964 .deadline => |d| d,
965 };
966 }
967
968 pub fn toDurationFromNow(t: Timeout, io: Io) Clock.Error!?Clock.Duration {
969 return switch (t) {
970 .none => null,
971 .duration => |d| d,
972 .deadline => |d| try d.durationFromNow(io),
973 };
974 }
975
976 pub fn sleep(timeout: Timeout, io: Io) SleepError!void {
977 return io.vtable.sleep(io.userdata, timeout);
978 }
979};
980
981pub const AnyFuture = opaque {};
982
983pub fn Future(Result: type) type {
984 return struct {
985 any_future: ?*AnyFuture,
986 result: Result,
987
988 /// Equivalent to `await` but places a cancellation request.
989 ///
990 /// Idempotent. Not threadsafe.
991 pub fn cancel(f: *@This(), io: Io) Result {
992 const any_future = f.any_future orelse return f.result;
993 io.vtable.cancel(io.userdata, any_future, @ptrCast((&f.result)[0..1]), .of(Result));
994 f.any_future = null;
995 return f.result;
996 }
997
998 /// Idempotent. Not threadsafe.
999 pub fn await(f: *@This(), io: Io) Result {
1000 const any_future = f.any_future orelse return f.result;
1001 io.vtable.await(io.userdata, any_future, @ptrCast((&f.result)[0..1]), .of(Result));
1002 f.any_future = null;
1003 return f.result;
1004 }
1005 };
1006}
1007
1008pub const Group = struct {
1009 state: usize,
1010 context: ?*anyopaque,
1011 token: ?*anyopaque,
1012
1013 pub const init: Group = .{ .state = 0, .context = null, .token = null };
1014
1015 /// Calls `function` with `args` asynchronously. The resource spawned is
1016 /// owned by the group.
1017 ///
1018 /// `function` *may* be called immediately, before `async` returns.
1019 ///
1020 /// After this is called, `wait` or `cancel` must be called before the
1021 /// group is deinitialized.
1022 ///
1023 /// Threadsafe.
1024 ///
1025 /// See also:
1026 /// * `Io.async`
1027 /// * `concurrent`
1028 pub fn async(g: *Group, io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) void {
1029 const Args = @TypeOf(args);
1030 const TypeErased = struct {
1031 fn start(group: *Group, context: *const anyopaque) void {
1032 _ = group;
1033 const args_casted: *const Args = @ptrCast(@alignCast(context));
1034 @call(.auto, function, args_casted.*);
1035 }
1036 };
1037 io.vtable.groupAsync(io.userdata, g, @ptrCast((&args)[0..1]), .of(Args), TypeErased.start);
1038 }
1039
1040 /// Blocks until all tasks of the group finish. During this time,
1041 /// cancellation requests propagate to all members of the group.
1042 ///
1043 /// Idempotent. Not threadsafe.
1044 pub fn wait(g: *Group, io: Io) void {
1045 const token = g.token orelse return;
1046 g.token = null;
1047 io.vtable.groupWait(io.userdata, g, token);
1048 }
1049
1050 /// Equivalent to `wait` but immediately requests cancellation on all
1051 /// members of the group.
1052 ///
1053 /// Idempotent. Not threadsafe.
1054 pub fn cancel(g: *Group, io: Io) void {
1055 const token = g.token orelse return;
1056 g.token = null;
1057 io.vtable.groupCancel(io.userdata, g, token);
1058 }
1059};
1060
1061pub fn Select(comptime U: type) type {
1062 return struct {
1063 io: Io,
1064 group: Group,
1065 queue: Queue(U),
1066 outstanding: usize,
1067
1068 const S = @This();
1069
1070 pub const Union = U;
1071
1072 pub const Field = std.meta.FieldEnum(U);
1073
1074 pub fn init(io: Io, buffer: []U) S {
1075 return .{
1076 .io = io,
1077 .queue = .init(buffer),
1078 .group = .init,
1079 .outstanding = 0,
1080 };
1081 }
1082
1083 /// Calls `function` with `args` asynchronously. The resource spawned is
1084 /// owned by the select.
1085 ///
1086 /// `function` must have return type matching the `field` field of `Union`.
1087 ///
1088 /// `function` *may* be called immediately, before `async` returns.
1089 ///
1090 /// After this is called, `wait` or `cancel` must be called before the
1091 /// select is deinitialized.
1092 ///
1093 /// Threadsafe.
1094 ///
1095 /// Related:
1096 /// * `Io.async`
1097 /// * `Group.async`
1098 pub fn async(
1099 s: *S,
1100 comptime field: Field,
1101 function: anytype,
1102 args: std.meta.ArgsTuple(@TypeOf(function)),
1103 ) void {
1104 const Args = @TypeOf(args);
1105 const TypeErased = struct {
1106 fn start(group: *Group, context: *const anyopaque) void {
1107 const args_casted: *const Args = @ptrCast(@alignCast(context));
1108 const unerased_select: *S = @fieldParentPtr("group", group);
1109 const elem = @unionInit(U, @tagName(field), @call(.auto, function, args_casted.*));
1110 unerased_select.queue.putOneUncancelable(unerased_select.io, elem);
1111 }
1112 };
1113 _ = @atomicRmw(usize, &s.outstanding, .Add, 1, .monotonic);
1114 s.io.vtable.groupAsync(s.io.userdata, &s.group, @ptrCast((&args)[0..1]), .of(Args), TypeErased.start);
1115 }
1116
1117 /// Blocks until another task of the select finishes.
1118 ///
1119 /// Asserts there is at least one more `outstanding` task.
1120 ///
1121 /// Not threadsafe.
1122 pub fn wait(s: *S) Cancelable!U {
1123 s.outstanding -= 1;
1124 return s.queue.getOne(s.io);
1125 }
1126
1127 /// Equivalent to `wait` but requests cancellation on all remaining
1128 /// tasks owned by the select.
1129 ///
1130 /// It is illegal to call `wait` after this.
1131 ///
1132 /// Idempotent. Not threadsafe.
1133 pub fn cancel(s: *S) void {
1134 s.outstanding = 0;
1135 s.group.cancel(s.io);
1136 }
1137 };
1138}
1139
1140pub const Mutex = struct {
1141 state: State,
1142
1143 pub const State = enum(usize) {
1144 locked_once = 0b00,
1145 unlocked = 0b01,
1146 contended = 0b10,
1147 /// contended
1148 _,
1149
1150 pub fn isUnlocked(state: State) bool {
1151 return @intFromEnum(state) & @intFromEnum(State.unlocked) == @intFromEnum(State.unlocked);
1152 }
1153 };
1154
1155 pub const init: Mutex = .{ .state = .unlocked };
1156
1157 pub fn tryLock(mutex: *Mutex) bool {
1158 const prev_state: State = @enumFromInt(@atomicRmw(
1159 usize,
1160 @as(*usize, @ptrCast(&mutex.state)),
1161 .And,
1162 ~@intFromEnum(State.unlocked),
1163 .acquire,
1164 ));
1165 return prev_state.isUnlocked();
1166 }
1167
1168 pub fn lock(mutex: *Mutex, io: std.Io) Cancelable!void {
1169 const prev_state: State = @enumFromInt(@atomicRmw(
1170 usize,
1171 @as(*usize, @ptrCast(&mutex.state)),
1172 .And,
1173 ~@intFromEnum(State.unlocked),
1174 .acquire,
1175 ));
1176 if (prev_state.isUnlocked()) {
1177 @branchHint(.likely);
1178 return;
1179 }
1180 return io.vtable.mutexLock(io.userdata, prev_state, mutex);
1181 }
1182
1183 /// Same as `lock` but cannot be canceled.
1184 pub fn lockUncancelable(mutex: *Mutex, io: std.Io) void {
1185 const prev_state: State = @enumFromInt(@atomicRmw(
1186 usize,
1187 @as(*usize, @ptrCast(&mutex.state)),
1188 .And,
1189 ~@intFromEnum(State.unlocked),
1190 .acquire,
1191 ));
1192 if (prev_state.isUnlocked()) {
1193 @branchHint(.likely);
1194 return;
1195 }
1196 return io.vtable.mutexLockUncancelable(io.userdata, prev_state, mutex);
1197 }
1198
1199 pub fn unlock(mutex: *Mutex, io: std.Io) void {
1200 const prev_state = @cmpxchgWeak(State, &mutex.state, .locked_once, .unlocked, .release, .acquire) orelse {
1201 @branchHint(.likely);
1202 return;
1203 };
1204 assert(prev_state != .unlocked); // mutex not locked
1205 return io.vtable.mutexUnlock(io.userdata, prev_state, mutex);
1206 }
1207};
1208
1209pub const Condition = struct {
1210 state: u64 = 0,
1211
1212 pub fn wait(cond: *Condition, io: Io, mutex: *Mutex) Cancelable!void {
1213 return io.vtable.conditionWait(io.userdata, cond, mutex);
1214 }
1215
1216 pub fn waitUncancelable(cond: *Condition, io: Io, mutex: *Mutex) void {
1217 return io.vtable.conditionWaitUncancelable(io.userdata, cond, mutex);
1218 }
1219
1220 pub fn signal(cond: *Condition, io: Io) void {
1221 io.vtable.conditionWake(io.userdata, cond, .one);
1222 }
1223
1224 pub fn broadcast(cond: *Condition, io: Io) void {
1225 io.vtable.conditionWake(io.userdata, cond, .all);
1226 }
1227
1228 pub const Wake = enum {
1229 /// Wake up only one thread.
1230 one,
1231 /// Wake up all threads.
1232 all,
1233 };
1234};
1235
1236pub const TypeErasedQueue = struct {
1237 mutex: Mutex,
1238
1239 /// Ring buffer. This data is logically *after* queued getters.
1240 buffer: []u8,
1241 put_index: usize,
1242 get_index: usize,
1243
1244 putters: std.DoublyLinkedList,
1245 getters: std.DoublyLinkedList,
1246
1247 const Put = struct {
1248 remaining: []const u8,
1249 condition: Condition,
1250 node: std.DoublyLinkedList.Node,
1251 };
1252
1253 const Get = struct {
1254 remaining: []u8,
1255 condition: Condition,
1256 node: std.DoublyLinkedList.Node,
1257 };
1258
1259 pub fn init(buffer: []u8) TypeErasedQueue {
1260 return .{
1261 .mutex = .init,
1262 .buffer = buffer,
1263 .put_index = 0,
1264 .get_index = 0,
1265 .putters = .{},
1266 .getters = .{},
1267 };
1268 }
1269
1270 pub fn put(q: *TypeErasedQueue, io: Io, elements: []const u8, min: usize) Cancelable!usize {
1271 assert(elements.len >= min);
1272 if (elements.len == 0) return 0;
1273 try q.mutex.lock(io);
1274 defer q.mutex.unlock(io);
1275 return putLocked(q, io, elements, min, false);
1276 }
1277
1278 /// Same as `put` but cannot be canceled.
1279 pub fn putUncancelable(q: *TypeErasedQueue, io: Io, elements: []const u8, min: usize) usize {
1280 assert(elements.len >= min);
1281 if (elements.len == 0) return 0;
1282 q.mutex.lockUncancelable(io);
1283 defer q.mutex.unlock(io);
1284 return putLocked(q, io, elements, min, true) catch |err| switch (err) {
1285 error.Canceled => unreachable,
1286 };
1287 }
1288
1289 fn putLocked(q: *TypeErasedQueue, io: Io, elements: []const u8, min: usize, uncancelable: bool) Cancelable!usize {
1290 // Getters have first priority on the data, and only when the getters
1291 // queue is empty do we start populating the buffer.
1292
1293 var remaining = elements;
1294 while (true) {
1295 const getter: *Get = @alignCast(@fieldParentPtr("node", q.getters.popFirst() orelse break));
1296 const copy_len = @min(getter.remaining.len, remaining.len);
1297 @memcpy(getter.remaining[0..copy_len], remaining[0..copy_len]);
1298 remaining = remaining[copy_len..];
1299 getter.remaining = getter.remaining[copy_len..];
1300 if (getter.remaining.len == 0) {
1301 getter.condition.signal(io);
1302 continue;
1303 }
1304 q.getters.prepend(&getter.node);
1305 assert(remaining.len == 0);
1306 return elements.len;
1307 }
1308
1309 while (true) {
1310 {
1311 const available = q.buffer[q.put_index..];
1312 const copy_len = @min(available.len, remaining.len);
1313 @memcpy(available[0..copy_len], remaining[0..copy_len]);
1314 remaining = remaining[copy_len..];
1315 q.put_index += copy_len;
1316 if (remaining.len == 0) return elements.len;
1317 }
1318 {
1319 const available = q.buffer[0..q.get_index];
1320 const copy_len = @min(available.len, remaining.len);
1321 @memcpy(available[0..copy_len], remaining[0..copy_len]);
1322 remaining = remaining[copy_len..];
1323 q.put_index = copy_len;
1324 if (remaining.len == 0) return elements.len;
1325 }
1326
1327 const total_filled = elements.len - remaining.len;
1328 if (total_filled >= min) return total_filled;
1329
1330 var pending: Put = .{ .remaining = remaining, .condition = .{}, .node = .{} };
1331 q.putters.append(&pending.node);
1332 if (uncancelable)
1333 pending.condition.waitUncancelable(io, &q.mutex)
1334 else
1335 try pending.condition.wait(io, &q.mutex);
1336 remaining = pending.remaining;
1337 }
1338 }
1339
1340 pub fn get(q: *@This(), io: Io, buffer: []u8, min: usize) Cancelable!usize {
1341 assert(buffer.len >= min);
1342 if (buffer.len == 0) return 0;
1343 try q.mutex.lock(io);
1344 defer q.mutex.unlock(io);
1345 return getLocked(q, io, buffer, min, false);
1346 }
1347
1348 pub fn getUncancelable(q: *@This(), io: Io, buffer: []u8, min: usize) usize {
1349 assert(buffer.len >= min);
1350 if (buffer.len == 0) return 0;
1351 q.mutex.lockUncancelable(io);
1352 defer q.mutex.unlock(io);
1353 return getLocked(q, io, buffer, min, true) catch |err| switch (err) {
1354 error.Canceled => unreachable,
1355 };
1356 }
1357
1358 pub fn getLocked(q: *@This(), io: Io, buffer: []u8, min: usize, uncancelable: bool) Cancelable!usize {
1359 // The ring buffer gets first priority, then data should come from any
1360 // queued putters, then finally the ring buffer should be filled with
1361 // data from putters so they can be resumed.
1362
1363 var remaining = buffer;
1364 while (true) {
1365 if (q.get_index <= q.put_index) {
1366 const available = q.buffer[q.get_index..q.put_index];
1367 const copy_len = @min(available.len, remaining.len);
1368 @memcpy(remaining[0..copy_len], available[0..copy_len]);
1369 q.get_index += copy_len;
1370 remaining = remaining[copy_len..];
1371 if (remaining.len == 0) return fillRingBufferFromPutters(q, io, buffer.len);
1372 } else {
1373 {
1374 const available = q.buffer[q.get_index..];
1375 const copy_len = @min(available.len, remaining.len);
1376 @memcpy(remaining[0..copy_len], available[0..copy_len]);
1377 q.get_index += copy_len;
1378 remaining = remaining[copy_len..];
1379 if (remaining.len == 0) return fillRingBufferFromPutters(q, io, buffer.len);
1380 }
1381 {
1382 const available = q.buffer[0..q.put_index];
1383 const copy_len = @min(available.len, remaining.len);
1384 @memcpy(remaining[0..copy_len], available[0..copy_len]);
1385 q.get_index = copy_len;
1386 remaining = remaining[copy_len..];
1387 if (remaining.len == 0) return fillRingBufferFromPutters(q, io, buffer.len);
1388 }
1389 }
1390 // Copy directly from putters into buffer.
1391 while (remaining.len > 0) {
1392 const putter: *Put = @alignCast(@fieldParentPtr("node", q.putters.popFirst() orelse break));
1393 const copy_len = @min(putter.remaining.len, remaining.len);
1394 @memcpy(remaining[0..copy_len], putter.remaining[0..copy_len]);
1395 putter.remaining = putter.remaining[copy_len..];
1396 remaining = remaining[copy_len..];
1397 if (putter.remaining.len == 0) {
1398 putter.condition.signal(io);
1399 } else {
1400 assert(remaining.len == 0);
1401 q.putters.prepend(&putter.node);
1402 return fillRingBufferFromPutters(q, io, buffer.len);
1403 }
1404 }
1405 // Both ring buffer and putters queue is empty.
1406 const total_filled = buffer.len - remaining.len;
1407 if (total_filled >= min) return total_filled;
1408
1409 var pending: Get = .{ .remaining = remaining, .condition = .{}, .node = .{} };
1410 q.getters.append(&pending.node);
1411 if (uncancelable)
1412 pending.condition.waitUncancelable(io, &q.mutex)
1413 else
1414 try pending.condition.wait(io, &q.mutex);
1415 remaining = pending.remaining;
1416 }
1417 }
1418
1419 /// Called when there is nonzero space available in the ring buffer and
1420 /// potentially putters waiting. The mutex is already held and the task is
1421 /// to copy putter data to the ring buffer and signal any putters whose
1422 /// buffers been fully copied.
1423 fn fillRingBufferFromPutters(q: *TypeErasedQueue, io: Io, len: usize) usize {
1424 while (true) {
1425 const putter: *Put = @alignCast(@fieldParentPtr("node", q.putters.popFirst() orelse return len));
1426 const available = q.buffer[q.put_index..];
1427 const copy_len = @min(available.len, putter.remaining.len);
1428 @memcpy(available[0..copy_len], putter.remaining[0..copy_len]);
1429 putter.remaining = putter.remaining[copy_len..];
1430 q.put_index += copy_len;
1431 if (putter.remaining.len == 0) {
1432 putter.condition.signal(io);
1433 continue;
1434 }
1435 const second_available = q.buffer[0..q.get_index];
1436 const second_copy_len = @min(second_available.len, putter.remaining.len);
1437 @memcpy(second_available[0..second_copy_len], putter.remaining[0..second_copy_len]);
1438 putter.remaining = putter.remaining[copy_len..];
1439 q.put_index = copy_len;
1440 if (putter.remaining.len == 0) {
1441 putter.condition.signal(io);
1442 continue;
1443 }
1444 q.putters.prepend(&putter.node);
1445 return len;
1446 }
1447 }
1448};
1449
1450/// Many producer, many consumer, thread-safe, runtime configurable buffer size.
1451/// When buffer is empty, consumers suspend and are resumed by producers.
1452/// When buffer is full, producers suspend and are resumed by consumers.
1453pub fn Queue(Elem: type) type {
1454 return struct {
1455 type_erased: TypeErasedQueue,
1456
1457 pub fn init(buffer: []Elem) @This() {
1458 return .{ .type_erased = .init(@ptrCast(buffer)) };
1459 }
1460
1461 /// Appends elements to the end of the queue. The function returns when
1462 /// at least `min` elements have been added to the buffer or sent
1463 /// directly to a consumer.
1464 ///
1465 /// Returns how many elements have been added to the queue.
1466 ///
1467 /// Asserts that `elements.len >= min`.
1468 pub fn put(q: *@This(), io: Io, elements: []const Elem, min: usize) Cancelable!usize {
1469 return @divExact(try q.type_erased.put(io, @ptrCast(elements), min * @sizeOf(Elem)), @sizeOf(Elem));
1470 }
1471
1472 /// Same as `put` but blocks until all elements have been added to the queue.
1473 pub fn putAll(q: *@This(), io: Io, elements: []const Elem) Cancelable!void {
1474 assert(try q.put(io, elements, elements.len) == elements.len);
1475 }
1476
1477 /// Same as `put` but cannot be interrupted.
1478 pub fn putUncancelable(q: *@This(), io: Io, elements: []const Elem, min: usize) usize {
1479 return @divExact(q.type_erased.putUncancelable(io, @ptrCast(elements), min * @sizeOf(Elem)), @sizeOf(Elem));
1480 }
1481
1482 pub fn putOne(q: *@This(), io: Io, item: Elem) Cancelable!void {
1483 assert(try q.put(io, &.{item}, 1) == 1);
1484 }
1485
1486 pub fn putOneUncancelable(q: *@This(), io: Io, item: Elem) void {
1487 assert(q.putUncancelable(io, &.{item}, 1) == 1);
1488 }
1489
1490 /// Receives elements from the beginning of the queue. The function
1491 /// returns when at least `min` elements have been populated inside
1492 /// `buffer`.
1493 ///
1494 /// Returns how many elements of `buffer` have been populated.
1495 ///
1496 /// Asserts that `buffer.len >= min`.
1497 pub fn get(q: *@This(), io: Io, buffer: []Elem, min: usize) Cancelable!usize {
1498 return @divExact(try q.type_erased.get(io, @ptrCast(buffer), min * @sizeOf(Elem)), @sizeOf(Elem));
1499 }
1500
1501 pub fn getUncancelable(q: *@This(), io: Io, buffer: []Elem, min: usize) usize {
1502 return @divExact(q.type_erased.getUncancelable(io, @ptrCast(buffer), min * @sizeOf(Elem)), @sizeOf(Elem));
1503 }
1504
1505 pub fn getOne(q: *@This(), io: Io) Cancelable!Elem {
1506 var buf: [1]Elem = undefined;
1507 assert(try q.get(io, &buf, 1) == 1);
1508 return buf[0];
1509 }
1510
1511 pub fn getOneUncancelable(q: *@This(), io: Io) Elem {
1512 var buf: [1]Elem = undefined;
1513 assert(q.getUncancelable(io, &buf, 1) == 1);
1514 return buf[0];
1515 }
1516
1517 /// Returns buffer length in `Elem` units.
1518 pub fn capacity(q: *const @This()) usize {
1519 return @divExact(q.type_erased.buffer.len, @sizeOf(Elem));
1520 }
1521 };
1522}
1523
1524/// Calls `function` with `args`, such that the return value of the function is
1525/// not guaranteed to be available until `await` is called.
1526///
1527/// `function` *may* be called immediately, before `async` returns. This has
1528/// weaker guarantees than `concurrent`, making more portable and
1529/// reusable.
1530///
1531/// See also:
1532/// * `Group`
1533pub fn async(
1534 io: Io,
1535 function: anytype,
1536 args: std.meta.ArgsTuple(@TypeOf(function)),
1537) Future(@typeInfo(@TypeOf(function)).@"fn".return_type.?) {
1538 const Result = @typeInfo(@TypeOf(function)).@"fn".return_type.?;
1539 const Args = @TypeOf(args);
1540 const TypeErased = struct {
1541 fn start(context: *const anyopaque, result: *anyopaque) void {
1542 const args_casted: *const Args = @ptrCast(@alignCast(context));
1543 const result_casted: *Result = @ptrCast(@alignCast(result));
1544 result_casted.* = @call(.auto, function, args_casted.*);
1545 }
1546 };
1547 var future: Future(Result) = undefined;
1548 future.any_future = io.vtable.async(
1549 io.userdata,
1550 @ptrCast((&future.result)[0..1]),
1551 .of(Result),
1552 @ptrCast((&args)[0..1]),
1553 .of(Args),
1554 TypeErased.start,
1555 );
1556 return future;
1557}
1558
1559pub const ConcurrentError = error{
1560 /// May occur due to a temporary condition such as resource exhaustion, or
1561 /// to the Io implementation not supporting concurrency.
1562 ConcurrencyUnavailable,
1563};
1564
1565/// Calls `function` with `args`, such that the return value of the function is
1566/// not guaranteed to be available until `await` is called, allowing the caller
1567/// to progress while waiting for any `Io` operations.
1568///
1569/// This has stronger guarantee than `async`, placing restrictions on what kind
1570/// of `Io` implementations are supported. By calling `async` instead, one
1571/// allows, for example, stackful single-threaded blocking I/O.
1572pub fn concurrent(
1573 io: Io,
1574 function: anytype,
1575 args: std.meta.ArgsTuple(@TypeOf(function)),
1576) ConcurrentError!Future(@typeInfo(@TypeOf(function)).@"fn".return_type.?) {
1577 const Result = @typeInfo(@TypeOf(function)).@"fn".return_type.?;
1578 const Args = @TypeOf(args);
1579 const TypeErased = struct {
1580 fn start(context: *const anyopaque, result: *anyopaque) void {
1581 const args_casted: *const Args = @ptrCast(@alignCast(context));
1582 const result_casted: *Result = @ptrCast(@alignCast(result));
1583 result_casted.* = @call(.auto, function, args_casted.*);
1584 }
1585 };
1586 var future: Future(Result) = undefined;
1587 future.any_future = try io.vtable.concurrent(
1588 io.userdata,
1589 @sizeOf(Result),
1590 .of(Result),
1591 @ptrCast((&args)[0..1]),
1592 .of(Args),
1593 TypeErased.start,
1594 );
1595 return future;
1596}
1597
1598pub fn cancelRequested(io: Io) bool {
1599 return io.vtable.cancelRequested(io.userdata);
1600}
1601
1602pub const SleepError = error{UnsupportedClock} || UnexpectedError || Cancelable;
1603
1604pub fn sleep(io: Io, duration: Duration, clock: Clock) SleepError!void {
1605 return io.vtable.sleep(io.userdata, .{ .duration = .{
1606 .raw = duration,
1607 .clock = clock,
1608 } });
1609}
1610
1611/// Given a struct with each field a `*Future`, returns a union with the same
1612/// fields, each field type the future's result.
1613pub fn SelectUnion(S: type) type {
1614 const struct_fields = @typeInfo(S).@"struct".fields;
1615 var fields: [struct_fields.len]std.builtin.Type.UnionField = undefined;
1616 for (&fields, struct_fields) |*union_field, struct_field| {
1617 const F = @typeInfo(struct_field.type).pointer.child;
1618 const Result = @TypeOf(@as(F, undefined).result);
1619 union_field.* = .{
1620 .name = struct_field.name,
1621 .type = Result,
1622 .alignment = struct_field.alignment,
1623 };
1624 }
1625 return @Type(.{ .@"union" = .{
1626 .layout = .auto,
1627 .tag_type = std.meta.FieldEnum(S),
1628 .fields = &fields,
1629 .decls = &.{},
1630 } });
1631}
1632
1633/// `s` is a struct with every field a `*Future(T)`, where `T` can be any type,
1634/// and can be different for each field.
1635pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {
1636 const U = SelectUnion(@TypeOf(s));
1637 const S = @TypeOf(s);
1638 const fields = @typeInfo(S).@"struct".fields;
1639 var futures: [fields.len]*AnyFuture = undefined;
1640 inline for (fields, &futures) |field, *any_future| {
1641 const future = @field(s, field.name);
1642 any_future.* = future.any_future orelse return @unionInit(U, field.name, future.result);
1643 }
1644 switch (try io.vtable.select(io.userdata, &futures)) {
1645 inline 0...(fields.len - 1) => |selected_index| {
1646 const field_name = fields[selected_index].name;
1647 return @unionInit(U, field_name, @field(s, field_name).await(io));
1648 },
1649 else => unreachable,
1650 }
1651}
lib/std/Io/Dir.zig created+392
......@@ -0,0 +1,392 @@
1const Dir = @This();
2
3const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5
6const std = @import("../std.zig");
7const Io = std.Io;
8const File = Io.File;
9
10handle: Handle,
11
12pub const Mode = Io.File.Mode;
13pub const default_mode: Mode = 0o755;
14
15/// Returns a handle to the current working directory.
16///
17/// It is not opened with iteration capability. Iterating over the result is
18/// illegal behavior.
19///
20/// Closing the returned `Dir` is checked illegal behavior.
21///
22/// On POSIX targets, this function is comptime-callable.
23pub fn cwd() Dir {
24 return switch (native_os) {
25 .windows => .{ .handle = std.os.windows.peb().ProcessParameters.CurrentDirectory.Handle },
26 .wasi => .{ .handle = std.options.wasiCwd() },
27 else => .{ .handle = std.posix.AT.FDCWD },
28 };
29}
30
31pub const Handle = std.posix.fd_t;
32
33pub const PathNameError = error{
34 NameTooLong,
35 /// File system cannot encode the requested file name bytes.
36 /// Could be due to invalid WTF-8 on Windows, invalid UTF-8 on WASI,
37 /// invalid characters on Windows, etc. Filesystem and operating specific.
38 BadPathName,
39};
40
41pub const AccessError = error{
42 AccessDenied,
43 PermissionDenied,
44 FileNotFound,
45 InputOutput,
46 SystemResources,
47 FileBusy,
48 SymLinkLoop,
49 ReadOnlyFileSystem,
50} || PathNameError || Io.Cancelable || Io.UnexpectedError;
51
52pub const AccessOptions = packed struct {
53 follow_symlinks: bool = true,
54 read: bool = false,
55 write: bool = false,
56 execute: bool = false,
57};
58
59/// Test accessing `sub_path`.
60///
61/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
62/// On WASI, `sub_path` should be encoded as valid UTF-8.
63/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
64///
65/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this
66/// function. For example, instead of testing if a file exists and then opening
67/// it, just open it and handle the error for file not found.
68pub fn access(dir: Dir, io: Io, sub_path: []const u8, options: AccessOptions) AccessError!void {
69 return io.vtable.dirAccess(io.userdata, dir, sub_path, options);
70}
71
72pub const OpenError = error{
73 FileNotFound,
74 NotDir,
75 AccessDenied,
76 PermissionDenied,
77 SymLinkLoop,
78 ProcessFdQuotaExceeded,
79 SystemFdQuotaExceeded,
80 NoDevice,
81 SystemResources,
82 DeviceBusy,
83 /// On Windows, `\\server` or `\\server\share` was not found.
84 NetworkNotFound,
85} || PathNameError || Io.Cancelable || Io.UnexpectedError;
86
87pub const OpenOptions = struct {
88 /// `true` means the opened directory can be used as the `Dir` parameter
89 /// for functions which operate based on an open directory handle. When `false`,
90 /// such operations are Illegal Behavior.
91 access_sub_paths: bool = true,
92 /// `true` means the opened directory can be scanned for the files and sub-directories
93 /// of the result. It means the `iterate` function can be called.
94 iterate: bool = false,
95 /// `false` means it won't dereference the symlinks.
96 follow_symlinks: bool = true,
97};
98
99/// Opens a directory at the given path. The directory is a system resource that remains
100/// open until `close` is called on the result.
101///
102/// The directory cannot be iterated unless the `iterate` option is set to `true`.
103///
104/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
105/// On WASI, `sub_path` should be encoded as valid UTF-8.
106/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
107pub fn openDir(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) OpenError!Dir {
108 return io.vtable.dirOpenDir(io.userdata, dir, sub_path, options);
109}
110
111pub fn close(dir: Dir, io: Io) void {
112 return io.vtable.dirClose(io.userdata, dir);
113}
114
115/// Opens a file for reading or writing, without attempting to create a new file.
116///
117/// To create a new file, see `createFile`.
118///
119/// Allocates a resource to be released with `File.close`.
120///
121/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
122/// On WASI, `sub_path` should be encoded as valid UTF-8.
123/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
124pub fn openFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
125 return io.vtable.dirOpenFile(io.userdata, dir, sub_path, flags);
126}
127
128/// Creates, opens, or overwrites a file with write access.
129///
130/// Allocates a resource to be dellocated with `File.close`.
131///
132/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
133/// On WASI, `sub_path` should be encoded as valid UTF-8.
134/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
135pub fn createFile(dir: Dir, io: Io, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
136 return io.vtable.dirCreateFile(io.userdata, dir, sub_path, flags);
137}
138
139pub const WriteFileOptions = struct {
140 /// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
141 /// On WASI, `sub_path` should be encoded as valid UTF-8.
142 /// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
143 sub_path: []const u8,
144 data: []const u8,
145 flags: File.CreateFlags = .{},
146};
147
148pub const WriteFileError = File.WriteError || File.OpenError || Io.Cancelable;
149
150/// Writes content to the file system, using the file creation flags provided.
151pub fn writeFile(dir: Dir, io: Io, options: WriteFileOptions) WriteFileError!void {
152 var file = try dir.createFile(io, options.sub_path, options.flags);
153 defer file.close(io);
154 try file.writeAll(io, options.data);
155}
156
157pub const PrevStatus = enum {
158 stale,
159 fresh,
160};
161
162pub const UpdateFileError = File.OpenError;
163
164/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If
165/// they are equal, does nothing. Otherwise, atomically copies `source_path` to
166/// `dest_path`, creating the parent directory hierarchy as needed. The
167/// destination file gains the mtime, atime, and mode of the source file so
168/// that the next call to `updateFile` will not need a copy.
169///
170/// Returns the previous status of the file before updating.
171///
172/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
173/// * On WASI, both paths should be encoded as valid UTF-8.
174/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
175pub fn updateFile(
176 source_dir: Dir,
177 io: Io,
178 source_path: []const u8,
179 dest_dir: Dir,
180 /// If directories in this path do not exist, they are created.
181 dest_path: []const u8,
182 options: std.fs.Dir.CopyFileOptions,
183) !PrevStatus {
184 var src_file = try source_dir.openFile(io, source_path, .{});
185 defer src_file.close(io);
186
187 const src_stat = try src_file.stat(io);
188 const actual_mode = options.override_mode orelse src_stat.mode;
189 check_dest_stat: {
190 const dest_stat = blk: {
191 var dest_file = dest_dir.openFile(io, dest_path, .{}) catch |err| switch (err) {
192 error.FileNotFound => break :check_dest_stat,
193 else => |e| return e,
194 };
195 defer dest_file.close(io);
196
197 break :blk try dest_file.stat(io);
198 };
199
200 if (src_stat.size == dest_stat.size and
201 src_stat.mtime.nanoseconds == dest_stat.mtime.nanoseconds and
202 actual_mode == dest_stat.mode)
203 {
204 return .fresh;
205 }
206 }
207
208 if (std.fs.path.dirname(dest_path)) |dirname| {
209 try dest_dir.makePath(io, dirname);
210 }
211
212 var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available.
213 var atomic_file = try std.fs.Dir.atomicFile(.adaptFromNewApi(dest_dir), dest_path, .{
214 .mode = actual_mode,
215 .write_buffer = &buffer,
216 });
217 defer atomic_file.deinit();
218
219 var src_reader: File.Reader = .initSize(src_file, io, &.{}, src_stat.size);
220 const dest_writer = &atomic_file.file_writer.interface;
221
222 _ = dest_writer.sendFileAll(&src_reader, .unlimited) catch |err| switch (err) {
223 error.ReadFailed => return src_reader.err.?,
224 error.WriteFailed => return atomic_file.file_writer.err.?,
225 };
226 try atomic_file.flush();
227 try atomic_file.file_writer.file.updateTimes(src_stat.atime, src_stat.mtime);
228 try atomic_file.renameIntoPlace();
229 return .stale;
230}
231
232pub const ReadFileError = File.OpenError || File.Reader.Error;
233
234/// Read all of file contents using a preallocated buffer.
235///
236/// The returned slice has the same pointer as `buffer`. If the length matches `buffer.len`
237/// the situation is ambiguous. It could either mean that the entire file was read, and
238/// it exactly fits the buffer, or it could mean the buffer was not big enough for the
239/// entire file.
240///
241/// * On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
242/// * On WASI, `file_path` should be encoded as valid UTF-8.
243/// * On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
244pub fn readFile(dir: Dir, io: Io, file_path: []const u8, buffer: []u8) ReadFileError![]u8 {
245 var file = try dir.openFile(io, file_path, .{});
246 defer file.close(io);
247
248 var reader = file.reader(io, &.{});
249 const n = reader.interface.readSliceShort(buffer) catch |err| switch (err) {
250 error.ReadFailed => return reader.err.?,
251 };
252
253 return buffer[0..n];
254}
255
256pub const MakeError = error{
257 /// In WASI, this error may occur when the file descriptor does
258 /// not hold the required rights to create a new directory relative to it.
259 AccessDenied,
260 PermissionDenied,
261 DiskQuota,
262 PathAlreadyExists,
263 SymLinkLoop,
264 LinkQuotaExceeded,
265 FileNotFound,
266 SystemResources,
267 NoSpaceLeft,
268 NotDir,
269 ReadOnlyFileSystem,
270 NoDevice,
271 /// On Windows, `\\server` or `\\server\share` was not found.
272 NetworkNotFound,
273} || PathNameError || Io.Cancelable || Io.UnexpectedError;
274
275/// Creates a single directory with a relative or absolute path.
276///
277/// * On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
278/// * On WASI, `sub_path` should be encoded as valid UTF-8.
279/// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
280///
281/// Related:
282/// * `makePath`
283/// * `makeDirAbsolute`
284pub fn makeDir(dir: Dir, io: Io, sub_path: []const u8) MakeError!void {
285 return io.vtable.dirMake(io.userdata, dir, sub_path, default_mode);
286}
287
288pub const MakePathError = MakeError || StatPathError;
289
290/// Calls makeDir iteratively to make an entire path, creating any parent
291/// directories that do not exist.
292///
293/// Returns success if the path already exists and is a directory.
294///
295/// This function is not atomic, and if it returns an error, the file system
296/// may have been modified regardless.
297///
298/// Fails on an empty path with `error.BadPathName` as that is not a path that
299/// can be created.
300///
301/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
302/// On WASI, `sub_path` should be encoded as valid UTF-8.
303/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
304///
305/// Paths containing `..` components are handled differently depending on the platform:
306/// - On Windows, `..` are resolved before the path is passed to NtCreateFile, meaning
307/// a `sub_path` like "first/../second" will resolve to "second" and only a
308/// `./second` directory will be created.
309/// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`,
310/// meaning a `sub_path` like "first/../second" will create both a `./first`
311/// and a `./second` directory.
312pub fn makePath(dir: Dir, io: Io, sub_path: []const u8) MakePathError!void {
313 _ = try makePathStatus(dir, io, sub_path);
314}
315
316pub const MakePathStatus = enum { existed, created };
317
318/// Same as `makePath` except returns whether the path already existed or was
319/// successfully created.
320pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8) MakePathError!MakePathStatus {
321 var it = try std.fs.path.componentIterator(sub_path);
322 var status: MakePathStatus = .existed;
323 var component = it.last() orelse return error.BadPathName;
324 while (true) {
325 if (makeDir(dir, io, component.path)) |_| {
326 status = .created;
327 } else |err| switch (err) {
328 error.PathAlreadyExists => {
329 // stat the file and return an error if it's not a directory
330 // this is important because otherwise a dangling symlink
331 // could cause an infinite loop
332 check_dir: {
333 // workaround for windows, see https://github.com/ziglang/zig/issues/16738
334 const fstat = statPath(dir, io, component.path, .{}) catch |stat_err| switch (stat_err) {
335 error.IsDir => break :check_dir,
336 else => |e| return e,
337 };
338 if (fstat.kind != .directory) return error.NotDir;
339 }
340 },
341 error.FileNotFound => |e| {
342 component = it.previous() orelse return e;
343 continue;
344 },
345 else => |e| return e,
346 }
347 component = it.next() orelse return status;
348 }
349}
350
351pub const MakeOpenPathError = MakeError || OpenError || StatPathError;
352
353/// Performs the equivalent of `makePath` followed by `openDir`, atomically if possible.
354///
355/// When this operation is canceled, it may leave the file system in a
356/// partially modified state.
357///
358/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
359/// On WASI, `sub_path` should be encoded as valid UTF-8.
360/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
361pub fn makeOpenPath(dir: Dir, io: Io, sub_path: []const u8, options: OpenOptions) MakeOpenPathError!Dir {
362 return io.vtable.dirMakeOpenPath(io.userdata, dir, sub_path, options);
363}
364
365pub const Stat = File.Stat;
366pub const StatError = File.StatError;
367
368pub fn stat(dir: Dir, io: Io) StatError!Stat {
369 return io.vtable.dirStat(io.userdata, dir);
370}
371
372pub const StatPathError = File.OpenError || File.StatError;
373
374pub const StatPathOptions = struct {
375 follow_symlinks: bool = true,
376};
377
378/// Returns metadata for a file inside the directory.
379///
380/// On Windows, this requires three syscalls. On other operating systems, it
381/// only takes one.
382///
383/// Symlinks are followed.
384///
385/// `sub_path` may be absolute, in which case `self` is ignored.
386///
387/// * On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
388/// * On WASI, `sub_path` should be encoded as valid UTF-8.
389/// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
390pub fn statPath(dir: Dir, io: Io, sub_path: []const u8, options: StatPathOptions) StatPathError!Stat {
391 return io.vtable.dirStatPath(io.userdata, dir, sub_path, options);
392}
lib/std/Io/File.zig created+659
......@@ -0,0 +1,659 @@
1const File = @This();
2
3const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5const is_windows = native_os == .windows;
6
7const std = @import("../std.zig");
8const Io = std.Io;
9const assert = std.debug.assert;
10
11handle: Handle,
12
13pub const Handle = std.posix.fd_t;
14pub const Mode = std.posix.mode_t;
15pub const INode = std.posix.ino_t;
16
17pub const Kind = enum {
18 block_device,
19 character_device,
20 directory,
21 named_pipe,
22 sym_link,
23 file,
24 unix_domain_socket,
25 whiteout,
26 door,
27 event_port,
28 unknown,
29};
30
31pub const Stat = struct {
32 /// A number that the system uses to point to the file metadata. This
33 /// number is not guaranteed to be unique across time, as some file
34 /// systems may reuse an inode after its file has been deleted. Some
35 /// systems may change the inode of a file over time.
36 ///
37 /// On Linux, the inode is a structure that stores the metadata, and
38 /// the inode _number_ is what you see here: the index number of the
39 /// inode.
40 ///
41 /// The FileIndex on Windows is similar. It is a number for a file that
42 /// is unique to each filesystem.
43 inode: INode,
44 size: u64,
45 /// This is available on POSIX systems and is always 0 otherwise.
46 mode: Mode,
47 kind: Kind,
48 /// Last access time in nanoseconds, relative to UTC 1970-01-01.
49 atime: Io.Timestamp,
50 /// Last modification time in nanoseconds, relative to UTC 1970-01-01.
51 mtime: Io.Timestamp,
52 /// Last status/metadata change time in nanoseconds, relative to UTC 1970-01-01.
53 ctime: Io.Timestamp,
54};
55
56pub fn stdout() File {
57 return .{ .handle = if (is_windows) std.os.windows.peb().ProcessParameters.hStdOutput else std.posix.STDOUT_FILENO };
58}
59
60pub fn stderr() File {
61 return .{ .handle = if (is_windows) std.os.windows.peb().ProcessParameters.hStdError else std.posix.STDERR_FILENO };
62}
63
64pub fn stdin() File {
65 return .{ .handle = if (is_windows) std.os.windows.peb().ProcessParameters.hStdInput else std.posix.STDIN_FILENO };
66}
67
68pub const StatError = error{
69 SystemResources,
70 /// In WASI, this error may occur when the file descriptor does
71 /// not hold the required rights to get its filestat information.
72 AccessDenied,
73 PermissionDenied,
74 /// Attempted to stat a non-file stream.
75 Streaming,
76} || Io.Cancelable || Io.UnexpectedError;
77
78/// Returns `Stat` containing basic information about the `File`.
79pub fn stat(file: File, io: Io) StatError!Stat {
80 return io.vtable.fileStat(io.userdata, file);
81}
82
83pub const OpenMode = enum {
84 read_only,
85 write_only,
86 read_write,
87};
88
89pub const Lock = enum {
90 none,
91 shared,
92 exclusive,
93};
94
95pub const OpenFlags = struct {
96 mode: OpenMode = .read_only,
97
98 /// Open the file with an advisory lock to coordinate with other processes
99 /// accessing it at the same time. An exclusive lock will prevent other
100 /// processes from acquiring a lock. A shared lock will prevent other
101 /// processes from acquiring a exclusive lock, but does not prevent
102 /// other process from getting their own shared locks.
103 ///
104 /// The lock is advisory, except on Linux in very specific circumstances[1].
105 /// This means that a process that does not respect the locking API can still get access
106 /// to the file, despite the lock.
107 ///
108 /// On these operating systems, the lock is acquired atomically with
109 /// opening the file:
110 /// * Darwin
111 /// * DragonFlyBSD
112 /// * FreeBSD
113 /// * Haiku
114 /// * NetBSD
115 /// * OpenBSD
116 /// On these operating systems, the lock is acquired via a separate syscall
117 /// after opening the file:
118 /// * Linux
119 /// * Windows
120 ///
121 /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
122 lock: Lock = .none,
123
124 /// Sets whether or not to wait until the file is locked to return. If set to true,
125 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
126 /// is available to proceed.
127 lock_nonblocking: bool = false,
128
129 /// Set this to allow the opened file to automatically become the
130 /// controlling TTY for the current process.
131 allow_ctty: bool = false,
132
133 follow_symlinks: bool = true,
134
135 pub fn isRead(self: OpenFlags) bool {
136 return self.mode != .write_only;
137 }
138
139 pub fn isWrite(self: OpenFlags) bool {
140 return self.mode != .read_only;
141 }
142};
143
144pub const CreateFlags = std.fs.File.CreateFlags;
145
146pub const OpenError = error{
147 SharingViolation,
148 PipeBusy,
149 NoDevice,
150 /// On Windows, `\\server` or `\\server\share` was not found.
151 NetworkNotFound,
152 ProcessNotFound,
153 /// On Windows, antivirus software is enabled by default. It can be
154 /// disabled, but Windows Update sometimes ignores the user's preference
155 /// and re-enables it. When enabled, antivirus software on Windows
156 /// intercepts file system operations and makes them significantly slower
157 /// in addition to possibly failing with this error code.
158 AntivirusInterference,
159 /// In WASI, this error may occur when the file descriptor does
160 /// not hold the required rights to open a new resource relative to it.
161 AccessDenied,
162 PermissionDenied,
163 SymLinkLoop,
164 ProcessFdQuotaExceeded,
165 SystemFdQuotaExceeded,
166 /// Either:
167 /// * One of the path components does not exist.
168 /// * Cwd was used, but cwd has been deleted.
169 /// * The path associated with the open directory handle has been deleted.
170 /// * On macOS, multiple processes or threads raced to create the same file
171 /// with `O.EXCL` set to `false`.
172 FileNotFound,
173 /// The path exceeded `max_path_bytes` bytes.
174 /// Insufficient kernel memory was available, or
175 /// the named file is a FIFO and per-user hard limit on
176 /// memory allocation for pipes has been reached.
177 SystemResources,
178 /// The file is too large to be opened. This error is unreachable
179 /// for 64-bit targets, as well as when opening directories.
180 FileTooBig,
181 /// The path refers to directory but the `DIRECTORY` flag was not provided.
182 IsDir,
183 /// A new path cannot be created because the device has no room for the new file.
184 /// This error is only reachable when the `CREAT` flag is provided.
185 NoSpaceLeft,
186 /// A component used as a directory in the path was not, in fact, a directory, or
187 /// `DIRECTORY` was specified and the path was not a directory.
188 NotDir,
189 /// The path already exists and the `CREAT` and `EXCL` flags were provided.
190 PathAlreadyExists,
191 DeviceBusy,
192 FileLocksNotSupported,
193 /// One of these three things:
194 /// * pathname refers to an executable image which is currently being
195 /// executed and write access was requested.
196 /// * pathname refers to a file that is currently in use as a swap
197 /// file, and the O_TRUNC flag was specified.
198 /// * pathname refers to a file that is currently being read by the
199 /// kernel (e.g., for module/firmware loading), and write access was
200 /// requested.
201 FileBusy,
202 /// Non-blocking was requested and the operation cannot return immediately.
203 WouldBlock,
204} || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError;
205
206pub fn close(file: File, io: Io) void {
207 return io.vtable.fileClose(io.userdata, file);
208}
209
210pub const OpenSelfExeError = OpenError || std.fs.SelfExePathError || std.posix.FlockError;
211
212pub fn openSelfExe(io: Io, flags: OpenFlags) OpenSelfExeError!File {
213 return io.vtable.openSelfExe(io.userdata, flags);
214}
215
216pub const ReadPositionalError = Reader.Error || error{Unseekable};
217
218pub fn readPositional(file: File, io: Io, buffer: []u8, offset: u64) ReadPositionalError!usize {
219 return io.vtable.fileReadPositional(io.userdata, file, buffer, offset);
220}
221
222pub const WriteStreamingError = error{} || Io.UnexpectedError || Io.Cancelable;
223
224pub fn writeStreaming(file: File, io: Io, buffer: [][]const u8) WriteStreamingError!usize {
225 return file.fileWriteStreaming(io, buffer);
226}
227
228pub const WritePositionalError = WriteStreamingError || error{Unseekable};
229
230pub fn writePositional(file: File, io: Io, buffer: [][]const u8, offset: u64) WritePositionalError!usize {
231 return io.vtable.fileWritePositional(io.userdata, file, buffer, offset);
232}
233
234pub fn openAbsolute(io: Io, absolute_path: []const u8, flags: OpenFlags) OpenError!File {
235 assert(std.fs.path.isAbsolute(absolute_path));
236 return Io.Dir.cwd().openFile(io, absolute_path, flags);
237}
238
239/// Defaults to positional reading; falls back to streaming.
240///
241/// Positional is more threadsafe, since the global seek position is not
242/// affected.
243pub fn reader(file: File, io: Io, buffer: []u8) Reader {
244 return .init(file, io, buffer);
245}
246
247/// Positional is more threadsafe, since the global seek position is not
248/// affected, but when such syscalls are not available, preemptively
249/// initializing in streaming mode skips a failed syscall.
250pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader {
251 return .initStreaming(file, io, buffer);
252}
253
254pub const SeekError = error{
255 Unseekable,
256 /// The file descriptor does not hold the required rights to seek on it.
257 AccessDenied,
258} || Io.Cancelable || Io.UnexpectedError;
259
260/// Memoizes key information about a file handle such as:
261/// * The size from calling stat, or the error that occurred therein.
262/// * The current seek position.
263/// * The error that occurred when trying to seek.
264/// * Whether reading should be done positionally or streaming.
265/// * Whether reading should be done via fd-to-fd syscalls (e.g. `sendfile`)
266/// versus plain variants (e.g. `read`).
267///
268/// Fulfills the `Io.Reader` interface.
269pub const Reader = struct {
270 io: Io,
271 file: File,
272 err: ?Error = null,
273 mode: Reader.Mode = .positional,
274 /// Tracks the true seek position in the file. To obtain the logical
275 /// position, use `logicalPos`.
276 pos: u64 = 0,
277 size: ?u64 = null,
278 size_err: ?SizeError = null,
279 seek_err: ?Reader.SeekError = null,
280 interface: Io.Reader,
281
282 pub const Error = error{
283 InputOutput,
284 SystemResources,
285 IsDir,
286 BrokenPipe,
287 ConnectionResetByPeer,
288 Timeout,
289 /// In WASI, EBADF is mapped to this error because it is returned when
290 /// trying to read a directory file descriptor as if it were a file.
291 NotOpenForReading,
292 SocketUnconnected,
293 /// This error occurs when no global event loop is configured,
294 /// and reading from the file descriptor would block.
295 WouldBlock,
296 /// In WASI, this error occurs when the file descriptor does
297 /// not hold the required rights to read from it.
298 AccessDenied,
299 /// This error occurs in Linux if the process to be read from
300 /// no longer exists.
301 ProcessNotFound,
302 /// Unable to read file due to lock.
303 LockViolation,
304 } || Io.Cancelable || Io.UnexpectedError;
305
306 pub const SizeError = std.os.windows.GetFileSizeError || StatError || error{
307 /// Occurs if, for example, the file handle is a network socket and therefore does not have a size.
308 Streaming,
309 };
310
311 pub const SeekError = File.SeekError || error{
312 /// Seeking fell back to reading, and reached the end before the requested seek position.
313 /// `pos` remains at the end of the file.
314 EndOfStream,
315 /// Seeking fell back to reading, which failed.
316 ReadFailed,
317 };
318
319 pub const Mode = enum {
320 streaming,
321 positional,
322 /// Avoid syscalls other than `read` and `readv`.
323 streaming_reading,
324 /// Avoid syscalls other than `pread` and `preadv`.
325 positional_reading,
326 /// Indicates reading cannot continue because of a seek failure.
327 failure,
328
329 pub fn toStreaming(m: @This()) @This() {
330 return switch (m) {
331 .positional, .streaming => .streaming,
332 .positional_reading, .streaming_reading => .streaming_reading,
333 .failure => .failure,
334 };
335 }
336
337 pub fn toReading(m: @This()) @This() {
338 return switch (m) {
339 .positional, .positional_reading => .positional_reading,
340 .streaming, .streaming_reading => .streaming_reading,
341 .failure => .failure,
342 };
343 }
344 };
345
346 pub fn initInterface(buffer: []u8) Io.Reader {
347 return .{
348 .vtable = &.{
349 .stream = Reader.stream,
350 .discard = Reader.discard,
351 .readVec = Reader.readVec,
352 },
353 .buffer = buffer,
354 .seek = 0,
355 .end = 0,
356 };
357 }
358
359 pub fn init(file: File, io: Io, buffer: []u8) Reader {
360 return .{
361 .io = io,
362 .file = file,
363 .interface = initInterface(buffer),
364 };
365 }
366
367 /// Takes a legacy `std.fs.File` to help with upgrading.
368 pub fn initAdapted(file: std.fs.File, io: Io, buffer: []u8) Reader {
369 return .init(.{ .handle = file.handle }, io, buffer);
370 }
371
372 pub fn initSize(file: File, io: Io, buffer: []u8, size: ?u64) Reader {
373 return .{
374 .io = io,
375 .file = file,
376 .interface = initInterface(buffer),
377 .size = size,
378 };
379 }
380
381 /// Positional is more threadsafe, since the global seek position is not
382 /// affected, but when such syscalls are not available, preemptively
383 /// initializing in streaming mode skips a failed syscall.
384 pub fn initStreaming(file: File, io: Io, buffer: []u8) Reader {
385 return .{
386 .io = io,
387 .file = file,
388 .interface = Reader.initInterface(buffer),
389 .mode = .streaming,
390 .seek_err = error.Unseekable,
391 .size_err = error.Streaming,
392 };
393 }
394
395 pub fn getSize(r: *Reader) SizeError!u64 {
396 return r.size orelse {
397 if (r.size_err) |err| return err;
398 if (stat(r.file, r.io)) |st| {
399 if (st.kind == .file) {
400 r.size = st.size;
401 return st.size;
402 } else {
403 r.mode = r.mode.toStreaming();
404 r.size_err = error.Streaming;
405 return error.Streaming;
406 }
407 } else |err| {
408 r.size_err = err;
409 return err;
410 }
411 };
412 }
413
414 pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void {
415 const io = r.io;
416 switch (r.mode) {
417 .positional, .positional_reading => {
418 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
419 },
420 .streaming, .streaming_reading => {
421 const seek_err = r.seek_err orelse e: {
422 if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) |_| {
423 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
424 return;
425 } else |err| {
426 r.seek_err = err;
427 break :e err;
428 }
429 };
430 var remaining = std.math.cast(u64, offset) orelse return seek_err;
431 while (remaining > 0) {
432 remaining -= discard(&r.interface, .limited64(remaining)) catch |err| {
433 r.seek_err = err;
434 return err;
435 };
436 }
437 r.interface.seek = 0;
438 r.interface.end = 0;
439 },
440 .failure => return r.seek_err.?,
441 }
442 }
443
444 /// Repositions logical read offset relative to the beginning of the file.
445 pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void {
446 const io = r.io;
447 switch (r.mode) {
448 .positional, .positional_reading => {
449 setLogicalPos(r, offset);
450 },
451 .streaming, .streaming_reading => {
452 const logical_pos = logicalPos(r);
453 if (offset >= logical_pos) return Reader.seekBy(r, @intCast(offset - logical_pos));
454 if (r.seek_err) |err| return err;
455 io.vtable.fileSeekTo(io.userdata, r.file, offset) catch |err| {
456 r.seek_err = err;
457 return err;
458 };
459 setLogicalPos(r, offset);
460 },
461 .failure => return r.seek_err.?,
462 }
463 }
464
465 pub fn logicalPos(r: *const Reader) u64 {
466 return r.pos - r.interface.bufferedLen();
467 }
468
469 fn setLogicalPos(r: *Reader, offset: u64) void {
470 const logical_pos = logicalPos(r);
471 if (offset < logical_pos or offset >= r.pos) {
472 r.interface.seek = 0;
473 r.interface.end = 0;
474 r.pos = offset;
475 } else {
476 const logical_delta: usize = @intCast(offset - logical_pos);
477 r.interface.seek += logical_delta;
478 }
479 }
480
481 /// Number of slices to store on the stack, when trying to send as many byte
482 /// vectors through the underlying read calls as possible.
483 const max_buffers_len = 16;
484
485 fn stream(io_reader: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
486 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
487 return streamMode(r, w, limit, r.mode);
488 }
489
490 pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Reader.Mode) Io.Reader.StreamError!usize {
491 switch (mode) {
492 .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {
493 error.Unimplemented => {
494 r.mode = r.mode.toReading();
495 return 0;
496 },
497 else => |e| return e,
498 },
499 .positional_reading => {
500 const dest = limit.slice(try w.writableSliceGreedy(1));
501 var data: [1][]u8 = .{dest};
502 const n = try readVecPositional(r, &data);
503 w.advance(n);
504 return n;
505 },
506 .streaming_reading => {
507 const dest = limit.slice(try w.writableSliceGreedy(1));
508 var data: [1][]u8 = .{dest};
509 const n = try readVecStreaming(r, &data);
510 w.advance(n);
511 return n;
512 },
513 .failure => return error.ReadFailed,
514 }
515 }
516
517 fn readVec(io_reader: *Io.Reader, data: [][]u8) Io.Reader.Error!usize {
518 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
519 switch (r.mode) {
520 .positional, .positional_reading => return readVecPositional(r, data),
521 .streaming, .streaming_reading => return readVecStreaming(r, data),
522 .failure => return error.ReadFailed,
523 }
524 }
525
526 fn readVecPositional(r: *Reader, data: [][]u8) Io.Reader.Error!usize {
527 const io = r.io;
528 var iovecs_buffer: [max_buffers_len][]u8 = undefined;
529 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data);
530 const dest = iovecs_buffer[0..dest_n];
531 assert(dest[0].len > 0);
532 const n = io.vtable.fileReadPositional(io.userdata, r.file, dest, r.pos) catch |err| switch (err) {
533 error.Unseekable => {
534 r.mode = r.mode.toStreaming();
535 const pos = r.pos;
536 if (pos != 0) {
537 r.pos = 0;
538 r.seekBy(@intCast(pos)) catch {
539 r.mode = .failure;
540 return error.ReadFailed;
541 };
542 }
543 return 0;
544 },
545 else => |e| {
546 r.err = e;
547 return error.ReadFailed;
548 },
549 };
550 if (n == 0) {
551 r.size = r.pos;
552 return error.EndOfStream;
553 }
554 r.pos += n;
555 if (n > data_size) {
556 r.interface.end += n - data_size;
557 return data_size;
558 }
559 return n;
560 }
561
562 fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize {
563 const io = r.io;
564 var iovecs_buffer: [max_buffers_len][]u8 = undefined;
565 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data);
566 const dest = iovecs_buffer[0..dest_n];
567 assert(dest[0].len > 0);
568 const n = io.vtable.fileReadStreaming(io.userdata, r.file, dest) catch |err| {
569 r.err = err;
570 return error.ReadFailed;
571 };
572 if (n == 0) {
573 r.size = r.pos;
574 return error.EndOfStream;
575 }
576 r.pos += n;
577 if (n > data_size) {
578 r.interface.end += n - data_size;
579 return data_size;
580 }
581 return n;
582 }
583
584 fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
585 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
586 const io = r.io;
587 const file = r.file;
588 switch (r.mode) {
589 .positional, .positional_reading => {
590 const size = r.getSize() catch {
591 r.mode = r.mode.toStreaming();
592 return 0;
593 };
594 const logical_pos = logicalPos(r);
595 const delta = @min(@intFromEnum(limit), size - logical_pos);
596 setLogicalPos(r, logical_pos + delta);
597 return delta;
598 },
599 .streaming, .streaming_reading => {
600 // Unfortunately we can't seek forward without knowing the
601 // size because the seek syscalls provided to us will not
602 // return the true end position if a seek would exceed the
603 // end.
604 fallback: {
605 if (r.size_err == null and r.seek_err == null) break :fallback;
606
607 const buffered_len = r.interface.bufferedLen();
608 var remaining = @intFromEnum(limit);
609 if (remaining <= buffered_len) {
610 r.interface.seek += remaining;
611 return remaining;
612 }
613 remaining -= buffered_len;
614 r.interface.seek = 0;
615 r.interface.end = 0;
616
617 var trash_buffer: [128]u8 = undefined;
618 var data: [1][]u8 = .{trash_buffer[0..@min(trash_buffer.len, remaining)]};
619 var iovecs_buffer: [max_buffers_len][]u8 = undefined;
620 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data);
621 const dest = iovecs_buffer[0..dest_n];
622 assert(dest[0].len > 0);
623 const n = io.vtable.fileReadStreaming(io.userdata, file, dest) catch |err| {
624 r.err = err;
625 return error.ReadFailed;
626 };
627 if (n == 0) {
628 r.size = r.pos;
629 return error.EndOfStream;
630 }
631 r.pos += n;
632 if (n > data_size) {
633 r.interface.end += n - data_size;
634 remaining -= data_size;
635 } else {
636 remaining -= n;
637 }
638 return @intFromEnum(limit) - remaining;
639 }
640 const size = r.getSize() catch return 0;
641 const n = @min(size - r.pos, std.math.maxInt(i64), @intFromEnum(limit));
642 io.vtable.fileSeekBy(io.userdata, file, n) catch |err| {
643 r.seek_err = err;
644 return 0;
645 };
646 r.pos += n;
647 return n;
648 },
649 .failure => return error.ReadFailed,
650 }
651 }
652
653 /// Returns whether the stream is at the logical end.
654 pub fn atEnd(r: *Reader) bool {
655 // Even if stat fails, size is set when end is encountered.
656 const size = r.size orelse return false;
657 return size - logicalPos(r) == 0;
658 }
659};
lib/std/Io/IoUring.zig created+1497
......@@ -0,0 +1,1497 @@
1const EventLoop = @This();
2const builtin = @import("builtin");
3
4const std = @import("../std.zig");
5const Io = std.Io;
6const assert = std.debug.assert;
7const Allocator = std.mem.Allocator;
8const Alignment = std.mem.Alignment;
9const IoUring = std.os.linux.IoUring;
10
11/// Must be a thread-safe allocator.
12gpa: Allocator,
13mutex: std.Thread.Mutex,
14main_fiber_buffer: [@sizeOf(Fiber) + Fiber.max_result_size]u8 align(@alignOf(Fiber)),
15threads: Thread.List,
16
17/// Empirically saw >128KB being used by the self-hosted backend to panic.
18const idle_stack_size = 256 * 1024;
19
20const max_idle_search = 4;
21const max_steal_ready_search = 4;
22
23const io_uring_entries = 64;
24
25const Thread = struct {
26 thread: std.Thread,
27 idle_context: Context,
28 current_context: *Context,
29 ready_queue: ?*Fiber,
30 io_uring: IoUring,
31 idle_search_index: u32,
32 steal_ready_search_index: u32,
33
34 const canceling: ?*Thread = @ptrFromInt(@alignOf(Thread));
35
36 threadlocal var self: *Thread = undefined;
37
38 fn current() *Thread {
39 return self;
40 }
41
42 fn currentFiber(thread: *Thread) *Fiber {
43 return @fieldParentPtr("context", thread.current_context);
44 }
45
46 const List = struct {
47 allocated: []Thread,
48 reserved: u32,
49 active: u32,
50 };
51};
52
53const Fiber = struct {
54 required_align: void align(4),
55 context: Context,
56 awaiter: ?*Fiber,
57 queue_next: ?*Fiber,
58 cancel_thread: ?*Thread,
59 awaiting_completions: std.StaticBitSet(3),
60
61 const finished: ?*Fiber = @ptrFromInt(@alignOf(Thread));
62
63 const max_result_align: Alignment = .@"16";
64 const max_result_size = max_result_align.forward(64);
65 /// This includes any stack realignments that need to happen, and also the
66 /// initial frame return address slot and argument frame, depending on target.
67 const min_stack_size = 4 * 1024 * 1024;
68 const max_context_align: Alignment = .@"16";
69 const max_context_size = max_context_align.forward(1024);
70 const max_closure_size: usize = @sizeOf(AsyncClosure);
71 const max_closure_align: Alignment = .of(AsyncClosure);
72 const allocation_size = std.mem.alignForward(
73 usize,
74 max_closure_align.max(max_context_align).forward(
75 max_result_align.forward(@sizeOf(Fiber)) + max_result_size + min_stack_size,
76 ) + max_closure_size + max_context_size,
77 std.heap.page_size_max,
78 );
79
80 fn allocate(el: *EventLoop) error{OutOfMemory}!*Fiber {
81 return @ptrCast(try el.gpa.alignedAlloc(u8, .of(Fiber), allocation_size));
82 }
83
84 fn allocatedSlice(f: *Fiber) []align(@alignOf(Fiber)) u8 {
85 return @as([*]align(@alignOf(Fiber)) u8, @ptrCast(f))[0..allocation_size];
86 }
87
88 fn allocatedEnd(f: *Fiber) [*]u8 {
89 const allocated_slice = f.allocatedSlice();
90 return allocated_slice[allocated_slice.len..].ptr;
91 }
92
93 fn resultPointer(f: *Fiber, comptime Result: type) *Result {
94 return @ptrCast(@alignCast(f.resultBytes(.of(Result))));
95 }
96
97 fn resultBytes(f: *Fiber, alignment: Alignment) [*]u8 {
98 return @ptrFromInt(alignment.forward(@intFromPtr(f) + @sizeOf(Fiber)));
99 }
100
101 fn enterCancelRegion(fiber: *Fiber, thread: *Thread) error{Canceled}!void {
102 if (@cmpxchgStrong(
103 ?*Thread,
104 &fiber.cancel_thread,
105 null,
106 thread,
107 .acq_rel,
108 .acquire,
109 )) |cancel_thread| {
110 assert(cancel_thread == Thread.canceling);
111 return error.Canceled;
112 }
113 }
114
115 fn exitCancelRegion(fiber: *Fiber, thread: *Thread) void {
116 if (@cmpxchgStrong(
117 ?*Thread,
118 &fiber.cancel_thread,
119 thread,
120 null,
121 .acq_rel,
122 .acquire,
123 )) |cancel_thread| assert(cancel_thread == Thread.canceling);
124 }
125
126 const Queue = struct { head: *Fiber, tail: *Fiber };
127};
128
129fn recycle(el: *EventLoop, fiber: *Fiber) void {
130 std.log.debug("recyling {*}", .{fiber});
131 assert(fiber.queue_next == null);
132 el.gpa.free(fiber.allocatedSlice());
133}
134
135pub fn io(el: *EventLoop) Io {
136 return .{
137 .userdata = el,
138 .vtable = &.{
139 .async = async,
140 .concurrent = concurrent,
141 .await = await,
142 .select = select,
143 .cancel = cancel,
144 .cancelRequested = cancelRequested,
145
146 .mutexLock = mutexLock,
147 .mutexUnlock = mutexUnlock,
148
149 .conditionWait = conditionWait,
150 .conditionWake = conditionWake,
151
152 .createFile = createFile,
153 .fileOpen = fileOpen,
154 .fileClose = fileClose,
155 .pread = pread,
156 .pwrite = pwrite,
157
158 .now = now,
159 .sleep = sleep,
160 },
161 };
162}
163
164pub fn init(el: *EventLoop, gpa: Allocator) !void {
165 const threads_size = @max(std.Thread.getCpuCount() catch 1, 1) * @sizeOf(Thread);
166 const idle_stack_end_offset = std.mem.alignForward(usize, threads_size + idle_stack_size, std.heap.page_size_max);
167 const allocated_slice = try gpa.alignedAlloc(u8, .of(Thread), idle_stack_end_offset);
168 errdefer gpa.free(allocated_slice);
169 el.* = .{
170 .gpa = gpa,
171 .mutex = .{},
172 .main_fiber_buffer = undefined,
173 .threads = .{
174 .allocated = @ptrCast(allocated_slice[0..threads_size]),
175 .reserved = 1,
176 .active = 1,
177 },
178 };
179 const main_fiber: *Fiber = @ptrCast(&el.main_fiber_buffer);
180 main_fiber.* = .{
181 .required_align = {},
182 .context = undefined,
183 .awaiter = null,
184 .queue_next = null,
185 .cancel_thread = null,
186 .awaiting_completions = .initEmpty(),
187 };
188 const main_thread = &el.threads.allocated[0];
189 Thread.self = main_thread;
190 const idle_stack_end: [*]align(16) usize = @ptrCast(@alignCast(allocated_slice[idle_stack_end_offset..].ptr));
191 (idle_stack_end - 1)[0..1].* = .{@intFromPtr(el)};
192 main_thread.* = .{
193 .thread = undefined,
194 .idle_context = switch (builtin.cpu.arch) {
195 .aarch64 => .{
196 .sp = @intFromPtr(idle_stack_end),
197 .fp = 0,
198 .pc = @intFromPtr(&mainIdleEntry),
199 },
200 .x86_64 => .{
201 .rsp = @intFromPtr(idle_stack_end - 1),
202 .rbp = 0,
203 .rip = @intFromPtr(&mainIdleEntry),
204 },
205 else => @compileError("unimplemented architecture"),
206 },
207 .current_context = &main_fiber.context,
208 .ready_queue = null,
209 .io_uring = try IoUring.init(io_uring_entries, 0),
210 .idle_search_index = 1,
211 .steal_ready_search_index = 1,
212 };
213 errdefer main_thread.io_uring.deinit();
214 std.log.debug("created main idle {*}", .{&main_thread.idle_context});
215 std.log.debug("created main {*}", .{main_fiber});
216}
217
218pub fn deinit(el: *EventLoop) void {
219 const active_threads = @atomicLoad(u32, &el.threads.active, .acquire);
220 for (el.threads.allocated[0..active_threads]) |*thread| {
221 const ready_fiber = @atomicLoad(?*Fiber, &thread.ready_queue, .monotonic);
222 assert(ready_fiber == null or ready_fiber == Fiber.finished); // pending async
223 }
224 el.yield(null, .exit);
225 const allocated_ptr: [*]align(@alignOf(Thread)) u8 = @ptrCast(@alignCast(el.threads.allocated.ptr));
226 const idle_stack_end_offset = std.mem.alignForward(usize, el.threads.allocated.len * @sizeOf(Thread) + idle_stack_size, std.heap.page_size_max);
227 for (el.threads.allocated[1..active_threads]) |*thread| thread.thread.join();
228 el.gpa.free(allocated_ptr[0..idle_stack_end_offset]);
229 el.* = undefined;
230}
231
232fn findReadyFiber(el: *EventLoop, thread: *Thread) ?*Fiber {
233 if (@atomicRmw(?*Fiber, &thread.ready_queue, .Xchg, Fiber.finished, .acquire)) |ready_fiber| {
234 @atomicStore(?*Fiber, &thread.ready_queue, ready_fiber.queue_next, .release);
235 ready_fiber.queue_next = null;
236 return ready_fiber;
237 }
238 const active_threads = @atomicLoad(u32, &el.threads.active, .acquire);
239 for (0..@min(max_steal_ready_search, active_threads)) |_| {
240 defer thread.steal_ready_search_index += 1;
241 if (thread.steal_ready_search_index == active_threads) thread.steal_ready_search_index = 0;
242 const steal_ready_search_thread = &el.threads.allocated[0..active_threads][thread.steal_ready_search_index];
243 if (steal_ready_search_thread == thread) continue;
244 const ready_fiber = @atomicLoad(?*Fiber, &steal_ready_search_thread.ready_queue, .acquire) orelse continue;
245 if (ready_fiber == Fiber.finished) continue;
246 if (@cmpxchgWeak(
247 ?*Fiber,
248 &steal_ready_search_thread.ready_queue,
249 ready_fiber,
250 null,
251 .acquire,
252 .monotonic,
253 )) |_| continue;
254 @atomicStore(?*Fiber, &thread.ready_queue, ready_fiber.queue_next, .release);
255 ready_fiber.queue_next = null;
256 return ready_fiber;
257 }
258 // couldn't find anything to do, so we are now open for business
259 @atomicStore(?*Fiber, &thread.ready_queue, null, .monotonic);
260 return null;
261}
262
263fn yield(el: *EventLoop, maybe_ready_fiber: ?*Fiber, pending_task: SwitchMessage.PendingTask) void {
264 const thread: *Thread = .current();
265 const ready_context = if (maybe_ready_fiber orelse el.findReadyFiber(thread)) |ready_fiber|
266 &ready_fiber.context
267 else
268 &thread.idle_context;
269 const message: SwitchMessage = .{
270 .contexts = .{
271 .prev = thread.current_context,
272 .ready = ready_context,
273 },
274 .pending_task = pending_task,
275 };
276 std.log.debug("switching from {*} to {*}", .{ message.contexts.prev, message.contexts.ready });
277 contextSwitch(&message).handle(el);
278}
279
280fn schedule(el: *EventLoop, thread: *Thread, ready_queue: Fiber.Queue) void {
281 {
282 var fiber = ready_queue.head;
283 while (true) {
284 std.log.debug("scheduling {*}", .{fiber});
285 fiber = fiber.queue_next orelse break;
286 }
287 assert(fiber == ready_queue.tail);
288 }
289 // shared fields of previous `Thread` must be initialized before later ones are marked as active
290 const new_thread_index = @atomicLoad(u32, &el.threads.active, .acquire);
291 for (0..@min(max_idle_search, new_thread_index)) |_| {
292 defer thread.idle_search_index += 1;
293 if (thread.idle_search_index == new_thread_index) thread.idle_search_index = 0;
294 const idle_search_thread = &el.threads.allocated[0..new_thread_index][thread.idle_search_index];
295 if (idle_search_thread == thread) continue;
296 if (@cmpxchgWeak(
297 ?*Fiber,
298 &idle_search_thread.ready_queue,
299 null,
300 ready_queue.head,
301 .release,
302 .monotonic,
303 )) |_| continue;
304 getSqe(&thread.io_uring).* = .{
305 .opcode = .MSG_RING,
306 .flags = std.os.linux.IOSQE_CQE_SKIP_SUCCESS,
307 .ioprio = 0,
308 .fd = idle_search_thread.io_uring.fd,
309 .off = @intFromEnum(Completion.UserData.wakeup),
310 .addr = 0,
311 .len = 0,
312 .rw_flags = 0,
313 .user_data = @intFromEnum(Completion.UserData.wakeup),
314 .buf_index = 0,
315 .personality = 0,
316 .splice_fd_in = 0,
317 .addr3 = 0,
318 .resv = 0,
319 };
320 return;
321 }
322 spawn_thread: {
323 // previous failed reservations must have completed before retrying
324 if (new_thread_index == el.threads.allocated.len or @cmpxchgWeak(
325 u32,
326 &el.threads.reserved,
327 new_thread_index,
328 new_thread_index + 1,
329 .acquire,
330 .monotonic,
331 ) != null) break :spawn_thread;
332 const new_thread = &el.threads.allocated[new_thread_index];
333 const next_thread_index = new_thread_index + 1;
334 new_thread.* = .{
335 .thread = undefined,
336 .idle_context = undefined,
337 .current_context = &new_thread.idle_context,
338 .ready_queue = ready_queue.head,
339 .io_uring = IoUring.init(io_uring_entries, 0) catch |err| {
340 @atomicStore(u32, &el.threads.reserved, new_thread_index, .release);
341 // no more access to `thread` after giving up reservation
342 std.log.warn("unable to create worker thread due to io_uring init failure: {s}", .{@errorName(err)});
343 break :spawn_thread;
344 },
345 .idle_search_index = 0,
346 .steal_ready_search_index = 0,
347 };
348 new_thread.thread = std.Thread.spawn(.{
349 .stack_size = idle_stack_size,
350 .allocator = el.gpa,
351 }, threadEntry, .{ el, new_thread_index }) catch |err| {
352 new_thread.io_uring.deinit();
353 @atomicStore(u32, &el.threads.reserved, new_thread_index, .release);
354 // no more access to `thread` after giving up reservation
355 std.log.warn("unable to create worker thread due spawn failure: {s}", .{@errorName(err)});
356 break :spawn_thread;
357 };
358 // shared fields of `Thread` must be initialized before being marked active
359 @atomicStore(u32, &el.threads.active, next_thread_index, .release);
360 return;
361 }
362 // nobody wanted it, so just queue it on ourselves
363 while (@cmpxchgWeak(
364 ?*Fiber,
365 &thread.ready_queue,
366 ready_queue.tail.queue_next,
367 ready_queue.head,
368 .acq_rel,
369 .acquire,
370 )) |old_head| ready_queue.tail.queue_next = old_head;
371}
372
373fn mainIdle(el: *EventLoop, message: *const SwitchMessage) callconv(.withStackAlign(.c, @max(@alignOf(Thread), @alignOf(Context)))) noreturn {
374 message.handle(el);
375 el.idle(&el.threads.allocated[0]);
376 el.yield(@ptrCast(&el.main_fiber_buffer), .nothing);
377 unreachable; // switched to dead fiber
378}
379
380fn threadEntry(el: *EventLoop, index: u32) void {
381 const thread: *Thread = &el.threads.allocated[index];
382 Thread.self = thread;
383 std.log.debug("created thread idle {*}", .{&thread.idle_context});
384 el.idle(thread);
385}
386
387const Completion = struct {
388 const UserData = enum(usize) {
389 unused,
390 wakeup,
391 cleanup,
392 exit,
393 /// *Fiber
394 _,
395 };
396 result: i32,
397 flags: u32,
398};
399
400fn idle(el: *EventLoop, thread: *Thread) void {
401 var maybe_ready_fiber: ?*Fiber = null;
402 while (true) {
403 while (maybe_ready_fiber orelse el.findReadyFiber(thread)) |ready_fiber| {
404 el.yield(ready_fiber, .nothing);
405 maybe_ready_fiber = null;
406 }
407 _ = thread.io_uring.submit_and_wait(1) catch |err| switch (err) {
408 error.SignalInterrupt => std.log.warn("submit_and_wait failed with SignalInterrupt", .{}),
409 else => |e| @panic(@errorName(e)),
410 };
411 var cqes_buffer: [io_uring_entries]std.os.linux.io_uring_cqe = undefined;
412 var maybe_ready_queue: ?Fiber.Queue = null;
413 for (cqes_buffer[0 .. thread.io_uring.copy_cqes(&cqes_buffer, 0) catch |err| switch (err) {
414 error.SignalInterrupt => cqes_len: {
415 std.log.warn("copy_cqes failed with SignalInterrupt", .{});
416 break :cqes_len 0;
417 },
418 else => |e| @panic(@errorName(e)),
419 }]) |cqe| switch (@as(Completion.UserData, @enumFromInt(cqe.user_data))) {
420 .unused => unreachable, // bad submission queued?
421 .wakeup => {},
422 .cleanup => @panic("failed to notify other threads that we are exiting"),
423 .exit => {
424 assert(maybe_ready_fiber == null and maybe_ready_queue == null); // pending async
425 return;
426 },
427 _ => switch (errno(cqe.res)) {
428 .INTR => getSqe(&thread.io_uring).* = .{
429 .opcode = .ASYNC_CANCEL,
430 .flags = std.os.linux.IOSQE_CQE_SKIP_SUCCESS,
431 .ioprio = 0,
432 .fd = 0,
433 .off = 0,
434 .addr = cqe.user_data,
435 .len = 0,
436 .rw_flags = 0,
437 .user_data = @intFromEnum(Completion.UserData.wakeup),
438 .buf_index = 0,
439 .personality = 0,
440 .splice_fd_in = 0,
441 .addr3 = 0,
442 .resv = 0,
443 },
444 else => {
445 const fiber: *Fiber = @ptrFromInt(cqe.user_data);
446 assert(fiber.queue_next == null);
447 fiber.resultPointer(Completion).* = .{
448 .result = cqe.res,
449 .flags = cqe.flags,
450 };
451 if (maybe_ready_fiber == null) maybe_ready_fiber = fiber else if (maybe_ready_queue) |*ready_queue| {
452 ready_queue.tail.queue_next = fiber;
453 ready_queue.tail = fiber;
454 } else maybe_ready_queue = .{ .head = fiber, .tail = fiber };
455 },
456 },
457 };
458 if (maybe_ready_queue) |ready_queue| el.schedule(thread, ready_queue);
459 }
460}
461
462const SwitchMessage = struct {
463 contexts: extern struct {
464 prev: *Context,
465 ready: *Context,
466 },
467 pending_task: PendingTask,
468
469 const PendingTask = union(enum) {
470 nothing,
471 reschedule,
472 recycle: *Fiber,
473 register_awaiter: *?*Fiber,
474 register_select: []const *Io.AnyFuture,
475 mutex_lock: struct {
476 prev_state: Io.Mutex.State,
477 mutex: *Io.Mutex,
478 },
479 condition_wait: struct {
480 cond: *Io.Condition,
481 mutex: *Io.Mutex,
482 },
483 exit,
484 };
485
486 fn handle(message: *const SwitchMessage, el: *EventLoop) void {
487 const thread: *Thread = .current();
488 thread.current_context = message.contexts.ready;
489 switch (message.pending_task) {
490 .nothing => {},
491 .reschedule => if (message.contexts.prev != &thread.idle_context) {
492 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
493 assert(prev_fiber.queue_next == null);
494 el.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });
495 },
496 .recycle => |fiber| {
497 el.recycle(fiber);
498 },
499 .register_awaiter => |awaiter| {
500 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
501 assert(prev_fiber.queue_next == null);
502 if (@atomicRmw(?*Fiber, awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished)
503 el.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });
504 },
505 .register_select => |futures| {
506 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
507 assert(prev_fiber.queue_next == null);
508 for (futures) |any_future| {
509 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
510 if (@atomicRmw(?*Fiber, &future_fiber.awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished) {
511 const closure: *AsyncClosure = .fromFiber(future_fiber);
512 if (!@atomicRmw(bool, &closure.already_awaited, .Xchg, true, .seq_cst)) {
513 el.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });
514 }
515 }
516 }
517 },
518 .mutex_lock => |mutex_lock| {
519 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
520 assert(prev_fiber.queue_next == null);
521 var prev_state = mutex_lock.prev_state;
522 while (switch (prev_state) {
523 else => next_state: {
524 prev_fiber.queue_next = @ptrFromInt(@intFromEnum(prev_state));
525 break :next_state @cmpxchgWeak(
526 Io.Mutex.State,
527 &mutex_lock.mutex.state,
528 prev_state,
529 @enumFromInt(@intFromPtr(prev_fiber)),
530 .release,
531 .acquire,
532 );
533 },
534 .unlocked => @cmpxchgWeak(
535 Io.Mutex.State,
536 &mutex_lock.mutex.state,
537 .unlocked,
538 .locked_once,
539 .acquire,
540 .acquire,
541 ) orelse {
542 prev_fiber.queue_next = null;
543 el.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });
544 return;
545 },
546 }) |next_state| prev_state = next_state;
547 },
548 .condition_wait => |condition_wait| {
549 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
550 assert(prev_fiber.queue_next == null);
551 const cond_impl = prev_fiber.resultPointer(ConditionImpl);
552 cond_impl.* = .{
553 .tail = prev_fiber,
554 .event = .queued,
555 };
556 if (@cmpxchgStrong(
557 ?*Fiber,
558 @as(*?*Fiber, @ptrCast(&condition_wait.cond.state)),
559 null,
560 prev_fiber,
561 .release,
562 .acquire,
563 )) |waiting_fiber| {
564 const waiting_cond_impl = waiting_fiber.?.resultPointer(ConditionImpl);
565 assert(waiting_cond_impl.tail.queue_next == null);
566 waiting_cond_impl.tail.queue_next = prev_fiber;
567 waiting_cond_impl.tail = prev_fiber;
568 }
569 condition_wait.mutex.unlock(el.io());
570 },
571 .exit => for (el.threads.allocated[0..@atomicLoad(u32, &el.threads.active, .acquire)]) |*each_thread| {
572 getSqe(&thread.io_uring).* = .{
573 .opcode = .MSG_RING,
574 .flags = std.os.linux.IOSQE_CQE_SKIP_SUCCESS,
575 .ioprio = 0,
576 .fd = each_thread.io_uring.fd,
577 .off = @intFromEnum(Completion.UserData.exit),
578 .addr = 0,
579 .len = 0,
580 .rw_flags = 0,
581 .user_data = @intFromEnum(Completion.UserData.cleanup),
582 .buf_index = 0,
583 .personality = 0,
584 .splice_fd_in = 0,
585 .addr3 = 0,
586 .resv = 0,
587 };
588 },
589 }
590 }
591};
592
593const Context = switch (builtin.cpu.arch) {
594 .aarch64 => extern struct {
595 sp: u64,
596 fp: u64,
597 pc: u64,
598 },
599 .x86_64 => extern struct {
600 rsp: u64,
601 rbp: u64,
602 rip: u64,
603 },
604 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
605};
606
607inline fn contextSwitch(message: *const SwitchMessage) *const SwitchMessage {
608 return @fieldParentPtr("contexts", switch (builtin.cpu.arch) {
609 .aarch64 => asm volatile (
610 \\ ldp x0, x2, [x1]
611 \\ ldr x3, [x2, #16]
612 \\ mov x4, sp
613 \\ stp x4, fp, [x0]
614 \\ adr x5, 0f
615 \\ ldp x4, fp, [x2]
616 \\ str x5, [x0, #16]
617 \\ mov sp, x4
618 \\ br x3
619 \\0:
620 : [received_message] "={x1}" (-> *const @FieldType(SwitchMessage, "contexts")),
621 : [message_to_send] "{x1}" (&message.contexts),
622 : .{
623 .x0 = true,
624 .x1 = true,
625 .x2 = true,
626 .x3 = true,
627 .x4 = true,
628 .x5 = true,
629 .x6 = true,
630 .x7 = true,
631 .x8 = true,
632 .x9 = true,
633 .x10 = true,
634 .x11 = true,
635 .x12 = true,
636 .x13 = true,
637 .x14 = true,
638 .x15 = true,
639 .x16 = true,
640 .x17 = true,
641 .x18 = true,
642 .x19 = true,
643 .x20 = true,
644 .x21 = true,
645 .x22 = true,
646 .x23 = true,
647 .x24 = true,
648 .x25 = true,
649 .x26 = true,
650 .x27 = true,
651 .x28 = true,
652 .x30 = true,
653 .z0 = true,
654 .z1 = true,
655 .z2 = true,
656 .z3 = true,
657 .z4 = true,
658 .z5 = true,
659 .z6 = true,
660 .z7 = true,
661 .z8 = true,
662 .z9 = true,
663 .z10 = true,
664 .z11 = true,
665 .z12 = true,
666 .z13 = true,
667 .z14 = true,
668 .z15 = true,
669 .z16 = true,
670 .z17 = true,
671 .z18 = true,
672 .z19 = true,
673 .z20 = true,
674 .z21 = true,
675 .z22 = true,
676 .z23 = true,
677 .z24 = true,
678 .z25 = true,
679 .z26 = true,
680 .z27 = true,
681 .z28 = true,
682 .z29 = true,
683 .z30 = true,
684 .z31 = true,
685 .p0 = true,
686 .p1 = true,
687 .p2 = true,
688 .p3 = true,
689 .p4 = true,
690 .p5 = true,
691 .p6 = true,
692 .p7 = true,
693 .p8 = true,
694 .p9 = true,
695 .p10 = true,
696 .p11 = true,
697 .p12 = true,
698 .p13 = true,
699 .p14 = true,
700 .p15 = true,
701 .fpcr = true,
702 .fpsr = true,
703 .ffr = true,
704 .memory = true,
705 }),
706 .x86_64 => asm volatile (
707 \\ movq 0(%%rsi), %%rax
708 \\ movq 8(%%rsi), %%rcx
709 \\ leaq 0f(%%rip), %%rdx
710 \\ movq %%rsp, 0(%%rax)
711 \\ movq %%rbp, 8(%%rax)
712 \\ movq %%rdx, 16(%%rax)
713 \\ movq 0(%%rcx), %%rsp
714 \\ movq 8(%%rcx), %%rbp
715 \\ jmpq *16(%%rcx)
716 \\0:
717 : [received_message] "={rsi}" (-> *const @FieldType(SwitchMessage, "contexts")),
718 : [message_to_send] "{rsi}" (&message.contexts),
719 : .{
720 .rax = true,
721 .rcx = true,
722 .rdx = true,
723 .rbx = true,
724 .rsi = true,
725 .rdi = true,
726 .r8 = true,
727 .r9 = true,
728 .r10 = true,
729 .r11 = true,
730 .r12 = true,
731 .r13 = true,
732 .r14 = true,
733 .r15 = true,
734 .mm0 = true,
735 .mm1 = true,
736 .mm2 = true,
737 .mm3 = true,
738 .mm4 = true,
739 .mm5 = true,
740 .mm6 = true,
741 .mm7 = true,
742 .zmm0 = true,
743 .zmm1 = true,
744 .zmm2 = true,
745 .zmm3 = true,
746 .zmm4 = true,
747 .zmm5 = true,
748 .zmm6 = true,
749 .zmm7 = true,
750 .zmm8 = true,
751 .zmm9 = true,
752 .zmm10 = true,
753 .zmm11 = true,
754 .zmm12 = true,
755 .zmm13 = true,
756 .zmm14 = true,
757 .zmm15 = true,
758 .zmm16 = true,
759 .zmm17 = true,
760 .zmm18 = true,
761 .zmm19 = true,
762 .zmm20 = true,
763 .zmm21 = true,
764 .zmm22 = true,
765 .zmm23 = true,
766 .zmm24 = true,
767 .zmm25 = true,
768 .zmm26 = true,
769 .zmm27 = true,
770 .zmm28 = true,
771 .zmm29 = true,
772 .zmm30 = true,
773 .zmm31 = true,
774 .fpsr = true,
775 .fpcr = true,
776 .mxcsr = true,
777 .rflags = true,
778 .dirflag = true,
779 .memory = true,
780 }),
781 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
782 });
783}
784
785fn mainIdleEntry() callconv(.naked) void {
786 switch (builtin.cpu.arch) {
787 .x86_64 => asm volatile (
788 \\ movq (%%rsp), %%rdi
789 \\ jmp %[mainIdle:P]
790 :
791 : [mainIdle] "X" (&mainIdle),
792 ),
793 .aarch64 => asm volatile (
794 \\ ldr x0, [sp, #-8]
795 \\ b %[mainIdle]
796 :
797 : [mainIdle] "X" (&mainIdle),
798 ),
799 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
800 }
801}
802
803fn fiberEntry() callconv(.naked) void {
804 switch (builtin.cpu.arch) {
805 .x86_64 => asm volatile (
806 \\ leaq 8(%%rsp), %%rdi
807 \\ jmp %[AsyncClosure_call:P]
808 :
809 : [AsyncClosure_call] "X" (&AsyncClosure.call),
810 ),
811 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
812 }
813}
814
815const AsyncClosure = struct {
816 event_loop: *EventLoop,
817 fiber: *Fiber,
818 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
819 result_align: Alignment,
820 already_awaited: bool,
821
822 fn contextPointer(closure: *AsyncClosure) [*]align(Fiber.max_context_align.toByteUnits()) u8 {
823 return @alignCast(@as([*]u8, @ptrCast(closure)) + @sizeOf(AsyncClosure));
824 }
825
826 fn call(closure: *AsyncClosure, message: *const SwitchMessage) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn {
827 message.handle(closure.event_loop);
828 const fiber = closure.fiber;
829 std.log.debug("{*} performing async", .{fiber});
830 closure.start(closure.contextPointer(), fiber.resultBytes(closure.result_align));
831 const awaiter = @atomicRmw(?*Fiber, &fiber.awaiter, .Xchg, Fiber.finished, .acq_rel);
832 const ready_awaiter = r: {
833 const a = awaiter orelse break :r null;
834 if (@atomicRmw(bool, &closure.already_awaited, .Xchg, true, .acq_rel)) break :r null;
835 break :r a;
836 };
837 closure.event_loop.yield(ready_awaiter, .nothing);
838 unreachable; // switched to dead fiber
839 }
840
841 fn fromFiber(fiber: *Fiber) *AsyncClosure {
842 return @ptrFromInt(Fiber.max_context_align.max(.of(AsyncClosure)).backward(
843 @intFromPtr(fiber.allocatedEnd()) - Fiber.max_context_size,
844 ) - @sizeOf(AsyncClosure));
845 }
846};
847
848fn async(
849 userdata: ?*anyopaque,
850 result: []u8,
851 result_alignment: Alignment,
852 context: []const u8,
853 context_alignment: Alignment,
854 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
855) ?*std.Io.AnyFuture {
856 return concurrent(userdata, result.len, result_alignment, context, context_alignment, start) catch {
857 start(context.ptr, result.ptr);
858 return null;
859 };
860}
861
862fn concurrent(
863 userdata: ?*anyopaque,
864 result_len: usize,
865 result_alignment: Alignment,
866 context: []const u8,
867 context_alignment: Alignment,
868 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
869) Io.ConcurrentError!*std.Io.AnyFuture {
870 assert(result_alignment.compare(.lte, Fiber.max_result_align)); // TODO
871 assert(context_alignment.compare(.lte, Fiber.max_context_align)); // TODO
872 assert(result_len <= Fiber.max_result_size); // TODO
873 assert(context.len <= Fiber.max_context_size); // TODO
874
875 const event_loop: *EventLoop = @ptrCast(@alignCast(userdata));
876 const fiber = try Fiber.allocate(event_loop);
877 std.log.debug("allocated {*}", .{fiber});
878
879 const closure: *AsyncClosure = .fromFiber(fiber);
880 fiber.* = .{
881 .required_align = {},
882 .context = switch (builtin.cpu.arch) {
883 .x86_64 => .{
884 .rsp = @intFromPtr(closure) - @sizeOf(usize),
885 .rbp = 0,
886 .rip = @intFromPtr(&fiberEntry),
887 },
888 .aarch64 => .{
889 .sp = @intFromPtr(closure),
890 .fp = 0,
891 .pc = @intFromPtr(&fiberEntry),
892 },
893 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
894 },
895 .awaiter = null,
896 .queue_next = null,
897 .cancel_thread = null,
898 .awaiting_completions = .initEmpty(),
899 };
900 closure.* = .{
901 .event_loop = event_loop,
902 .fiber = fiber,
903 .start = start,
904 .result_align = result_alignment,
905 .already_awaited = false,
906 };
907 @memcpy(closure.contextPointer(), context);
908
909 event_loop.schedule(.current(), .{ .head = fiber, .tail = fiber });
910 return @ptrCast(fiber);
911}
912
913fn await(
914 userdata: ?*anyopaque,
915 any_future: *std.Io.AnyFuture,
916 result: []u8,
917 result_alignment: Alignment,
918) void {
919 const event_loop: *EventLoop = @ptrCast(@alignCast(userdata));
920 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
921 if (@atomicLoad(?*Fiber, &future_fiber.awaiter, .acquire) != Fiber.finished)
922 event_loop.yield(null, .{ .register_awaiter = &future_fiber.awaiter });
923 @memcpy(result, future_fiber.resultBytes(result_alignment));
924 event_loop.recycle(future_fiber);
925}
926
927fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
928 const el: *EventLoop = @ptrCast(@alignCast(userdata));
929
930 // Optimization to avoid the yield below.
931 for (futures, 0..) |any_future, i| {
932 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
933 if (@atomicLoad(?*Fiber, &future_fiber.awaiter, .acquire) == Fiber.finished)
934 return i;
935 }
936
937 el.yield(null, .{ .register_select = futures });
938
939 std.log.debug("back from select yield", .{});
940
941 const my_thread: *Thread = .current();
942 const my_fiber = my_thread.currentFiber();
943 var result: ?usize = null;
944
945 for (futures, 0..) |any_future, i| {
946 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
947 if (@cmpxchgStrong(?*Fiber, &future_fiber.awaiter, my_fiber, null, .seq_cst, .seq_cst)) |awaiter| {
948 if (awaiter == Fiber.finished) {
949 if (result == null) result = i;
950 } else if (awaiter) |a| {
951 const closure: *AsyncClosure = .fromFiber(a);
952 closure.already_awaited = false;
953 }
954 } else {
955 const closure: *AsyncClosure = .fromFiber(my_fiber);
956 closure.already_awaited = false;
957 }
958 }
959
960 return result.?;
961}
962
963fn cancel(
964 userdata: ?*anyopaque,
965 any_future: *std.Io.AnyFuture,
966 result: []u8,
967 result_alignment: Alignment,
968) void {
969 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
970 if (@atomicRmw(
971 ?*Thread,
972 &future_fiber.cancel_thread,
973 .Xchg,
974 Thread.canceling,
975 .acq_rel,
976 )) |cancel_thread| if (cancel_thread != Thread.canceling) {
977 getSqe(&Thread.current().io_uring).* = .{
978 .opcode = .MSG_RING,
979 .flags = std.os.linux.IOSQE_CQE_SKIP_SUCCESS,
980 .ioprio = 0,
981 .fd = cancel_thread.io_uring.fd,
982 .off = @intFromPtr(future_fiber),
983 .addr = 0,
984 .len = @bitCast(-@as(i32, @intFromEnum(std.os.linux.E.INTR))),
985 .rw_flags = 0,
986 .user_data = @intFromEnum(Completion.UserData.cleanup),
987 .buf_index = 0,
988 .personality = 0,
989 .splice_fd_in = 0,
990 .addr3 = 0,
991 .resv = 0,
992 };
993 };
994 await(userdata, any_future, result, result_alignment);
995}
996
997fn cancelRequested(userdata: ?*anyopaque) bool {
998 _ = userdata;
999 return @atomicLoad(?*Thread, &Thread.current().currentFiber().cancel_thread, .acquire) == Thread.canceling;
1000}
1001
1002fn createFile(
1003 userdata: ?*anyopaque,
1004 dir: Io.Dir,
1005 sub_path: []const u8,
1006 flags: Io.File.CreateFlags,
1007) Io.File.OpenError!Io.File {
1008 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1009 const thread: *Thread = .current();
1010 const iou = &thread.io_uring;
1011 const fiber = thread.currentFiber();
1012 try fiber.enterCancelRegion(thread);
1013
1014 const posix = std.posix;
1015 const sub_path_c = try posix.toPosixPath(sub_path);
1016
1017 var os_flags: posix.O = .{
1018 .ACCMODE = if (flags.read) .RDWR else .WRONLY,
1019 .CREAT = true,
1020 .TRUNC = flags.truncate,
1021 .EXCL = flags.exclusive,
1022 };
1023 if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;
1024 if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;
1025
1026 // Use the O locking flags if the os supports them to acquire the lock
1027 // atomically. Note that the NONBLOCK flag is removed after the openat()
1028 // call is successful.
1029 const has_flock_open_flags = @hasField(posix.O, "EXLOCK");
1030 if (has_flock_open_flags) switch (flags.lock) {
1031 .none => {},
1032 .shared => {
1033 os_flags.SHLOCK = true;
1034 os_flags.NONBLOCK = flags.lock_nonblocking;
1035 },
1036 .exclusive => {
1037 os_flags.EXLOCK = true;
1038 os_flags.NONBLOCK = flags.lock_nonblocking;
1039 },
1040 };
1041 const have_flock = @TypeOf(posix.system.flock) != void;
1042
1043 if (have_flock and !has_flock_open_flags and flags.lock != .none) {
1044 @panic("TODO");
1045 }
1046
1047 if (has_flock_open_flags and flags.lock_nonblocking) {
1048 @panic("TODO");
1049 }
1050
1051 getSqe(iou).* = .{
1052 .opcode = .OPENAT,
1053 .flags = 0,
1054 .ioprio = 0,
1055 .fd = dir.handle,
1056 .off = 0,
1057 .addr = @intFromPtr(&sub_path_c),
1058 .len = @intCast(flags.mode),
1059 .rw_flags = @bitCast(os_flags),
1060 .user_data = @intFromPtr(fiber),
1061 .buf_index = 0,
1062 .personality = 0,
1063 .splice_fd_in = 0,
1064 .addr3 = 0,
1065 .resv = 0,
1066 };
1067
1068 el.yield(null, .nothing);
1069 fiber.exitCancelRegion(thread);
1070
1071 const completion = fiber.resultPointer(Completion);
1072 switch (errno(completion.result)) {
1073 .SUCCESS => return .{ .handle = completion.result },
1074 .INTR => unreachable,
1075 .CANCELED => return error.Canceled,
1076
1077 .FAULT => unreachable,
1078 .INVAL => return error.BadPathName,
1079 .BADF => unreachable,
1080 .ACCES => return error.AccessDenied,
1081 .FBIG => return error.FileTooBig,
1082 .OVERFLOW => return error.FileTooBig,
1083 .ISDIR => return error.IsDir,
1084 .LOOP => return error.SymLinkLoop,
1085 .MFILE => return error.ProcessFdQuotaExceeded,
1086 .NAMETOOLONG => return error.NameTooLong,
1087 .NFILE => return error.SystemFdQuotaExceeded,
1088 .NODEV => return error.NoDevice,
1089 .NOENT => return error.FileNotFound,
1090 .NOMEM => return error.SystemResources,
1091 .NOSPC => return error.NoSpaceLeft,
1092 .NOTDIR => return error.NotDir,
1093 .PERM => return error.PermissionDenied,
1094 .EXIST => return error.PathAlreadyExists,
1095 .BUSY => return error.DeviceBusy,
1096 .OPNOTSUPP => return error.FileLocksNotSupported,
1097 .AGAIN => return error.WouldBlock,
1098 .TXTBSY => return error.FileBusy,
1099 .NXIO => return error.NoDevice,
1100 else => |err| return posix.unexpectedErrno(err),
1101 }
1102}
1103
1104fn fileOpen(
1105 userdata: ?*anyopaque,
1106 dir: Io.Dir,
1107 sub_path: []const u8,
1108 flags: Io.File.OpenFlags,
1109) Io.File.OpenError!Io.File {
1110 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1111 const thread: *Thread = .current();
1112 const iou = &thread.io_uring;
1113 const fiber = thread.currentFiber();
1114 try fiber.enterCancelRegion(thread);
1115
1116 const posix = std.posix;
1117 const sub_path_c = try posix.toPosixPath(sub_path);
1118
1119 var os_flags: posix.O = .{
1120 .ACCMODE = switch (flags.mode) {
1121 .read_only => .RDONLY,
1122 .write_only => .WRONLY,
1123 .read_write => .RDWR,
1124 },
1125 };
1126
1127 if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;
1128 if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;
1129 if (@hasField(posix.O, "NOCTTY")) os_flags.NOCTTY = !flags.allow_ctty;
1130
1131 // Use the O locking flags if the os supports them to acquire the lock
1132 // atomically.
1133 const has_flock_open_flags = @hasField(posix.O, "EXLOCK");
1134 if (has_flock_open_flags) {
1135 // Note that the NONBLOCK flag is removed after the openat() call
1136 // is successful.
1137 switch (flags.lock) {
1138 .none => {},
1139 .shared => {
1140 os_flags.SHLOCK = true;
1141 os_flags.NONBLOCK = flags.lock_nonblocking;
1142 },
1143 .exclusive => {
1144 os_flags.EXLOCK = true;
1145 os_flags.NONBLOCK = flags.lock_nonblocking;
1146 },
1147 }
1148 }
1149 const have_flock = @TypeOf(posix.system.flock) != void;
1150
1151 if (have_flock and !has_flock_open_flags and flags.lock != .none) {
1152 @panic("TODO");
1153 }
1154
1155 if (has_flock_open_flags and flags.lock_nonblocking) {
1156 @panic("TODO");
1157 }
1158
1159 getSqe(iou).* = .{
1160 .opcode = .OPENAT,
1161 .flags = 0,
1162 .ioprio = 0,
1163 .fd = dir.handle,
1164 .off = 0,
1165 .addr = @intFromPtr(&sub_path_c),
1166 .len = 0,
1167 .rw_flags = @bitCast(os_flags),
1168 .user_data = @intFromPtr(fiber),
1169 .buf_index = 0,
1170 .personality = 0,
1171 .splice_fd_in = 0,
1172 .addr3 = 0,
1173 .resv = 0,
1174 };
1175
1176 el.yield(null, .nothing);
1177 fiber.exitCancelRegion(thread);
1178
1179 const completion = fiber.resultPointer(Completion);
1180 switch (errno(completion.result)) {
1181 .SUCCESS => return .{ .handle = completion.result },
1182 .INTR => unreachable,
1183 .CANCELED => return error.Canceled,
1184
1185 .FAULT => unreachable,
1186 .INVAL => return error.BadPathName,
1187 .BADF => unreachable,
1188 .ACCES => return error.AccessDenied,
1189 .FBIG => return error.FileTooBig,
1190 .OVERFLOW => return error.FileTooBig,
1191 .ISDIR => return error.IsDir,
1192 .LOOP => return error.SymLinkLoop,
1193 .MFILE => return error.ProcessFdQuotaExceeded,
1194 .NAMETOOLONG => return error.NameTooLong,
1195 .NFILE => return error.SystemFdQuotaExceeded,
1196 .NODEV => return error.NoDevice,
1197 .NOENT => return error.FileNotFound,
1198 .NOMEM => return error.SystemResources,
1199 .NOSPC => return error.NoSpaceLeft,
1200 .NOTDIR => return error.NotDir,
1201 .PERM => return error.PermissionDenied,
1202 .EXIST => return error.PathAlreadyExists,
1203 .BUSY => return error.DeviceBusy,
1204 .OPNOTSUPP => return error.FileLocksNotSupported,
1205 .AGAIN => return error.WouldBlock,
1206 .TXTBSY => return error.FileBusy,
1207 .NXIO => return error.NoDevice,
1208 else => |err| return posix.unexpectedErrno(err),
1209 }
1210}
1211
1212fn fileClose(userdata: ?*anyopaque, file: Io.File) void {
1213 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1214 const thread: *Thread = .current();
1215 const iou = &thread.io_uring;
1216 const fiber = thread.currentFiber();
1217
1218 getSqe(iou).* = .{
1219 .opcode = .CLOSE,
1220 .flags = 0,
1221 .ioprio = 0,
1222 .fd = file.handle,
1223 .off = 0,
1224 .addr = 0,
1225 .len = 0,
1226 .rw_flags = 0,
1227 .user_data = @intFromPtr(fiber),
1228 .buf_index = 0,
1229 .personality = 0,
1230 .splice_fd_in = 0,
1231 .addr3 = 0,
1232 .resv = 0,
1233 };
1234
1235 el.yield(null, .nothing);
1236
1237 const completion = fiber.resultPointer(Completion);
1238 switch (errno(completion.result)) {
1239 .SUCCESS => return,
1240 .INTR => unreachable,
1241 .CANCELED => return,
1242
1243 .BADF => unreachable, // Always a race condition.
1244 else => return,
1245 }
1246}
1247
1248fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: std.posix.off_t) Io.File.PReadError!usize {
1249 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1250 const thread: *Thread = .current();
1251 const iou = &thread.io_uring;
1252 const fiber = thread.currentFiber();
1253 try fiber.enterCancelRegion(thread);
1254
1255 getSqe(iou).* = .{
1256 .opcode = .READ,
1257 .flags = 0,
1258 .ioprio = 0,
1259 .fd = file.handle,
1260 .off = @bitCast(offset),
1261 .addr = @intFromPtr(buffer.ptr),
1262 .len = @min(buffer.len, 0x7ffff000),
1263 .rw_flags = 0,
1264 .user_data = @intFromPtr(fiber),
1265 .buf_index = 0,
1266 .personality = 0,
1267 .splice_fd_in = 0,
1268 .addr3 = 0,
1269 .resv = 0,
1270 };
1271
1272 el.yield(null, .nothing);
1273 fiber.exitCancelRegion(thread);
1274
1275 const completion = fiber.resultPointer(Completion);
1276 switch (errno(completion.result)) {
1277 .SUCCESS => return @as(u32, @bitCast(completion.result)),
1278 .INTR => unreachable,
1279 .CANCELED => return error.Canceled,
1280
1281 .INVAL => unreachable,
1282 .FAULT => unreachable,
1283 .NOENT => return error.ProcessNotFound,
1284 .AGAIN => return error.WouldBlock,
1285 .BADF => return error.NotOpenForReading, // Can be a race condition.
1286 .IO => return error.InputOutput,
1287 .ISDIR => return error.IsDir,
1288 .NOBUFS => return error.SystemResources,
1289 .NOMEM => return error.SystemResources,
1290 .NOTCONN => return error.SocketUnconnected,
1291 .CONNRESET => return error.ConnectionResetByPeer,
1292 .TIMEDOUT => return error.Timeout,
1293 .NXIO => return error.Unseekable,
1294 .SPIPE => return error.Unseekable,
1295 .OVERFLOW => return error.Unseekable,
1296 else => |err| return std.posix.unexpectedErrno(err),
1297 }
1298}
1299
1300fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: std.posix.off_t) Io.File.PWriteError!usize {
1301 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1302 const thread: *Thread = .current();
1303 const iou = &thread.io_uring;
1304 const fiber = thread.currentFiber();
1305 try fiber.enterCancelRegion(thread);
1306
1307 getSqe(iou).* = .{
1308 .opcode = .WRITE,
1309 .flags = 0,
1310 .ioprio = 0,
1311 .fd = file.handle,
1312 .off = @bitCast(offset),
1313 .addr = @intFromPtr(buffer.ptr),
1314 .len = @min(buffer.len, 0x7ffff000),
1315 .rw_flags = 0,
1316 .user_data = @intFromPtr(fiber),
1317 .buf_index = 0,
1318 .personality = 0,
1319 .splice_fd_in = 0,
1320 .addr3 = 0,
1321 .resv = 0,
1322 };
1323
1324 el.yield(null, .nothing);
1325 fiber.exitCancelRegion(thread);
1326
1327 const completion = fiber.resultPointer(Completion);
1328 switch (errno(completion.result)) {
1329 .SUCCESS => return @as(u32, @bitCast(completion.result)),
1330 .INTR => unreachable,
1331 .CANCELED => return error.Canceled,
1332
1333 .INVAL => return error.InvalidArgument,
1334 .FAULT => unreachable,
1335 .NOENT => return error.ProcessNotFound,
1336 .AGAIN => return error.WouldBlock,
1337 .BADF => return error.NotOpenForWriting, // can be a race condition.
1338 .DESTADDRREQ => unreachable, // `connect` was never called.
1339 .DQUOT => return error.DiskQuota,
1340 .FBIG => return error.FileTooBig,
1341 .IO => return error.InputOutput,
1342 .NOSPC => return error.NoSpaceLeft,
1343 .ACCES => return error.AccessDenied,
1344 .PERM => return error.PermissionDenied,
1345 .PIPE => return error.BrokenPipe,
1346 .NXIO => return error.Unseekable,
1347 .SPIPE => return error.Unseekable,
1348 .OVERFLOW => return error.Unseekable,
1349 .BUSY => return error.DeviceBusy,
1350 .CONNRESET => return error.ConnectionResetByPeer,
1351 .MSGSIZE => return error.MessageOversize,
1352 else => |err| return std.posix.unexpectedErrno(err),
1353 }
1354}
1355
1356fn now(userdata: ?*anyopaque, clockid: std.posix.clockid_t) Io.ClockGetTimeError!Io.Timestamp {
1357 _ = userdata;
1358 const timespec = try std.posix.clock_gettime(clockid);
1359 return @enumFromInt(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec);
1360}
1361
1362fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadline) Io.SleepError!void {
1363 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1364 const thread: *Thread = .current();
1365 const iou = &thread.io_uring;
1366 const fiber = thread.currentFiber();
1367 try fiber.enterCancelRegion(thread);
1368
1369 const deadline_nanoseconds: i96 = switch (deadline) {
1370 .duration => |duration| duration.nanoseconds,
1371 .timestamp => |timestamp| @intFromEnum(timestamp),
1372 };
1373 const timespec: std.os.linux.kernel_timespec = .{
1374 .sec = @intCast(@divFloor(deadline_nanoseconds, std.time.ns_per_s)),
1375 .nsec = @intCast(@mod(deadline_nanoseconds, std.time.ns_per_s)),
1376 };
1377 getSqe(iou).* = .{
1378 .opcode = .TIMEOUT,
1379 .flags = 0,
1380 .ioprio = 0,
1381 .fd = 0,
1382 .off = 0,
1383 .addr = @intFromPtr(&timespec),
1384 .len = 1,
1385 .rw_flags = @as(u32, switch (deadline) {
1386 .duration => 0,
1387 .timestamp => std.os.linux.IORING_TIMEOUT_ABS,
1388 }) | @as(u32, switch (clockid) {
1389 .REALTIME => std.os.linux.IORING_TIMEOUT_REALTIME,
1390 .MONOTONIC => 0,
1391 .BOOTTIME => std.os.linux.IORING_TIMEOUT_BOOTTIME,
1392 else => return error.UnsupportedClock,
1393 }),
1394 .user_data = @intFromPtr(fiber),
1395 .buf_index = 0,
1396 .personality = 0,
1397 .splice_fd_in = 0,
1398 .addr3 = 0,
1399 .resv = 0,
1400 };
1401
1402 el.yield(null, .nothing);
1403 fiber.exitCancelRegion(thread);
1404
1405 const completion = fiber.resultPointer(Completion);
1406 switch (errno(completion.result)) {
1407 .SUCCESS, .TIME => return,
1408 .INTR => unreachable,
1409 .CANCELED => return error.Canceled,
1410
1411 else => |err| return std.posix.unexpectedErrno(err),
1412 }
1413}
1414
1415fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) error{Canceled}!void {
1416 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1417 el.yield(null, .{ .mutex_lock = .{ .prev_state = prev_state, .mutex = mutex } });
1418}
1419fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void {
1420 var maybe_waiting_fiber: ?*Fiber = @ptrFromInt(@intFromEnum(prev_state));
1421 while (if (maybe_waiting_fiber) |waiting_fiber| @cmpxchgWeak(
1422 Io.Mutex.State,
1423 &mutex.state,
1424 @enumFromInt(@intFromPtr(waiting_fiber)),
1425 @enumFromInt(@intFromPtr(waiting_fiber.queue_next)),
1426 .release,
1427 .acquire,
1428 ) else @cmpxchgWeak(
1429 Io.Mutex.State,
1430 &mutex.state,
1431 .locked_once,
1432 .unlocked,
1433 .release,
1434 .acquire,
1435 ) orelse return) |next_state| maybe_waiting_fiber = @ptrFromInt(@intFromEnum(next_state));
1436 maybe_waiting_fiber.?.queue_next = null;
1437 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1438 el.yield(maybe_waiting_fiber.?, .reschedule);
1439}
1440
1441const ConditionImpl = struct {
1442 tail: *Fiber,
1443 event: union(enum) {
1444 queued,
1445 wake: Io.Condition.Wake,
1446 },
1447};
1448
1449fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) Io.Cancelable!void {
1450 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1451 el.yield(null, .{ .condition_wait = .{ .cond = cond, .mutex = mutex } });
1452 const thread = Thread.current();
1453 const fiber = thread.currentFiber();
1454 const cond_impl = fiber.resultPointer(ConditionImpl);
1455 try mutex.lock(el.io());
1456 switch (cond_impl.event) {
1457 .queued => {},
1458 .wake => |wake| if (fiber.queue_next) |next_fiber| switch (wake) {
1459 .one => if (@cmpxchgStrong(
1460 ?*Fiber,
1461 @as(*?*Fiber, @ptrCast(&cond.state)),
1462 null,
1463 next_fiber,
1464 .release,
1465 .acquire,
1466 )) |old_fiber| {
1467 const old_cond_impl = old_fiber.?.resultPointer(ConditionImpl);
1468 assert(old_cond_impl.tail.queue_next == null);
1469 old_cond_impl.tail.queue_next = next_fiber;
1470 old_cond_impl.tail = cond_impl.tail;
1471 },
1472 .all => el.schedule(thread, .{ .head = next_fiber, .tail = cond_impl.tail }),
1473 },
1474 }
1475 fiber.queue_next = null;
1476}
1477
1478fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.Wake) void {
1479 const el: *EventLoop = @ptrCast(@alignCast(userdata));
1480 const waiting_fiber = @atomicRmw(?*Fiber, @as(*?*Fiber, @ptrCast(&cond.state)), .Xchg, null, .acquire) orelse return;
1481 waiting_fiber.resultPointer(ConditionImpl).event = .{ .wake = wake };
1482 el.yield(waiting_fiber, .reschedule);
1483}
1484
1485fn errno(signed: i32) std.os.linux.E {
1486 return .init(@bitCast(@as(isize, signed)));
1487}
1488
1489fn getSqe(iou: *IoUring) *std.os.linux.io_uring_sqe {
1490 while (true) return iou.get_sqe() catch {
1491 _ = iou.submit_and_wait(0) catch |err| switch (err) {
1492 error.SignalInterrupt => std.log.warn("submit_and_wait failed with SignalInterrupt", .{}),
1493 else => |e| @panic(@errorName(e)),
1494 };
1495 continue;
1496 };
1497}
lib/std/Io/Kqueue.zig created+1743
......@@ -0,0 +1,1743 @@
1const Kqueue = @This();
2const builtin = @import("builtin");
3
4const std = @import("../std.zig");
5const Io = std.Io;
6const Dir = std.Io.Dir;
7const File = std.Io.File;
8const net = std.Io.net;
9const assert = std.debug.assert;
10const Allocator = std.mem.Allocator;
11const Alignment = std.mem.Alignment;
12const IpAddress = std.Io.net.IpAddress;
13const errnoBug = std.Io.Threaded.errnoBug;
14const posix = std.posix;
15
16/// Must be a thread-safe allocator.
17gpa: Allocator,
18mutex: std.Thread.Mutex,
19main_fiber_buffer: [@sizeOf(Fiber) + Fiber.max_result_size]u8 align(@alignOf(Fiber)),
20threads: Thread.List,
21
22/// Empirically saw >128KB being used by the self-hosted backend to panic.
23const idle_stack_size = 256 * 1024;
24
25const max_idle_search = 4;
26const max_steal_ready_search = 4;
27const max_iovecs_len = 8;
28
29const changes_buffer_len = 64;
30
31const Thread = struct {
32 thread: std.Thread,
33 idle_context: Context,
34 current_context: *Context,
35 ready_queue: ?*Fiber,
36 kq_fd: posix.fd_t,
37 idle_search_index: u32,
38 steal_ready_search_index: u32,
39 /// For ensuring multiple fibers waiting on the same file descriptor and
40 /// filter use the same kevent.
41 wait_queues: std.AutoArrayHashMapUnmanaged(WaitQueueKey, *Fiber),
42
43 const WaitQueueKey = struct {
44 ident: usize,
45 filter: i32,
46 };
47
48 const canceling: ?*Thread = @ptrFromInt(@alignOf(Thread));
49
50 threadlocal var self: *Thread = undefined;
51
52 fn current() *Thread {
53 return self;
54 }
55
56 fn currentFiber(thread: *Thread) *Fiber {
57 return @fieldParentPtr("context", thread.current_context);
58 }
59
60 const List = struct {
61 allocated: []Thread,
62 reserved: u32,
63 active: u32,
64 };
65
66 fn deinit(thread: *Thread, gpa: Allocator) void {
67 posix.close(thread.kq_fd);
68 assert(thread.wait_queues.count() == 0);
69 thread.wait_queues.deinit(gpa);
70 thread.* = undefined;
71 }
72};
73
74const Fiber = struct {
75 required_align: void align(4),
76 context: Context,
77 awaiter: ?*Fiber,
78 queue_next: ?*Fiber,
79 cancel_thread: ?*Thread,
80 awaiting_completions: std.StaticBitSet(3),
81
82 const finished: ?*Fiber = @ptrFromInt(@alignOf(Thread));
83
84 const max_result_align: Alignment = .@"16";
85 const max_result_size = max_result_align.forward(64);
86 /// This includes any stack realignments that need to happen, and also the
87 /// initial frame return address slot and argument frame, depending on target.
88 const min_stack_size = 4 * 1024 * 1024;
89 const max_context_align: Alignment = .@"16";
90 const max_context_size = max_context_align.forward(1024);
91 const max_closure_size: usize = @sizeOf(AsyncClosure);
92 const max_closure_align: Alignment = .of(AsyncClosure);
93 const allocation_size = std.mem.alignForward(
94 usize,
95 max_closure_align.max(max_context_align).forward(
96 max_result_align.forward(@sizeOf(Fiber)) + max_result_size + min_stack_size,
97 ) + max_closure_size + max_context_size,
98 std.heap.page_size_max,
99 );
100
101 fn allocate(k: *Kqueue) error{OutOfMemory}!*Fiber {
102 return @ptrCast(try k.gpa.alignedAlloc(u8, .of(Fiber), allocation_size));
103 }
104
105 fn allocatedSlice(f: *Fiber) []align(@alignOf(Fiber)) u8 {
106 return @as([*]align(@alignOf(Fiber)) u8, @ptrCast(f))[0..allocation_size];
107 }
108
109 fn allocatedEnd(f: *Fiber) [*]u8 {
110 const allocated_slice = f.allocatedSlice();
111 return allocated_slice[allocated_slice.len..].ptr;
112 }
113
114 fn resultPointer(f: *Fiber, comptime Result: type) *Result {
115 return @ptrCast(@alignCast(f.resultBytes(.of(Result))));
116 }
117
118 fn resultBytes(f: *Fiber, alignment: Alignment) [*]u8 {
119 return @ptrFromInt(alignment.forward(@intFromPtr(f) + @sizeOf(Fiber)));
120 }
121
122 fn enterCancelRegion(fiber: *Fiber, thread: *Thread) error{Canceled}!void {
123 if (@cmpxchgStrong(
124 ?*Thread,
125 &fiber.cancel_thread,
126 null,
127 thread,
128 .acq_rel,
129 .acquire,
130 )) |cancel_thread| {
131 assert(cancel_thread == Thread.canceling);
132 return error.Canceled;
133 }
134 }
135
136 fn exitCancelRegion(fiber: *Fiber, thread: *Thread) void {
137 if (@cmpxchgStrong(
138 ?*Thread,
139 &fiber.cancel_thread,
140 thread,
141 null,
142 .acq_rel,
143 .acquire,
144 )) |cancel_thread| assert(cancel_thread == Thread.canceling);
145 }
146
147 const Queue = struct { head: *Fiber, tail: *Fiber };
148};
149
150fn recycle(k: *Kqueue, fiber: *Fiber) void {
151 std.log.debug("recyling {*}", .{fiber});
152 assert(fiber.queue_next == null);
153 k.gpa.free(fiber.allocatedSlice());
154}
155
156pub const InitOptions = struct {
157 n_threads: ?usize = null,
158};
159
160pub fn init(k: *Kqueue, gpa: Allocator, options: InitOptions) !void {
161 assert(options.n_threads != 0);
162 const n_threads = @max(1, options.n_threads orelse std.Thread.getCpuCount() catch 1);
163 const threads_size = n_threads * @sizeOf(Thread);
164 const idle_stack_end_offset = std.mem.alignForward(usize, threads_size + idle_stack_size, std.heap.page_size_max);
165 const allocated_slice = try gpa.alignedAlloc(u8, .of(Thread), idle_stack_end_offset);
166 errdefer gpa.free(allocated_slice);
167 k.* = .{
168 .gpa = gpa,
169 .mutex = .{},
170 .main_fiber_buffer = undefined,
171 .threads = .{
172 .allocated = @ptrCast(allocated_slice[0..threads_size]),
173 .reserved = 1,
174 .active = 1,
175 },
176 };
177 const main_fiber: *Fiber = @ptrCast(&k.main_fiber_buffer);
178 main_fiber.* = .{
179 .required_align = {},
180 .context = undefined,
181 .awaiter = null,
182 .queue_next = null,
183 .cancel_thread = null,
184 .awaiting_completions = .initEmpty(),
185 };
186 const main_thread = &k.threads.allocated[0];
187 Thread.self = main_thread;
188 const idle_stack_end: [*]align(16) usize = @ptrCast(@alignCast(allocated_slice[idle_stack_end_offset..].ptr));
189 (idle_stack_end - 1)[0..1].* = .{@intFromPtr(k)};
190 main_thread.* = .{
191 .thread = undefined,
192 .idle_context = switch (builtin.cpu.arch) {
193 .aarch64 => .{
194 .sp = @intFromPtr(idle_stack_end),
195 .fp = 0,
196 .pc = @intFromPtr(&mainIdleEntry),
197 },
198 .x86_64 => .{
199 .rsp = @intFromPtr(idle_stack_end - 1),
200 .rbp = 0,
201 .rip = @intFromPtr(&mainIdleEntry),
202 },
203 else => @compileError("unimplemented architecture"),
204 },
205 .current_context = &main_fiber.context,
206 .ready_queue = null,
207 .kq_fd = try posix.kqueue(),
208 .idle_search_index = 1,
209 .steal_ready_search_index = 1,
210 .wait_queues = .empty,
211 };
212 errdefer std.posix.close(main_thread.kq_fd);
213 std.log.debug("created main idle {*}", .{&main_thread.idle_context});
214 std.log.debug("created main {*}", .{main_fiber});
215}
216
217pub fn deinit(k: *Kqueue) void {
218 const active_threads = @atomicLoad(u32, &k.threads.active, .acquire);
219 for (k.threads.allocated[0..active_threads]) |*thread| {
220 const ready_fiber = @atomicLoad(?*Fiber, &thread.ready_queue, .monotonic);
221 assert(ready_fiber == null or ready_fiber == Fiber.finished); // pending async
222 }
223 k.yield(null, .exit);
224 const main_thread = &k.threads.allocated[0];
225 const gpa = k.gpa;
226 main_thread.deinit(gpa);
227 const allocated_ptr: [*]align(@alignOf(Thread)) u8 = @ptrCast(@alignCast(k.threads.allocated.ptr));
228 const idle_stack_end_offset = std.mem.alignForward(usize, k.threads.allocated.len * @sizeOf(Thread) + idle_stack_size, std.heap.page_size_max);
229 for (k.threads.allocated[1..active_threads]) |*thread| thread.thread.join();
230 gpa.free(allocated_ptr[0..idle_stack_end_offset]);
231 k.* = undefined;
232}
233
234fn findReadyFiber(k: *Kqueue, thread: *Thread) ?*Fiber {
235 if (@atomicRmw(?*Fiber, &thread.ready_queue, .Xchg, Fiber.finished, .acquire)) |ready_fiber| {
236 @atomicStore(?*Fiber, &thread.ready_queue, ready_fiber.queue_next, .release);
237 ready_fiber.queue_next = null;
238 return ready_fiber;
239 }
240 const active_threads = @atomicLoad(u32, &k.threads.active, .acquire);
241 for (0..@min(max_steal_ready_search, active_threads)) |_| {
242 defer thread.steal_ready_search_index += 1;
243 if (thread.steal_ready_search_index == active_threads) thread.steal_ready_search_index = 0;
244 const steal_ready_search_thread = &k.threads.allocated[0..active_threads][thread.steal_ready_search_index];
245 if (steal_ready_search_thread == thread) continue;
246 const ready_fiber = @atomicLoad(?*Fiber, &steal_ready_search_thread.ready_queue, .acquire) orelse continue;
247 if (ready_fiber == Fiber.finished) continue;
248 if (@cmpxchgWeak(
249 ?*Fiber,
250 &steal_ready_search_thread.ready_queue,
251 ready_fiber,
252 null,
253 .acquire,
254 .monotonic,
255 )) |_| continue;
256 @atomicStore(?*Fiber, &thread.ready_queue, ready_fiber.queue_next, .release);
257 ready_fiber.queue_next = null;
258 return ready_fiber;
259 }
260 // couldn't find anything to do, so we are now open for business
261 @atomicStore(?*Fiber, &thread.ready_queue, null, .monotonic);
262 return null;
263}
264
265fn yield(k: *Kqueue, maybe_ready_fiber: ?*Fiber, pending_task: SwitchMessage.PendingTask) void {
266 const thread: *Thread = .current();
267 const ready_context = if (maybe_ready_fiber orelse k.findReadyFiber(thread)) |ready_fiber|
268 &ready_fiber.context
269 else
270 &thread.idle_context;
271 const message: SwitchMessage = .{
272 .contexts = .{
273 .prev = thread.current_context,
274 .ready = ready_context,
275 },
276 .pending_task = pending_task,
277 };
278 std.log.debug("switching from {*} to {*}", .{ message.contexts.prev, message.contexts.ready });
279 contextSwitch(&message).handle(k);
280}
281
282fn schedule(k: *Kqueue, thread: *Thread, ready_queue: Fiber.Queue) void {
283 {
284 var fiber = ready_queue.head;
285 while (true) {
286 std.log.debug("scheduling {*}", .{fiber});
287 fiber = fiber.queue_next orelse break;
288 }
289 assert(fiber == ready_queue.tail);
290 }
291 // shared fields of previous `Thread` must be initialized before later ones are marked as active
292 const new_thread_index = @atomicLoad(u32, &k.threads.active, .acquire);
293 for (0..@min(max_idle_search, new_thread_index)) |_| {
294 defer thread.idle_search_index += 1;
295 if (thread.idle_search_index == new_thread_index) thread.idle_search_index = 0;
296 const idle_search_thread = &k.threads.allocated[0..new_thread_index][thread.idle_search_index];
297 if (idle_search_thread == thread) continue;
298 if (@cmpxchgWeak(
299 ?*Fiber,
300 &idle_search_thread.ready_queue,
301 null,
302 ready_queue.head,
303 .release,
304 .monotonic,
305 )) |_| continue;
306 const changes = [_]posix.Kevent{
307 .{
308 .ident = 0,
309 .filter = std.c.EVFILT.USER,
310 .flags = std.c.EV.ADD | std.c.EV.ONESHOT,
311 .fflags = std.c.NOTE.TRIGGER,
312 .data = 0,
313 .udata = @intFromEnum(Completion.UserData.wakeup),
314 },
315 };
316 // If an error occurs it only pessimises scheduling.
317 _ = posix.kevent(idle_search_thread.kq_fd, &changes, &.{}, null) catch {};
318 return;
319 }
320 spawn_thread: {
321 // previous failed reservations must have completed before retrying
322 if (new_thread_index == k.threads.allocated.len or @cmpxchgWeak(
323 u32,
324 &k.threads.reserved,
325 new_thread_index,
326 new_thread_index + 1,
327 .acquire,
328 .monotonic,
329 ) != null) break :spawn_thread;
330 const new_thread = &k.threads.allocated[new_thread_index];
331 const next_thread_index = new_thread_index + 1;
332 new_thread.* = .{
333 .thread = undefined,
334 .idle_context = undefined,
335 .current_context = &new_thread.idle_context,
336 .ready_queue = ready_queue.head,
337 .kq_fd = posix.kqueue() catch |err| {
338 @atomicStore(u32, &k.threads.reserved, new_thread_index, .release);
339 // no more access to `thread` after giving up reservation
340 std.log.warn("unable to create worker thread due to kqueue init failure: {t}", .{err});
341 break :spawn_thread;
342 },
343 .idle_search_index = 0,
344 .steal_ready_search_index = 0,
345 .wait_queues = .empty,
346 };
347 new_thread.thread = std.Thread.spawn(.{
348 .stack_size = idle_stack_size,
349 .allocator = k.gpa,
350 }, threadEntry, .{ k, new_thread_index }) catch |err| {
351 posix.close(new_thread.kq_fd);
352 @atomicStore(u32, &k.threads.reserved, new_thread_index, .release);
353 // no more access to `thread` after giving up reservation
354 std.log.warn("unable to create worker thread due spawn failure: {s}", .{@errorName(err)});
355 break :spawn_thread;
356 };
357 // shared fields of `Thread` must be initialized before being marked active
358 @atomicStore(u32, &k.threads.active, next_thread_index, .release);
359 return;
360 }
361 // nobody wanted it, so just queue it on ourselves
362 while (@cmpxchgWeak(
363 ?*Fiber,
364 &thread.ready_queue,
365 ready_queue.tail.queue_next,
366 ready_queue.head,
367 .acq_rel,
368 .acquire,
369 )) |old_head| ready_queue.tail.queue_next = old_head;
370}
371
372fn mainIdle(k: *Kqueue, message: *const SwitchMessage) callconv(.withStackAlign(.c, @max(@alignOf(Thread), @alignOf(Context)))) noreturn {
373 message.handle(k);
374 k.idle(&k.threads.allocated[0]);
375 k.yield(@ptrCast(&k.main_fiber_buffer), .nothing);
376 unreachable; // switched to dead fiber
377}
378
379fn threadEntry(k: *Kqueue, index: u32) void {
380 const thread: *Thread = &k.threads.allocated[index];
381 Thread.self = thread;
382 std.log.debug("created thread idle {*}", .{&thread.idle_context});
383 k.idle(thread);
384 thread.deinit(k.gpa);
385}
386
387const Completion = struct {
388 const UserData = enum(usize) {
389 unused,
390 wakeup,
391 cleanup,
392 exit,
393 /// *Fiber
394 _,
395 };
396 /// Corresponds to Kevent field.
397 flags: u16,
398 /// Corresponds to Kevent field.
399 fflags: u32,
400 /// Corresponds to Kevent field.
401 data: isize,
402};
403
404fn idle(k: *Kqueue, thread: *Thread) void {
405 var events_buffer: [changes_buffer_len]posix.Kevent = undefined;
406 var maybe_ready_fiber: ?*Fiber = null;
407 while (true) {
408 while (maybe_ready_fiber orelse k.findReadyFiber(thread)) |ready_fiber| {
409 k.yield(ready_fiber, .nothing);
410 maybe_ready_fiber = null;
411 }
412 const n = posix.kevent(thread.kq_fd, &.{}, &events_buffer, null) catch |err| {
413 // TODO handle EINTR for cancellation purposes
414 @panic(@errorName(err));
415 };
416 var maybe_ready_queue: ?Fiber.Queue = null;
417 for (events_buffer[0..n]) |event| switch (@as(Completion.UserData, @enumFromInt(event.udata))) {
418 .unused => unreachable, // bad submission queued?
419 .wakeup => {},
420 .cleanup => @panic("failed to notify other threads that we are exiting"),
421 .exit => {
422 assert(maybe_ready_fiber == null and maybe_ready_queue == null); // pending async
423 return;
424 },
425 _ => {
426 const event_head_fiber: *Fiber = @ptrFromInt(event.udata);
427 const event_tail_fiber = thread.wait_queues.fetchSwapRemove(.{
428 .ident = event.ident,
429 .filter = event.filter,
430 }).?.value;
431 assert(event_tail_fiber.queue_next == null);
432
433 // TODO reevaluate this logic
434 event_head_fiber.resultPointer(Completion).* = .{
435 .flags = event.flags,
436 .fflags = event.fflags,
437 .data = event.data,
438 };
439
440 queue_ready: {
441 const head: *Fiber = if (maybe_ready_fiber == null) f: {
442 maybe_ready_fiber = event_head_fiber;
443 const next = event_head_fiber.queue_next orelse break :queue_ready;
444 event_head_fiber.queue_next = null;
445 break :f next;
446 } else event_head_fiber;
447
448 if (maybe_ready_queue) |*ready_queue| {
449 ready_queue.tail.queue_next = head;
450 ready_queue.tail = event_tail_fiber;
451 } else {
452 maybe_ready_queue = .{ .head = head, .tail = event_tail_fiber };
453 }
454 }
455 },
456 };
457 if (maybe_ready_queue) |ready_queue| k.schedule(thread, ready_queue);
458 }
459}
460
461const SwitchMessage = struct {
462 contexts: extern struct {
463 prev: *Context,
464 ready: *Context,
465 },
466 pending_task: PendingTask,
467
468 const PendingTask = union(enum) {
469 nothing,
470 reschedule,
471 recycle: *Fiber,
472 register_awaiter: *?*Fiber,
473 register_select: []const *Io.AnyFuture,
474 mutex_lock: struct {
475 prev_state: Io.Mutex.State,
476 mutex: *Io.Mutex,
477 },
478 condition_wait: struct {
479 cond: *Io.Condition,
480 mutex: *Io.Mutex,
481 },
482 exit,
483 };
484
485 fn handle(message: *const SwitchMessage, k: *Kqueue) void {
486 const thread: *Thread = .current();
487 thread.current_context = message.contexts.ready;
488 switch (message.pending_task) {
489 .nothing => {},
490 .reschedule => if (message.contexts.prev != &thread.idle_context) {
491 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
492 assert(prev_fiber.queue_next == null);
493 k.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });
494 },
495 .recycle => |fiber| {
496 k.recycle(fiber);
497 },
498 .register_awaiter => |awaiter| {
499 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
500 assert(prev_fiber.queue_next == null);
501 if (@atomicRmw(?*Fiber, awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished)
502 k.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });
503 },
504 .register_select => |futures| {
505 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
506 assert(prev_fiber.queue_next == null);
507 for (futures) |any_future| {
508 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
509 if (@atomicRmw(?*Fiber, &future_fiber.awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished) {
510 const closure: *AsyncClosure = .fromFiber(future_fiber);
511 if (!@atomicRmw(bool, &closure.already_awaited, .Xchg, true, .seq_cst)) {
512 k.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });
513 }
514 }
515 }
516 },
517 .mutex_lock => |mutex_lock| {
518 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
519 assert(prev_fiber.queue_next == null);
520 var prev_state = mutex_lock.prev_state;
521 while (switch (prev_state) {
522 else => next_state: {
523 prev_fiber.queue_next = @ptrFromInt(@intFromEnum(prev_state));
524 break :next_state @cmpxchgWeak(
525 Io.Mutex.State,
526 &mutex_lock.mutex.state,
527 prev_state,
528 @enumFromInt(@intFromPtr(prev_fiber)),
529 .release,
530 .acquire,
531 );
532 },
533 .unlocked => @cmpxchgWeak(
534 Io.Mutex.State,
535 &mutex_lock.mutex.state,
536 .unlocked,
537 .locked_once,
538 .acquire,
539 .acquire,
540 ) orelse {
541 prev_fiber.queue_next = null;
542 k.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });
543 return;
544 },
545 }) |next_state| prev_state = next_state;
546 },
547 .condition_wait => |condition_wait| {
548 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
549 assert(prev_fiber.queue_next == null);
550 const cond_impl = prev_fiber.resultPointer(Condition);
551 cond_impl.* = .{
552 .tail = prev_fiber,
553 .event = .queued,
554 };
555 if (@cmpxchgStrong(
556 ?*Fiber,
557 @as(*?*Fiber, @ptrCast(&condition_wait.cond.state)),
558 null,
559 prev_fiber,
560 .release,
561 .acquire,
562 )) |waiting_fiber| {
563 const waiting_cond_impl = waiting_fiber.?.resultPointer(Condition);
564 assert(waiting_cond_impl.tail.queue_next == null);
565 waiting_cond_impl.tail.queue_next = prev_fiber;
566 waiting_cond_impl.tail = prev_fiber;
567 }
568 condition_wait.mutex.unlock(k.io());
569 },
570 .exit => for (k.threads.allocated[0..@atomicLoad(u32, &k.threads.active, .acquire)]) |*each_thread| {
571 const changes = [_]posix.Kevent{
572 .{
573 .ident = 0,
574 .filter = std.c.EVFILT.USER,
575 .flags = std.c.EV.ADD | std.c.EV.ONESHOT,
576 .fflags = std.c.NOTE.TRIGGER,
577 .data = 0,
578 .udata = @intFromEnum(Completion.UserData.exit),
579 },
580 };
581 _ = posix.kevent(each_thread.kq_fd, &changes, &.{}, null) catch |err| {
582 @panic(@errorName(err));
583 };
584 },
585 }
586 }
587};
588
589const Context = switch (builtin.cpu.arch) {
590 .aarch64 => extern struct {
591 sp: u64,
592 fp: u64,
593 pc: u64,
594 },
595 .x86_64 => extern struct {
596 rsp: u64,
597 rbp: u64,
598 rip: u64,
599 },
600 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
601};
602
603inline fn contextSwitch(message: *const SwitchMessage) *const SwitchMessage {
604 return @fieldParentPtr("contexts", switch (builtin.cpu.arch) {
605 .aarch64 => asm volatile (
606 \\ ldp x0, x2, [x1]
607 \\ ldr x3, [x2, #16]
608 \\ mov x4, sp
609 \\ stp x4, fp, [x0]
610 \\ adr x5, 0f
611 \\ ldp x4, fp, [x2]
612 \\ str x5, [x0, #16]
613 \\ mov sp, x4
614 \\ br x3
615 \\0:
616 : [received_message] "={x1}" (-> *const @FieldType(SwitchMessage, "contexts")),
617 : [message_to_send] "{x1}" (&message.contexts),
618 : .{
619 .x0 = true,
620 .x1 = true,
621 .x2 = true,
622 .x3 = true,
623 .x4 = true,
624 .x5 = true,
625 .x6 = true,
626 .x7 = true,
627 .x8 = true,
628 .x9 = true,
629 .x10 = true,
630 .x11 = true,
631 .x12 = true,
632 .x13 = true,
633 .x14 = true,
634 .x15 = true,
635 .x16 = true,
636 .x17 = true,
637 .x19 = true,
638 .x20 = true,
639 .x21 = true,
640 .x22 = true,
641 .x23 = true,
642 .x24 = true,
643 .x25 = true,
644 .x26 = true,
645 .x27 = true,
646 .x28 = true,
647 .x30 = true,
648 .z0 = true,
649 .z1 = true,
650 .z2 = true,
651 .z3 = true,
652 .z4 = true,
653 .z5 = true,
654 .z6 = true,
655 .z7 = true,
656 .z8 = true,
657 .z9 = true,
658 .z10 = true,
659 .z11 = true,
660 .z12 = true,
661 .z13 = true,
662 .z14 = true,
663 .z15 = true,
664 .z16 = true,
665 .z17 = true,
666 .z18 = true,
667 .z19 = true,
668 .z20 = true,
669 .z21 = true,
670 .z22 = true,
671 .z23 = true,
672 .z24 = true,
673 .z25 = true,
674 .z26 = true,
675 .z27 = true,
676 .z28 = true,
677 .z29 = true,
678 .z30 = true,
679 .z31 = true,
680 .p0 = true,
681 .p1 = true,
682 .p2 = true,
683 .p3 = true,
684 .p4 = true,
685 .p5 = true,
686 .p6 = true,
687 .p7 = true,
688 .p8 = true,
689 .p9 = true,
690 .p10 = true,
691 .p11 = true,
692 .p12 = true,
693 .p13 = true,
694 .p14 = true,
695 .p15 = true,
696 .fpcr = true,
697 .fpsr = true,
698 .ffr = true,
699 .memory = true,
700 }),
701 .x86_64 => asm volatile (
702 \\ movq 0(%%rsi), %%rax
703 \\ movq 8(%%rsi), %%rcx
704 \\ leaq 0f(%%rip), %%rdx
705 \\ movq %%rsp, 0(%%rax)
706 \\ movq %%rbp, 8(%%rax)
707 \\ movq %%rdx, 16(%%rax)
708 \\ movq 0(%%rcx), %%rsp
709 \\ movq 8(%%rcx), %%rbp
710 \\ jmpq *16(%%rcx)
711 \\0:
712 : [received_message] "={rsi}" (-> *const @FieldType(SwitchMessage, "contexts")),
713 : [message_to_send] "{rsi}" (&message.contexts),
714 : .{
715 .rax = true,
716 .rcx = true,
717 .rdx = true,
718 .rbx = true,
719 .rsi = true,
720 .rdi = true,
721 .r8 = true,
722 .r9 = true,
723 .r10 = true,
724 .r11 = true,
725 .r12 = true,
726 .r13 = true,
727 .r14 = true,
728 .r15 = true,
729 .mm0 = true,
730 .mm1 = true,
731 .mm2 = true,
732 .mm3 = true,
733 .mm4 = true,
734 .mm5 = true,
735 .mm6 = true,
736 .mm7 = true,
737 .zmm0 = true,
738 .zmm1 = true,
739 .zmm2 = true,
740 .zmm3 = true,
741 .zmm4 = true,
742 .zmm5 = true,
743 .zmm6 = true,
744 .zmm7 = true,
745 .zmm8 = true,
746 .zmm9 = true,
747 .zmm10 = true,
748 .zmm11 = true,
749 .zmm12 = true,
750 .zmm13 = true,
751 .zmm14 = true,
752 .zmm15 = true,
753 .zmm16 = true,
754 .zmm17 = true,
755 .zmm18 = true,
756 .zmm19 = true,
757 .zmm20 = true,
758 .zmm21 = true,
759 .zmm22 = true,
760 .zmm23 = true,
761 .zmm24 = true,
762 .zmm25 = true,
763 .zmm26 = true,
764 .zmm27 = true,
765 .zmm28 = true,
766 .zmm29 = true,
767 .zmm30 = true,
768 .zmm31 = true,
769 .fpsr = true,
770 .fpcr = true,
771 .mxcsr = true,
772 .rflags = true,
773 .dirflag = true,
774 .memory = true,
775 }),
776 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
777 });
778}
779
780fn mainIdleEntry() callconv(.naked) void {
781 switch (builtin.cpu.arch) {
782 .x86_64 => asm volatile (
783 \\ movq (%%rsp), %%rdi
784 \\ jmp %[mainIdle:P]
785 :
786 : [mainIdle] "X" (&mainIdle),
787 ),
788 .aarch64 => asm volatile (
789 \\ ldr x0, [sp, #-8]
790 \\ b %[mainIdle]
791 :
792 : [mainIdle] "X" (&mainIdle),
793 ),
794 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
795 }
796}
797
798fn fiberEntry() callconv(.naked) void {
799 switch (builtin.cpu.arch) {
800 .x86_64 => asm volatile (
801 \\ leaq 8(%%rsp), %%rdi
802 \\ jmp %[AsyncClosure_call:P]
803 :
804 : [AsyncClosure_call] "X" (&AsyncClosure.call),
805 ),
806 .aarch64 => asm volatile (
807 \\ mov x0, sp
808 \\ b %[AsyncClosure_call]
809 :
810 : [AsyncClosure_call] "X" (&AsyncClosure.call),
811 ),
812 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
813 }
814}
815
816const AsyncClosure = struct {
817 kqueue: *Kqueue,
818 fiber: *Fiber,
819 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
820 result_align: Alignment,
821 already_awaited: bool,
822
823 fn contextPointer(closure: *AsyncClosure) [*]align(Fiber.max_context_align.toByteUnits()) u8 {
824 return @alignCast(@as([*]u8, @ptrCast(closure)) + @sizeOf(AsyncClosure));
825 }
826
827 fn call(closure: *AsyncClosure, message: *const SwitchMessage) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn {
828 message.handle(closure.kqueue);
829 const fiber = closure.fiber;
830 std.log.debug("{*} performing async", .{fiber});
831 closure.start(closure.contextPointer(), fiber.resultBytes(closure.result_align));
832 const awaiter = @atomicRmw(?*Fiber, &fiber.awaiter, .Xchg, Fiber.finished, .acq_rel);
833 const ready_awaiter = r: {
834 const a = awaiter orelse break :r null;
835 if (@atomicRmw(bool, &closure.already_awaited, .Xchg, true, .acq_rel)) break :r null;
836 break :r a;
837 };
838 closure.kqueue.yield(ready_awaiter, .nothing);
839 unreachable; // switched to dead fiber
840 }
841
842 fn fromFiber(fiber: *Fiber) *AsyncClosure {
843 return @ptrFromInt(Fiber.max_context_align.max(.of(AsyncClosure)).backward(
844 @intFromPtr(fiber.allocatedEnd()) - Fiber.max_context_size,
845 ) - @sizeOf(AsyncClosure));
846 }
847};
848
849pub fn io(k: *Kqueue) Io {
850 return .{
851 .userdata = k,
852 .vtable = &.{
853 .async = async,
854 .concurrent = concurrent,
855 .await = await,
856 .cancel = cancel,
857 .cancelRequested = cancelRequested,
858 .select = select,
859
860 .groupAsync = groupAsync,
861 .groupWait = groupWait,
862 .groupCancel = groupCancel,
863
864 .mutexLock = mutexLock,
865 .mutexLockUncancelable = mutexLockUncancelable,
866 .mutexUnlock = mutexUnlock,
867
868 .conditionWait = conditionWait,
869 .conditionWaitUncancelable = conditionWaitUncancelable,
870 .conditionWake = conditionWake,
871
872 .dirMake = dirMake,
873 .dirMakePath = dirMakePath,
874 .dirMakeOpenPath = dirMakeOpenPath,
875 .dirStat = dirStat,
876 .dirStatPath = dirStatPath,
877
878 .fileStat = fileStat,
879 .dirAccess = dirAccess,
880 .dirCreateFile = dirCreateFile,
881 .dirOpenFile = dirOpenFile,
882 .dirOpenDir = dirOpenDir,
883 .dirClose = dirClose,
884 .fileClose = fileClose,
885 .fileWriteStreaming = fileWriteStreaming,
886 .fileWritePositional = fileWritePositional,
887 .fileReadStreaming = fileReadStreaming,
888 .fileReadPositional = fileReadPositional,
889 .fileSeekBy = fileSeekBy,
890 .fileSeekTo = fileSeekTo,
891 .openSelfExe = openSelfExe,
892
893 .now = now,
894 .sleep = sleep,
895
896 .netListenIp = netListenIp,
897 .netListenUnix = netListenUnix,
898 .netAccept = netAccept,
899 .netBindIp = netBindIp,
900 .netConnectIp = netConnectIp,
901 .netConnectUnix = netConnectUnix,
902 .netClose = netClose,
903 .netRead = netRead,
904 .netWrite = netWrite,
905 .netSend = netSend,
906 .netReceive = netReceive,
907 .netInterfaceNameResolve = netInterfaceNameResolve,
908 .netInterfaceName = netInterfaceName,
909 .netLookup = netLookup,
910 },
911 };
912}
913
914fn async(
915 userdata: ?*anyopaque,
916 result: []u8,
917 result_alignment: std.mem.Alignment,
918 context: []const u8,
919 context_alignment: std.mem.Alignment,
920 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
921) ?*Io.AnyFuture {
922 return concurrent(userdata, result.len, result_alignment, context, context_alignment, start) catch {
923 start(context.ptr, result.ptr);
924 return null;
925 };
926}
927
928fn concurrent(
929 userdata: ?*anyopaque,
930 result_len: usize,
931 result_alignment: Alignment,
932 context: []const u8,
933 context_alignment: Alignment,
934 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
935) Io.ConcurrentError!*Io.AnyFuture {
936 const k: *Kqueue = @ptrCast(@alignCast(userdata));
937 assert(result_alignment.compare(.lte, Fiber.max_result_align)); // TODO
938 assert(context_alignment.compare(.lte, Fiber.max_context_align)); // TODO
939 assert(result_len <= Fiber.max_result_size); // TODO
940 assert(context.len <= Fiber.max_context_size); // TODO
941
942 const fiber = Fiber.allocate(k) catch return error.ConcurrencyUnavailable;
943 std.log.debug("allocated {*}", .{fiber});
944
945 const closure: *AsyncClosure = .fromFiber(fiber);
946 fiber.* = .{
947 .required_align = {},
948 .context = switch (builtin.cpu.arch) {
949 .x86_64 => .{
950 .rsp = @intFromPtr(closure) - @sizeOf(usize),
951 .rbp = 0,
952 .rip = @intFromPtr(&fiberEntry),
953 },
954 .aarch64 => .{
955 .sp = @intFromPtr(closure),
956 .fp = 0,
957 .pc = @intFromPtr(&fiberEntry),
958 },
959 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
960 },
961 .awaiter = null,
962 .queue_next = null,
963 .cancel_thread = null,
964 .awaiting_completions = .initEmpty(),
965 };
966 closure.* = .{
967 .kqueue = k,
968 .fiber = fiber,
969 .start = start,
970 .result_align = result_alignment,
971 .already_awaited = false,
972 };
973 @memcpy(closure.contextPointer(), context);
974
975 k.schedule(.current(), .{ .head = fiber, .tail = fiber });
976 return @ptrCast(fiber);
977}
978
979fn await(
980 userdata: ?*anyopaque,
981 any_future: *Io.AnyFuture,
982 result: []u8,
983 result_alignment: std.mem.Alignment,
984) void {
985 const k: *Kqueue = @ptrCast(@alignCast(userdata));
986 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
987 if (@atomicLoad(?*Fiber, &future_fiber.awaiter, .acquire) != Fiber.finished)
988 k.yield(null, .{ .register_awaiter = &future_fiber.awaiter });
989 @memcpy(result, future_fiber.resultBytes(result_alignment));
990 k.recycle(future_fiber);
991}
992
993fn cancel(
994 userdata: ?*anyopaque,
995 any_future: *Io.AnyFuture,
996 result: []u8,
997 result_alignment: std.mem.Alignment,
998) void {
999 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1000 _ = k;
1001 _ = any_future;
1002 _ = result;
1003 _ = result_alignment;
1004 @panic("TODO");
1005}
1006
1007fn cancelRequested(userdata: ?*anyopaque) bool {
1008 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1009 _ = k;
1010 return false; // TODO
1011}
1012
1013fn groupAsync(
1014 userdata: ?*anyopaque,
1015 group: *Io.Group,
1016 context: []const u8,
1017 context_alignment: std.mem.Alignment,
1018 start: *const fn (*Io.Group, context: *const anyopaque) void,
1019) void {
1020 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1021 _ = k;
1022 _ = group;
1023 _ = context;
1024 _ = context_alignment;
1025 _ = start;
1026 @panic("TODO");
1027}
1028
1029fn groupWait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void {
1030 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1031 _ = k;
1032 _ = group;
1033 _ = token;
1034 @panic("TODO");
1035}
1036
1037fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void {
1038 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1039 _ = k;
1040 _ = group;
1041 _ = token;
1042 @panic("TODO");
1043}
1044
1045fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!usize {
1046 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1047 _ = k;
1048 _ = futures;
1049 @panic("TODO");
1050}
1051
1052fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) Io.Cancelable!void {
1053 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1054 _ = k;
1055 _ = prev_state;
1056 _ = mutex;
1057 @panic("TODO");
1058}
1059fn mutexLockUncancelable(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void {
1060 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1061 _ = k;
1062 _ = prev_state;
1063 _ = mutex;
1064 @panic("TODO");
1065}
1066fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void {
1067 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1068 _ = k;
1069 _ = prev_state;
1070 _ = mutex;
1071 @panic("TODO");
1072}
1073
1074fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) Io.Cancelable!void {
1075 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1076 k.yield(null, .{ .condition_wait = .{ .cond = cond, .mutex = mutex } });
1077 const thread = Thread.current();
1078 const fiber = thread.currentFiber();
1079 const cond_impl = fiber.resultPointer(Condition);
1080 try mutex.lock(k.io());
1081 switch (cond_impl.event) {
1082 .queued => {},
1083 .wake => |wake| if (fiber.queue_next) |next_fiber| switch (wake) {
1084 .one => if (@cmpxchgStrong(
1085 ?*Fiber,
1086 @as(*?*Fiber, @ptrCast(&cond.state)),
1087 null,
1088 next_fiber,
1089 .release,
1090 .acquire,
1091 )) |old_fiber| {
1092 const old_cond_impl = old_fiber.?.resultPointer(Condition);
1093 assert(old_cond_impl.tail.queue_next == null);
1094 old_cond_impl.tail.queue_next = next_fiber;
1095 old_cond_impl.tail = cond_impl.tail;
1096 },
1097 .all => k.schedule(thread, .{ .head = next_fiber, .tail = cond_impl.tail }),
1098 },
1099 }
1100 fiber.queue_next = null;
1101}
1102
1103fn conditionWaitUncancelable(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) void {
1104 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1105 _ = k;
1106 _ = cond;
1107 _ = mutex;
1108 @panic("TODO");
1109}
1110fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.Wake) void {
1111 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1112 const waiting_fiber = @atomicRmw(?*Fiber, @as(*?*Fiber, @ptrCast(&cond.state)), .Xchg, null, .acquire) orelse return;
1113 waiting_fiber.resultPointer(Condition).event = .{ .wake = wake };
1114 k.yield(waiting_fiber, .reschedule);
1115}
1116
1117fn dirMake(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void {
1118 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1119 _ = k;
1120 _ = dir;
1121 _ = sub_path;
1122 _ = mode;
1123 @panic("TODO");
1124}
1125fn dirMakePath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void {
1126 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1127 _ = k;
1128 _ = dir;
1129 _ = sub_path;
1130 _ = mode;
1131 @panic("TODO");
1132}
1133fn dirMakeOpenPath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.OpenOptions) Dir.MakeOpenPathError!Dir {
1134 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1135 _ = k;
1136 _ = dir;
1137 _ = sub_path;
1138 _ = options;
1139 @panic("TODO");
1140}
1141fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {
1142 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1143 _ = k;
1144 _ = dir;
1145 @panic("TODO");
1146}
1147fn dirStatPath(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.StatPathOptions) Dir.StatPathError!File.Stat {
1148 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1149 _ = k;
1150 _ = dir;
1151 _ = sub_path;
1152 _ = options;
1153 @panic("TODO");
1154}
1155fn dirAccess(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.AccessOptions) Dir.AccessError!void {
1156 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1157 _ = k;
1158 _ = dir;
1159 _ = sub_path;
1160 _ = options;
1161 @panic("TODO");
1162}
1163fn dirCreateFile(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
1164 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1165 _ = k;
1166 _ = dir;
1167 _ = sub_path;
1168 _ = flags;
1169 @panic("TODO");
1170}
1171fn dirOpenFile(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
1172 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1173 _ = k;
1174 _ = dir;
1175 _ = sub_path;
1176 _ = flags;
1177 @panic("TODO");
1178}
1179fn dirOpenDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, options: Dir.OpenOptions) Dir.OpenError!Dir {
1180 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1181 _ = k;
1182 _ = dir;
1183 _ = sub_path;
1184 _ = options;
1185 @panic("TODO");
1186}
1187fn dirClose(userdata: ?*anyopaque, dir: Dir) void {
1188 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1189 _ = k;
1190 _ = dir;
1191 @panic("TODO");
1192}
1193fn fileStat(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
1194 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1195 _ = k;
1196 _ = file;
1197 @panic("TODO");
1198}
1199fn fileClose(userdata: ?*anyopaque, file: File) void {
1200 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1201 _ = k;
1202 _ = file;
1203 @panic("TODO");
1204}
1205fn fileWriteStreaming(userdata: ?*anyopaque, file: File, buffer: [][]const u8) File.WriteStreamingError!usize {
1206 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1207 _ = k;
1208 _ = file;
1209 _ = buffer;
1210 @panic("TODO");
1211}
1212fn fileWritePositional(userdata: ?*anyopaque, file: File, buffer: [][]const u8, offset: u64) File.WritePositionalError!usize {
1213 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1214 _ = k;
1215 _ = file;
1216 _ = buffer;
1217 _ = offset;
1218 @panic("TODO");
1219}
1220fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: [][]u8) File.Reader.Error!usize {
1221 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1222 _ = k;
1223 _ = file;
1224 _ = data;
1225 @panic("TODO");
1226}
1227fn fileReadPositional(userdata: ?*anyopaque, file: File, data: [][]u8, offset: u64) File.ReadPositionalError!usize {
1228 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1229 _ = k;
1230 _ = file;
1231 _ = data;
1232 _ = offset;
1233 @panic("TODO");
1234}
1235fn fileSeekBy(userdata: ?*anyopaque, file: File, relative_offset: i64) File.SeekError!void {
1236 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1237 _ = k;
1238 _ = file;
1239 _ = relative_offset;
1240 @panic("TODO");
1241}
1242fn fileSeekTo(userdata: ?*anyopaque, file: File, absolute_offset: u64) File.SeekError!void {
1243 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1244 _ = k;
1245 _ = file;
1246 _ = absolute_offset;
1247 @panic("TODO");
1248}
1249fn openSelfExe(userdata: ?*anyopaque, file: File.OpenFlags) File.OpenSelfExeError!File {
1250 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1251 _ = k;
1252 _ = file;
1253 @panic("TODO");
1254}
1255
1256fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
1257 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1258 _ = k;
1259 _ = clock;
1260 @panic("TODO");
1261}
1262fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
1263 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1264 _ = k;
1265 _ = timeout;
1266 @panic("TODO");
1267}
1268
1269fn netListenIp(
1270 userdata: ?*anyopaque,
1271 address: net.IpAddress,
1272 options: net.IpAddress.ListenOptions,
1273) net.IpAddress.ListenError!net.Server {
1274 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1275 _ = k;
1276 _ = address;
1277 _ = options;
1278 @panic("TODO");
1279}
1280fn netAccept(userdata: ?*anyopaque, server: net.Socket.Handle) net.Server.AcceptError!net.Stream {
1281 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1282 _ = k;
1283 _ = server;
1284 @panic("TODO");
1285}
1286fn netBindIp(
1287 userdata: ?*anyopaque,
1288 address: *const net.IpAddress,
1289 options: net.IpAddress.BindOptions,
1290) net.IpAddress.BindError!net.Socket {
1291 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1292 const family = Io.Threaded.posixAddressFamily(address);
1293 const socket_fd = try openSocketPosix(k, family, options);
1294 errdefer std.posix.close(socket_fd);
1295 var storage: Io.Threaded.PosixAddress = undefined;
1296 var addr_len = Io.Threaded.addressToPosix(address, &storage);
1297 try posixBind(k, socket_fd, &storage.any, addr_len);
1298 try posixGetSockName(k, socket_fd, &storage.any, &addr_len);
1299 return .{
1300 .handle = socket_fd,
1301 .address = Io.Threaded.addressFromPosix(&storage),
1302 };
1303}
1304fn netConnectIp(userdata: ?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.ConnectOptions) net.IpAddress.ConnectError!net.Stream {
1305 if (options.timeout != .none) @panic("TODO");
1306 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1307 const family = Io.Threaded.posixAddressFamily(address);
1308 const socket_fd = try openSocketPosix(k, family, .{
1309 .mode = options.mode,
1310 .protocol = options.protocol,
1311 });
1312 errdefer posix.close(socket_fd);
1313 var storage: Io.Threaded.PosixAddress = undefined;
1314 var addr_len = Io.Threaded.addressToPosix(address, &storage);
1315 try posixConnect(k, socket_fd, &storage.any, addr_len);
1316 try posixGetSockName(k, socket_fd, &storage.any, &addr_len);
1317 return .{ .socket = .{
1318 .handle = socket_fd,
1319 .address = Io.Threaded.addressFromPosix(&storage),
1320 } };
1321}
1322
1323fn posixConnect(k: *Kqueue, socket_fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void {
1324 while (true) {
1325 try k.checkCancel();
1326 switch (posix.errno(posix.system.connect(socket_fd, addr, addr_len))) {
1327 .SUCCESS => return,
1328 .INTR => continue,
1329 .CANCELED => return error.Canceled,
1330 .AGAIN => @panic("TODO"),
1331 .INPROGRESS => return, // Due to TCP fast open, we find out possible error later.
1332
1333 .ADDRNOTAVAIL => return error.AddressUnavailable,
1334 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
1335 .ALREADY => return error.ConnectionPending,
1336 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
1337 .CONNREFUSED => return error.ConnectionRefused,
1338 .CONNRESET => return error.ConnectionResetByPeer,
1339 .FAULT => |err| return errnoBug(err),
1340 .ISCONN => |err| return errnoBug(err),
1341 .HOSTUNREACH => return error.HostUnreachable,
1342 .NETUNREACH => return error.NetworkUnreachable,
1343 .NOTSOCK => |err| return errnoBug(err),
1344 .PROTOTYPE => |err| return errnoBug(err),
1345 .TIMEDOUT => return error.Timeout,
1346 .CONNABORTED => |err| return errnoBug(err),
1347 .ACCES => return error.AccessDenied,
1348 .PERM => |err| return errnoBug(err),
1349 .NOENT => |err| return errnoBug(err),
1350 .NETDOWN => return error.NetworkDown,
1351 else => |err| return posix.unexpectedErrno(err),
1352 }
1353 }
1354}
1355
1356fn netListenUnix(
1357 userdata: ?*anyopaque,
1358 unix_address: *const net.UnixAddress,
1359 options: net.UnixAddress.ListenOptions,
1360) net.UnixAddress.ListenError!net.Socket.Handle {
1361 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1362 _ = k;
1363 _ = unix_address;
1364 _ = options;
1365 @panic("TODO");
1366}
1367fn netConnectUnix(
1368 userdata: ?*anyopaque,
1369 unix_address: *const net.UnixAddress,
1370) net.UnixAddress.ConnectError!net.Socket.Handle {
1371 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1372 _ = k;
1373 _ = unix_address;
1374 @panic("TODO");
1375}
1376
1377fn netSend(
1378 userdata: ?*anyopaque,
1379 handle: net.Socket.Handle,
1380 outgoing_messages: []net.OutgoingMessage,
1381 flags: net.SendFlags,
1382) struct { ?net.Socket.SendError, usize } {
1383 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1384
1385 const posix_flags: u32 =
1386 @as(u32, if (@hasDecl(posix.MSG, "CONFIRM") and flags.confirm) posix.MSG.CONFIRM else 0) |
1387 @as(u32, if (@hasDecl(posix.MSG, "DONTROUTE") and flags.dont_route) posix.MSG.DONTROUTE else 0) |
1388 @as(u32, if (@hasDecl(posix.MSG, "EOR") and flags.eor) posix.MSG.EOR else 0) |
1389 @as(u32, if (@hasDecl(posix.MSG, "OOB") and flags.oob) posix.MSG.OOB else 0) |
1390 @as(u32, if (@hasDecl(posix.MSG, "FASTOPEN") and flags.fastopen) posix.MSG.FASTOPEN else 0) |
1391 posix.MSG.NOSIGNAL;
1392
1393 for (outgoing_messages, 0..) |*msg, i| {
1394 netSendOne(k, handle, msg, posix_flags) catch |err| return .{ err, i };
1395 }
1396
1397 return .{ null, outgoing_messages.len };
1398}
1399
1400fn netSendOne(
1401 k: *Kqueue,
1402 handle: net.Socket.Handle,
1403 message: *net.OutgoingMessage,
1404 flags: u32,
1405) net.Socket.SendError!void {
1406 var addr: Io.Threaded.PosixAddress = undefined;
1407 var iovec: posix.iovec_const = .{ .base = @constCast(message.data_ptr), .len = message.data_len };
1408 const msg: posix.msghdr_const = .{
1409 .name = &addr.any,
1410 .namelen = Io.Threaded.addressToPosix(message.address, &addr),
1411 .iov = (&iovec)[0..1],
1412 .iovlen = 1,
1413 // OS returns EINVAL if this pointer is invalid even if controllen is zero.
1414 .control = if (message.control.len == 0) null else @constCast(message.control.ptr),
1415 .controllen = @intCast(message.control.len),
1416 .flags = 0,
1417 };
1418 while (true) {
1419 try k.checkCancel();
1420 const rc = posix.system.sendmsg(handle, &msg, flags);
1421 switch (posix.errno(rc)) {
1422 .SUCCESS => {
1423 message.data_len = @intCast(rc);
1424 return;
1425 },
1426 .INTR => continue,
1427 .CANCELED => return error.Canceled,
1428 .AGAIN => @panic("TODO register kevent"),
1429
1430 .ACCES => return error.AccessDenied,
1431 .ALREADY => return error.FastOpenAlreadyInProgress,
1432 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
1433 .CONNRESET => return error.ConnectionResetByPeer,
1434 .DESTADDRREQ => |err| return errnoBug(err),
1435 .FAULT => |err| return errnoBug(err),
1436 .INVAL => |err| return errnoBug(err),
1437 .ISCONN => |err| return errnoBug(err),
1438 .MSGSIZE => return error.MessageOversize,
1439 .NOBUFS => return error.SystemResources,
1440 .NOMEM => return error.SystemResources,
1441 .NOTSOCK => |err| return errnoBug(err),
1442 .OPNOTSUPP => |err| return errnoBug(err),
1443 .PIPE => return error.SocketUnconnected,
1444 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
1445 .HOSTUNREACH => return error.HostUnreachable,
1446 .NETUNREACH => return error.NetworkUnreachable,
1447 .NOTCONN => return error.SocketUnconnected,
1448 .NETDOWN => return error.NetworkDown,
1449 else => |err| return posix.unexpectedErrno(err),
1450 }
1451 }
1452}
1453
1454fn netReceive(
1455 userdata: ?*anyopaque,
1456 handle: net.Socket.Handle,
1457 message_buffer: []net.IncomingMessage,
1458 data_buffer: []u8,
1459 flags: net.ReceiveFlags,
1460 timeout: Io.Timeout,
1461) struct { ?net.Socket.ReceiveTimeoutError, usize } {
1462 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1463 _ = k;
1464 _ = handle;
1465 _ = message_buffer;
1466 _ = data_buffer;
1467 _ = flags;
1468 _ = timeout;
1469 @panic("TODO");
1470}
1471
1472fn netRead(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize {
1473 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1474
1475 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
1476 var i: usize = 0;
1477 for (data) |buf| {
1478 if (iovecs_buffer.len - i == 0) break;
1479 if (buf.len != 0) {
1480 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };
1481 i += 1;
1482 }
1483 }
1484 const dest = iovecs_buffer[0..i];
1485 assert(dest[0].len > 0);
1486
1487 while (true) {
1488 try k.checkCancel();
1489 const rc = posix.system.readv(fd, dest.ptr, @intCast(dest.len));
1490 switch (posix.errno(rc)) {
1491 .SUCCESS => return @intCast(rc),
1492 .INTR => continue,
1493 .CANCELED => return error.Canceled,
1494 .AGAIN => {
1495 const thread: *Thread = .current();
1496 const fiber = thread.currentFiber();
1497 const ident: u32 = @bitCast(fd);
1498 const filter = std.c.EVFILT.READ;
1499 const gop = thread.wait_queues.getOrPut(k.gpa, .{
1500 .ident = ident,
1501 .filter = filter,
1502 }) catch return error.SystemResources;
1503 if (gop.found_existing) {
1504 const tail_fiber = gop.value_ptr.*;
1505 assert(tail_fiber.queue_next == null);
1506 tail_fiber.queue_next = fiber;
1507 gop.value_ptr.* = fiber;
1508 } else {
1509 gop.value_ptr.* = fiber;
1510 const changes = [_]posix.Kevent{
1511 .{
1512 .ident = ident,
1513 .filter = filter,
1514 .flags = std.c.EV.ADD | std.c.EV.ONESHOT,
1515 .fflags = 0,
1516 .data = 0,
1517 .udata = @intFromPtr(fiber),
1518 },
1519 };
1520 assert(0 == (posix.kevent(thread.kq_fd, &changes, &.{}, null) catch |err| {
1521 @panic(@errorName(err)); // TODO
1522 }));
1523 }
1524 yield(k, null, .nothing);
1525 continue;
1526 },
1527
1528 .INVAL => |err| return errnoBug(err),
1529 .FAULT => |err| return errnoBug(err),
1530 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
1531 .NOBUFS => return error.SystemResources,
1532 .NOMEM => return error.SystemResources,
1533 .NOTCONN => return error.SocketUnconnected,
1534 .CONNRESET => return error.ConnectionResetByPeer,
1535 .TIMEDOUT => return error.Timeout,
1536 .PIPE => return error.SocketUnconnected,
1537 .NETDOWN => return error.NetworkDown,
1538 else => |err| return posix.unexpectedErrno(err),
1539 }
1540 }
1541}
1542
1543fn netWrite(userdata: ?*anyopaque, dest: net.Socket.Handle, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize {
1544 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1545 _ = k;
1546 _ = dest;
1547 _ = header;
1548 _ = data;
1549 _ = splat;
1550 @panic("TODO");
1551}
1552fn netClose(userdata: ?*anyopaque, handle: net.Socket.Handle) void {
1553 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1554 _ = k;
1555 _ = handle;
1556 @panic("TODO");
1557}
1558fn netInterfaceNameResolve(
1559 userdata: ?*anyopaque,
1560 name: *const net.Interface.Name,
1561) net.Interface.Name.ResolveError!net.Interface {
1562 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1563 _ = k;
1564 _ = name;
1565 @panic("TODO");
1566}
1567fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interface.NameError!net.Interface.Name {
1568 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1569 _ = k;
1570 _ = interface;
1571 @panic("TODO");
1572}
1573fn netLookup(
1574 userdata: ?*anyopaque,
1575 host_name: net.HostName,
1576 result: *Io.Queue(net.HostName.LookupResult),
1577 options: net.HostName.LookupOptions,
1578) void {
1579 const k: *Kqueue = @ptrCast(@alignCast(userdata));
1580 _ = k;
1581 _ = host_name;
1582 _ = result;
1583 _ = options;
1584 @panic("TODO");
1585}
1586
1587fn openSocketPosix(
1588 k: *Kqueue,
1589 family: posix.sa_family_t,
1590 options: IpAddress.BindOptions,
1591) error{
1592 AddressFamilyUnsupported,
1593 ProtocolUnsupportedBySystem,
1594 ProcessFdQuotaExceeded,
1595 SystemFdQuotaExceeded,
1596 SystemResources,
1597 ProtocolUnsupportedByAddressFamily,
1598 SocketModeUnsupported,
1599 OptionUnsupported,
1600 Unexpected,
1601 Canceled,
1602}!posix.socket_t {
1603 const mode = Io.Threaded.posixSocketMode(options.mode);
1604 const protocol = Io.Threaded.posixProtocol(options.protocol);
1605 const socket_fd = while (true) {
1606 try k.checkCancel();
1607 const flags: u32 = mode | if (Io.Threaded.socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC;
1608 const socket_rc = posix.system.socket(family, flags, protocol);
1609 switch (posix.errno(socket_rc)) {
1610 .SUCCESS => {
1611 const fd: posix.fd_t = @intCast(socket_rc);
1612 errdefer posix.close(fd);
1613 if (Io.Threaded.socket_flags_unsupported) {
1614 while (true) {
1615 try k.checkCancel();
1616 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) {
1617 .SUCCESS => break,
1618 .INTR => continue,
1619 .CANCELED => return error.Canceled,
1620 else => |err| return posix.unexpectedErrno(err),
1621 }
1622 }
1623
1624 var fl_flags: usize = while (true) {
1625 try k.checkCancel();
1626 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));
1627 switch (posix.errno(rc)) {
1628 .SUCCESS => break @intCast(rc),
1629 .INTR => continue,
1630 .CANCELED => return error.Canceled,
1631 else => |err| return posix.unexpectedErrno(err),
1632 }
1633 };
1634 fl_flags |= @as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK"));
1635 while (true) {
1636 try k.checkCancel();
1637 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, fl_flags))) {
1638 .SUCCESS => break,
1639 .INTR => continue,
1640 .CANCELED => return error.Canceled,
1641 else => |err| return posix.unexpectedErrno(err),
1642 }
1643 }
1644 }
1645 break fd;
1646 },
1647 .INTR => continue,
1648 .CANCELED => return error.Canceled,
1649
1650 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
1651 .INVAL => return error.ProtocolUnsupportedBySystem,
1652 .MFILE => return error.ProcessFdQuotaExceeded,
1653 .NFILE => return error.SystemFdQuotaExceeded,
1654 .NOBUFS => return error.SystemResources,
1655 .NOMEM => return error.SystemResources,
1656 .PROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily,
1657 .PROTOTYPE => return error.SocketModeUnsupported,
1658 else => |err| return posix.unexpectedErrno(err),
1659 }
1660 };
1661 errdefer posix.close(socket_fd);
1662
1663 if (options.ip6_only) {
1664 if (posix.IPV6 == void) return error.OptionUnsupported;
1665 try setSocketOption(k, socket_fd, posix.IPPROTO.IPV6, posix.IPV6.V6ONLY, 0);
1666 }
1667
1668 return socket_fd;
1669}
1670
1671fn posixBind(
1672 k: *Kqueue,
1673 socket_fd: posix.socket_t,
1674 addr: *const posix.sockaddr,
1675 addr_len: posix.socklen_t,
1676) !void {
1677 while (true) {
1678 try k.checkCancel();
1679 switch (posix.errno(posix.system.bind(socket_fd, addr, addr_len))) {
1680 .SUCCESS => break,
1681 .INTR => continue,
1682 .CANCELED => return error.Canceled,
1683
1684 .ADDRINUSE => return error.AddressInUse,
1685 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
1686 .INVAL => |err| return errnoBug(err), // invalid parameters
1687 .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd`
1688 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
1689 .ADDRNOTAVAIL => return error.AddressUnavailable,
1690 .FAULT => |err| return errnoBug(err), // invalid `addr` pointer
1691 .NOMEM => return error.SystemResources,
1692 else => |err| return posix.unexpectedErrno(err),
1693 }
1694 }
1695}
1696
1697fn posixGetSockName(k: *Kqueue, socket_fd: posix.fd_t, addr: *posix.sockaddr, addr_len: *posix.socklen_t) !void {
1698 while (true) {
1699 try k.checkCancel();
1700 switch (posix.errno(posix.system.getsockname(socket_fd, addr, addr_len))) {
1701 .SUCCESS => break,
1702 .INTR => continue,
1703 .CANCELED => return error.Canceled,
1704
1705 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
1706 .FAULT => |err| return errnoBug(err),
1707 .INVAL => |err| return errnoBug(err), // invalid parameters
1708 .NOTSOCK => |err| return errnoBug(err), // always a race condition
1709 .NOBUFS => return error.SystemResources,
1710 else => |err| return posix.unexpectedErrno(err),
1711 }
1712 }
1713}
1714
1715fn setSocketOption(k: *Kqueue, fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void {
1716 const o: []const u8 = @ptrCast(&option);
1717 while (true) {
1718 try k.checkCancel();
1719 switch (posix.errno(posix.system.setsockopt(fd, level, opt_name, o.ptr, @intCast(o.len)))) {
1720 .SUCCESS => return,
1721 .INTR => continue,
1722 .CANCELED => return error.Canceled,
1723
1724 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
1725 .NOTSOCK => |err| return errnoBug(err),
1726 .INVAL => |err| return errnoBug(err),
1727 .FAULT => |err| return errnoBug(err),
1728 else => |err| return posix.unexpectedErrno(err),
1729 }
1730 }
1731}
1732
1733fn checkCancel(k: *Kqueue) error{Canceled}!void {
1734 if (cancelRequested(k)) return error.Canceled;
1735}
1736
1737const Condition = struct {
1738 tail: *Fiber,
1739 event: union(enum) {
1740 queued,
1741 wake: Io.Condition.Wake,
1742 },
1743};
lib/std/Io/Threaded.zig created+6156
......@@ -0,0 +1,6156 @@
1const Threaded = @This();
2
3const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5const is_windows = native_os == .windows;
6const windows = std.os.windows;
7const ws2_32 = std.os.windows.ws2_32;
8const is_debug = builtin.mode == .Debug;
9
10const std = @import("../std.zig");
11const Io = std.Io;
12const net = std.Io.net;
13const HostName = std.Io.net.HostName;
14const IpAddress = std.Io.net.IpAddress;
15const Allocator = std.mem.Allocator;
16const assert = std.debug.assert;
17const posix = std.posix;
18
19/// Thread-safe.
20allocator: Allocator,
21mutex: std.Thread.Mutex = .{},
22cond: std.Thread.Condition = .{},
23run_queue: std.SinglyLinkedList = .{},
24join_requested: bool = false,
25threads: std.ArrayListUnmanaged(std.Thread),
26stack_size: usize,
27cpu_count: std.Thread.CpuCountError!usize,
28concurrent_count: usize,
29
30wsa: if (is_windows) Wsa else struct {} = .{},
31
32have_signal_handler: bool,
33old_sig_io: if (have_sig_io) posix.Sigaction else void,
34old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void,
35
36threadlocal var current_closure: ?*Closure = null;
37
38const max_iovecs_len = 8;
39const splat_buffer_size = 64;
40
41comptime {
42 if (@TypeOf(posix.IOV_MAX) != void) assert(max_iovecs_len <= posix.IOV_MAX);
43}
44
45const CancelId = enum(usize) {
46 none = 0,
47 canceling = std.math.maxInt(usize),
48 _,
49
50 const ThreadId = if (std.Thread.use_pthreads) std.c.pthread_t else std.Thread.Id;
51
52 fn currentThread() CancelId {
53 if (std.Thread.use_pthreads) {
54 return @enumFromInt(@intFromPtr(std.c.pthread_self()));
55 } else {
56 return @enumFromInt(std.Thread.getCurrentId());
57 }
58 }
59
60 fn toThreadId(cancel_id: CancelId) ThreadId {
61 if (std.Thread.use_pthreads) {
62 return @ptrFromInt(@intFromEnum(cancel_id));
63 } else {
64 return @intCast(@intFromEnum(cancel_id));
65 }
66 }
67};
68
69const Closure = struct {
70 start: Start,
71 node: std.SinglyLinkedList.Node = .{},
72 cancel_tid: CancelId,
73 /// Whether this task bumps minimum number of threads in the pool.
74 is_concurrent: bool,
75
76 const Start = *const fn (*Closure) void;
77
78 fn requestCancel(closure: *Closure) void {
79 switch (@atomicRmw(CancelId, &closure.cancel_tid, .Xchg, .canceling, .acq_rel)) {
80 .none, .canceling => {},
81 else => |tid| {
82 if (std.Thread.use_pthreads) {
83 const rc = std.c.pthread_kill(tid.toThreadId(), .IO);
84 if (is_debug) assert(rc == 0);
85 } else if (native_os == .linux) {
86 _ = std.os.linux.tgkill(std.os.linux.getpid(), @bitCast(tid.toThreadId()), .IO);
87 }
88 },
89 }
90 }
91};
92
93pub const InitError = std.Thread.CpuCountError || Allocator.Error;
94
95/// Related:
96/// * `init_single_threaded`
97pub fn init(
98 /// Must be threadsafe. Only used for the following functions:
99 /// * `Io.VTable.async`
100 /// * `Io.VTable.concurrent`
101 /// * `Io.VTable.groupAsync`
102 /// If these functions are avoided, then `Allocator.failing` may be passed
103 /// here.
104 gpa: Allocator,
105) Threaded {
106 var t: Threaded = .{
107 .allocator = gpa,
108 .threads = .empty,
109 .stack_size = std.Thread.SpawnConfig.default_stack_size,
110 .cpu_count = std.Thread.getCpuCount(),
111 .concurrent_count = 0,
112 .old_sig_io = undefined,
113 .old_sig_pipe = undefined,
114 .have_signal_handler = false,
115 };
116
117 if (t.cpu_count) |n| {
118 t.threads.ensureTotalCapacityPrecise(gpa, n - 1) catch {};
119 } else |_| {}
120
121 if (posix.Sigaction != void) {
122 // This causes sending `posix.SIG.IO` to thread to interrupt blocking
123 // syscalls, returning `posix.E.INTR`.
124 const act: posix.Sigaction = .{
125 .handler = .{ .handler = doNothingSignalHandler },
126 .mask = posix.sigemptyset(),
127 .flags = 0,
128 };
129 if (have_sig_io) posix.sigaction(.IO, &act, &t.old_sig_io);
130 if (have_sig_pipe) posix.sigaction(.PIPE, &act, &t.old_sig_pipe);
131 t.have_signal_handler = true;
132 }
133
134 return t;
135}
136
137/// Statically initialize such that calls to `Io.VTable.concurrent` will fail
138/// with `error.ConcurrencyUnavailable`.
139///
140/// When initialized this way:
141/// * cancel requests have no effect.
142/// * `deinit` is safe, but unnecessary to call.
143pub const init_single_threaded: Threaded = .{
144 .allocator = .failing,
145 .threads = .empty,
146 .stack_size = std.Thread.SpawnConfig.default_stack_size,
147 .cpu_count = 1,
148 .concurrent_count = 0,
149 .old_sig_io = undefined,
150 .old_sig_pipe = undefined,
151 .have_signal_handler = false,
152};
153
154pub fn deinit(t: *Threaded) void {
155 const gpa = t.allocator;
156 t.join();
157 t.threads.deinit(gpa);
158 if (is_windows and t.wsa.status == .initialized) {
159 if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected();
160 }
161 if (posix.Sigaction != void and t.have_signal_handler) {
162 if (have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null);
163 if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null);
164 }
165 t.* = undefined;
166}
167
168fn join(t: *Threaded) void {
169 if (builtin.single_threaded) return;
170 {
171 t.mutex.lock();
172 defer t.mutex.unlock();
173 t.join_requested = true;
174 }
175 t.cond.broadcast();
176 for (t.threads.items) |thread| thread.join();
177}
178
179fn worker(t: *Threaded) void {
180 t.mutex.lock();
181 defer t.mutex.unlock();
182
183 while (true) {
184 while (t.run_queue.popFirst()) |closure_node| {
185 t.mutex.unlock();
186 const closure: *Closure = @fieldParentPtr("node", closure_node);
187 const is_concurrent = closure.is_concurrent;
188 closure.start(closure);
189 t.mutex.lock();
190 if (is_concurrent) {
191 t.concurrent_count -= 1;
192 }
193 }
194 if (t.join_requested) break;
195 t.cond.wait(&t.mutex);
196 }
197}
198
199pub fn io(t: *Threaded) Io {
200 return .{
201 .userdata = t,
202 .vtable = &.{
203 .async = async,
204 .concurrent = concurrent,
205 .await = await,
206 .cancel = cancel,
207 .cancelRequested = cancelRequested,
208 .select = select,
209
210 .groupAsync = groupAsync,
211 .groupWait = groupWait,
212 .groupCancel = groupCancel,
213
214 .mutexLock = mutexLock,
215 .mutexLockUncancelable = mutexLockUncancelable,
216 .mutexUnlock = mutexUnlock,
217
218 .conditionWait = conditionWait,
219 .conditionWaitUncancelable = conditionWaitUncancelable,
220 .conditionWake = conditionWake,
221
222 .dirMake = dirMake,
223 .dirMakePath = dirMakePath,
224 .dirMakeOpenPath = dirMakeOpenPath,
225 .dirStat = dirStat,
226 .dirStatPath = dirStatPath,
227 .fileStat = fileStat,
228 .dirAccess = dirAccess,
229 .dirCreateFile = dirCreateFile,
230 .dirOpenFile = dirOpenFile,
231 .dirOpenDir = dirOpenDir,
232 .dirClose = dirClose,
233 .fileClose = fileClose,
234 .fileWriteStreaming = fileWriteStreaming,
235 .fileWritePositional = fileWritePositional,
236 .fileReadStreaming = fileReadStreaming,
237 .fileReadPositional = fileReadPositional,
238 .fileSeekBy = fileSeekBy,
239 .fileSeekTo = fileSeekTo,
240 .openSelfExe = openSelfExe,
241
242 .now = now,
243 .sleep = sleep,
244
245 .netListenIp = switch (native_os) {
246 .windows => netListenIpWindows,
247 else => netListenIpPosix,
248 },
249 .netListenUnix = switch (native_os) {
250 .windows => netListenUnixWindows,
251 else => netListenUnixPosix,
252 },
253 .netAccept = switch (native_os) {
254 .windows => netAcceptWindows,
255 else => netAcceptPosix,
256 },
257 .netBindIp = switch (native_os) {
258 .windows => netBindIpWindows,
259 else => netBindIpPosix,
260 },
261 .netConnectIp = switch (native_os) {
262 .windows => netConnectIpWindows,
263 else => netConnectIpPosix,
264 },
265 .netConnectUnix = switch (native_os) {
266 .windows => netConnectUnixWindows,
267 else => netConnectUnixPosix,
268 },
269 .netClose = netClose,
270 .netRead = switch (native_os) {
271 .windows => netReadWindows,
272 else => netReadPosix,
273 },
274 .netWrite = switch (native_os) {
275 .windows => netWriteWindows,
276 else => netWritePosix,
277 },
278 .netSend = switch (native_os) {
279 .windows => netSendWindows,
280 else => netSendPosix,
281 },
282 .netReceive = switch (native_os) {
283 .windows => netReceiveWindows,
284 else => netReceivePosix,
285 },
286 .netInterfaceNameResolve = netInterfaceNameResolve,
287 .netInterfaceName = netInterfaceName,
288 .netLookup = netLookup,
289 },
290 };
291}
292
293/// Same as `io` but disables all networking functionality, which has
294/// an additional dependency on Windows (ws2_32).
295pub fn ioBasic(t: *Threaded) Io {
296 return .{
297 .userdata = t,
298 .vtable = &.{
299 .async = async,
300 .concurrent = concurrent,
301 .await = await,
302 .cancel = cancel,
303 .cancelRequested = cancelRequested,
304 .select = select,
305
306 .groupAsync = groupAsync,
307 .groupWait = groupWait,
308 .groupCancel = groupCancel,
309
310 .mutexLock = mutexLock,
311 .mutexLockUncancelable = mutexLockUncancelable,
312 .mutexUnlock = mutexUnlock,
313
314 .conditionWait = conditionWait,
315 .conditionWaitUncancelable = conditionWaitUncancelable,
316 .conditionWake = conditionWake,
317
318 .dirMake = dirMake,
319 .dirMakePath = dirMakePath,
320 .dirMakeOpenPath = dirMakeOpenPath,
321 .dirStat = dirStat,
322 .dirStatPath = dirStatPath,
323 .fileStat = fileStat,
324 .dirAccess = dirAccess,
325 .dirCreateFile = dirCreateFile,
326 .dirOpenFile = dirOpenFile,
327 .dirOpenDir = dirOpenDir,
328 .dirClose = dirClose,
329 .fileClose = fileClose,
330 .fileWriteStreaming = fileWriteStreaming,
331 .fileWritePositional = fileWritePositional,
332 .fileReadStreaming = fileReadStreaming,
333 .fileReadPositional = fileReadPositional,
334 .fileSeekBy = fileSeekBy,
335 .fileSeekTo = fileSeekTo,
336 .openSelfExe = openSelfExe,
337
338 .now = now,
339 .sleep = sleep,
340
341 .netListenIp = netListenIpUnavailable,
342 .netListenUnix = netListenUnixUnavailable,
343 .netAccept = netAcceptUnavailable,
344 .netBindIp = netBindIpUnavailable,
345 .netConnectIp = netConnectIpUnavailable,
346 .netConnectUnix = netConnectUnixUnavailable,
347 .netClose = netCloseUnavailable,
348 .netRead = netReadUnavailable,
349 .netWrite = netWriteUnavailable,
350 .netSend = netSendUnavailable,
351 .netReceive = netReceiveUnavailable,
352 .netInterfaceNameResolve = netInterfaceNameResolveUnavailable,
353 .netInterfaceName = netInterfaceNameUnavailable,
354 .netLookup = netLookupUnavailable,
355 },
356 };
357}
358
359pub const socket_flags_unsupported = native_os.isDarwin() or native_os == .haiku; // 💩💩
360const have_accept4 = !socket_flags_unsupported;
361const have_flock_open_flags = @hasField(posix.O, "EXLOCK");
362const have_networking = native_os != .wasi;
363const have_flock = @TypeOf(posix.system.flock) != void;
364const have_sendmmsg = native_os == .linux;
365const have_futex = switch (builtin.cpu.arch) {
366 .wasm32, .wasm64 => builtin.cpu.has(.wasm, .atomics),
367 else => true,
368};
369const have_preadv = switch (native_os) {
370 .windows, .haiku, .serenity => false, // 💩💩💩
371 else => true,
372};
373const have_sig_io = posix.SIG != void and @hasField(posix.SIG, "IO");
374const have_sig_pipe = posix.SIG != void and @hasField(posix.SIG, "PIPE");
375
376const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
377const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;
378const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;
379const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;
380const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;
381
382/// Trailing data:
383/// 1. context
384/// 2. result
385const AsyncClosure = struct {
386 closure: Closure,
387 func: *const fn (context: *anyopaque, result: *anyopaque) void,
388 reset_event: ResetEvent,
389 select_condition: ?*ResetEvent,
390 context_alignment: std.mem.Alignment,
391 result_offset: usize,
392
393 const done_reset_event: *ResetEvent = @ptrFromInt(@alignOf(ResetEvent));
394
395 fn start(closure: *Closure) void {
396 const ac: *AsyncClosure = @alignCast(@fieldParentPtr("closure", closure));
397 const tid: CancelId = .currentThread();
398 if (@cmpxchgStrong(CancelId, &closure.cancel_tid, .none, tid, .acq_rel, .acquire)) |cancel_tid| {
399 assert(cancel_tid == .canceling);
400 // Even though we already know the task is canceled, we must still
401 // run the closure in order to make the return value valid and in
402 // case there are side effects.
403 }
404 current_closure = closure;
405 ac.func(ac.contextPointer(), ac.resultPointer());
406 current_closure = null;
407
408 // In case a cancel happens after successful task completion, prevents
409 // signal from being delivered to the thread in `requestCancel`.
410 if (@cmpxchgStrong(CancelId, &closure.cancel_tid, tid, .none, .acq_rel, .acquire)) |cancel_tid| {
411 assert(cancel_tid == .canceling);
412 }
413
414 if (@atomicRmw(?*ResetEvent, &ac.select_condition, .Xchg, done_reset_event, .release)) |select_reset| {
415 assert(select_reset != done_reset_event);
416 select_reset.set();
417 }
418 ac.reset_event.set();
419 }
420
421 fn resultPointer(ac: *AsyncClosure) [*]u8 {
422 const base: [*]u8 = @ptrCast(ac);
423 return base + ac.result_offset;
424 }
425
426 fn contextPointer(ac: *AsyncClosure) [*]u8 {
427 const base: [*]u8 = @ptrCast(ac);
428 return base + ac.context_alignment.forward(@sizeOf(AsyncClosure));
429 }
430
431 fn waitAndFree(ac: *AsyncClosure, gpa: Allocator, result: []u8) void {
432 ac.reset_event.waitUncancelable();
433 @memcpy(result, ac.resultPointer()[0..result.len]);
434 free(ac, gpa, result.len);
435 }
436
437 fn free(ac: *AsyncClosure, gpa: Allocator, result_len: usize) void {
438 const base: [*]align(@alignOf(AsyncClosure)) u8 = @ptrCast(ac);
439 gpa.free(base[0 .. ac.result_offset + result_len]);
440 }
441};
442
443fn async(
444 userdata: ?*anyopaque,
445 result: []u8,
446 result_alignment: std.mem.Alignment,
447 context: []const u8,
448 context_alignment: std.mem.Alignment,
449 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
450) ?*Io.AnyFuture {
451 if (builtin.single_threaded) {
452 start(context.ptr, result.ptr);
453 return null;
454 }
455 const t: *Threaded = @ptrCast(@alignCast(userdata));
456 const cpu_count = t.cpu_count catch {
457 return concurrent(userdata, result.len, result_alignment, context, context_alignment, start) catch {
458 start(context.ptr, result.ptr);
459 return null;
460 };
461 };
462 const gpa = t.allocator;
463 const context_offset = context_alignment.forward(@sizeOf(AsyncClosure));
464 const result_offset = result_alignment.forward(context_offset + context.len);
465 const n = result_offset + result.len;
466 const ac: *AsyncClosure = @ptrCast(@alignCast(gpa.alignedAlloc(u8, .of(AsyncClosure), n) catch {
467 start(context.ptr, result.ptr);
468 return null;
469 }));
470
471 ac.* = .{
472 .closure = .{
473 .cancel_tid = .none,
474 .start = AsyncClosure.start,
475 .is_concurrent = false,
476 },
477 .func = start,
478 .context_alignment = context_alignment,
479 .result_offset = result_offset,
480 .reset_event = .unset,
481 .select_condition = null,
482 };
483
484 @memcpy(ac.contextPointer()[0..context.len], context);
485
486 t.mutex.lock();
487
488 const thread_capacity = cpu_count - 1 + t.concurrent_count;
489
490 t.threads.ensureTotalCapacityPrecise(gpa, thread_capacity) catch {
491 t.mutex.unlock();
492 ac.free(gpa, result.len);
493 start(context.ptr, result.ptr);
494 return null;
495 };
496
497 t.run_queue.prepend(&ac.closure.node);
498
499 if (t.threads.items.len < thread_capacity) {
500 const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch {
501 if (t.threads.items.len == 0) {
502 assert(t.run_queue.popFirst() == &ac.closure.node);
503 t.mutex.unlock();
504 ac.free(gpa, result.len);
505 start(context.ptr, result.ptr);
506 return null;
507 }
508 // Rely on other workers to do it.
509 t.mutex.unlock();
510 t.cond.signal();
511 return @ptrCast(ac);
512 };
513 t.threads.appendAssumeCapacity(thread);
514 }
515
516 t.mutex.unlock();
517 t.cond.signal();
518 return @ptrCast(ac);
519}
520
521fn concurrent(
522 userdata: ?*anyopaque,
523 result_len: usize,
524 result_alignment: std.mem.Alignment,
525 context: []const u8,
526 context_alignment: std.mem.Alignment,
527 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
528) Io.ConcurrentError!*Io.AnyFuture {
529 if (builtin.single_threaded) return error.ConcurrencyUnavailable;
530
531 const t: *Threaded = @ptrCast(@alignCast(userdata));
532 const cpu_count = t.cpu_count catch 1;
533 const gpa = t.allocator;
534 const context_offset = context_alignment.forward(@sizeOf(AsyncClosure));
535 const result_offset = result_alignment.forward(context_offset + context.len);
536 const n = result_offset + result_len;
537 const ac_bytes = gpa.alignedAlloc(u8, .of(AsyncClosure), n) catch
538 return error.ConcurrencyUnavailable;
539 const ac: *AsyncClosure = @ptrCast(@alignCast(ac_bytes));
540
541 ac.* = .{
542 .closure = .{
543 .cancel_tid = .none,
544 .start = AsyncClosure.start,
545 .is_concurrent = true,
546 },
547 .func = start,
548 .context_alignment = context_alignment,
549 .result_offset = result_offset,
550 .reset_event = .unset,
551 .select_condition = null,
552 };
553 @memcpy(ac.contextPointer()[0..context.len], context);
554
555 t.mutex.lock();
556
557 t.concurrent_count += 1;
558 const thread_capacity = cpu_count - 1 + t.concurrent_count;
559
560 t.threads.ensureTotalCapacity(gpa, thread_capacity) catch {
561 t.mutex.unlock();
562 ac.free(gpa, result_len);
563 return error.ConcurrencyUnavailable;
564 };
565
566 t.run_queue.prepend(&ac.closure.node);
567
568 if (t.threads.items.len < thread_capacity) {
569 const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch {
570 assert(t.run_queue.popFirst() == &ac.closure.node);
571 t.mutex.unlock();
572 ac.free(gpa, result_len);
573 return error.ConcurrencyUnavailable;
574 };
575 t.threads.appendAssumeCapacity(thread);
576 }
577
578 t.mutex.unlock();
579 t.cond.signal();
580 return @ptrCast(ac);
581}
582
583const GroupClosure = struct {
584 closure: Closure,
585 t: *Threaded,
586 group: *Io.Group,
587 /// Points to sibling `GroupClosure`. Used for walking the group to cancel all.
588 node: std.SinglyLinkedList.Node,
589 func: *const fn (*Io.Group, context: *anyopaque) void,
590 context_alignment: std.mem.Alignment,
591 context_len: usize,
592
593 fn start(closure: *Closure) void {
594 const gc: *GroupClosure = @alignCast(@fieldParentPtr("closure", closure));
595 const tid: CancelId = .currentThread();
596 const group = gc.group;
597 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
598 const reset_event: *ResetEvent = @ptrCast(&group.context);
599 if (@cmpxchgStrong(CancelId, &closure.cancel_tid, .none, tid, .acq_rel, .acquire)) |cancel_tid| {
600 assert(cancel_tid == .canceling);
601 // Even though we already know the task is canceled, we must still
602 // run the closure in case there are side effects.
603 }
604 current_closure = closure;
605 gc.func(group, gc.contextPointer());
606 current_closure = null;
607
608 // In case a cancel happens after successful task completion, prevents
609 // signal from being delivered to the thread in `requestCancel`.
610 if (@cmpxchgStrong(CancelId, &closure.cancel_tid, tid, .none, .acq_rel, .acquire)) |cancel_tid| {
611 assert(cancel_tid == .canceling);
612 }
613
614 const prev_state = group_state.fetchSub(sync_one_pending, .acq_rel);
615 assert((prev_state / sync_one_pending) > 0);
616 if (prev_state == (sync_one_pending | sync_is_waiting)) reset_event.set();
617 }
618
619 fn free(gc: *GroupClosure, gpa: Allocator) void {
620 const base: [*]align(@alignOf(GroupClosure)) u8 = @ptrCast(gc);
621 gpa.free(base[0..contextEnd(gc.context_alignment, gc.context_len)]);
622 }
623
624 fn contextOffset(context_alignment: std.mem.Alignment) usize {
625 return context_alignment.forward(@sizeOf(GroupClosure));
626 }
627
628 fn contextEnd(context_alignment: std.mem.Alignment, context_len: usize) usize {
629 return contextOffset(context_alignment) + context_len;
630 }
631
632 fn contextPointer(gc: *GroupClosure) [*]u8 {
633 const base: [*]u8 = @ptrCast(gc);
634 return base + contextOffset(gc.context_alignment);
635 }
636
637 const sync_is_waiting: usize = 1 << 0;
638 const sync_one_pending: usize = 1 << 1;
639};
640
641fn groupAsync(
642 userdata: ?*anyopaque,
643 group: *Io.Group,
644 context: []const u8,
645 context_alignment: std.mem.Alignment,
646 start: *const fn (*Io.Group, context: *const anyopaque) void,
647) void {
648 if (builtin.single_threaded) return start(group, context.ptr);
649 const t: *Threaded = @ptrCast(@alignCast(userdata));
650 const cpu_count = t.cpu_count catch 1;
651 const gpa = t.allocator;
652 const n = GroupClosure.contextEnd(context_alignment, context.len);
653 const gc: *GroupClosure = @ptrCast(@alignCast(gpa.alignedAlloc(u8, .of(GroupClosure), n) catch {
654 return start(group, context.ptr);
655 }));
656 gc.* = .{
657 .closure = .{
658 .cancel_tid = .none,
659 .start = GroupClosure.start,
660 .is_concurrent = false,
661 },
662 .t = t,
663 .group = group,
664 .node = undefined,
665 .func = start,
666 .context_alignment = context_alignment,
667 .context_len = context.len,
668 };
669 @memcpy(gc.contextPointer()[0..context.len], context);
670
671 t.mutex.lock();
672
673 // Append to the group linked list inside the mutex to make `Io.Group.async` thread-safe.
674 gc.node = .{ .next = @ptrCast(@alignCast(group.token)) };
675 group.token = &gc.node;
676
677 const thread_capacity = cpu_count - 1 + t.concurrent_count;
678
679 t.threads.ensureTotalCapacityPrecise(gpa, thread_capacity) catch {
680 t.mutex.unlock();
681 gc.free(gpa);
682 return start(group, context.ptr);
683 };
684
685 t.run_queue.prepend(&gc.closure.node);
686
687 if (t.threads.items.len < thread_capacity) {
688 const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch {
689 assert(t.run_queue.popFirst() == &gc.closure.node);
690 t.mutex.unlock();
691 gc.free(gpa);
692 return start(group, context.ptr);
693 };
694 t.threads.appendAssumeCapacity(thread);
695 }
696
697 // This needs to be done before unlocking the mutex to avoid a race with
698 // the associated task finishing.
699 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
700 const prev_state = group_state.fetchAdd(GroupClosure.sync_one_pending, .monotonic);
701 assert((prev_state / GroupClosure.sync_one_pending) < (std.math.maxInt(usize) / GroupClosure.sync_one_pending));
702
703 t.mutex.unlock();
704 t.cond.signal();
705}
706
707fn groupWait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void {
708 const t: *Threaded = @ptrCast(@alignCast(userdata));
709 const gpa = t.allocator;
710
711 if (builtin.single_threaded) return;
712
713 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
714 const reset_event: *ResetEvent = @ptrCast(&group.context);
715 const prev_state = group_state.fetchAdd(GroupClosure.sync_is_waiting, .acquire);
716 assert(prev_state & GroupClosure.sync_is_waiting == 0);
717 if ((prev_state / GroupClosure.sync_one_pending) > 0) reset_event.wait(t) catch |err| switch (err) {
718 error.Canceled => {
719 var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token));
720 while (true) {
721 const gc: *GroupClosure = @fieldParentPtr("node", node);
722 gc.closure.requestCancel();
723 node = node.next orelse break;
724 }
725 reset_event.waitUncancelable();
726 },
727 };
728
729 var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token));
730 while (true) {
731 const gc: *GroupClosure = @fieldParentPtr("node", node);
732 const node_next = node.next;
733 gc.free(gpa);
734 node = node_next orelse break;
735 }
736}
737
738fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void {
739 const t: *Threaded = @ptrCast(@alignCast(userdata));
740 const gpa = t.allocator;
741
742 if (builtin.single_threaded) return;
743
744 {
745 var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token));
746 while (true) {
747 const gc: *GroupClosure = @fieldParentPtr("node", node);
748 gc.closure.requestCancel();
749 node = node.next orelse break;
750 }
751 }
752
753 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
754 const reset_event: *ResetEvent = @ptrCast(&group.context);
755 const prev_state = group_state.fetchAdd(GroupClosure.sync_is_waiting, .acquire);
756 assert(prev_state & GroupClosure.sync_is_waiting == 0);
757 if ((prev_state / GroupClosure.sync_one_pending) > 0) reset_event.waitUncancelable();
758
759 {
760 var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token));
761 while (true) {
762 const gc: *GroupClosure = @fieldParentPtr("node", node);
763 const node_next = node.next;
764 gc.free(gpa);
765 node = node_next orelse break;
766 }
767 }
768}
769
770fn await(
771 userdata: ?*anyopaque,
772 any_future: *Io.AnyFuture,
773 result: []u8,
774 result_alignment: std.mem.Alignment,
775) void {
776 _ = result_alignment;
777 const t: *Threaded = @ptrCast(@alignCast(userdata));
778 const closure: *AsyncClosure = @ptrCast(@alignCast(any_future));
779 closure.waitAndFree(t.allocator, result);
780}
781
782fn cancel(
783 userdata: ?*anyopaque,
784 any_future: *Io.AnyFuture,
785 result: []u8,
786 result_alignment: std.mem.Alignment,
787) void {
788 _ = result_alignment;
789 const t: *Threaded = @ptrCast(@alignCast(userdata));
790 const ac: *AsyncClosure = @ptrCast(@alignCast(any_future));
791 ac.closure.requestCancel();
792 ac.waitAndFree(t.allocator, result);
793}
794
795fn cancelRequested(userdata: ?*anyopaque) bool {
796 const t: *Threaded = @ptrCast(@alignCast(userdata));
797 _ = t;
798 const closure = current_closure orelse return false;
799 return @atomicLoad(CancelId, &closure.cancel_tid, .acquire) == .canceling;
800}
801
802fn checkCancel(t: *Threaded) error{Canceled}!void {
803 if (cancelRequested(t)) return error.Canceled;
804}
805
806fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) Io.Cancelable!void {
807 if (builtin.single_threaded) unreachable; // Interface should have prevented this.
808 if (native_os == .netbsd) @panic("TODO");
809 const t: *Threaded = @ptrCast(@alignCast(userdata));
810 if (prev_state == .contended) {
811 try futexWait(t, @ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended));
812 }
813 while (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .contended, .acquire) != .unlocked) {
814 try futexWait(t, @ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended));
815 }
816}
817
818fn mutexLockUncancelable(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void {
819 if (builtin.single_threaded) unreachable; // Interface should have prevented this.
820 if (native_os == .netbsd) @panic("TODO");
821 _ = userdata;
822 if (prev_state == .contended) {
823 futexWaitUncancelable(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended));
824 }
825 while (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .contended, .acquire) != .unlocked) {
826 futexWaitUncancelable(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended));
827 }
828}
829
830fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void {
831 if (builtin.single_threaded) unreachable; // Interface should have prevented this.
832 if (native_os == .netbsd) @panic("TODO");
833 _ = userdata;
834 _ = prev_state;
835 if (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .unlocked, .release) == .contended) {
836 futexWake(@ptrCast(&mutex.state), 1);
837 }
838}
839
840fn conditionWaitUncancelable(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) void {
841 if (builtin.single_threaded) unreachable; // Deadlock.
842 if (native_os == .netbsd) @panic("TODO");
843 const t: *Threaded = @ptrCast(@alignCast(userdata));
844 const t_io = ioBasic(t);
845 comptime assert(@TypeOf(cond.state) == u64);
846 const ints: *[2]std.atomic.Value(u32) = @ptrCast(&cond.state);
847 const cond_state = &ints[0];
848 const cond_epoch = &ints[1];
849 const one_waiter = 1;
850 const waiter_mask = 0xffff;
851 const one_signal = 1 << 16;
852 const signal_mask = 0xffff << 16;
853 var epoch = cond_epoch.load(.acquire);
854 var state = cond_state.fetchAdd(one_waiter, .monotonic);
855 assert(state & waiter_mask != waiter_mask);
856 state += one_waiter;
857
858 mutex.unlock(t_io);
859 defer mutex.lockUncancelable(t_io);
860
861 while (true) {
862 futexWaitUncancelable(cond_epoch, epoch);
863 epoch = cond_epoch.load(.acquire);
864 state = cond_state.load(.monotonic);
865 while (state & signal_mask != 0) {
866 const new_state = state - one_waiter - one_signal;
867 state = cond_state.cmpxchgWeak(state, new_state, .acquire, .monotonic) orelse return;
868 }
869 }
870}
871
872fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) Io.Cancelable!void {
873 if (builtin.single_threaded) unreachable; // Deadlock.
874 if (native_os == .netbsd) @panic("TODO");
875 const t: *Threaded = @ptrCast(@alignCast(userdata));
876 const t_io = ioBasic(t);
877 comptime assert(@TypeOf(cond.state) == u64);
878 const ints: *[2]std.atomic.Value(u32) = @ptrCast(&cond.state);
879 const cond_state = &ints[0];
880 const cond_epoch = &ints[1];
881 const one_waiter = 1;
882 const waiter_mask = 0xffff;
883 const one_signal = 1 << 16;
884 const signal_mask = 0xffff << 16;
885 // Observe the epoch, then check the state again to see if we should wake up.
886 // The epoch must be observed before we check the state or we could potentially miss a wake() and deadlock:
887 //
888 // - T1: s = LOAD(&state)
889 // - T2: UPDATE(&s, signal)
890 // - T2: UPDATE(&epoch, 1) + FUTEX_WAKE(&epoch)
891 // - T1: e = LOAD(&epoch) (was reordered after the state load)
892 // - T1: s & signals == 0 -> FUTEX_WAIT(&epoch, e) (missed the state update + the epoch change)
893 //
894 // Acquire barrier to ensure the epoch load happens before the state load.
895 var epoch = cond_epoch.load(.acquire);
896 var state = cond_state.fetchAdd(one_waiter, .monotonic);
897 assert(state & waiter_mask != waiter_mask);
898 state += one_waiter;
899
900 mutex.unlock(t_io);
901 defer mutex.lockUncancelable(t_io);
902
903 while (true) {
904 try futexWait(t, cond_epoch, epoch);
905
906 epoch = cond_epoch.load(.acquire);
907 state = cond_state.load(.monotonic);
908
909 // Try to wake up by consuming a signal and decremented the waiter we
910 // added previously. Acquire barrier ensures code before the wake()
911 // which added the signal happens before we decrement it and return.
912 while (state & signal_mask != 0) {
913 const new_state = state - one_waiter - one_signal;
914 state = cond_state.cmpxchgWeak(state, new_state, .acquire, .monotonic) orelse return;
915 }
916 }
917}
918
919fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.Wake) void {
920 if (builtin.single_threaded) unreachable; // Nothing to wake up.
921 const t: *Threaded = @ptrCast(@alignCast(userdata));
922 _ = t;
923 comptime assert(@TypeOf(cond.state) == u64);
924 const ints: *[2]std.atomic.Value(u32) = @ptrCast(&cond.state);
925 const cond_state = &ints[0];
926 const cond_epoch = &ints[1];
927 const one_waiter = 1;
928 const waiter_mask = 0xffff;
929 const one_signal = 1 << 16;
930 const signal_mask = 0xffff << 16;
931 var state = cond_state.load(.monotonic);
932 while (true) {
933 const waiters = (state & waiter_mask) / one_waiter;
934 const signals = (state & signal_mask) / one_signal;
935
936 // Reserves which waiters to wake up by incrementing the signals count.
937 // Therefore, the signals count is always less than or equal to the
938 // waiters count. We don't need to Futex.wake if there's nothing to
939 // wake up or if other wake() threads have reserved to wake up the
940 // current waiters.
941 const wakeable = waiters - signals;
942 if (wakeable == 0) {
943 return;
944 }
945
946 const to_wake = switch (wake) {
947 .one => 1,
948 .all => wakeable,
949 };
950
951 // Reserve the amount of waiters to wake by incrementing the signals
952 // count. Release barrier ensures code before the wake() happens before
953 // the signal it posted and consumed by the wait() threads.
954 const new_state = state + (one_signal * to_wake);
955 state = cond_state.cmpxchgWeak(state, new_state, .release, .monotonic) orelse {
956 // Wake up the waiting threads we reserved above by changing the epoch value.
957 //
958 // A waiting thread could miss a wake up if *exactly* ((1<<32)-1)
959 // wake()s happen between it observing the epoch and sleeping on
960 // it. This is very unlikely due to how many precise amount of
961 // Futex.wake() calls that would be between the waiting thread's
962 // potential preemption.
963 //
964 // Release barrier ensures the signal being added to the state
965 // happens before the epoch is changed. If not, the waiting thread
966 // could potentially deadlock from missing both the state and epoch
967 // change:
968 //
969 // - T2: UPDATE(&epoch, 1) (reordered before the state change)
970 // - T1: e = LOAD(&epoch)
971 // - T1: s = LOAD(&state)
972 // - T2: UPDATE(&state, signal) + FUTEX_WAKE(&epoch)
973 // - T1: s & signals == 0 -> FUTEX_WAIT(&epoch, e) (missed both epoch change and state change)
974 _ = cond_epoch.fetchAdd(1, .release);
975 if (native_os == .netbsd) @panic("TODO");
976 futexWake(cond_epoch, to_wake);
977 return;
978 };
979 }
980}
981
982const dirMake = switch (native_os) {
983 .windows => dirMakeWindows,
984 .wasi => dirMakeWasi,
985 else => dirMakePosix,
986};
987
988fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
989 const t: *Threaded = @ptrCast(@alignCast(userdata));
990
991 var path_buffer: [posix.PATH_MAX]u8 = undefined;
992 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
993
994 while (true) {
995 try t.checkCancel();
996 switch (posix.errno(posix.system.mkdirat(dir.handle, sub_path_posix, mode))) {
997 .SUCCESS => return,
998 .INTR => continue,
999 .CANCELED => return error.Canceled,
1000
1001 .ACCES => return error.AccessDenied,
1002 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
1003 .PERM => return error.PermissionDenied,
1004 .DQUOT => return error.DiskQuota,
1005 .EXIST => return error.PathAlreadyExists,
1006 .FAULT => |err| return errnoBug(err),
1007 .LOOP => return error.SymLinkLoop,
1008 .MLINK => return error.LinkQuotaExceeded,
1009 .NAMETOOLONG => return error.NameTooLong,
1010 .NOENT => return error.FileNotFound,
1011 .NOMEM => return error.SystemResources,
1012 .NOSPC => return error.NoSpaceLeft,
1013 .NOTDIR => return error.NotDir,
1014 .ROFS => return error.ReadOnlyFileSystem,
1015 // dragonfly: when dir_fd is unlinked from filesystem
1016 .NOTCONN => return error.FileNotFound,
1017 .ILSEQ => return error.BadPathName,
1018 else => |err| return posix.unexpectedErrno(err),
1019 }
1020 }
1021}
1022
1023fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
1024 if (builtin.link_libc) return dirMakePosix(userdata, dir, sub_path, mode);
1025 const t: *Threaded = @ptrCast(@alignCast(userdata));
1026 while (true) {
1027 try t.checkCancel();
1028 switch (std.os.wasi.path_create_directory(dir.handle, sub_path.ptr, sub_path.len)) {
1029 .SUCCESS => return,
1030 .INTR => continue,
1031 .CANCELED => return error.Canceled,
1032
1033 .ACCES => return error.AccessDenied,
1034 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
1035 .PERM => return error.PermissionDenied,
1036 .DQUOT => return error.DiskQuota,
1037 .EXIST => return error.PathAlreadyExists,
1038 .FAULT => |err| return errnoBug(err),
1039 .LOOP => return error.SymLinkLoop,
1040 .MLINK => return error.LinkQuotaExceeded,
1041 .NAMETOOLONG => return error.NameTooLong,
1042 .NOENT => return error.FileNotFound,
1043 .NOMEM => return error.SystemResources,
1044 .NOSPC => return error.NoSpaceLeft,
1045 .NOTDIR => return error.NotDir,
1046 .ROFS => return error.ReadOnlyFileSystem,
1047 .NOTCAPABLE => return error.AccessDenied,
1048 .ILSEQ => return error.BadPathName,
1049 else => |err| return posix.unexpectedErrno(err),
1050 }
1051 }
1052}
1053
1054fn dirMakeWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
1055 const t: *Threaded = @ptrCast(@alignCast(userdata));
1056 try t.checkCancel();
1057
1058 const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
1059 _ = mode;
1060 const sub_dir_handle = windows.OpenFile(sub_path_w.span(), .{
1061 .dir = dir.handle,
1062 .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
1063 .creation = windows.FILE_CREATE,
1064 .filter = .dir_only,
1065 }) catch |err| switch (err) {
1066 error.IsDir => return error.Unexpected,
1067 error.PipeBusy => return error.Unexpected,
1068 error.NoDevice => return error.Unexpected,
1069 error.WouldBlock => return error.Unexpected,
1070 error.AntivirusInterference => return error.Unexpected,
1071 else => |e| return e,
1072 };
1073 windows.CloseHandle(sub_dir_handle);
1074}
1075
1076const dirMakePath = switch (native_os) {
1077 .windows => dirMakePathWindows,
1078 else => dirMakePathPosix,
1079};
1080
1081fn dirMakePathPosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
1082 const t: *Threaded = @ptrCast(@alignCast(userdata));
1083 _ = t;
1084 _ = dir;
1085 _ = sub_path;
1086 _ = mode;
1087 @panic("TODO implement dirMakePathPosix");
1088}
1089
1090fn dirMakePathWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
1091 const t: *Threaded = @ptrCast(@alignCast(userdata));
1092 _ = t;
1093 _ = dir;
1094 _ = sub_path;
1095 _ = mode;
1096 @panic("TODO implement dirMakePathWindows");
1097}
1098
1099const dirMakeOpenPath = switch (native_os) {
1100 .windows => dirMakeOpenPathWindows,
1101 .wasi => dirMakeOpenPathWasi,
1102 else => dirMakeOpenPathPosix,
1103};
1104
1105fn dirMakeOpenPathPosix(
1106 userdata: ?*anyopaque,
1107 dir: Io.Dir,
1108 sub_path: []const u8,
1109 options: Io.Dir.OpenOptions,
1110) Io.Dir.MakeOpenPathError!Io.Dir {
1111 const t: *Threaded = @ptrCast(@alignCast(userdata));
1112 const t_io = ioBasic(t);
1113 return dirOpenDirPosix(t, dir, sub_path, options) catch |err| switch (err) {
1114 error.FileNotFound => {
1115 try dir.makePath(t_io, sub_path);
1116 return dirOpenDirPosix(t, dir, sub_path, options);
1117 },
1118 else => |e| return e,
1119 };
1120}
1121
1122fn dirMakeOpenPathWindows(
1123 userdata: ?*anyopaque,
1124 dir: Io.Dir,
1125 sub_path: []const u8,
1126 options: Io.Dir.OpenOptions,
1127) Io.Dir.MakeOpenPathError!Io.Dir {
1128 const t: *Threaded = @ptrCast(@alignCast(userdata));
1129 const w = windows;
1130 const access_mask = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1131 w.SYNCHRONIZE | w.FILE_TRAVERSE |
1132 (if (options.iterate) w.FILE_LIST_DIRECTORY else @as(u32, 0));
1133
1134 var it = try std.fs.path.componentIterator(sub_path);
1135 // If there are no components in the path, then create a dummy component with the full path.
1136 var component: std.fs.path.NativeComponentIterator.Component = it.last() orelse .{
1137 .name = "",
1138 .path = sub_path,
1139 };
1140
1141 while (true) {
1142 try t.checkCancel();
1143
1144 const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, component.path);
1145 const sub_path_w = sub_path_w_array.span();
1146 const is_last = it.peekNext() == null;
1147 const create_disposition: u32 = if (is_last) w.FILE_OPEN_IF else w.FILE_CREATE;
1148
1149 var result: Io.Dir = .{ .handle = undefined };
1150
1151 const path_len_bytes: u16 = @intCast(sub_path_w.len * 2);
1152 var nt_name: w.UNICODE_STRING = .{
1153 .Length = path_len_bytes,
1154 .MaximumLength = path_len_bytes,
1155 .Buffer = @constCast(sub_path_w.ptr),
1156 };
1157 var attr: w.OBJECT_ATTRIBUTES = .{
1158 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
1159 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
1160 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
1161 .ObjectName = &nt_name,
1162 .SecurityDescriptor = null,
1163 .SecurityQualityOfService = null,
1164 };
1165 const open_reparse_point: w.DWORD = if (!options.follow_symlinks) w.FILE_OPEN_REPARSE_POINT else 0x0;
1166 var io_status_block: w.IO_STATUS_BLOCK = undefined;
1167 const rc = w.ntdll.NtCreateFile(
1168 &result.handle,
1169 access_mask,
1170 &attr,
1171 &io_status_block,
1172 null,
1173 w.FILE_ATTRIBUTE_NORMAL,
1174 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
1175 create_disposition,
1176 w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
1177 null,
1178 0,
1179 );
1180
1181 switch (rc) {
1182 .SUCCESS => {
1183 component = it.next() orelse return result;
1184 w.CloseHandle(result.handle);
1185 continue;
1186 },
1187 .OBJECT_NAME_INVALID => return error.BadPathName,
1188 .OBJECT_NAME_COLLISION => {
1189 assert(!is_last);
1190 // stat the file and return an error if it's not a directory
1191 // this is important because otherwise a dangling symlink
1192 // could cause an infinite loop
1193 check_dir: {
1194 // workaround for windows, see https://github.com/ziglang/zig/issues/16738
1195 const fstat = dirStatPathWindows(t, dir, component.path, .{
1196 .follow_symlinks = options.follow_symlinks,
1197 }) catch |stat_err| switch (stat_err) {
1198 error.IsDir => break :check_dir,
1199 else => |e| return e,
1200 };
1201 if (fstat.kind != .directory) return error.NotDir;
1202 }
1203
1204 component = it.next().?;
1205 continue;
1206 },
1207
1208 .OBJECT_NAME_NOT_FOUND,
1209 .OBJECT_PATH_NOT_FOUND,
1210 => {
1211 component = it.previous() orelse return error.FileNotFound;
1212 continue;
1213 },
1214
1215 .NOT_A_DIRECTORY => return error.NotDir,
1216 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
1217 // and the directory is trying to be opened for iteration.
1218 .ACCESS_DENIED => return error.AccessDenied,
1219 .INVALID_PARAMETER => |err| return w.statusBug(err),
1220 else => return w.unexpectedStatus(rc),
1221 }
1222 }
1223}
1224
1225fn dirMakeOpenPathWasi(
1226 userdata: ?*anyopaque,
1227 dir: Io.Dir,
1228 sub_path: []const u8,
1229 options: Io.Dir.OpenOptions,
1230) Io.Dir.MakeOpenPathError!Io.Dir {
1231 const t: *Threaded = @ptrCast(@alignCast(userdata));
1232 const t_io = ioBasic(t);
1233 return dirOpenDirWasi(t, dir, sub_path, options) catch |err| switch (err) {
1234 error.FileNotFound => {
1235 try dir.makePath(t_io, sub_path);
1236 return dirOpenDirWasi(t, dir, sub_path, options);
1237 },
1238 else => |e| return e,
1239 };
1240}
1241
1242fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat {
1243 const t: *Threaded = @ptrCast(@alignCast(userdata));
1244 try t.checkCancel();
1245
1246 _ = dir;
1247 @panic("TODO implement dirStat");
1248}
1249
1250const dirStatPath = switch (native_os) {
1251 .linux => dirStatPathLinux,
1252 .windows => dirStatPathWindows,
1253 .wasi => dirStatPathWasi,
1254 else => dirStatPathPosix,
1255};
1256
1257fn dirStatPathLinux(
1258 userdata: ?*anyopaque,
1259 dir: Io.Dir,
1260 sub_path: []const u8,
1261 options: Io.Dir.StatPathOptions,
1262) Io.Dir.StatPathError!Io.File.Stat {
1263 const t: *Threaded = @ptrCast(@alignCast(userdata));
1264 const linux = std.os.linux;
1265
1266 var path_buffer: [posix.PATH_MAX]u8 = undefined;
1267 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
1268
1269 const flags: u32 = linux.AT.NO_AUTOMOUNT |
1270 @as(u32, if (!options.follow_symlinks) linux.AT.SYMLINK_NOFOLLOW else 0);
1271
1272 while (true) {
1273 try t.checkCancel();
1274 var statx = std.mem.zeroes(linux.Statx);
1275 const rc = linux.statx(
1276 dir.handle,
1277 sub_path_posix,
1278 flags,
1279 linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME,
1280 &statx,
1281 );
1282 switch (linux.E.init(rc)) {
1283 .SUCCESS => return statFromLinux(&statx),
1284 .INTR => continue,
1285 .CANCELED => return error.Canceled,
1286
1287 .ACCES => return error.AccessDenied,
1288 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
1289 .FAULT => |err| return errnoBug(err),
1290 .INVAL => |err| return errnoBug(err),
1291 .LOOP => return error.SymLinkLoop,
1292 .NAMETOOLONG => |err| return errnoBug(err), // Handled by pathToPosix() above.
1293 .NOENT => return error.FileNotFound,
1294 .NOTDIR => return error.NotDir,
1295 .NOMEM => return error.SystemResources,
1296 else => |err| return posix.unexpectedErrno(err),
1297 }
1298 }
1299}
1300
1301fn dirStatPathPosix(
1302 userdata: ?*anyopaque,
1303 dir: Io.Dir,
1304 sub_path: []const u8,
1305 options: Io.Dir.StatPathOptions,
1306) Io.Dir.StatPathError!Io.File.Stat {
1307 const t: *Threaded = @ptrCast(@alignCast(userdata));
1308
1309 var path_buffer: [posix.PATH_MAX]u8 = undefined;
1310 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
1311
1312 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
1313
1314 while (true) {
1315 try t.checkCancel();
1316 var stat = std.mem.zeroes(posix.Stat);
1317 switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &stat, flags))) {
1318 .SUCCESS => return statFromPosix(&stat),
1319 .INTR => continue,
1320 .CANCELED => return error.Canceled,
1321
1322 .INVAL => |err| return errnoBug(err),
1323 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
1324 .NOMEM => return error.SystemResources,
1325 .ACCES => return error.AccessDenied,
1326 .PERM => return error.PermissionDenied,
1327 .FAULT => |err| return errnoBug(err),
1328 .NAMETOOLONG => return error.NameTooLong,
1329 .LOOP => return error.SymLinkLoop,
1330 .NOENT => return error.FileNotFound,
1331 .NOTDIR => return error.FileNotFound,
1332 .ILSEQ => return error.BadPathName,
1333 else => |err| return posix.unexpectedErrno(err),
1334 }
1335 }
1336}
1337
1338fn dirStatPathWindows(
1339 userdata: ?*anyopaque,
1340 dir: Io.Dir,
1341 sub_path: []const u8,
1342 options: Io.Dir.StatPathOptions,
1343) Io.Dir.StatPathError!Io.File.Stat {
1344 const t: *Threaded = @ptrCast(@alignCast(userdata));
1345 const file = try dirOpenFileWindows(t, dir, sub_path, .{
1346 .follow_symlinks = options.follow_symlinks,
1347 });
1348 defer windows.CloseHandle(file.handle);
1349 return fileStatWindows(t, file);
1350}
1351
1352fn dirStatPathWasi(
1353 userdata: ?*anyopaque,
1354 dir: Io.Dir,
1355 sub_path: []const u8,
1356 options: Io.Dir.StatPathOptions,
1357) Io.Dir.StatPathError!Io.File.Stat {
1358 if (builtin.link_libc) return dirStatPathPosix(userdata, dir, sub_path, options);
1359 const t: *Threaded = @ptrCast(@alignCast(userdata));
1360 const wasi = std.os.wasi;
1361 const flags: wasi.lookupflags_t = .{
1362 .SYMLINK_FOLLOW = options.follow_symlinks,
1363 };
1364 var stat: wasi.filestat_t = undefined;
1365 while (true) {
1366 try t.checkCancel();
1367 switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) {
1368 .SUCCESS => return statFromWasi(&stat),
1369 .INTR => continue,
1370 .CANCELED => return error.Canceled,
1371
1372 .INVAL => |err| return errnoBug(err),
1373 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
1374 .NOMEM => return error.SystemResources,
1375 .ACCES => return error.AccessDenied,
1376 .FAULT => |err| return errnoBug(err),
1377 .NAMETOOLONG => return error.NameTooLong,
1378 .NOENT => return error.FileNotFound,
1379 .NOTDIR => return error.FileNotFound,
1380 .NOTCAPABLE => return error.AccessDenied,
1381 .ILSEQ => return error.BadPathName,
1382 else => |err| return posix.unexpectedErrno(err),
1383 }
1384 }
1385}
1386
1387const fileStat = switch (native_os) {
1388 .linux => fileStatLinux,
1389 .windows => fileStatWindows,
1390 .wasi => fileStatWasi,
1391 else => fileStatPosix,
1392};
1393
1394fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {
1395 const t: *Threaded = @ptrCast(@alignCast(userdata));
1396
1397 if (posix.Stat == void) return error.Streaming;
1398
1399 while (true) {
1400 try t.checkCancel();
1401 var stat = std.mem.zeroes(posix.Stat);
1402 switch (posix.errno(fstat_sym(file.handle, &stat))) {
1403 .SUCCESS => return statFromPosix(&stat),
1404 .INTR => continue,
1405 .CANCELED => return error.Canceled,
1406
1407 .INVAL => |err| return errnoBug(err),
1408 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
1409 .NOMEM => return error.SystemResources,
1410 .ACCES => return error.AccessDenied,
1411 else => |err| return posix.unexpectedErrno(err),
1412 }
1413 }
1414}
1415
1416fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {
1417 const t: *Threaded = @ptrCast(@alignCast(userdata));
1418 const linux = std.os.linux;
1419 while (true) {
1420 try t.checkCancel();
1421 var statx = std.mem.zeroes(linux.Statx);
1422 const rc = linux.statx(
1423 file.handle,
1424 "",
1425 linux.AT.EMPTY_PATH,
1426 linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME,
1427 &statx,
1428 );
1429 switch (linux.E.init(rc)) {
1430 .SUCCESS => return statFromLinux(&statx),
1431 .INTR => continue,
1432 .CANCELED => return error.Canceled,
1433
1434 .ACCES => |err| return errnoBug(err),
1435 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
1436 .FAULT => |err| return errnoBug(err),
1437 .INVAL => |err| return errnoBug(err),
1438 .LOOP => |err| return errnoBug(err),
1439 .NAMETOOLONG => |err| return errnoBug(err),
1440 .NOENT => |err| return errnoBug(err),
1441 .NOMEM => return error.SystemResources,
1442 .NOTDIR => |err| return errnoBug(err),
1443 else => |err| return posix.unexpectedErrno(err),
1444 }
1445 }
1446}
1447
1448fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {
1449 const t: *Threaded = @ptrCast(@alignCast(userdata));
1450 try t.checkCancel();
1451
1452 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1453 var info: windows.FILE_ALL_INFORMATION = undefined;
1454 const rc = windows.ntdll.NtQueryInformationFile(file.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
1455 switch (rc) {
1456 .SUCCESS => {},
1457 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
1458 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
1459 // (name, volume name, etc) we don't care about.
1460 .BUFFER_OVERFLOW => {},
1461 .INVALID_PARAMETER => unreachable,
1462 .ACCESS_DENIED => return error.AccessDenied,
1463 else => return windows.unexpectedStatus(rc),
1464 }
1465 return .{
1466 .inode = info.InternalInformation.IndexNumber,
1467 .size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
1468 .mode = 0,
1469 .kind = if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) reparse_point: {
1470 var tag_info: windows.FILE_ATTRIBUTE_TAG_INFO = undefined;
1471 const tag_rc = windows.ntdll.NtQueryInformationFile(file.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE_ATTRIBUTE_TAG_INFO), .FileAttributeTagInformation);
1472 switch (tag_rc) {
1473 .SUCCESS => {},
1474 // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors
1475 // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e
1476 .INFO_LENGTH_MISMATCH => unreachable,
1477 .ACCESS_DENIED => return error.AccessDenied,
1478 else => return windows.unexpectedStatus(rc),
1479 }
1480 if (tag_info.ReparseTag & windows.reparse_tag_name_surrogate_bit != 0) {
1481 break :reparse_point .sym_link;
1482 }
1483 // Unknown reparse point
1484 break :reparse_point .unknown;
1485 } else if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0)
1486 .directory
1487 else
1488 .file,
1489 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
1490 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
1491 .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime),
1492 };
1493}
1494
1495fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {
1496 if (builtin.link_libc) return fileStatPosix(userdata, file);
1497 const t: *Threaded = @ptrCast(@alignCast(userdata));
1498 while (true) {
1499 try t.checkCancel();
1500 var stat: std.os.wasi.filestat_t = undefined;
1501 switch (std.os.wasi.fd_filestat_get(file.handle, &stat)) {
1502 .SUCCESS => return statFromWasi(&stat),
1503 .INTR => continue,
1504 .CANCELED => return error.Canceled,
1505
1506 .INVAL => |err| return errnoBug(err),
1507 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
1508 .NOMEM => return error.SystemResources,
1509 .ACCES => return error.AccessDenied,
1510 .NOTCAPABLE => return error.AccessDenied,
1511 else => |err| return posix.unexpectedErrno(err),
1512 }
1513 }
1514}
1515
1516const dirAccess = switch (native_os) {
1517 .windows => dirAccessWindows,
1518 .wasi => dirAccessWasi,
1519 else => dirAccessPosix,
1520};
1521
1522fn dirAccessPosix(
1523 userdata: ?*anyopaque,
1524 dir: Io.Dir,
1525 sub_path: []const u8,
1526 options: Io.Dir.AccessOptions,
1527) Io.Dir.AccessError!void {
1528 const t: *Threaded = @ptrCast(@alignCast(userdata));
1529
1530 var path_buffer: [posix.PATH_MAX]u8 = undefined;
1531 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
1532
1533 const flags: u32 = @as(u32, if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0);
1534
1535 const mode: u32 =
1536 @as(u32, if (options.read) posix.R_OK else 0) |
1537 @as(u32, if (options.write) posix.W_OK else 0) |
1538 @as(u32, if (options.execute) posix.X_OK else 0);
1539
1540 while (true) {
1541 try t.checkCancel();
1542 switch (posix.errno(posix.system.faccessat(dir.handle, sub_path_posix, mode, flags))) {
1543 .SUCCESS => return,
1544 .INTR => continue,
1545 .CANCELED => return error.Canceled,
1546
1547 .ACCES => return error.AccessDenied,
1548 .PERM => return error.PermissionDenied,
1549 .ROFS => return error.ReadOnlyFileSystem,
1550 .LOOP => return error.SymLinkLoop,
1551 .TXTBSY => return error.FileBusy,
1552 .NOTDIR => return error.FileNotFound,
1553 .NOENT => return error.FileNotFound,
1554 .NAMETOOLONG => return error.NameTooLong,
1555 .INVAL => |err| return errnoBug(err),
1556 .FAULT => |err| return errnoBug(err),
1557 .IO => return error.InputOutput,
1558 .NOMEM => return error.SystemResources,
1559 .ILSEQ => return error.BadPathName,
1560 else => |err| return posix.unexpectedErrno(err),
1561 }
1562 }
1563}
1564
1565fn dirAccessWasi(
1566 userdata: ?*anyopaque,
1567 dir: Io.Dir,
1568 sub_path: []const u8,
1569 options: Io.Dir.AccessOptions,
1570) Io.Dir.AccessError!void {
1571 if (builtin.link_libc) return dirAccessPosix(userdata, dir, sub_path, options);
1572 const t: *Threaded = @ptrCast(@alignCast(userdata));
1573 const wasi = std.os.wasi;
1574 const flags: wasi.lookupflags_t = .{
1575 .SYMLINK_FOLLOW = options.follow_symlinks,
1576 };
1577 var stat: wasi.filestat_t = undefined;
1578 while (true) {
1579 try t.checkCancel();
1580 switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) {
1581 .SUCCESS => break,
1582 .INTR => continue,
1583 .CANCELED => return error.Canceled,
1584
1585 .INVAL => |err| return errnoBug(err),
1586 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
1587 .NOMEM => return error.SystemResources,
1588 .ACCES => return error.AccessDenied,
1589 .FAULT => |err| return errnoBug(err),
1590 .NAMETOOLONG => return error.NameTooLong,
1591 .NOENT => return error.FileNotFound,
1592 .NOTDIR => return error.FileNotFound,
1593 .NOTCAPABLE => return error.AccessDenied,
1594 .ILSEQ => return error.BadPathName,
1595 else => |err| return posix.unexpectedErrno(err),
1596 }
1597 }
1598
1599 if (!options.read and !options.write and !options.execute)
1600 return;
1601
1602 var directory: wasi.fdstat_t = undefined;
1603 if (wasi.fd_fdstat_get(dir.handle, &directory) != .SUCCESS)
1604 return error.AccessDenied;
1605
1606 var rights: wasi.rights_t = .{};
1607 if (options.read) {
1608 if (stat.filetype == .DIRECTORY) {
1609 rights.FD_READDIR = true;
1610 } else {
1611 rights.FD_READ = true;
1612 }
1613 }
1614 if (options.write)
1615 rights.FD_WRITE = true;
1616
1617 // No validation for execution.
1618
1619 // https://github.com/ziglang/zig/issues/18882
1620 const rights_int: u64 = @bitCast(rights);
1621 const inheriting_int: u64 = @bitCast(directory.fs_rights_inheriting);
1622 if ((rights_int & inheriting_int) != rights_int)
1623 return error.AccessDenied;
1624}
1625
1626fn dirAccessWindows(
1627 userdata: ?*anyopaque,
1628 dir: Io.Dir,
1629 sub_path: []const u8,
1630 options: Io.Dir.AccessOptions,
1631) Io.Dir.AccessError!void {
1632 const t: *Threaded = @ptrCast(@alignCast(userdata));
1633 try t.checkCancel();
1634
1635 _ = options; // TODO
1636
1637 const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
1638 const sub_path_w = sub_path_w_array.span();
1639
1640 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) return;
1641 if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) return;
1642
1643 const path_len_bytes = std.math.cast(u16, std.mem.sliceTo(sub_path_w, 0).len * 2) orelse
1644 return error.NameTooLong;
1645 var nt_name: windows.UNICODE_STRING = .{
1646 .Length = path_len_bytes,
1647 .MaximumLength = path_len_bytes,
1648 .Buffer = @constCast(sub_path_w.ptr),
1649 };
1650 var attr = windows.OBJECT_ATTRIBUTES{
1651 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
1652 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
1653 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
1654 .ObjectName = &nt_name,
1655 .SecurityDescriptor = null,
1656 .SecurityQualityOfService = null,
1657 };
1658 var basic_info: windows.FILE_BASIC_INFORMATION = undefined;
1659 switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) {
1660 .SUCCESS => return,
1661 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
1662 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
1663 .OBJECT_NAME_INVALID => |err| return windows.statusBug(err),
1664 .INVALID_PARAMETER => |err| return windows.statusBug(err),
1665 .ACCESS_DENIED => return error.AccessDenied,
1666 .OBJECT_PATH_SYNTAX_BAD => |err| return windows.statusBug(err),
1667 else => |rc| return windows.unexpectedStatus(rc),
1668 }
1669}
1670
1671const dirCreateFile = switch (native_os) {
1672 .windows => dirCreateFileWindows,
1673 .wasi => dirCreateFileWasi,
1674 else => dirCreateFilePosix,
1675};
1676
1677fn dirCreateFilePosix(
1678 userdata: ?*anyopaque,
1679 dir: Io.Dir,
1680 sub_path: []const u8,
1681 flags: Io.File.CreateFlags,
1682) Io.File.OpenError!Io.File {
1683 const t: *Threaded = @ptrCast(@alignCast(userdata));
1684
1685 var path_buffer: [posix.PATH_MAX]u8 = undefined;
1686 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
1687
1688 var os_flags: posix.O = .{
1689 .ACCMODE = if (flags.read) .RDWR else .WRONLY,
1690 .CREAT = true,
1691 .TRUNC = flags.truncate,
1692 .EXCL = flags.exclusive,
1693 };
1694 if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;
1695 if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;
1696
1697 // Use the O locking flags if the os supports them to acquire the lock
1698 // atomically. Note that the NONBLOCK flag is removed after the openat()
1699 // call is successful.
1700 if (have_flock_open_flags) switch (flags.lock) {
1701 .none => {},
1702 .shared => {
1703 os_flags.SHLOCK = true;
1704 os_flags.NONBLOCK = flags.lock_nonblocking;
1705 },
1706 .exclusive => {
1707 os_flags.EXLOCK = true;
1708 os_flags.NONBLOCK = flags.lock_nonblocking;
1709 },
1710 };
1711
1712 const fd: posix.fd_t = while (true) {
1713 try t.checkCancel();
1714 const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.mode);
1715 switch (posix.errno(rc)) {
1716 .SUCCESS => break @intCast(rc),
1717 .INTR => continue,
1718 .CANCELED => return error.Canceled,
1719
1720 .FAULT => |err| return errnoBug(err),
1721 .INVAL => return error.BadPathName,
1722 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
1723 .ACCES => return error.AccessDenied,
1724 .FBIG => return error.FileTooBig,
1725 .OVERFLOW => return error.FileTooBig,
1726 .ISDIR => return error.IsDir,
1727 .LOOP => return error.SymLinkLoop,
1728 .MFILE => return error.ProcessFdQuotaExceeded,
1729 .NAMETOOLONG => return error.NameTooLong,
1730 .NFILE => return error.SystemFdQuotaExceeded,
1731 .NODEV => return error.NoDevice,
1732 .NOENT => return error.FileNotFound,
1733 .SRCH => return error.ProcessNotFound,
1734 .NOMEM => return error.SystemResources,
1735 .NOSPC => return error.NoSpaceLeft,
1736 .NOTDIR => return error.NotDir,
1737 .PERM => return error.PermissionDenied,
1738 .EXIST => return error.PathAlreadyExists,
1739 .BUSY => return error.DeviceBusy,
1740 .OPNOTSUPP => return error.FileLocksNotSupported,
1741 .AGAIN => return error.WouldBlock,
1742 .TXTBSY => return error.FileBusy,
1743 .NXIO => return error.NoDevice,
1744 .ILSEQ => return error.BadPathName,
1745 else => |err| return posix.unexpectedErrno(err),
1746 }
1747 };
1748 errdefer posix.close(fd);
1749
1750 if (have_flock and !have_flock_open_flags and flags.lock != .none) {
1751 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
1752 const lock_flags = switch (flags.lock) {
1753 .none => unreachable,
1754 .shared => posix.LOCK.SH | lock_nonblocking,
1755 .exclusive => posix.LOCK.EX | lock_nonblocking,
1756 };
1757 while (true) {
1758 try t.checkCancel();
1759 switch (posix.errno(posix.system.flock(fd, lock_flags))) {
1760 .SUCCESS => break,
1761 .INTR => continue,
1762 .CANCELED => return error.Canceled,
1763
1764 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
1765 .INVAL => |err| return errnoBug(err), // invalid parameters
1766 .NOLCK => return error.SystemResources,
1767 .AGAIN => return error.WouldBlock,
1768 .OPNOTSUPP => return error.FileLocksNotSupported,
1769 else => |err| return posix.unexpectedErrno(err),
1770 }
1771 }
1772 }
1773
1774 if (have_flock_open_flags and flags.lock_nonblocking) {
1775 var fl_flags: usize = while (true) {
1776 try t.checkCancel();
1777 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));
1778 switch (posix.errno(rc)) {
1779 .SUCCESS => break @intCast(rc),
1780 .INTR => continue,
1781 .CANCELED => return error.Canceled,
1782 else => |err| return posix.unexpectedErrno(err),
1783 }
1784 };
1785 fl_flags |= @as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK"));
1786 while (true) {
1787 try t.checkCancel();
1788 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, fl_flags))) {
1789 .SUCCESS => break,
1790 .INTR => continue,
1791 .CANCELED => return error.Canceled,
1792 else => |err| return posix.unexpectedErrno(err),
1793 }
1794 }
1795 }
1796
1797 return .{ .handle = fd };
1798}
1799
1800fn dirCreateFileWindows(
1801 userdata: ?*anyopaque,
1802 dir: Io.Dir,
1803 sub_path: []const u8,
1804 flags: Io.File.CreateFlags,
1805) Io.File.OpenError!Io.File {
1806 const w = windows;
1807 const t: *Threaded = @ptrCast(@alignCast(userdata));
1808 try t.checkCancel();
1809
1810 const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, sub_path);
1811 const sub_path_w = sub_path_w_array.span();
1812
1813 const read_flag = if (flags.read) @as(u32, w.GENERIC_READ) else 0;
1814 const handle = try w.OpenFile(sub_path_w, .{
1815 .dir = dir.handle,
1816 .access_mask = w.SYNCHRONIZE | w.GENERIC_WRITE | read_flag,
1817 .creation = if (flags.exclusive)
1818 @as(u32, w.FILE_CREATE)
1819 else if (flags.truncate)
1820 @as(u32, w.FILE_OVERWRITE_IF)
1821 else
1822 @as(u32, w.FILE_OPEN_IF),
1823 });
1824 errdefer w.CloseHandle(handle);
1825 var io_status_block: w.IO_STATUS_BLOCK = undefined;
1826 const range_off: w.LARGE_INTEGER = 0;
1827 const range_len: w.LARGE_INTEGER = 1;
1828 const exclusive = switch (flags.lock) {
1829 .none => return .{ .handle = handle },
1830 .shared => false,
1831 .exclusive => true,
1832 };
1833 try w.LockFile(
1834 handle,
1835 null,
1836 null,
1837 null,
1838 &io_status_block,
1839 &range_off,
1840 &range_len,
1841 null,
1842 @intFromBool(flags.lock_nonblocking),
1843 @intFromBool(exclusive),
1844 );
1845 return .{ .handle = handle };
1846}
1847
1848fn dirCreateFileWasi(
1849 userdata: ?*anyopaque,
1850 dir: Io.Dir,
1851 sub_path: []const u8,
1852 flags: Io.File.CreateFlags,
1853) Io.File.OpenError!Io.File {
1854 const t: *Threaded = @ptrCast(@alignCast(userdata));
1855 const wasi = std.os.wasi;
1856 const lookup_flags: wasi.lookupflags_t = .{};
1857 const oflags: wasi.oflags_t = .{
1858 .CREAT = true,
1859 .TRUNC = flags.truncate,
1860 .EXCL = flags.exclusive,
1861 };
1862 const fdflags: wasi.fdflags_t = .{};
1863 const base: wasi.rights_t = .{
1864 .FD_READ = flags.read,
1865 .FD_WRITE = true,
1866 .FD_DATASYNC = true,
1867 .FD_SEEK = true,
1868 .FD_TELL = true,
1869 .FD_FDSTAT_SET_FLAGS = true,
1870 .FD_SYNC = true,
1871 .FD_ALLOCATE = true,
1872 .FD_ADVISE = true,
1873 .FD_FILESTAT_SET_TIMES = true,
1874 .FD_FILESTAT_SET_SIZE = true,
1875 .FD_FILESTAT_GET = true,
1876 // POLL_FD_READWRITE only grants extra rights if the corresponding FD_READ and/or
1877 // FD_WRITE is also set.
1878 .POLL_FD_READWRITE = true,
1879 };
1880 const inheriting: wasi.rights_t = .{};
1881 var fd: posix.fd_t = undefined;
1882 while (true) {
1883 try t.checkCancel();
1884 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) {
1885 .SUCCESS => return .{ .handle = fd },
1886 .INTR => continue,
1887 .CANCELED => return error.Canceled,
1888
1889 .FAULT => |err| return errnoBug(err),
1890 .INVAL => return error.BadPathName,
1891 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
1892 .ACCES => return error.AccessDenied,
1893 .FBIG => return error.FileTooBig,
1894 .OVERFLOW => return error.FileTooBig,
1895 .ISDIR => return error.IsDir,
1896 .LOOP => return error.SymLinkLoop,
1897 .MFILE => return error.ProcessFdQuotaExceeded,
1898 .NAMETOOLONG => return error.NameTooLong,
1899 .NFILE => return error.SystemFdQuotaExceeded,
1900 .NODEV => return error.NoDevice,
1901 .NOENT => return error.FileNotFound,
1902 .NOMEM => return error.SystemResources,
1903 .NOSPC => return error.NoSpaceLeft,
1904 .NOTDIR => return error.NotDir,
1905 .PERM => return error.PermissionDenied,
1906 .EXIST => return error.PathAlreadyExists,
1907 .BUSY => return error.DeviceBusy,
1908 .NOTCAPABLE => return error.AccessDenied,
1909 .ILSEQ => return error.BadPathName,
1910 else => |err| return posix.unexpectedErrno(err),
1911 }
1912 }
1913}
1914
1915const dirOpenFile = switch (native_os) {
1916 .windows => dirOpenFileWindows,
1917 .wasi => dirOpenFileWasi,
1918 else => dirOpenFilePosix,
1919};
1920
1921fn dirOpenFilePosix(
1922 userdata: ?*anyopaque,
1923 dir: Io.Dir,
1924 sub_path: []const u8,
1925 flags: Io.File.OpenFlags,
1926) Io.File.OpenError!Io.File {
1927 const t: *Threaded = @ptrCast(@alignCast(userdata));
1928
1929 var path_buffer: [posix.PATH_MAX]u8 = undefined;
1930 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
1931
1932 var os_flags: posix.O = switch (native_os) {
1933 .wasi => .{
1934 .read = flags.mode != .write_only,
1935 .write = flags.mode != .read_only,
1936 },
1937 else => .{
1938 .ACCMODE = switch (flags.mode) {
1939 .read_only => .RDONLY,
1940 .write_only => .WRONLY,
1941 .read_write => .RDWR,
1942 },
1943 },
1944 };
1945 if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;
1946 if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;
1947 if (@hasField(posix.O, "NOCTTY")) os_flags.NOCTTY = !flags.allow_ctty;
1948
1949 // Use the O locking flags if the os supports them to acquire the lock
1950 // atomically. Note that the NONBLOCK flag is removed after the openat()
1951 // call is successful.
1952 if (have_flock_open_flags) switch (flags.lock) {
1953 .none => {},
1954 .shared => {
1955 os_flags.SHLOCK = true;
1956 os_flags.NONBLOCK = flags.lock_nonblocking;
1957 },
1958 .exclusive => {
1959 os_flags.EXLOCK = true;
1960 os_flags.NONBLOCK = flags.lock_nonblocking;
1961 },
1962 };
1963
1964 const fd: posix.fd_t = while (true) {
1965 try t.checkCancel();
1966 const rc = openat_sym(dir.handle, sub_path_posix, os_flags, @as(posix.mode_t, 0));
1967 switch (posix.errno(rc)) {
1968 .SUCCESS => break @intCast(rc),
1969 .INTR => continue,
1970 .CANCELED => return error.Canceled,
1971
1972 .FAULT => |err| return errnoBug(err),
1973 .INVAL => return error.BadPathName,
1974 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
1975 .ACCES => return error.AccessDenied,
1976 .FBIG => return error.FileTooBig,
1977 .OVERFLOW => return error.FileTooBig,
1978 .ISDIR => return error.IsDir,
1979 .LOOP => return error.SymLinkLoop,
1980 .MFILE => return error.ProcessFdQuotaExceeded,
1981 .NAMETOOLONG => return error.NameTooLong,
1982 .NFILE => return error.SystemFdQuotaExceeded,
1983 .NODEV => return error.NoDevice,
1984 .NOENT => return error.FileNotFound,
1985 .SRCH => return error.ProcessNotFound,
1986 .NOMEM => return error.SystemResources,
1987 .NOSPC => return error.NoSpaceLeft,
1988 .NOTDIR => return error.NotDir,
1989 .PERM => return error.PermissionDenied,
1990 .EXIST => return error.PathAlreadyExists,
1991 .BUSY => return error.DeviceBusy,
1992 .OPNOTSUPP => return error.FileLocksNotSupported,
1993 .AGAIN => return error.WouldBlock,
1994 .TXTBSY => return error.FileBusy,
1995 .NXIO => return error.NoDevice,
1996 .ILSEQ => return error.BadPathName,
1997 else => |err| return posix.unexpectedErrno(err),
1998 }
1999 };
2000 errdefer posix.close(fd);
2001
2002 if (have_flock and !have_flock_open_flags and flags.lock != .none) {
2003 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
2004 const lock_flags = switch (flags.lock) {
2005 .none => unreachable,
2006 .shared => posix.LOCK.SH | lock_nonblocking,
2007 .exclusive => posix.LOCK.EX | lock_nonblocking,
2008 };
2009 while (true) {
2010 try t.checkCancel();
2011 switch (posix.errno(posix.system.flock(fd, lock_flags))) {
2012 .SUCCESS => break,
2013 .INTR => continue,
2014 .CANCELED => return error.Canceled,
2015
2016 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
2017 .INVAL => |err| return errnoBug(err), // invalid parameters
2018 .NOLCK => return error.SystemResources,
2019 .AGAIN => return error.WouldBlock,
2020 .OPNOTSUPP => return error.FileLocksNotSupported,
2021 else => |err| return posix.unexpectedErrno(err),
2022 }
2023 }
2024 }
2025
2026 if (have_flock_open_flags and flags.lock_nonblocking) {
2027 var fl_flags: usize = while (true) {
2028 try t.checkCancel();
2029 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));
2030 switch (posix.errno(rc)) {
2031 .SUCCESS => break @intCast(rc),
2032 .INTR => continue,
2033 .CANCELED => return error.Canceled,
2034 else => |err| return posix.unexpectedErrno(err),
2035 }
2036 };
2037 fl_flags |= @as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK"));
2038 while (true) {
2039 try t.checkCancel();
2040 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, fl_flags))) {
2041 .SUCCESS => break,
2042 .INTR => continue,
2043 .CANCELED => return error.Canceled,
2044 else => |err| return posix.unexpectedErrno(err),
2045 }
2046 }
2047 }
2048
2049 return .{ .handle = fd };
2050}
2051
2052fn dirOpenFileWindows(
2053 userdata: ?*anyopaque,
2054 dir: Io.Dir,
2055 sub_path: []const u8,
2056 flags: Io.File.OpenFlags,
2057) Io.File.OpenError!Io.File {
2058 const t: *Threaded = @ptrCast(@alignCast(userdata));
2059 const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
2060 const sub_path_w = sub_path_w_array.span();
2061 const dir_handle = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle;
2062 return dirOpenFileWtf16(t, dir_handle, sub_path_w, flags);
2063}
2064
2065pub fn dirOpenFileWtf16(
2066 t: *Threaded,
2067 dir_handle: ?windows.HANDLE,
2068 sub_path_w: [:0]const u16,
2069 flags: Io.File.OpenFlags,
2070) Io.File.OpenError!Io.File {
2071 if (std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir;
2072 if (std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir;
2073 const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong;
2074
2075 const w = windows;
2076
2077 var nt_name: w.UNICODE_STRING = .{
2078 .Length = path_len_bytes,
2079 .MaximumLength = path_len_bytes,
2080 .Buffer = @constCast(sub_path_w.ptr),
2081 };
2082 var attr: w.OBJECT_ATTRIBUTES = .{
2083 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
2084 .RootDirectory = dir_handle,
2085 .Attributes = 0,
2086 .ObjectName = &nt_name,
2087 .SecurityDescriptor = null,
2088 .SecurityQualityOfService = null,
2089 };
2090 var io_status_block: w.IO_STATUS_BLOCK = undefined;
2091 const blocking_flag: w.ULONG = w.FILE_SYNCHRONOUS_IO_NONALERT;
2092 const file_or_dir_flag: w.ULONG = w.FILE_NON_DIRECTORY_FILE;
2093 // If we're not following symlinks, we need to ensure we don't pass in any
2094 // synchronization flags such as FILE_SYNCHRONOUS_IO_NONALERT.
2095 const create_file_flags: w.ULONG = file_or_dir_flag |
2096 if (flags.follow_symlinks) blocking_flag else w.FILE_OPEN_REPARSE_POINT;
2097
2098 // There are multiple kernel bugs being worked around with retries.
2099 const max_attempts = 13;
2100 var attempt: u5 = 0;
2101
2102 const handle = while (true) {
2103 try t.checkCancel();
2104
2105 var result: w.HANDLE = undefined;
2106 const rc = w.ntdll.NtCreateFile(
2107 &result,
2108 w.SYNCHRONIZE |
2109 (if (flags.isRead()) @as(u32, w.GENERIC_READ) else 0) |
2110 (if (flags.isWrite()) @as(u32, w.GENERIC_WRITE) else 0),
2111 &attr,
2112 &io_status_block,
2113 null,
2114 w.FILE_ATTRIBUTE_NORMAL,
2115 w.FILE_SHARE_WRITE | w.FILE_SHARE_READ | w.FILE_SHARE_DELETE,
2116 w.FILE_OPEN,
2117 create_file_flags,
2118 null,
2119 0,
2120 );
2121 switch (rc) {
2122 .SUCCESS => break result,
2123 .OBJECT_NAME_INVALID => return error.BadPathName,
2124 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
2125 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
2126 .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found
2127 .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't
2128 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
2129 .INVALID_PARAMETER => |err| return w.statusBug(err),
2130 .SHARING_VIOLATION => {
2131 // This occurs if the file attempting to be opened is a running
2132 // executable. However, there's a kernel bug: the error may be
2133 // incorrectly returned for an indeterminate amount of time
2134 // after an executable file is closed. Here we work around the
2135 // kernel bug with retry attempts.
2136 if (attempt - max_attempts == 0) return error.SharingViolation;
2137 _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE);
2138 attempt += 1;
2139 continue;
2140 },
2141 .ACCESS_DENIED => return error.AccessDenied,
2142 .PIPE_BUSY => return error.PipeBusy,
2143 .PIPE_NOT_AVAILABLE => return error.NoDevice,
2144 .OBJECT_PATH_SYNTAX_BAD => |err| return w.statusBug(err),
2145 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
2146 .FILE_IS_A_DIRECTORY => return error.IsDir,
2147 .NOT_A_DIRECTORY => return error.NotDir,
2148 .USER_MAPPED_FILE => return error.AccessDenied,
2149 .INVALID_HANDLE => |err| return w.statusBug(err),
2150 .DELETE_PENDING => {
2151 // This error means that there *was* a file in this location on
2152 // the file system, but it was deleted. However, the OS is not
2153 // finished with the deletion operation, and so this CreateFile
2154 // call has failed. Here, we simulate the kernel bug being
2155 // fixed by sleeping and retrying until the error goes away.
2156 if (attempt - max_attempts == 0) return error.SharingViolation;
2157 _ = w.kernel32.SleepEx((@as(u32, 1) << attempt) >> 1, w.TRUE);
2158 attempt += 1;
2159 continue;
2160 },
2161 .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
2162 else => return w.unexpectedStatus(rc),
2163 }
2164 };
2165 errdefer w.CloseHandle(handle);
2166
2167 const range_off: w.LARGE_INTEGER = 0;
2168 const range_len: w.LARGE_INTEGER = 1;
2169 const exclusive = switch (flags.lock) {
2170 .none => return .{ .handle = handle },
2171 .shared => false,
2172 .exclusive => true,
2173 };
2174 try w.LockFile(
2175 handle,
2176 null,
2177 null,
2178 null,
2179 &io_status_block,
2180 &range_off,
2181 &range_len,
2182 null,
2183 @intFromBool(flags.lock_nonblocking),
2184 @intFromBool(exclusive),
2185 );
2186 return .{ .handle = handle };
2187}
2188
2189fn dirOpenFileWasi(
2190 userdata: ?*anyopaque,
2191 dir: Io.Dir,
2192 sub_path: []const u8,
2193 flags: Io.File.OpenFlags,
2194) Io.File.OpenError!Io.File {
2195 if (builtin.link_libc) return dirOpenFilePosix(userdata, dir, sub_path, flags);
2196 const t: *Threaded = @ptrCast(@alignCast(userdata));
2197 const wasi = std.os.wasi;
2198 var base: std.os.wasi.rights_t = .{};
2199 // POLL_FD_READWRITE only grants extra rights if the corresponding FD_READ and/or FD_WRITE
2200 // is also set.
2201 if (flags.isRead()) {
2202 base.FD_READ = true;
2203 base.FD_TELL = true;
2204 base.FD_SEEK = true;
2205 base.FD_FILESTAT_GET = true;
2206 base.POLL_FD_READWRITE = true;
2207 }
2208 if (flags.isWrite()) {
2209 base.FD_WRITE = true;
2210 base.FD_TELL = true;
2211 base.FD_SEEK = true;
2212 base.FD_DATASYNC = true;
2213 base.FD_FDSTAT_SET_FLAGS = true;
2214 base.FD_SYNC = true;
2215 base.FD_ALLOCATE = true;
2216 base.FD_ADVISE = true;
2217 base.FD_FILESTAT_SET_TIMES = true;
2218 base.FD_FILESTAT_SET_SIZE = true;
2219 base.POLL_FD_READWRITE = true;
2220 }
2221 const lookup_flags: wasi.lookupflags_t = .{};
2222 const oflags: wasi.oflags_t = .{};
2223 const inheriting: wasi.rights_t = .{};
2224 const fdflags: wasi.fdflags_t = .{};
2225 var fd: posix.fd_t = undefined;
2226 while (true) {
2227 try t.checkCancel();
2228 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) {
2229 .SUCCESS => return .{ .handle = fd },
2230 .INTR => continue,
2231 .CANCELED => return error.Canceled,
2232
2233 .FAULT => |err| return errnoBug(err),
2234 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
2235 .ACCES => return error.AccessDenied,
2236 .FBIG => return error.FileTooBig,
2237 .OVERFLOW => return error.FileTooBig,
2238 .ISDIR => return error.IsDir,
2239 .LOOP => return error.SymLinkLoop,
2240 .MFILE => return error.ProcessFdQuotaExceeded,
2241 .NFILE => return error.SystemFdQuotaExceeded,
2242 .NODEV => return error.NoDevice,
2243 .NOENT => return error.FileNotFound,
2244 .NOMEM => return error.SystemResources,
2245 .NOTDIR => return error.NotDir,
2246 .PERM => return error.PermissionDenied,
2247 .BUSY => return error.DeviceBusy,
2248 .NOTCAPABLE => return error.AccessDenied,
2249 .NAMETOOLONG => return error.NameTooLong,
2250 .INVAL => return error.BadPathName,
2251 .ILSEQ => return error.BadPathName,
2252 else => |err| return posix.unexpectedErrno(err),
2253 }
2254 }
2255}
2256
2257const dirOpenDir = switch (native_os) {
2258 .wasi => dirOpenDirWasi,
2259 .haiku => dirOpenDirHaiku,
2260 else => dirOpenDirPosix,
2261};
2262
2263/// This function is also used for WASI when libc is linked.
2264fn dirOpenDirPosix(
2265 userdata: ?*anyopaque,
2266 dir: Io.Dir,
2267 sub_path: []const u8,
2268 options: Io.Dir.OpenOptions,
2269) Io.Dir.OpenError!Io.Dir {
2270 const t: *Threaded = @ptrCast(@alignCast(userdata));
2271
2272 if (is_windows) {
2273 const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path);
2274 return dirOpenDirWindows(t, dir, sub_path_w.span(), options);
2275 }
2276
2277 var path_buffer: [posix.PATH_MAX]u8 = undefined;
2278 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
2279
2280 var flags: posix.O = switch (native_os) {
2281 .wasi => .{
2282 .read = true,
2283 .NOFOLLOW = !options.follow_symlinks,
2284 .DIRECTORY = true,
2285 },
2286 else => .{
2287 .ACCMODE = .RDONLY,
2288 .NOFOLLOW = !options.follow_symlinks,
2289 .DIRECTORY = true,
2290 .CLOEXEC = true,
2291 },
2292 };
2293
2294 if (@hasField(posix.O, "PATH") and !options.iterate)
2295 flags.PATH = true;
2296
2297 while (true) {
2298 try t.checkCancel();
2299 const rc = openat_sym(dir.handle, sub_path_posix, flags, @as(usize, 0));
2300 switch (posix.errno(rc)) {
2301 .SUCCESS => return .{ .handle = @intCast(rc) },
2302 .INTR => continue,
2303 .CANCELED => return error.Canceled,
2304
2305 .FAULT => |err| return errnoBug(err),
2306 .INVAL => return error.BadPathName,
2307 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
2308 .ACCES => return error.AccessDenied,
2309 .LOOP => return error.SymLinkLoop,
2310 .MFILE => return error.ProcessFdQuotaExceeded,
2311 .NAMETOOLONG => return error.NameTooLong,
2312 .NFILE => return error.SystemFdQuotaExceeded,
2313 .NODEV => return error.NoDevice,
2314 .NOENT => return error.FileNotFound,
2315 .NOMEM => return error.SystemResources,
2316 .NOTDIR => return error.NotDir,
2317 .PERM => return error.PermissionDenied,
2318 .BUSY => return error.DeviceBusy,
2319 .NXIO => return error.NoDevice,
2320 .ILSEQ => return error.BadPathName,
2321 else => |err| return posix.unexpectedErrno(err),
2322 }
2323 }
2324}
2325
2326fn dirOpenDirHaiku(
2327 userdata: ?*anyopaque,
2328 dir: Io.Dir,
2329 sub_path: []const u8,
2330 options: Io.Dir.OpenOptions,
2331) Io.Dir.OpenError!Io.Dir {
2332 const t: *Threaded = @ptrCast(@alignCast(userdata));
2333
2334 var path_buffer: [posix.PATH_MAX]u8 = undefined;
2335 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
2336
2337 _ = options;
2338
2339 while (true) {
2340 try t.checkCancel();
2341 const rc = posix.system._kern_open_dir(dir.handle, sub_path_posix);
2342 if (rc >= 0) return .{ .handle = rc };
2343 switch (@as(posix.E, @enumFromInt(rc))) {
2344 .INTR => continue,
2345 .CANCELED => return error.Canceled,
2346 .FAULT => |err| return errnoBug(err),
2347 .INVAL => |err| return errnoBug(err),
2348 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
2349 .ACCES => return error.AccessDenied,
2350 .LOOP => return error.SymLinkLoop,
2351 .MFILE => return error.ProcessFdQuotaExceeded,
2352 .NAMETOOLONG => return error.NameTooLong,
2353 .NFILE => return error.SystemFdQuotaExceeded,
2354 .NODEV => return error.NoDevice,
2355 .NOENT => return error.FileNotFound,
2356 .NOMEM => return error.SystemResources,
2357 .NOTDIR => return error.NotDir,
2358 .PERM => return error.PermissionDenied,
2359 .BUSY => return error.DeviceBusy,
2360 else => |err| return posix.unexpectedErrno(err),
2361 }
2362 }
2363}
2364
2365pub fn dirOpenDirWindows(
2366 t: *Io.Threaded,
2367 dir: Io.Dir,
2368 sub_path_w: [:0]const u16,
2369 options: Io.Dir.OpenOptions,
2370) Io.Dir.OpenError!Io.Dir {
2371 const w = windows;
2372 // TODO remove some of these flags if options.access_sub_paths is false
2373 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
2374 w.SYNCHRONIZE | w.FILE_TRAVERSE;
2375 const access_mask: u32 = if (options.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
2376
2377 const path_len_bytes: u16 = @intCast(sub_path_w.len * 2);
2378 var nt_name: w.UNICODE_STRING = .{
2379 .Length = path_len_bytes,
2380 .MaximumLength = path_len_bytes,
2381 .Buffer = @constCast(sub_path_w.ptr),
2382 };
2383 var attr: w.OBJECT_ATTRIBUTES = .{
2384 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
2385 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle,
2386 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
2387 .ObjectName = &nt_name,
2388 .SecurityDescriptor = null,
2389 .SecurityQualityOfService = null,
2390 };
2391 const open_reparse_point: w.DWORD = if (!options.follow_symlinks) w.FILE_OPEN_REPARSE_POINT else 0x0;
2392 var io_status_block: w.IO_STATUS_BLOCK = undefined;
2393 var result: Io.Dir = .{ .handle = undefined };
2394 try t.checkCancel();
2395 const rc = w.ntdll.NtCreateFile(
2396 &result.handle,
2397 access_mask,
2398 &attr,
2399 &io_status_block,
2400 null,
2401 w.FILE_ATTRIBUTE_NORMAL,
2402 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
2403 w.FILE_OPEN,
2404 w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
2405 null,
2406 0,
2407 );
2408
2409 switch (rc) {
2410 .SUCCESS => return result,
2411 .OBJECT_NAME_INVALID => return error.BadPathName,
2412 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
2413 .OBJECT_NAME_COLLISION => |err| return w.statusBug(err),
2414 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
2415 .NOT_A_DIRECTORY => return error.NotDir,
2416 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
2417 // and the directory is trying to be opened for iteration.
2418 .ACCESS_DENIED => return error.AccessDenied,
2419 .INVALID_PARAMETER => |err| return w.statusBug(err),
2420 else => return w.unexpectedStatus(rc),
2421 }
2422}
2423
2424const MakeOpenDirAccessMaskWOptions = struct {
2425 no_follow: bool,
2426 create_disposition: u32,
2427};
2428
2429fn dirClose(userdata: ?*anyopaque, dir: Io.Dir) void {
2430 const t: *Threaded = @ptrCast(@alignCast(userdata));
2431 _ = t;
2432 posix.close(dir.handle);
2433}
2434
2435fn dirOpenDirWasi(
2436 userdata: ?*anyopaque,
2437 dir: Io.Dir,
2438 sub_path: []const u8,
2439 options: Io.Dir.OpenOptions,
2440) Io.Dir.OpenError!Io.Dir {
2441 if (builtin.link_libc) return dirOpenDirPosix(userdata, dir, sub_path, options);
2442 const t: *Threaded = @ptrCast(@alignCast(userdata));
2443 const wasi = std.os.wasi;
2444
2445 var base: std.os.wasi.rights_t = .{
2446 .FD_FILESTAT_GET = true,
2447 .FD_FDSTAT_SET_FLAGS = true,
2448 .FD_FILESTAT_SET_TIMES = true,
2449 };
2450 if (options.access_sub_paths) {
2451 base.FD_READDIR = true;
2452 base.PATH_CREATE_DIRECTORY = true;
2453 base.PATH_CREATE_FILE = true;
2454 base.PATH_LINK_SOURCE = true;
2455 base.PATH_LINK_TARGET = true;
2456 base.PATH_OPEN = true;
2457 base.PATH_READLINK = true;
2458 base.PATH_RENAME_SOURCE = true;
2459 base.PATH_RENAME_TARGET = true;
2460 base.PATH_FILESTAT_GET = true;
2461 base.PATH_FILESTAT_SET_SIZE = true;
2462 base.PATH_FILESTAT_SET_TIMES = true;
2463 base.PATH_SYMLINK = true;
2464 base.PATH_REMOVE_DIRECTORY = true;
2465 base.PATH_UNLINK_FILE = true;
2466 }
2467
2468 const lookup_flags: wasi.lookupflags_t = .{ .SYMLINK_FOLLOW = options.follow_symlinks };
2469 const oflags: wasi.oflags_t = .{ .DIRECTORY = true };
2470 const fdflags: wasi.fdflags_t = .{};
2471 var fd: posix.fd_t = undefined;
2472
2473 while (true) {
2474 try t.checkCancel();
2475 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, base, fdflags, &fd)) {
2476 .SUCCESS => return .{ .handle = fd },
2477 .INTR => continue,
2478 .CANCELED => return error.Canceled,
2479
2480 .FAULT => |err| return errnoBug(err),
2481 .INVAL => return error.BadPathName,
2482 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
2483 .ACCES => return error.AccessDenied,
2484 .LOOP => return error.SymLinkLoop,
2485 .MFILE => return error.ProcessFdQuotaExceeded,
2486 .NAMETOOLONG => return error.NameTooLong,
2487 .NFILE => return error.SystemFdQuotaExceeded,
2488 .NODEV => return error.NoDevice,
2489 .NOENT => return error.FileNotFound,
2490 .NOMEM => return error.SystemResources,
2491 .NOTDIR => return error.NotDir,
2492 .PERM => return error.PermissionDenied,
2493 .BUSY => return error.DeviceBusy,
2494 .NOTCAPABLE => return error.AccessDenied,
2495 .ILSEQ => return error.BadPathName,
2496 else => |err| return posix.unexpectedErrno(err),
2497 }
2498 }
2499}
2500
2501fn fileClose(userdata: ?*anyopaque, file: Io.File) void {
2502 const t: *Threaded = @ptrCast(@alignCast(userdata));
2503 _ = t;
2504 posix.close(file.handle);
2505}
2506
2507const fileReadStreaming = switch (native_os) {
2508 .windows => fileReadStreamingWindows,
2509 else => fileReadStreamingPosix,
2510};
2511
2512fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.Reader.Error!usize {
2513 const t: *Threaded = @ptrCast(@alignCast(userdata));
2514
2515 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
2516 var i: usize = 0;
2517 for (data) |buf| {
2518 if (iovecs_buffer.len - i == 0) break;
2519 if (buf.len != 0) {
2520 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };
2521 i += 1;
2522 }
2523 }
2524 const dest = iovecs_buffer[0..i];
2525 assert(dest[0].len > 0);
2526
2527 if (native_os == .wasi and !builtin.link_libc) while (true) {
2528 try t.checkCancel();
2529 var nread: usize = undefined;
2530 switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) {
2531 .SUCCESS => return nread,
2532 .INTR => continue,
2533 .CANCELED => return error.Canceled,
2534
2535 .INVAL => |err| return errnoBug(err),
2536 .FAULT => |err| return errnoBug(err),
2537 .BADF => return error.NotOpenForReading, // File operation on directory.
2538 .IO => return error.InputOutput,
2539 .ISDIR => return error.IsDir,
2540 .NOBUFS => return error.SystemResources,
2541 .NOMEM => return error.SystemResources,
2542 .NOTCONN => return error.SocketUnconnected,
2543 .CONNRESET => return error.ConnectionResetByPeer,
2544 .TIMEDOUT => return error.Timeout,
2545 .NOTCAPABLE => return error.AccessDenied,
2546 else => |err| return posix.unexpectedErrno(err),
2547 }
2548 };
2549
2550 while (true) {
2551 try t.checkCancel();
2552 const rc = posix.system.readv(file.handle, dest.ptr, @intCast(dest.len));
2553 switch (posix.errno(rc)) {
2554 .SUCCESS => return @intCast(rc),
2555 .INTR => continue,
2556 .CANCELED => return error.Canceled,
2557
2558 .INVAL => |err| return errnoBug(err),
2559 .FAULT => |err| return errnoBug(err),
2560 .SRCH => return error.ProcessNotFound,
2561 .AGAIN => return error.WouldBlock,
2562 .BADF => |err| {
2563 if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory.
2564 return errnoBug(err); // File descriptor used after closed.
2565 },
2566 .IO => return error.InputOutput,
2567 .ISDIR => return error.IsDir,
2568 .NOBUFS => return error.SystemResources,
2569 .NOMEM => return error.SystemResources,
2570 .NOTCONN => return error.SocketUnconnected,
2571 .CONNRESET => return error.ConnectionResetByPeer,
2572 .TIMEDOUT => return error.Timeout,
2573 else => |err| return posix.unexpectedErrno(err),
2574 }
2575 }
2576}
2577
2578fn fileReadStreamingWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.Reader.Error!usize {
2579 const t: *Threaded = @ptrCast(@alignCast(userdata));
2580
2581 const DWORD = windows.DWORD;
2582 var index: usize = 0;
2583 while (data[index].len == 0) index += 1;
2584 const buffer = data[index];
2585 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
2586
2587 while (true) {
2588 try t.checkCancel();
2589 var n: DWORD = undefined;
2590 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0)
2591 return n;
2592 switch (windows.GetLastError()) {
2593 .IO_PENDING => |err| return windows.errorBug(err),
2594 .OPERATION_ABORTED => continue,
2595 .BROKEN_PIPE => return 0,
2596 .HANDLE_EOF => return 0,
2597 .NETNAME_DELETED => return error.ConnectionResetByPeer,
2598 .LOCK_VIOLATION => return error.LockViolation,
2599 .ACCESS_DENIED => return error.AccessDenied,
2600 .INVALID_HANDLE => return error.NotOpenForReading,
2601 else => |err| return windows.unexpectedError(err),
2602 }
2603 }
2604}
2605
2606fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize {
2607 const t: *Threaded = @ptrCast(@alignCast(userdata));
2608
2609 if (!have_preadv) @compileError("TODO");
2610
2611 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
2612 var i: usize = 0;
2613 for (data) |buf| {
2614 if (iovecs_buffer.len - i == 0) break;
2615 if (buf.len != 0) {
2616 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };
2617 i += 1;
2618 }
2619 }
2620 const dest = iovecs_buffer[0..i];
2621 assert(dest[0].len > 0);
2622
2623 if (native_os == .wasi and !builtin.link_libc) while (true) {
2624 try t.checkCancel();
2625 var nread: usize = undefined;
2626 switch (std.os.wasi.fd_pread(file.handle, dest.ptr, dest.len, offset, &nread)) {
2627 .SUCCESS => return nread,
2628 .INTR => continue,
2629 .CANCELED => return error.Canceled,
2630
2631 .INVAL => |err| return errnoBug(err),
2632 .FAULT => |err| return errnoBug(err),
2633 .AGAIN => |err| return errnoBug(err),
2634 .BADF => return error.NotOpenForReading, // File operation on directory.
2635 .IO => return error.InputOutput,
2636 .ISDIR => return error.IsDir,
2637 .NOBUFS => return error.SystemResources,
2638 .NOMEM => return error.SystemResources,
2639 .NOTCONN => return error.SocketUnconnected,
2640 .CONNRESET => return error.ConnectionResetByPeer,
2641 .TIMEDOUT => return error.Timeout,
2642 .NXIO => return error.Unseekable,
2643 .SPIPE => return error.Unseekable,
2644 .OVERFLOW => return error.Unseekable,
2645 .NOTCAPABLE => return error.AccessDenied,
2646 else => |err| return posix.unexpectedErrno(err),
2647 }
2648 };
2649
2650 while (true) {
2651 try t.checkCancel();
2652 const rc = preadv_sym(file.handle, dest.ptr, @intCast(dest.len), @bitCast(offset));
2653 switch (posix.errno(rc)) {
2654 .SUCCESS => return @bitCast(rc),
2655 .INTR => continue,
2656 .CANCELED => return error.Canceled,
2657
2658 .INVAL => |err| return errnoBug(err),
2659 .FAULT => |err| return errnoBug(err),
2660 .SRCH => return error.ProcessNotFound,
2661 .AGAIN => return error.WouldBlock,
2662 .BADF => |err| {
2663 if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory.
2664 return errnoBug(err); // File descriptor used after closed.
2665 },
2666 .IO => return error.InputOutput,
2667 .ISDIR => return error.IsDir,
2668 .NOBUFS => return error.SystemResources,
2669 .NOMEM => return error.SystemResources,
2670 .NOTCONN => return error.SocketUnconnected,
2671 .CONNRESET => return error.ConnectionResetByPeer,
2672 .TIMEDOUT => return error.Timeout,
2673 .NXIO => return error.Unseekable,
2674 .SPIPE => return error.Unseekable,
2675 .OVERFLOW => return error.Unseekable,
2676 else => |err| return posix.unexpectedErrno(err),
2677 }
2678 }
2679}
2680
2681const fileReadPositional = switch (native_os) {
2682 .windows => fileReadPositionalWindows,
2683 else => fileReadPositionalPosix,
2684};
2685
2686fn fileReadPositionalWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize {
2687 const t: *Threaded = @ptrCast(@alignCast(userdata));
2688
2689 const DWORD = windows.DWORD;
2690
2691 var index: usize = 0;
2692 while (data[index].len == 0) index += 1;
2693 const buffer = data[index];
2694 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
2695
2696 var overlapped: windows.OVERLAPPED = .{
2697 .Internal = 0,
2698 .InternalHigh = 0,
2699 .DUMMYUNIONNAME = .{
2700 .DUMMYSTRUCTNAME = .{
2701 .Offset = @truncate(offset),
2702 .OffsetHigh = @truncate(offset >> 32),
2703 },
2704 },
2705 .hEvent = null,
2706 };
2707
2708 while (true) {
2709 try t.checkCancel();
2710 var n: DWORD = undefined;
2711 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, &overlapped) != 0)
2712 return n;
2713 switch (windows.GetLastError()) {
2714 .IO_PENDING => |err| return windows.errorBug(err),
2715 .OPERATION_ABORTED => continue,
2716 .BROKEN_PIPE => return 0,
2717 .HANDLE_EOF => return 0,
2718 .NETNAME_DELETED => return error.ConnectionResetByPeer,
2719 .LOCK_VIOLATION => return error.LockViolation,
2720 .ACCESS_DENIED => return error.AccessDenied,
2721 .INVALID_HANDLE => return error.NotOpenForReading,
2722 else => |err| return windows.unexpectedError(err),
2723 }
2724 }
2725}
2726
2727fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, offset: i64) Io.File.SeekError!void {
2728 const t: *Threaded = @ptrCast(@alignCast(userdata));
2729 try t.checkCancel();
2730
2731 _ = file;
2732 _ = offset;
2733 @panic("TODO implement fileSeekBy");
2734}
2735
2736fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekError!void {
2737 const t: *Threaded = @ptrCast(@alignCast(userdata));
2738 const fd = file.handle;
2739
2740 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) while (true) {
2741 try t.checkCancel();
2742 var result: u64 = undefined;
2743 switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) {
2744 .SUCCESS => return,
2745 .INTR => continue,
2746 .CANCELED => return error.Canceled,
2747
2748 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
2749 .INVAL => return error.Unseekable,
2750 .OVERFLOW => return error.Unseekable,
2751 .SPIPE => return error.Unseekable,
2752 .NXIO => return error.Unseekable,
2753 else => |err| return posix.unexpectedErrno(err),
2754 }
2755 };
2756
2757 if (native_os == .windows) {
2758 try t.checkCancel();
2759 return windows.SetFilePointerEx_BEGIN(fd, offset);
2760 }
2761
2762 if (native_os == .wasi and !builtin.link_libc) while (true) {
2763 try t.checkCancel();
2764 var new_offset: std.os.wasi.filesize_t = undefined;
2765 switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) {
2766 .SUCCESS => return,
2767 .INTR => continue,
2768 .CANCELED => return error.Canceled,
2769
2770 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
2771 .INVAL => return error.Unseekable,
2772 .OVERFLOW => return error.Unseekable,
2773 .SPIPE => return error.Unseekable,
2774 .NXIO => return error.Unseekable,
2775 .NOTCAPABLE => return error.AccessDenied,
2776 else => |err| return posix.unexpectedErrno(err),
2777 }
2778 };
2779
2780 if (posix.SEEK == void) return error.Unseekable;
2781
2782 while (true) {
2783 try t.checkCancel();
2784 switch (posix.errno(lseek_sym(fd, @bitCast(offset), posix.SEEK.SET))) {
2785 .SUCCESS => return,
2786 .INTR => continue,
2787 .CANCELED => return error.Canceled,
2788
2789 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
2790 .INVAL => return error.Unseekable,
2791 .OVERFLOW => return error.Unseekable,
2792 .SPIPE => return error.Unseekable,
2793 .NXIO => return error.Unseekable,
2794 else => |err| return posix.unexpectedErrno(err),
2795 }
2796 }
2797}
2798
2799fn openSelfExe(userdata: ?*anyopaque, flags: Io.File.OpenFlags) Io.File.OpenSelfExeError!Io.File {
2800 const t: *Threaded = @ptrCast(@alignCast(userdata));
2801 switch (native_os) {
2802 .linux, .serenity => return dirOpenFilePosix(t, .{ .handle = posix.AT.FDCWD }, "/proc/self/exe", flags),
2803 .windows => {
2804 // If ImagePathName is a symlink, then it will contain the path of the symlink,
2805 // not the path that the symlink points to. However, because we are opening
2806 // the file, we can let the openFileW call follow the symlink for us.
2807 const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName;
2808 const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
2809 const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name);
2810 return dirOpenFileWtf16(t, null, prefixed_path_w.span(), flags);
2811 },
2812 else => @panic("TODO implement openSelfExe"),
2813 }
2814}
2815
2816fn fileWritePositional(
2817 userdata: ?*anyopaque,
2818 file: Io.File,
2819 buffer: [][]const u8,
2820 offset: u64,
2821) Io.File.WritePositionalError!usize {
2822 const t: *Threaded = @ptrCast(@alignCast(userdata));
2823 while (true) {
2824 try t.checkCancel();
2825 _ = file;
2826 _ = buffer;
2827 _ = offset;
2828 @panic("TODO implement fileWritePositional");
2829 }
2830}
2831
2832fn fileWriteStreaming(userdata: ?*anyopaque, file: Io.File, buffer: [][]const u8) Io.File.WriteStreamingError!usize {
2833 const t: *Threaded = @ptrCast(@alignCast(userdata));
2834 while (true) {
2835 try t.checkCancel();
2836 _ = file;
2837 _ = buffer;
2838 @panic("TODO implement fileWriteStreaming");
2839 }
2840}
2841
2842fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
2843 const t: *Threaded = @ptrCast(@alignCast(userdata));
2844 _ = t;
2845 const clock_id: posix.clockid_t = clockToPosix(clock);
2846 var tp: posix.timespec = undefined;
2847 switch (posix.errno(posix.system.clock_gettime(clock_id, &tp))) {
2848 .SUCCESS => return timestampFromPosix(&tp),
2849 .INVAL => return error.UnsupportedClock,
2850 else => |err| return posix.unexpectedErrno(err),
2851 }
2852}
2853
2854const now = switch (native_os) {
2855 .windows => nowWindows,
2856 .wasi => nowWasi,
2857 else => nowPosix,
2858};
2859
2860fn nowWindows(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
2861 const t: *Threaded = @ptrCast(@alignCast(userdata));
2862 _ = t;
2863 switch (clock) {
2864 .real => {
2865 // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds
2866 // and uses the NTFS/Windows epoch, which is 1601-01-01.
2867 return .{ .nanoseconds = @as(i96, windows.ntdll.RtlGetSystemTimePrecise()) * 100 };
2868 },
2869 .awake, .boot => {
2870 // QPC on windows doesn't fail on >= XP/2000 and includes time suspended.
2871 return .{ .nanoseconds = windows.QueryPerformanceCounter() };
2872 },
2873 .cpu_process,
2874 .cpu_thread,
2875 => return error.UnsupportedClock,
2876 }
2877}
2878
2879fn nowWasi(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
2880 const t: *Threaded = @ptrCast(@alignCast(userdata));
2881 _ = t;
2882 var ns: std.os.wasi.timestamp_t = undefined;
2883 const err = std.os.wasi.clock_time_get(clockToWasi(clock), 1, &ns);
2884 if (err != .SUCCESS) return error.Unexpected;
2885 return .fromNanoseconds(ns);
2886}
2887
2888const sleep = switch (native_os) {
2889 .windows => sleepWindows,
2890 .wasi => sleepWasi,
2891 .linux => sleepLinux,
2892 else => sleepPosix,
2893};
2894
2895fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
2896 const t: *Threaded = @ptrCast(@alignCast(userdata));
2897 const clock_id: posix.clockid_t = clockToPosix(switch (timeout) {
2898 .none => .awake,
2899 .duration => |d| d.clock,
2900 .deadline => |d| d.clock,
2901 });
2902 const deadline_nanoseconds: i96 = switch (timeout) {
2903 .none => std.math.maxInt(i96),
2904 .duration => |duration| duration.raw.nanoseconds,
2905 .deadline => |deadline| deadline.raw.nanoseconds,
2906 };
2907 var timespec: posix.timespec = timestampToPosix(deadline_nanoseconds);
2908 while (true) {
2909 try t.checkCancel();
2910 switch (std.os.linux.E.init(std.os.linux.clock_nanosleep(clock_id, .{ .ABSTIME = switch (timeout) {
2911 .none, .duration => false,
2912 .deadline => true,
2913 } }, &timespec, &timespec))) {
2914 .SUCCESS => return,
2915 .INTR => continue,
2916 .CANCELED => return error.Canceled,
2917 .INVAL => return error.UnsupportedClock,
2918 else => |err| return posix.unexpectedErrno(err),
2919 }
2920 }
2921}
2922
2923fn sleepWindows(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
2924 const t: *Threaded = @ptrCast(@alignCast(userdata));
2925 const t_io = ioBasic(t);
2926 try t.checkCancel();
2927 const ms = ms: {
2928 const d = (try timeout.toDurationFromNow(t_io)) orelse
2929 break :ms std.math.maxInt(windows.DWORD);
2930 break :ms std.math.lossyCast(windows.DWORD, d.raw.toMilliseconds());
2931 };
2932 // TODO: alertable true with checkCancel in a loop plus deadline
2933 _ = windows.kernel32.SleepEx(ms, windows.FALSE);
2934}
2935
2936fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
2937 const t: *Threaded = @ptrCast(@alignCast(userdata));
2938 const t_io = ioBasic(t);
2939 try t.checkCancel();
2940
2941 const w = std.os.wasi;
2942
2943 const clock: w.subscription_clock_t = if (try timeout.toDurationFromNow(t_io)) |d| .{
2944 .id = clockToWasi(d.clock),
2945 .timeout = std.math.lossyCast(u64, d.raw.nanoseconds),
2946 .precision = 0,
2947 .flags = 0,
2948 } else .{
2949 .id = .MONOTONIC,
2950 .timeout = std.math.maxInt(u64),
2951 .precision = 0,
2952 .flags = 0,
2953 };
2954 const in: w.subscription_t = .{
2955 .userdata = 0,
2956 .u = .{
2957 .tag = .CLOCK,
2958 .u = .{ .clock = clock },
2959 },
2960 };
2961 var event: w.event_t = undefined;
2962 var nevents: usize = undefined;
2963 _ = w.poll_oneoff(&in, &event, 1, &nevents);
2964}
2965
2966fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
2967 const t: *Threaded = @ptrCast(@alignCast(userdata));
2968 const t_io = ioBasic(t);
2969 const sec_type = @typeInfo(posix.timespec).@"struct".fields[0].type;
2970 const nsec_type = @typeInfo(posix.timespec).@"struct".fields[1].type;
2971
2972 var timespec: posix.timespec = t: {
2973 const d = (try timeout.toDurationFromNow(t_io)) orelse break :t .{
2974 .sec = std.math.maxInt(sec_type),
2975 .nsec = std.math.maxInt(nsec_type),
2976 };
2977 break :t timestampToPosix(d.raw.toNanoseconds());
2978 };
2979 while (true) {
2980 try t.checkCancel();
2981 switch (posix.errno(posix.system.nanosleep(&timespec, &timespec))) {
2982 .INTR => continue,
2983 .CANCELED => return error.Canceled,
2984 else => return, // This prong handles success as well as unexpected errors.
2985 }
2986 }
2987}
2988
2989fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!usize {
2990 const t: *Threaded = @ptrCast(@alignCast(userdata));
2991
2992 var reset_event: ResetEvent = .unset;
2993
2994 for (futures, 0..) |future, i| {
2995 const closure: *AsyncClosure = @ptrCast(@alignCast(future));
2996 if (@atomicRmw(?*ResetEvent, &closure.select_condition, .Xchg, &reset_event, .seq_cst) == AsyncClosure.done_reset_event) {
2997 for (futures[0..i]) |cleanup_future| {
2998 const cleanup_closure: *AsyncClosure = @ptrCast(@alignCast(cleanup_future));
2999 if (@atomicRmw(?*ResetEvent, &cleanup_closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_reset_event) {
3000 cleanup_closure.reset_event.waitUncancelable(); // Ensure no reference to our stack-allocated reset_event.
3001 }
3002 }
3003 return i;
3004 }
3005 }
3006
3007 try reset_event.wait(t);
3008
3009 var result: ?usize = null;
3010 for (futures, 0..) |future, i| {
3011 const closure: *AsyncClosure = @ptrCast(@alignCast(future));
3012 if (@atomicRmw(?*ResetEvent, &closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_reset_event) {
3013 closure.reset_event.waitUncancelable(); // Ensure no reference to our stack-allocated reset_event.
3014 if (result == null) result = i; // In case multiple are ready, return first.
3015 }
3016 }
3017 return result.?;
3018}
3019
3020fn netListenIpPosix(
3021 userdata: ?*anyopaque,
3022 address: IpAddress,
3023 options: IpAddress.ListenOptions,
3024) IpAddress.ListenError!net.Server {
3025 if (!have_networking) return error.NetworkDown;
3026 const t: *Threaded = @ptrCast(@alignCast(userdata));
3027 const family = posixAddressFamily(&address);
3028 const socket_fd = try openSocketPosix(t, family, .{
3029 .mode = options.mode,
3030 .protocol = options.protocol,
3031 });
3032 errdefer posix.close(socket_fd);
3033
3034 if (options.reuse_address) {
3035 try setSocketOption(t, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, 1);
3036 if (@hasDecl(posix.SO, "REUSEPORT"))
3037 try setSocketOption(t, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, 1);
3038 }
3039
3040 var storage: PosixAddress = undefined;
3041 var addr_len = addressToPosix(&address, &storage);
3042 try posixBind(t, socket_fd, &storage.any, addr_len);
3043
3044 while (true) {
3045 try t.checkCancel();
3046 switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) {
3047 .SUCCESS => break,
3048 .ADDRINUSE => return error.AddressInUse,
3049 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3050 else => |err| return posix.unexpectedErrno(err),
3051 }
3052 }
3053
3054 try posixGetSockName(t, socket_fd, &storage.any, &addr_len);
3055 return .{
3056 .socket = .{
3057 .handle = socket_fd,
3058 .address = addressFromPosix(&storage),
3059 },
3060 };
3061}
3062
3063fn netListenIpWindows(
3064 userdata: ?*anyopaque,
3065 address: IpAddress,
3066 options: IpAddress.ListenOptions,
3067) IpAddress.ListenError!net.Server {
3068 if (!have_networking) return error.NetworkDown;
3069 const t: *Threaded = @ptrCast(@alignCast(userdata));
3070 const family = posixAddressFamily(&address);
3071 const socket_handle = try openSocketWsa(t, family, .{
3072 .mode = options.mode,
3073 .protocol = options.protocol,
3074 });
3075 errdefer closeSocketWindows(socket_handle);
3076
3077 if (options.reuse_address)
3078 try setSocketOptionWsa(t, socket_handle, posix.SOL.SOCKET, posix.SO.REUSEADDR, 1);
3079
3080 var storage: WsaAddress = undefined;
3081 var addr_len = addressToWsa(&address, &storage);
3082
3083 while (true) {
3084 try t.checkCancel();
3085 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);
3086 if (rc != ws2_32.SOCKET_ERROR) break;
3087 switch (ws2_32.WSAGetLastError()) {
3088 .EINTR => continue,
3089 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
3090 .NOTINITIALISED => {
3091 try initializeWsa(t);
3092 continue;
3093 },
3094 .EADDRINUSE => return error.AddressInUse,
3095 .EADDRNOTAVAIL => return error.AddressUnavailable,
3096 .ENOTSOCK => |err| return wsaErrorBug(err),
3097 .EFAULT => |err| return wsaErrorBug(err),
3098 .EINVAL => |err| return wsaErrorBug(err),
3099 .ENOBUFS => return error.SystemResources,
3100 .ENETDOWN => return error.NetworkDown,
3101 else => |err| return windows.unexpectedWSAError(err),
3102 }
3103 }
3104
3105 while (true) {
3106 try t.checkCancel();
3107 const rc = ws2_32.listen(socket_handle, options.kernel_backlog);
3108 if (rc != ws2_32.SOCKET_ERROR) break;
3109 switch (ws2_32.WSAGetLastError()) {
3110 .EINTR => continue,
3111 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
3112 .NOTINITIALISED => {
3113 try initializeWsa(t);
3114 continue;
3115 },
3116 .ENETDOWN => return error.NetworkDown,
3117 .EADDRINUSE => return error.AddressInUse,
3118 .EISCONN => |err| return wsaErrorBug(err),
3119 .EINVAL => |err| return wsaErrorBug(err),
3120 .EMFILE, .ENOBUFS => return error.SystemResources,
3121 .ENOTSOCK => |err| return wsaErrorBug(err),
3122 .EOPNOTSUPP => |err| return wsaErrorBug(err),
3123 .EINPROGRESS => |err| return wsaErrorBug(err),
3124 else => |err| return windows.unexpectedWSAError(err),
3125 }
3126 }
3127
3128 try wsaGetSockName(t, socket_handle, &storage.any, &addr_len);
3129
3130 return .{
3131 .socket = .{
3132 .handle = socket_handle,
3133 .address = addressFromWsa(&storage),
3134 },
3135 };
3136}
3137
3138fn netListenIpUnavailable(
3139 userdata: ?*anyopaque,
3140 address: IpAddress,
3141 options: IpAddress.ListenOptions,
3142) IpAddress.ListenError!net.Server {
3143 _ = userdata;
3144 _ = address;
3145 _ = options;
3146 return error.NetworkDown;
3147}
3148
3149fn netListenUnixPosix(
3150 userdata: ?*anyopaque,
3151 address: *const net.UnixAddress,
3152 options: net.UnixAddress.ListenOptions,
3153) net.UnixAddress.ListenError!net.Socket.Handle {
3154 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported;
3155 const t: *Threaded = @ptrCast(@alignCast(userdata));
3156 const socket_fd = openSocketPosix(t, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) {
3157 error.ProtocolUnsupportedBySystem => return error.AddressFamilyUnsupported,
3158 error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported,
3159 error.SocketModeUnsupported => return error.AddressFamilyUnsupported,
3160 error.OptionUnsupported => return error.Unexpected,
3161 else => |e| return e,
3162 };
3163 errdefer posix.close(socket_fd);
3164
3165 var storage: UnixAddress = undefined;
3166 const addr_len = addressUnixToPosix(address, &storage);
3167 try posixBindUnix(t, socket_fd, &storage.any, addr_len);
3168
3169 while (true) {
3170 try t.checkCancel();
3171 switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) {
3172 .SUCCESS => break,
3173 .ADDRINUSE => return error.AddressInUse,
3174 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3175 else => |err| return posix.unexpectedErrno(err),
3176 }
3177 }
3178
3179 return socket_fd;
3180}
3181
3182fn netListenUnixWindows(
3183 userdata: ?*anyopaque,
3184 address: *const net.UnixAddress,
3185 options: net.UnixAddress.ListenOptions,
3186) net.UnixAddress.ListenError!net.Socket.Handle {
3187 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported;
3188 const t: *Threaded = @ptrCast(@alignCast(userdata));
3189
3190 const socket_handle = openSocketWsa(t, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) {
3191 error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported,
3192 else => |e| return e,
3193 };
3194 errdefer closeSocketWindows(socket_handle);
3195
3196 var storage: WsaAddress = undefined;
3197 const addr_len = addressUnixToWsa(address, &storage);
3198
3199 while (true) {
3200 try t.checkCancel();
3201 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);
3202 if (rc != ws2_32.SOCKET_ERROR) break;
3203 switch (ws2_32.WSAGetLastError()) {
3204 .EINTR => continue,
3205 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
3206 .NOTINITIALISED => {
3207 try initializeWsa(t);
3208 continue;
3209 },
3210 .EADDRINUSE => return error.AddressInUse,
3211 .EADDRNOTAVAIL => return error.AddressUnavailable,
3212 .ENOTSOCK => |err| return wsaErrorBug(err),
3213 .EFAULT => |err| return wsaErrorBug(err),
3214 .EINVAL => |err| return wsaErrorBug(err),
3215 .ENOBUFS => return error.SystemResources,
3216 .ENETDOWN => return error.NetworkDown,
3217 else => |err| return windows.unexpectedWSAError(err),
3218 }
3219 }
3220
3221 while (true) {
3222 try t.checkCancel();
3223 const rc = ws2_32.listen(socket_handle, options.kernel_backlog);
3224 if (rc != ws2_32.SOCKET_ERROR) break;
3225 switch (ws2_32.WSAGetLastError()) {
3226 .EINTR => continue,
3227 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
3228 .NOTINITIALISED => {
3229 try initializeWsa(t);
3230 continue;
3231 },
3232 .ENETDOWN => return error.NetworkDown,
3233 .EADDRINUSE => return error.AddressInUse,
3234 .EISCONN => |err| return wsaErrorBug(err),
3235 .EINVAL => |err| return wsaErrorBug(err),
3236 .EMFILE, .ENOBUFS => return error.SystemResources,
3237 .ENOTSOCK => |err| return wsaErrorBug(err),
3238 .EOPNOTSUPP => |err| return wsaErrorBug(err),
3239 .EINPROGRESS => |err| return wsaErrorBug(err),
3240 else => |err| return windows.unexpectedWSAError(err),
3241 }
3242 }
3243
3244 return socket_handle;
3245}
3246
3247fn netListenUnixUnavailable(
3248 userdata: ?*anyopaque,
3249 address: *const net.UnixAddress,
3250 options: net.UnixAddress.ListenOptions,
3251) net.UnixAddress.ListenError!net.Socket.Handle {
3252 _ = userdata;
3253 _ = address;
3254 _ = options;
3255 return error.AddressFamilyUnsupported;
3256}
3257
3258fn posixBindUnix(t: *Threaded, fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void {
3259 while (true) {
3260 try t.checkCancel();
3261 switch (posix.errno(posix.system.bind(fd, addr, addr_len))) {
3262 .SUCCESS => break,
3263 .INTR => continue,
3264 .CANCELED => return error.Canceled,
3265
3266 .ACCES => return error.AccessDenied,
3267 .ADDRINUSE => return error.AddressInUse,
3268 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
3269 .ADDRNOTAVAIL => return error.AddressUnavailable,
3270 .NOMEM => return error.SystemResources,
3271
3272 .LOOP => return error.SymLinkLoop,
3273 .NOENT => return error.FileNotFound,
3274 .NOTDIR => return error.NotDir,
3275 .ROFS => return error.ReadOnlyFileSystem,
3276 .PERM => return error.PermissionDenied,
3277
3278 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3279 .INVAL => |err| return errnoBug(err), // invalid parameters
3280 .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd`
3281 .FAULT => |err| return errnoBug(err), // invalid `addr` pointer
3282 .NAMETOOLONG => |err| return errnoBug(err),
3283 else => |err| return posix.unexpectedErrno(err),
3284 }
3285 }
3286}
3287
3288fn posixBind(t: *Threaded, socket_fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void {
3289 while (true) {
3290 try t.checkCancel();
3291 switch (posix.errno(posix.system.bind(socket_fd, addr, addr_len))) {
3292 .SUCCESS => break,
3293 .INTR => continue,
3294 .CANCELED => return error.Canceled,
3295
3296 .ADDRINUSE => return error.AddressInUse,
3297 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3298 .INVAL => |err| return errnoBug(err), // invalid parameters
3299 .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd`
3300 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
3301 .ADDRNOTAVAIL => return error.AddressUnavailable,
3302 .FAULT => |err| return errnoBug(err), // invalid `addr` pointer
3303 .NOMEM => return error.SystemResources,
3304 else => |err| return posix.unexpectedErrno(err),
3305 }
3306 }
3307}
3308
3309fn posixConnect(t: *Threaded, socket_fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void {
3310 while (true) {
3311 try t.checkCancel();
3312 switch (posix.errno(posix.system.connect(socket_fd, addr, addr_len))) {
3313 .SUCCESS => return,
3314 .INTR => continue,
3315 .CANCELED => return error.Canceled,
3316
3317 .ADDRNOTAVAIL => return error.AddressUnavailable,
3318 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
3319 .AGAIN, .INPROGRESS => return error.WouldBlock,
3320 .ALREADY => return error.ConnectionPending,
3321 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3322 .CONNREFUSED => return error.ConnectionRefused,
3323 .CONNRESET => return error.ConnectionResetByPeer,
3324 .FAULT => |err| return errnoBug(err),
3325 .ISCONN => |err| return errnoBug(err),
3326 .HOSTUNREACH => return error.HostUnreachable,
3327 .NETUNREACH => return error.NetworkUnreachable,
3328 .NOTSOCK => |err| return errnoBug(err),
3329 .PROTOTYPE => |err| return errnoBug(err),
3330 .TIMEDOUT => return error.Timeout,
3331 .CONNABORTED => |err| return errnoBug(err),
3332 .ACCES => return error.AccessDenied,
3333 .PERM => |err| return errnoBug(err),
3334 .NOENT => |err| return errnoBug(err),
3335 .NETDOWN => return error.NetworkDown,
3336 else => |err| return posix.unexpectedErrno(err),
3337 }
3338 }
3339}
3340
3341fn posixConnectUnix(t: *Threaded, fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void {
3342 while (true) {
3343 try t.checkCancel();
3344 switch (posix.errno(posix.system.connect(fd, addr, addr_len))) {
3345 .SUCCESS => return,
3346 .INTR => continue,
3347 .CANCELED => return error.Canceled,
3348
3349 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
3350 .AGAIN => return error.WouldBlock,
3351 .INPROGRESS => return error.WouldBlock,
3352 .ACCES => return error.AccessDenied,
3353
3354 .LOOP => return error.SymLinkLoop,
3355 .NOENT => return error.FileNotFound,
3356 .NOTDIR => return error.NotDir,
3357 .ROFS => return error.ReadOnlyFileSystem,
3358 .PERM => return error.PermissionDenied,
3359
3360 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3361 .CONNABORTED => |err| return errnoBug(err),
3362 .FAULT => |err| return errnoBug(err),
3363 .ISCONN => |err| return errnoBug(err),
3364 .NOTSOCK => |err| return errnoBug(err),
3365 .PROTOTYPE => |err| return errnoBug(err),
3366 else => |err| return posix.unexpectedErrno(err),
3367 }
3368 }
3369}
3370
3371fn posixGetSockName(t: *Threaded, socket_fd: posix.fd_t, addr: *posix.sockaddr, addr_len: *posix.socklen_t) !void {
3372 while (true) {
3373 try t.checkCancel();
3374 switch (posix.errno(posix.system.getsockname(socket_fd, addr, addr_len))) {
3375 .SUCCESS => break,
3376 .INTR => continue,
3377 .CANCELED => return error.Canceled,
3378
3379 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3380 .FAULT => |err| return errnoBug(err),
3381 .INVAL => |err| return errnoBug(err), // invalid parameters
3382 .NOTSOCK => |err| return errnoBug(err), // always a race condition
3383 .NOBUFS => return error.SystemResources,
3384 else => |err| return posix.unexpectedErrno(err),
3385 }
3386 }
3387}
3388
3389fn wsaGetSockName(t: *Threaded, handle: ws2_32.SOCKET, addr: *ws2_32.sockaddr, addr_len: *i32) !void {
3390 while (true) {
3391 try t.checkCancel();
3392 const rc = ws2_32.getsockname(handle, addr, addr_len);
3393 if (rc != ws2_32.SOCKET_ERROR) break;
3394 switch (ws2_32.WSAGetLastError()) {
3395 .EINTR => continue,
3396 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
3397 .NOTINITIALISED => {
3398 try initializeWsa(t);
3399 continue;
3400 },
3401 .ENETDOWN => return error.NetworkDown,
3402 .EFAULT => |err| return wsaErrorBug(err),
3403 .ENOTSOCK => |err| return wsaErrorBug(err),
3404 .EINVAL => |err| return wsaErrorBug(err),
3405 else => |err| return windows.unexpectedWSAError(err),
3406 }
3407 }
3408}
3409
3410fn setSocketOption(t: *Threaded, fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void {
3411 const o: []const u8 = @ptrCast(&option);
3412 while (true) {
3413 try t.checkCancel();
3414 switch (posix.errno(posix.system.setsockopt(fd, level, opt_name, o.ptr, @intCast(o.len)))) {
3415 .SUCCESS => return,
3416 .INTR => continue,
3417 .CANCELED => return error.Canceled,
3418
3419 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3420 .NOTSOCK => |err| return errnoBug(err),
3421 .INVAL => |err| return errnoBug(err),
3422 .FAULT => |err| return errnoBug(err),
3423 else => |err| return posix.unexpectedErrno(err),
3424 }
3425 }
3426}
3427
3428fn setSocketOptionWsa(t: *Threaded, socket: Io.net.Socket.Handle, level: i32, opt_name: u32, option: u32) !void {
3429 const o: []const u8 = @ptrCast(&option);
3430 const rc = ws2_32.setsockopt(socket, level, @bitCast(opt_name), o.ptr, @intCast(o.len));
3431 while (true) {
3432 if (rc != ws2_32.SOCKET_ERROR) return;
3433 switch (ws2_32.WSAGetLastError()) {
3434 .EINTR => continue,
3435 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
3436 .NOTINITIALISED => {
3437 try initializeWsa(t);
3438 continue;
3439 },
3440 .ENETDOWN => return error.NetworkDown,
3441 .EFAULT => |err| return wsaErrorBug(err),
3442 .ENOTSOCK => |err| return wsaErrorBug(err),
3443 .EINVAL => |err| return wsaErrorBug(err),
3444 else => |err| return windows.unexpectedWSAError(err),
3445 }
3446 }
3447}
3448
3449fn netConnectIpPosix(
3450 userdata: ?*anyopaque,
3451 address: *const IpAddress,
3452 options: IpAddress.ConnectOptions,
3453) IpAddress.ConnectError!net.Stream {
3454 if (!have_networking) return error.NetworkDown;
3455 if (options.timeout != .none) @panic("TODO implement netConnectIpPosix with timeout");
3456 const t: *Threaded = @ptrCast(@alignCast(userdata));
3457 const family = posixAddressFamily(address);
3458 const socket_fd = try openSocketPosix(t, family, .{
3459 .mode = options.mode,
3460 .protocol = options.protocol,
3461 });
3462 errdefer posix.close(socket_fd);
3463 var storage: PosixAddress = undefined;
3464 var addr_len = addressToPosix(address, &storage);
3465 try posixConnect(t, socket_fd, &storage.any, addr_len);
3466 try posixGetSockName(t, socket_fd, &storage.any, &addr_len);
3467 return .{ .socket = .{
3468 .handle = socket_fd,
3469 .address = addressFromPosix(&storage),
3470 } };
3471}
3472
3473fn netConnectIpWindows(
3474 userdata: ?*anyopaque,
3475 address: *const IpAddress,
3476 options: IpAddress.ConnectOptions,
3477) IpAddress.ConnectError!net.Stream {
3478 if (!have_networking) return error.NetworkDown;
3479 if (options.timeout != .none) @panic("TODO implement netConnectIpWindows with timeout");
3480 const t: *Threaded = @ptrCast(@alignCast(userdata));
3481 const family = posixAddressFamily(address);
3482 const socket_handle = try openSocketWsa(t, family, .{
3483 .mode = options.mode,
3484 .protocol = options.protocol,
3485 });
3486 errdefer closeSocketWindows(socket_handle);
3487
3488 var storage: WsaAddress = undefined;
3489 var addr_len = addressToWsa(address, &storage);
3490
3491 while (true) {
3492 const rc = ws2_32.connect(socket_handle, &storage.any, addr_len);
3493 if (rc != ws2_32.SOCKET_ERROR) break;
3494 switch (ws2_32.WSAGetLastError()) {
3495 .EINTR => continue,
3496 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
3497 .NOTINITIALISED => {
3498 try initializeWsa(t);
3499 continue;
3500 },
3501
3502 .EADDRNOTAVAIL => return error.AddressUnavailable,
3503 .ECONNREFUSED => return error.ConnectionRefused,
3504 .ECONNRESET => return error.ConnectionResetByPeer,
3505 .ETIMEDOUT => return error.Timeout,
3506 .EHOSTUNREACH => return error.HostUnreachable,
3507 .ENETUNREACH => return error.NetworkUnreachable,
3508 .EFAULT => |err| return wsaErrorBug(err),
3509 .EINVAL => |err| return wsaErrorBug(err),
3510 .EISCONN => |err| return wsaErrorBug(err),
3511 .ENOTSOCK => |err| return wsaErrorBug(err),
3512 .EWOULDBLOCK => return error.WouldBlock,
3513 .EACCES => return error.AccessDenied,
3514 .ENOBUFS => return error.SystemResources,
3515 .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
3516 else => |err| return windows.unexpectedWSAError(err),
3517 }
3518 }
3519
3520 try wsaGetSockName(t, socket_handle, &storage.any, &addr_len);
3521
3522 return .{ .socket = .{
3523 .handle = socket_handle,
3524 .address = addressFromWsa(&storage),
3525 } };
3526}
3527
3528fn netConnectIpUnavailable(
3529 userdata: ?*anyopaque,
3530 address: *const IpAddress,
3531 options: IpAddress.ConnectOptions,
3532) IpAddress.ConnectError!net.Stream {
3533 _ = userdata;
3534 _ = address;
3535 _ = options;
3536 return error.NetworkDown;
3537}
3538
3539fn netConnectUnixPosix(
3540 userdata: ?*anyopaque,
3541 address: *const net.UnixAddress,
3542) net.UnixAddress.ConnectError!net.Socket.Handle {
3543 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported;
3544 const t: *Threaded = @ptrCast(@alignCast(userdata));
3545 const socket_fd = openSocketPosix(t, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) {
3546 error.OptionUnsupported => return error.Unexpected,
3547 else => |e| return e,
3548 };
3549 errdefer posix.close(socket_fd);
3550 var storage: UnixAddress = undefined;
3551 const addr_len = addressUnixToPosix(address, &storage);
3552 try posixConnectUnix(t, socket_fd, &storage.any, addr_len);
3553 return socket_fd;
3554}
3555
3556fn netConnectUnixWindows(
3557 userdata: ?*anyopaque,
3558 address: *const net.UnixAddress,
3559) net.UnixAddress.ConnectError!net.Socket.Handle {
3560 if (!net.has_unix_sockets) return error.AddressFamilyUnsupported;
3561 const t: *Threaded = @ptrCast(@alignCast(userdata));
3562
3563 const socket_handle = try openSocketWsa(t, posix.AF.UNIX, .{ .mode = .stream });
3564 errdefer closeSocketWindows(socket_handle);
3565 var storage: WsaAddress = undefined;
3566 const addr_len = addressUnixToWsa(address, &storage);
3567
3568 while (true) {
3569 const rc = ws2_32.connect(socket_handle, &storage.any, addr_len);
3570 if (rc != ws2_32.SOCKET_ERROR) break;
3571 switch (ws2_32.WSAGetLastError()) {
3572 .EINTR => continue,
3573 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
3574 .NOTINITIALISED => {
3575 try initializeWsa(t);
3576 continue;
3577 },
3578
3579 .ECONNREFUSED => return error.FileNotFound,
3580 .EFAULT => |err| return wsaErrorBug(err),
3581 .EINVAL => |err| return wsaErrorBug(err),
3582 .EISCONN => |err| return wsaErrorBug(err),
3583 .ENOTSOCK => |err| return wsaErrorBug(err),
3584 .EWOULDBLOCK => return error.WouldBlock,
3585 .EACCES => return error.AccessDenied,
3586 .ENOBUFS => return error.SystemResources,
3587 .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
3588 else => |err| return windows.unexpectedWSAError(err),
3589 }
3590 }
3591
3592 return socket_handle;
3593}
3594
3595fn netConnectUnixUnavailable(
3596 userdata: ?*anyopaque,
3597 address: *const net.UnixAddress,
3598) net.UnixAddress.ConnectError!net.Socket.Handle {
3599 _ = userdata;
3600 _ = address;
3601 return error.AddressFamilyUnsupported;
3602}
3603
3604fn netBindIpPosix(
3605 userdata: ?*anyopaque,
3606 address: *const IpAddress,
3607 options: IpAddress.BindOptions,
3608) IpAddress.BindError!net.Socket {
3609 if (!have_networking) return error.NetworkDown;
3610 const t: *Threaded = @ptrCast(@alignCast(userdata));
3611 const family = posixAddressFamily(address);
3612 const socket_fd = try openSocketPosix(t, family, options);
3613 errdefer posix.close(socket_fd);
3614 var storage: PosixAddress = undefined;
3615 var addr_len = addressToPosix(address, &storage);
3616 try posixBind(t, socket_fd, &storage.any, addr_len);
3617 try posixGetSockName(t, socket_fd, &storage.any, &addr_len);
3618 return .{
3619 .handle = socket_fd,
3620 .address = addressFromPosix(&storage),
3621 };
3622}
3623
3624fn netBindIpWindows(
3625 userdata: ?*anyopaque,
3626 address: *const IpAddress,
3627 options: IpAddress.BindOptions,
3628) IpAddress.BindError!net.Socket {
3629 if (!have_networking) return error.NetworkDown;
3630 const t: *Threaded = @ptrCast(@alignCast(userdata));
3631 const family = posixAddressFamily(address);
3632 const socket_handle = try openSocketWsa(t, family, .{
3633 .mode = options.mode,
3634 .protocol = options.protocol,
3635 });
3636 errdefer closeSocketWindows(socket_handle);
3637
3638 var storage: WsaAddress = undefined;
3639 var addr_len = addressToWsa(address, &storage);
3640
3641 while (true) {
3642 try t.checkCancel();
3643 const rc = ws2_32.bind(socket_handle, &storage.any, addr_len);
3644 if (rc != ws2_32.SOCKET_ERROR) break;
3645 switch (ws2_32.WSAGetLastError()) {
3646 .EINTR => continue,
3647 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
3648 .NOTINITIALISED => {
3649 try initializeWsa(t);
3650 continue;
3651 },
3652 .EADDRINUSE => return error.AddressInUse,
3653 .EADDRNOTAVAIL => return error.AddressUnavailable,
3654 .ENOTSOCK => |err| return wsaErrorBug(err),
3655 .EFAULT => |err| return wsaErrorBug(err),
3656 .EINVAL => |err| return wsaErrorBug(err),
3657 .ENOBUFS => return error.SystemResources,
3658 .ENETDOWN => return error.NetworkDown,
3659 else => |err| return windows.unexpectedWSAError(err),
3660 }
3661 }
3662
3663 try wsaGetSockName(t, socket_handle, &storage.any, &addr_len);
3664
3665 return .{
3666 .handle = socket_handle,
3667 .address = addressFromWsa(&storage),
3668 };
3669}
3670
3671fn netBindIpUnavailable(
3672 userdata: ?*anyopaque,
3673 address: *const IpAddress,
3674 options: IpAddress.BindOptions,
3675) IpAddress.BindError!net.Socket {
3676 _ = userdata;
3677 _ = address;
3678 _ = options;
3679 return error.NetworkDown;
3680}
3681
3682fn openSocketPosix(
3683 t: *Threaded,
3684 family: posix.sa_family_t,
3685 options: IpAddress.BindOptions,
3686) error{
3687 AddressFamilyUnsupported,
3688 ProtocolUnsupportedBySystem,
3689 ProcessFdQuotaExceeded,
3690 SystemFdQuotaExceeded,
3691 SystemResources,
3692 ProtocolUnsupportedByAddressFamily,
3693 SocketModeUnsupported,
3694 OptionUnsupported,
3695 Unexpected,
3696 Canceled,
3697}!posix.socket_t {
3698 const mode = posixSocketMode(options.mode);
3699 const protocol = posixProtocol(options.protocol);
3700 const socket_fd = while (true) {
3701 try t.checkCancel();
3702 const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC;
3703 const socket_rc = posix.system.socket(family, flags, protocol);
3704 switch (posix.errno(socket_rc)) {
3705 .SUCCESS => {
3706 const fd: posix.fd_t = @intCast(socket_rc);
3707 errdefer posix.close(fd);
3708 if (socket_flags_unsupported) while (true) {
3709 try t.checkCancel();
3710 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) {
3711 .SUCCESS => break,
3712 .INTR => continue,
3713 .CANCELED => return error.Canceled,
3714 else => |err| return posix.unexpectedErrno(err),
3715 }
3716 };
3717 break fd;
3718 },
3719 .INTR => continue,
3720 .CANCELED => return error.Canceled,
3721
3722 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
3723 .INVAL => return error.ProtocolUnsupportedBySystem,
3724 .MFILE => return error.ProcessFdQuotaExceeded,
3725 .NFILE => return error.SystemFdQuotaExceeded,
3726 .NOBUFS => return error.SystemResources,
3727 .NOMEM => return error.SystemResources,
3728 .PROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily,
3729 .PROTOTYPE => return error.SocketModeUnsupported,
3730 else => |err| return posix.unexpectedErrno(err),
3731 }
3732 };
3733 errdefer posix.close(socket_fd);
3734
3735 if (options.ip6_only) {
3736 if (posix.IPV6 == void) return error.OptionUnsupported;
3737 try setSocketOption(t, socket_fd, posix.IPPROTO.IPV6, posix.IPV6.V6ONLY, 0);
3738 }
3739
3740 return socket_fd;
3741}
3742
3743fn openSocketWsa(t: *Threaded, family: posix.sa_family_t, options: IpAddress.BindOptions) !ws2_32.SOCKET {
3744 const mode = posixSocketMode(options.mode);
3745 const protocol = posixProtocol(options.protocol);
3746 const flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED | ws2_32.WSA_FLAG_NO_HANDLE_INHERIT;
3747 while (true) {
3748 try t.checkCancel();
3749 const rc = ws2_32.WSASocketW(family, @bitCast(mode), @bitCast(protocol), null, 0, flags);
3750 if (rc != ws2_32.INVALID_SOCKET) return rc;
3751 switch (ws2_32.WSAGetLastError()) {
3752 .EINTR => continue,
3753 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
3754 .NOTINITIALISED => {
3755 try initializeWsa(t);
3756 continue;
3757 },
3758 .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
3759 .EMFILE => return error.ProcessFdQuotaExceeded,
3760 .ENOBUFS => return error.SystemResources,
3761 .EPROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily,
3762 else => |err| return windows.unexpectedWSAError(err),
3763 }
3764 }
3765}
3766
3767fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Server.AcceptError!net.Stream {
3768 if (!have_networking) return error.NetworkDown;
3769 const t: *Threaded = @ptrCast(@alignCast(userdata));
3770 var storage: PosixAddress = undefined;
3771 var addr_len: posix.socklen_t = @sizeOf(PosixAddress);
3772 const fd = while (true) {
3773 try t.checkCancel();
3774 const rc = if (have_accept4)
3775 posix.system.accept4(listen_fd, &storage.any, &addr_len, posix.SOCK.CLOEXEC)
3776 else
3777 posix.system.accept(listen_fd, &storage.any, &addr_len);
3778 switch (posix.errno(rc)) {
3779 .SUCCESS => {
3780 const fd: posix.fd_t = @intCast(rc);
3781 errdefer posix.close(fd);
3782 if (!have_accept4) while (true) {
3783 try t.checkCancel();
3784 switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) {
3785 .SUCCESS => break,
3786 .INTR => continue,
3787 .CANCELED => return error.Canceled,
3788 else => |err| return posix.unexpectedErrno(err),
3789 }
3790 };
3791 break fd;
3792 },
3793 .INTR => continue,
3794 .CANCELED => return error.Canceled,
3795
3796 .AGAIN => |err| return errnoBug(err),
3797 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3798 .CONNABORTED => return error.ConnectionAborted,
3799 .FAULT => |err| return errnoBug(err),
3800 .INVAL => |err| return errnoBug(err),
3801 .NOTSOCK => |err| return errnoBug(err),
3802 .MFILE => return error.ProcessFdQuotaExceeded,
3803 .NFILE => return error.SystemFdQuotaExceeded,
3804 .NOBUFS => return error.SystemResources,
3805 .NOMEM => return error.SystemResources,
3806 .OPNOTSUPP => |err| return errnoBug(err),
3807 .PROTO => return error.ProtocolFailure,
3808 .PERM => return error.BlockedByFirewall,
3809 else => |err| return posix.unexpectedErrno(err),
3810 }
3811 };
3812 return .{ .socket = .{
3813 .handle = fd,
3814 .address = addressFromPosix(&storage),
3815 } };
3816}
3817
3818fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net.Server.AcceptError!net.Stream {
3819 if (!have_networking) return error.NetworkDown;
3820 const t: *Threaded = @ptrCast(@alignCast(userdata));
3821 var storage: WsaAddress = undefined;
3822 var addr_len: i32 = @sizeOf(WsaAddress);
3823 while (true) {
3824 try t.checkCancel();
3825 const rc = ws2_32.accept(listen_handle, &storage.any, &addr_len);
3826 if (rc != ws2_32.INVALID_SOCKET) return .{ .socket = .{
3827 .handle = rc,
3828 .address = addressFromWsa(&storage),
3829 } };
3830 switch (ws2_32.WSAGetLastError()) {
3831 .EINTR => continue,
3832 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
3833 .NOTINITIALISED => {
3834 try initializeWsa(t);
3835 continue;
3836 },
3837 .ECONNRESET => return error.ConnectionAborted,
3838 .EFAULT => |err| return wsaErrorBug(err),
3839 .ENOTSOCK => |err| return wsaErrorBug(err),
3840 .EINVAL => |err| return wsaErrorBug(err),
3841 .EMFILE => return error.ProcessFdQuotaExceeded,
3842 .ENETDOWN => return error.NetworkDown,
3843 .ENOBUFS => return error.SystemResources,
3844 .EOPNOTSUPP => |err| return wsaErrorBug(err),
3845 else => |err| return windows.unexpectedWSAError(err),
3846 }
3847 }
3848}
3849
3850fn netAcceptUnavailable(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net.Server.AcceptError!net.Stream {
3851 _ = userdata;
3852 _ = listen_handle;
3853 return error.NetworkDown;
3854}
3855
3856fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize {
3857 if (!have_networking) return error.NetworkDown;
3858 const t: *Threaded = @ptrCast(@alignCast(userdata));
3859
3860 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
3861 var i: usize = 0;
3862 for (data) |buf| {
3863 if (iovecs_buffer.len - i == 0) break;
3864 if (buf.len != 0) {
3865 iovecs_buffer[i] = .{ .base = buf.ptr, .len = buf.len };
3866 i += 1;
3867 }
3868 }
3869 const dest = iovecs_buffer[0..i];
3870 assert(dest[0].len > 0);
3871
3872 if (native_os == .wasi and !builtin.link_libc) while (true) {
3873 try t.checkCancel();
3874 var n: usize = undefined;
3875 switch (std.os.wasi.fd_read(fd, dest.ptr, dest.len, &n)) {
3876 .SUCCESS => return n,
3877 .INTR => continue,
3878 .CANCELED => return error.Canceled,
3879
3880 .INVAL => |err| return errnoBug(err),
3881 .FAULT => |err| return errnoBug(err),
3882 .AGAIN => |err| return errnoBug(err),
3883 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3884 .NOBUFS => return error.SystemResources,
3885 .NOMEM => return error.SystemResources,
3886 .NOTCONN => return error.SocketUnconnected,
3887 .CONNRESET => return error.ConnectionResetByPeer,
3888 .TIMEDOUT => return error.Timeout,
3889 .NOTCAPABLE => return error.AccessDenied,
3890 else => |err| return posix.unexpectedErrno(err),
3891 }
3892 };
3893
3894 while (true) {
3895 try t.checkCancel();
3896 const rc = posix.system.readv(fd, dest.ptr, @intCast(dest.len));
3897 switch (posix.errno(rc)) {
3898 .SUCCESS => return @intCast(rc),
3899 .INTR => continue,
3900 .CANCELED => return error.Canceled,
3901
3902 .INVAL => |err| return errnoBug(err),
3903 .FAULT => |err| return errnoBug(err),
3904 .AGAIN => |err| return errnoBug(err),
3905 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3906 .NOBUFS => return error.SystemResources,
3907 .NOMEM => return error.SystemResources,
3908 .NOTCONN => return error.SocketUnconnected,
3909 .CONNRESET => return error.ConnectionResetByPeer,
3910 .TIMEDOUT => return error.Timeout,
3911 .PIPE => return error.SocketUnconnected,
3912 .NETDOWN => return error.NetworkDown,
3913 else => |err| return posix.unexpectedErrno(err),
3914 }
3915 }
3916}
3917
3918fn netReadWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize {
3919 if (!have_networking) return error.NetworkDown;
3920 const t: *Threaded = @ptrCast(@alignCast(userdata));
3921
3922 const bufs = b: {
3923 var iovec_buffer: [max_iovecs_len]ws2_32.WSABUF = undefined;
3924 var i: usize = 0;
3925 var n: usize = 0;
3926 for (data) |buf| {
3927 if (iovec_buffer.len - i == 0) break;
3928 if (buf.len == 0) continue;
3929 if (std.math.cast(u32, buf.len)) |len| {
3930 iovec_buffer[i] = .{ .buf = buf.ptr, .len = len };
3931 i += 1;
3932 n += len;
3933 continue;
3934 }
3935 iovec_buffer[i] = .{ .buf = buf.ptr, .len = std.math.maxInt(u32) };
3936 i += 1;
3937 n += std.math.maxInt(u32);
3938 break;
3939 }
3940
3941 const bufs = iovec_buffer[0..i];
3942 assert(bufs[0].len != 0);
3943
3944 break :b bufs;
3945 };
3946
3947 while (true) {
3948 try t.checkCancel();
3949
3950 var flags: u32 = 0;
3951 var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED);
3952 var n: u32 = undefined;
3953 const rc = ws2_32.WSARecv(handle, bufs.ptr, @intCast(bufs.len), &n, &flags, &overlapped, null);
3954 if (rc != ws2_32.SOCKET_ERROR) return n;
3955 const wsa_error: ws2_32.WinsockError = switch (ws2_32.WSAGetLastError()) {
3956 .IO_PENDING => e: {
3957 var result_flags: u32 = undefined;
3958 const overlapped_rc = ws2_32.WSAGetOverlappedResult(
3959 handle,
3960 &overlapped,
3961 &n,
3962 windows.TRUE,
3963 &result_flags,
3964 );
3965 if (overlapped_rc == windows.FALSE) {
3966 break :e ws2_32.WSAGetLastError();
3967 } else {
3968 return n;
3969 }
3970 },
3971 else => |err| err,
3972 };
3973 switch (wsa_error) {
3974 .EINTR => continue,
3975 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
3976 .NOTINITIALISED => {
3977 try initializeWsa(t);
3978 continue;
3979 },
3980
3981 .ECONNRESET => return error.ConnectionResetByPeer,
3982 .EFAULT => unreachable, // a pointer is not completely contained in user address space.
3983 .EINVAL => |err| return wsaErrorBug(err),
3984 .EMSGSIZE => |err| return wsaErrorBug(err),
3985 .ENETDOWN => return error.NetworkDown,
3986 .ENETRESET => return error.ConnectionResetByPeer,
3987 .ENOTCONN => return error.SocketUnconnected,
3988 else => |err| return windows.unexpectedWSAError(err),
3989 }
3990 }
3991}
3992
3993fn netReadUnavailable(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize {
3994 _ = userdata;
3995 _ = fd;
3996 _ = data;
3997 return error.NetworkDown;
3998}
3999
4000fn netSendPosix(
4001 userdata: ?*anyopaque,
4002 handle: net.Socket.Handle,
4003 messages: []net.OutgoingMessage,
4004 flags: net.SendFlags,
4005) struct { ?net.Socket.SendError, usize } {
4006 if (!have_networking) return .{ error.NetworkDown, 0 };
4007 const t: *Threaded = @ptrCast(@alignCast(userdata));
4008
4009 const posix_flags: u32 =
4010 @as(u32, if (@hasDecl(posix.MSG, "CONFIRM") and flags.confirm) posix.MSG.CONFIRM else 0) |
4011 @as(u32, if (@hasDecl(posix.MSG, "DONTROUTE") and flags.dont_route) posix.MSG.DONTROUTE else 0) |
4012 @as(u32, if (@hasDecl(posix.MSG, "EOR") and flags.eor) posix.MSG.EOR else 0) |
4013 @as(u32, if (@hasDecl(posix.MSG, "OOB") and flags.oob) posix.MSG.OOB else 0) |
4014 @as(u32, if (@hasDecl(posix.MSG, "FASTOPEN") and flags.fastopen) posix.MSG.FASTOPEN else 0) |
4015 posix.MSG.NOSIGNAL;
4016
4017 var i: usize = 0;
4018 while (messages.len - i != 0) {
4019 if (have_sendmmsg) {
4020 i += netSendMany(t, handle, messages[i..], posix_flags) catch |err| return .{ err, i };
4021 continue;
4022 }
4023 netSendOne(t, handle, &messages[i], posix_flags) catch |err| return .{ err, i };
4024 i += 1;
4025 }
4026 return .{ null, i };
4027}
4028
4029fn netSendWindows(
4030 userdata: ?*anyopaque,
4031 handle: net.Socket.Handle,
4032 messages: []net.OutgoingMessage,
4033 flags: net.SendFlags,
4034) struct { ?net.Socket.SendError, usize } {
4035 if (!have_networking) return .{ error.NetworkDown, 0 };
4036 const t: *Threaded = @ptrCast(@alignCast(userdata));
4037 _ = t;
4038 _ = handle;
4039 _ = messages;
4040 _ = flags;
4041 @panic("TODO netSendWindows");
4042}
4043
4044fn netSendUnavailable(
4045 userdata: ?*anyopaque,
4046 handle: net.Socket.Handle,
4047 messages: []net.OutgoingMessage,
4048 flags: net.SendFlags,
4049) struct { ?net.Socket.SendError, usize } {
4050 _ = userdata;
4051 _ = handle;
4052 _ = messages;
4053 _ = flags;
4054 return .{ error.NetworkDown, 0 };
4055}
4056
4057fn netSendOne(
4058 t: *Threaded,
4059 handle: net.Socket.Handle,
4060 message: *net.OutgoingMessage,
4061 flags: u32,
4062) net.Socket.SendError!void {
4063 var addr: PosixAddress = undefined;
4064 var iovec: posix.iovec_const = .{ .base = @constCast(message.data_ptr), .len = message.data_len };
4065 const msg: posix.msghdr_const = .{
4066 .name = &addr.any,
4067 .namelen = addressToPosix(message.address, &addr),
4068 .iov = (&iovec)[0..1],
4069 .iovlen = 1,
4070 // OS returns EINVAL if this pointer is invalid even if controllen is zero.
4071 .control = if (message.control.len == 0) null else @constCast(message.control.ptr),
4072 .controllen = @intCast(message.control.len),
4073 .flags = 0,
4074 };
4075 while (true) {
4076 try t.checkCancel();
4077 const rc = posix.system.sendmsg(handle, &msg, flags);
4078 if (is_windows) {
4079 if (rc == ws2_32.SOCKET_ERROR) {
4080 switch (ws2_32.WSAGetLastError()) {
4081 .EINTR => continue,
4082 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
4083 .NOTINITIALISED => {
4084 try initializeWsa(t);
4085 continue;
4086 },
4087 .EACCES => return error.AccessDenied,
4088 .EADDRNOTAVAIL => return error.AddressUnavailable,
4089 .ECONNRESET => return error.ConnectionResetByPeer,
4090 .EMSGSIZE => return error.MessageOversize,
4091 .ENOBUFS => return error.SystemResources,
4092 .ENOTSOCK => return error.FileDescriptorNotASocket,
4093 .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
4094 .EDESTADDRREQ => unreachable, // A destination address is required.
4095 .EFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small.
4096 .EHOSTUNREACH => return error.NetworkUnreachable,
4097 .EINVAL => unreachable,
4098 .ENETDOWN => return error.NetworkDown,
4099 .ENETRESET => return error.ConnectionResetByPeer,
4100 .ENETUNREACH => return error.NetworkUnreachable,
4101 .ENOTCONN => return error.SocketUnconnected,
4102 .ESHUTDOWN => |err| return wsaErrorBug(err),
4103 else => |err| return windows.unexpectedWSAError(err),
4104 }
4105 } else {
4106 message.data_len = @intCast(rc);
4107 return;
4108 }
4109 }
4110 switch (posix.errno(rc)) {
4111 .SUCCESS => {
4112 message.data_len = @intCast(rc);
4113 return;
4114 },
4115 .INTR => continue,
4116 .CANCELED => return error.Canceled,
4117
4118 .ACCES => return error.AccessDenied,
4119 .ALREADY => return error.FastOpenAlreadyInProgress,
4120 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4121 .CONNRESET => return error.ConnectionResetByPeer,
4122 .DESTADDRREQ => |err| return errnoBug(err),
4123 .FAULT => |err| return errnoBug(err),
4124 .INVAL => |err| return errnoBug(err),
4125 .ISCONN => |err| return errnoBug(err),
4126 .MSGSIZE => return error.MessageOversize,
4127 .NOBUFS => return error.SystemResources,
4128 .NOMEM => return error.SystemResources,
4129 .NOTSOCK => |err| return errnoBug(err),
4130 .OPNOTSUPP => |err| return errnoBug(err),
4131 .PIPE => return error.SocketUnconnected,
4132 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
4133 .HOSTUNREACH => return error.HostUnreachable,
4134 .NETUNREACH => return error.NetworkUnreachable,
4135 .NOTCONN => return error.SocketUnconnected,
4136 .NETDOWN => return error.NetworkDown,
4137 else => |err| return posix.unexpectedErrno(err),
4138 }
4139 }
4140}
4141
4142fn netSendMany(
4143 t: *Threaded,
4144 handle: net.Socket.Handle,
4145 messages: []net.OutgoingMessage,
4146 flags: u32,
4147) net.Socket.SendError!usize {
4148 var msg_buffer: [64]std.os.linux.mmsghdr = undefined;
4149 var addr_buffer: [msg_buffer.len]PosixAddress = undefined;
4150 var iovecs_buffer: [msg_buffer.len]posix.iovec = undefined;
4151 const min_len: usize = @min(messages.len, msg_buffer.len);
4152 const clamped_messages = messages[0..min_len];
4153 const clamped_msgs = (&msg_buffer)[0..min_len];
4154 const clamped_addrs = (&addr_buffer)[0..min_len];
4155 const clamped_iovecs = (&iovecs_buffer)[0..min_len];
4156
4157 for (clamped_messages, clamped_msgs, clamped_addrs, clamped_iovecs) |*message, *msg, *addr, *iovec| {
4158 iovec.* = .{ .base = @constCast(message.data_ptr), .len = message.data_len };
4159 msg.* = .{
4160 .hdr = .{
4161 .name = &addr.any,
4162 .namelen = addressToPosix(message.address, addr),
4163 .iov = iovec[0..1],
4164 .iovlen = 1,
4165 .control = @constCast(message.control.ptr),
4166 .controllen = message.control.len,
4167 .flags = 0,
4168 },
4169 .len = undefined, // Populated by calling sendmmsg below.
4170 };
4171 }
4172
4173 while (true) {
4174 try t.checkCancel();
4175 const rc = posix.system.sendmmsg(handle, clamped_msgs.ptr, @intCast(clamped_msgs.len), flags);
4176 switch (posix.errno(rc)) {
4177 .SUCCESS => {
4178 const n: usize = @intCast(rc);
4179 for (clamped_messages[0..n], clamped_msgs[0..n]) |*message, *msg| {
4180 message.data_len = msg.len;
4181 }
4182 return n;
4183 },
4184 .INTR => continue,
4185 .CANCELED => return error.Canceled,
4186
4187 .AGAIN => |err| return errnoBug(err),
4188 .ALREADY => return error.FastOpenAlreadyInProgress,
4189 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4190 .CONNRESET => return error.ConnectionResetByPeer,
4191 .DESTADDRREQ => |err| return errnoBug(err), // The socket is not connection-mode, and no peer address is set.
4192 .FAULT => |err| return errnoBug(err), // An invalid user space address was specified for an argument.
4193 .INVAL => |err| return errnoBug(err), // Invalid argument passed.
4194 .ISCONN => |err| return errnoBug(err), // connection-mode socket was connected already but a recipient was specified
4195 .MSGSIZE => return error.MessageOversize,
4196 .NOBUFS => return error.SystemResources,
4197 .NOMEM => return error.SystemResources,
4198 .NOTSOCK => |err| return errnoBug(err), // The file descriptor sockfd does not refer to a socket.
4199 .OPNOTSUPP => |err| return errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type.
4200 .PIPE => return error.SocketUnconnected,
4201 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
4202 .HOSTUNREACH => return error.HostUnreachable,
4203 .NETUNREACH => return error.NetworkUnreachable,
4204 .NOTCONN => return error.SocketUnconnected,
4205 .NETDOWN => return error.NetworkDown,
4206 else => |err| return posix.unexpectedErrno(err),
4207 }
4208 }
4209}
4210
4211fn netReceivePosix(
4212 userdata: ?*anyopaque,
4213 handle: net.Socket.Handle,
4214 message_buffer: []net.IncomingMessage,
4215 data_buffer: []u8,
4216 flags: net.ReceiveFlags,
4217 timeout: Io.Timeout,
4218) struct { ?net.Socket.ReceiveTimeoutError, usize } {
4219 if (!have_networking) return .{ error.NetworkDown, 0 };
4220 const t: *Threaded = @ptrCast(@alignCast(userdata));
4221 const t_io = io(t);
4222
4223 // recvmmsg is useless, here's why:
4224 // * [timeout bug](https://bugzilla.kernel.org/show_bug.cgi?id=75371)
4225 // * it wants iovecs for each message but we have a better API: one data
4226 // buffer to handle all the messages. The better API cannot be lowered to
4227 // the split vectors though because reducing the buffer size might make
4228 // some messages unreceivable.
4229
4230 // So the strategy instead is to use non-blocking recvmsg calls, calling
4231 // poll() with timeout if the first one returns EAGAIN.
4232 const posix_flags: u32 =
4233 @as(u32, if (flags.oob) posix.MSG.OOB else 0) |
4234 @as(u32, if (flags.peek) posix.MSG.PEEK else 0) |
4235 @as(u32, if (flags.trunc) posix.MSG.TRUNC else 0) |
4236 posix.MSG.DONTWAIT | posix.MSG.NOSIGNAL;
4237
4238 var poll_fds: [1]posix.pollfd = .{
4239 .{
4240 .fd = handle,
4241 .events = posix.POLL.IN,
4242 .revents = undefined,
4243 },
4244 };
4245 var message_i: usize = 0;
4246 var data_i: usize = 0;
4247
4248 const deadline = timeout.toDeadline(t_io) catch |err| return .{ err, message_i };
4249
4250 recv: while (true) {
4251 t.checkCancel() catch |err| return .{ err, message_i };
4252
4253 if (message_buffer.len - message_i == 0) return .{ null, message_i };
4254 const message = &message_buffer[message_i];
4255 const remaining_data_buffer = data_buffer[data_i..];
4256 var storage: PosixAddress = undefined;
4257 var iov: posix.iovec = .{ .base = remaining_data_buffer.ptr, .len = remaining_data_buffer.len };
4258 var msg: posix.msghdr = .{
4259 .name = &storage.any,
4260 .namelen = @sizeOf(PosixAddress),
4261 .iov = (&iov)[0..1],
4262 .iovlen = 1,
4263 .control = message.control.ptr,
4264 .controllen = @intCast(message.control.len),
4265 .flags = undefined,
4266 };
4267
4268 const recv_rc = posix.system.recvmsg(handle, &msg, posix_flags);
4269 switch (posix.errno(recv_rc)) {
4270 .SUCCESS => {
4271 const data = remaining_data_buffer[0..@intCast(recv_rc)];
4272 data_i += data.len;
4273 message.* = .{
4274 .from = addressFromPosix(&storage),
4275 .data = data,
4276 .control = if (msg.control) |ptr| @as([*]u8, @ptrCast(ptr))[0..msg.controllen] else message.control,
4277 .flags = .{
4278 .eor = (msg.flags & posix.MSG.EOR) != 0,
4279 .trunc = (msg.flags & posix.MSG.TRUNC) != 0,
4280 .ctrunc = (msg.flags & posix.MSG.CTRUNC) != 0,
4281 .oob = (msg.flags & posix.MSG.OOB) != 0,
4282 .errqueue = if (@hasDecl(posix.MSG, "ERRQUEUE")) (msg.flags & posix.MSG.ERRQUEUE) != 0 else false,
4283 },
4284 };
4285 message_i += 1;
4286 continue;
4287 },
4288 .AGAIN => while (true) {
4289 t.checkCancel() catch |err| return .{ err, message_i };
4290 if (message_i != 0) return .{ null, message_i };
4291
4292 const max_poll_ms = std.math.maxInt(u31);
4293 const timeout_ms: u31 = if (deadline) |d| t: {
4294 const duration = d.durationFromNow(t_io) catch |err| return .{ err, message_i };
4295 if (duration.raw.nanoseconds <= 0) return .{ error.Timeout, message_i };
4296 break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds()));
4297 } else max_poll_ms;
4298
4299 const poll_rc = posix.system.poll(&poll_fds, poll_fds.len, timeout_ms);
4300 switch (posix.errno(poll_rc)) {
4301 .SUCCESS => {
4302 if (poll_rc == 0) {
4303 // Although spurious timeouts are OK, when no deadline
4304 // is passed we must not return `error.Timeout`.
4305 if (deadline == null) continue;
4306 return .{ error.Timeout, message_i };
4307 }
4308 continue :recv;
4309 },
4310 .INTR => continue,
4311 .CANCELED => return .{ error.Canceled, message_i },
4312
4313 .FAULT => |err| return .{ errnoBug(err), message_i },
4314 .INVAL => |err| return .{ errnoBug(err), message_i },
4315 .NOMEM => return .{ error.SystemResources, message_i },
4316 else => |err| return .{ posix.unexpectedErrno(err), message_i },
4317 }
4318 },
4319 .INTR => continue,
4320 .CANCELED => return .{ error.Canceled, message_i },
4321
4322 .BADF => |err| return .{ errnoBug(err), message_i },
4323 .NFILE => return .{ error.SystemFdQuotaExceeded, message_i },
4324 .MFILE => return .{ error.ProcessFdQuotaExceeded, message_i },
4325 .FAULT => |err| return .{ errnoBug(err), message_i },
4326 .INVAL => |err| return .{ errnoBug(err), message_i },
4327 .NOBUFS => return .{ error.SystemResources, message_i },
4328 .NOMEM => return .{ error.SystemResources, message_i },
4329 .NOTCONN => return .{ error.SocketUnconnected, message_i },
4330 .NOTSOCK => |err| return .{ errnoBug(err), message_i },
4331 .MSGSIZE => return .{ error.MessageOversize, message_i },
4332 .PIPE => return .{ error.SocketUnconnected, message_i },
4333 .OPNOTSUPP => |err| return .{ errnoBug(err), message_i },
4334 .CONNRESET => return .{ error.ConnectionResetByPeer, message_i },
4335 .NETDOWN => return .{ error.NetworkDown, message_i },
4336 else => |err| return .{ posix.unexpectedErrno(err), message_i },
4337 }
4338 }
4339}
4340
4341fn netReceiveWindows(
4342 userdata: ?*anyopaque,
4343 handle: net.Socket.Handle,
4344 message_buffer: []net.IncomingMessage,
4345 data_buffer: []u8,
4346 flags: net.ReceiveFlags,
4347 timeout: Io.Timeout,
4348) struct { ?net.Socket.ReceiveTimeoutError, usize } {
4349 if (!have_networking) return .{ error.NetworkDown, 0 };
4350 const t: *Threaded = @ptrCast(@alignCast(userdata));
4351 _ = t;
4352 _ = handle;
4353 _ = message_buffer;
4354 _ = data_buffer;
4355 _ = flags;
4356 _ = timeout;
4357 @panic("TODO implement netReceiveWindows");
4358}
4359
4360fn netReceiveUnavailable(
4361 userdata: ?*anyopaque,
4362 handle: net.Socket.Handle,
4363 message_buffer: []net.IncomingMessage,
4364 data_buffer: []u8,
4365 flags: net.ReceiveFlags,
4366 timeout: Io.Timeout,
4367) struct { ?net.Socket.ReceiveTimeoutError, usize } {
4368 _ = userdata;
4369 _ = handle;
4370 _ = message_buffer;
4371 _ = data_buffer;
4372 _ = flags;
4373 _ = timeout;
4374 return .{ error.NetworkDown, 0 };
4375}
4376
4377fn netWritePosix(
4378 userdata: ?*anyopaque,
4379 fd: net.Socket.Handle,
4380 header: []const u8,
4381 data: []const []const u8,
4382 splat: usize,
4383) net.Stream.Writer.Error!usize {
4384 if (!have_networking) return error.NetworkDown;
4385 const t: *Threaded = @ptrCast(@alignCast(userdata));
4386
4387 var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
4388 var msg: posix.msghdr_const = .{
4389 .name = null,
4390 .namelen = 0,
4391 .iov = &iovecs,
4392 .iovlen = 0,
4393 .control = null,
4394 .controllen = 0,
4395 .flags = 0,
4396 };
4397 addBuf(&iovecs, &msg.iovlen, header);
4398 for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &msg.iovlen, bytes);
4399 const pattern = data[data.len - 1];
4400 if (iovecs.len - msg.iovlen != 0) switch (splat) {
4401 0 => {},
4402 1 => addBuf(&iovecs, &msg.iovlen, pattern),
4403 else => switch (pattern.len) {
4404 0 => {},
4405 1 => {
4406 var backup_buffer: [splat_buffer_size]u8 = undefined;
4407 const splat_buffer = &backup_buffer;
4408 const memset_len = @min(splat_buffer.len, splat);
4409 const buf = splat_buffer[0..memset_len];
4410 @memset(buf, pattern[0]);
4411 addBuf(&iovecs, &msg.iovlen, buf);
4412 var remaining_splat = splat - buf.len;
4413 while (remaining_splat > splat_buffer.len and iovecs.len - msg.iovlen != 0) {
4414 assert(buf.len == splat_buffer.len);
4415 addBuf(&iovecs, &msg.iovlen, splat_buffer);
4416 remaining_splat -= splat_buffer.len;
4417 }
4418 addBuf(&iovecs, &msg.iovlen, splat_buffer[0..remaining_splat]);
4419 },
4420 else => for (0..@min(splat, iovecs.len - msg.iovlen)) |_| {
4421 addBuf(&iovecs, &msg.iovlen, pattern);
4422 },
4423 },
4424 };
4425 const flags = posix.MSG.NOSIGNAL;
4426 while (true) {
4427 try t.checkCancel();
4428 const rc = posix.system.sendmsg(fd, &msg, flags);
4429 switch (posix.errno(rc)) {
4430 .SUCCESS => return @intCast(rc),
4431 .INTR => continue,
4432 .CANCELED => return error.Canceled,
4433
4434 .ACCES => |err| return errnoBug(err),
4435 .AGAIN => |err| return errnoBug(err),
4436 .ALREADY => return error.FastOpenAlreadyInProgress,
4437 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4438 .CONNRESET => return error.ConnectionResetByPeer,
4439 .DESTADDRREQ => |err| return errnoBug(err), // The socket is not connection-mode, and no peer address is set.
4440 .FAULT => |err| return errnoBug(err), // An invalid user space address was specified for an argument.
4441 .INVAL => |err| return errnoBug(err), // Invalid argument passed.
4442 .ISCONN => |err| return errnoBug(err), // connection-mode socket was connected already but a recipient was specified
4443 .MSGSIZE => |err| return errnoBug(err),
4444 .NOBUFS => return error.SystemResources,
4445 .NOMEM => return error.SystemResources,
4446 .NOTSOCK => |err| return errnoBug(err), // The file descriptor sockfd does not refer to a socket.
4447 .OPNOTSUPP => |err| return errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type.
4448 .PIPE => return error.SocketUnconnected,
4449 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
4450 .HOSTUNREACH => return error.HostUnreachable,
4451 .NETUNREACH => return error.NetworkUnreachable,
4452 .NOTCONN => return error.SocketUnconnected,
4453 .NETDOWN => return error.NetworkDown,
4454 else => |err| return posix.unexpectedErrno(err),
4455 }
4456 }
4457}
4458
4459fn netWriteWindows(
4460 userdata: ?*anyopaque,
4461 handle: net.Socket.Handle,
4462 header: []const u8,
4463 data: []const []const u8,
4464 splat: usize,
4465) net.Stream.Writer.Error!usize {
4466 const t: *Threaded = @ptrCast(@alignCast(userdata));
4467 comptime assert(native_os == .windows);
4468
4469 var iovecs: [max_iovecs_len]ws2_32.WSABUF = undefined;
4470 var len: u32 = 0;
4471 addWsaBuf(&iovecs, &len, header);
4472 for (data[0 .. data.len - 1]) |bytes| addWsaBuf(&iovecs, &len, bytes);
4473 const pattern = data[data.len - 1];
4474 if (iovecs.len - len != 0) switch (splat) {
4475 0 => {},
4476 1 => addWsaBuf(&iovecs, &len, pattern),
4477 else => switch (pattern.len) {
4478 0 => {},
4479 1 => {
4480 var backup_buffer: [64]u8 = undefined;
4481 const splat_buffer = &backup_buffer;
4482 const memset_len = @min(splat_buffer.len, splat);
4483 const buf = splat_buffer[0..memset_len];
4484 @memset(buf, pattern[0]);
4485 addWsaBuf(&iovecs, &len, buf);
4486 var remaining_splat = splat - buf.len;
4487 while (remaining_splat > splat_buffer.len and len < iovecs.len) {
4488 addWsaBuf(&iovecs, &len, splat_buffer);
4489 remaining_splat -= splat_buffer.len;
4490 }
4491 addWsaBuf(&iovecs, &len, splat_buffer[0..remaining_splat]);
4492 },
4493 else => for (0..@min(splat, iovecs.len - len)) |_| {
4494 addWsaBuf(&iovecs, &len, pattern);
4495 },
4496 },
4497 };
4498
4499 while (true) {
4500 try t.checkCancel();
4501
4502 var n: u32 = undefined;
4503 var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED);
4504 const rc = ws2_32.WSASend(handle, &iovecs, len, &n, 0, &overlapped, null);
4505 if (rc != ws2_32.SOCKET_ERROR) return n;
4506 const wsa_error: ws2_32.WinsockError = switch (ws2_32.WSAGetLastError()) {
4507 .IO_PENDING => e: {
4508 var result_flags: u32 = undefined;
4509 const overlapped_rc = ws2_32.WSAGetOverlappedResult(
4510 handle,
4511 &overlapped,
4512 &n,
4513 windows.TRUE,
4514 &result_flags,
4515 );
4516 if (overlapped_rc == windows.FALSE) {
4517 break :e ws2_32.WSAGetLastError();
4518 } else {
4519 return n;
4520 }
4521 },
4522 else => |err| err,
4523 };
4524 switch (wsa_error) {
4525 .EINTR => continue,
4526 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
4527 .NOTINITIALISED => {
4528 try initializeWsa(t);
4529 continue;
4530 },
4531
4532 .ECONNABORTED => return error.ConnectionResetByPeer,
4533 .ECONNRESET => return error.ConnectionResetByPeer,
4534 .EINVAL => return error.SocketUnconnected,
4535 .ENETDOWN => return error.NetworkDown,
4536 .ENETRESET => return error.ConnectionResetByPeer,
4537 .ENOBUFS => return error.SystemResources,
4538 .ENOTCONN => return error.SocketUnconnected,
4539 .ENOTSOCK => |err| return wsaErrorBug(err),
4540 .EOPNOTSUPP => |err| return wsaErrorBug(err),
4541 .ESHUTDOWN => |err| return wsaErrorBug(err),
4542 else => |err| return windows.unexpectedWSAError(err),
4543 }
4544 }
4545}
4546
4547fn addWsaBuf(v: []ws2_32.WSABUF, i: *u32, bytes: []const u8) void {
4548 const cap = std.math.maxInt(u32);
4549 var remaining = bytes;
4550 while (remaining.len > cap) {
4551 if (v.len - i.* == 0) return;
4552 v[i.*] = .{ .buf = @constCast(remaining.ptr), .len = cap };
4553 i.* += 1;
4554 remaining = remaining[cap..];
4555 } else {
4556 @branchHint(.likely);
4557 if (v.len - i.* == 0) return;
4558 v[i.*] = .{ .buf = @constCast(remaining.ptr), .len = @intCast(remaining.len) };
4559 i.* += 1;
4560 }
4561}
4562
4563fn netWriteUnavailable(
4564 userdata: ?*anyopaque,
4565 handle: net.Socket.Handle,
4566 header: []const u8,
4567 data: []const []const u8,
4568 splat: usize,
4569) net.Stream.Writer.Error!usize {
4570 _ = userdata;
4571 _ = handle;
4572 _ = header;
4573 _ = data;
4574 _ = splat;
4575 return error.NetworkDown;
4576}
4577
4578fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"), bytes: []const u8) void {
4579 // OS checks ptr addr before length so zero length vectors must be omitted.
4580 if (bytes.len == 0) return;
4581 if (v.len - i.* == 0) return;
4582 v[i.*] = .{ .base = bytes.ptr, .len = bytes.len };
4583 i.* += 1;
4584}
4585
4586fn netClose(userdata: ?*anyopaque, handle: net.Socket.Handle) void {
4587 const t: *Threaded = @ptrCast(@alignCast(userdata));
4588 _ = t;
4589 switch (native_os) {
4590 .windows => closeSocketWindows(handle),
4591 else => posix.close(handle),
4592 }
4593}
4594
4595fn netCloseUnavailable(userdata: ?*anyopaque, handle: net.Socket.Handle) void {
4596 _ = userdata;
4597 _ = handle;
4598 unreachable; // How you gonna close something that was impossible to open?
4599}
4600
4601fn netInterfaceNameResolve(
4602 userdata: ?*anyopaque,
4603 name: *const net.Interface.Name,
4604) net.Interface.Name.ResolveError!net.Interface {
4605 if (!have_networking) return error.InterfaceNotFound;
4606 const t: *Threaded = @ptrCast(@alignCast(userdata));
4607
4608 if (native_os == .linux) {
4609 const sock_fd = openSocketPosix(t, posix.AF.UNIX, .{ .mode = .dgram }) catch |err| switch (err) {
4610 error.ProcessFdQuotaExceeded => return error.SystemResources,
4611 error.SystemFdQuotaExceeded => return error.SystemResources,
4612 error.AddressFamilyUnsupported => return error.Unexpected,
4613 error.ProtocolUnsupportedBySystem => return error.Unexpected,
4614 error.ProtocolUnsupportedByAddressFamily => return error.Unexpected,
4615 error.SocketModeUnsupported => return error.Unexpected,
4616 error.OptionUnsupported => return error.Unexpected,
4617 else => |e| return e,
4618 };
4619 defer posix.close(sock_fd);
4620
4621 var ifr: posix.ifreq = .{
4622 .ifrn = .{ .name = @bitCast(name.bytes) },
4623 .ifru = undefined,
4624 };
4625
4626 while (true) {
4627 try t.checkCancel();
4628 switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) {
4629 .SUCCESS => return .{ .index = @bitCast(ifr.ifru.ivalue) },
4630 .INTR => continue,
4631 .CANCELED => return error.Canceled,
4632
4633 .INVAL => |err| return errnoBug(err), // Bad parameters.
4634 .NOTTY => |err| return errnoBug(err),
4635 .NXIO => |err| return errnoBug(err),
4636 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
4637 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
4638 .IO => |err| return errnoBug(err), // sock_fd is not a file descriptor
4639 .NODEV => return error.InterfaceNotFound,
4640 else => |err| return posix.unexpectedErrno(err),
4641 }
4642 }
4643 }
4644
4645 if (native_os == .windows) {
4646 try t.checkCancel();
4647 @panic("TODO implement netInterfaceNameResolve for Windows");
4648 }
4649
4650 if (builtin.link_libc) {
4651 try t.checkCancel();
4652 const index = std.c.if_nametoindex(&name.bytes);
4653 if (index == 0) return error.InterfaceNotFound;
4654 return .{ .index = @bitCast(index) };
4655 }
4656
4657 @panic("unimplemented");
4658}
4659
4660fn netInterfaceNameResolveUnavailable(
4661 userdata: ?*anyopaque,
4662 name: *const net.Interface.Name,
4663) net.Interface.Name.ResolveError!net.Interface {
4664 _ = userdata;
4665 _ = name;
4666 return error.InterfaceNotFound;
4667}
4668
4669fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interface.NameError!net.Interface.Name {
4670 const t: *Threaded = @ptrCast(@alignCast(userdata));
4671 try t.checkCancel();
4672
4673 if (native_os == .linux) {
4674 _ = interface;
4675 @panic("TODO implement netInterfaceName for linux");
4676 }
4677
4678 if (native_os == .windows) {
4679 @panic("TODO implement netInterfaceName for windows");
4680 }
4681
4682 if (builtin.link_libc) {
4683 @panic("TODO implement netInterfaceName for libc");
4684 }
4685
4686 @panic("unimplemented");
4687}
4688
4689fn netInterfaceNameUnavailable(userdata: ?*anyopaque, interface: net.Interface) net.Interface.NameError!net.Interface.Name {
4690 _ = userdata;
4691 _ = interface;
4692 return error.Unexpected;
4693}
4694
4695fn netLookup(
4696 userdata: ?*anyopaque,
4697 host_name: HostName,
4698 resolved: *Io.Queue(HostName.LookupResult),
4699 options: HostName.LookupOptions,
4700) void {
4701 const t: *Threaded = @ptrCast(@alignCast(userdata));
4702 const t_io = io(t);
4703 resolved.putOneUncancelable(t_io, .{ .end = netLookupFallible(t, host_name, resolved, options) });
4704}
4705
4706fn netLookupUnavailable(
4707 userdata: ?*anyopaque,
4708 host_name: HostName,
4709 resolved: *Io.Queue(HostName.LookupResult),
4710 options: HostName.LookupOptions,
4711) void {
4712 _ = host_name;
4713 _ = options;
4714 const t: *Threaded = @ptrCast(@alignCast(userdata));
4715 const t_io = ioBasic(t);
4716 resolved.putOneUncancelable(t_io, .{ .end = error.NetworkDown });
4717}
4718
4719fn netLookupFallible(
4720 t: *Threaded,
4721 host_name: HostName,
4722 resolved: *Io.Queue(HostName.LookupResult),
4723 options: HostName.LookupOptions,
4724) !void {
4725 if (!have_networking) return error.NetworkDown;
4726 const t_io = io(t);
4727 const name = host_name.bytes;
4728 assert(name.len <= HostName.max_len);
4729
4730 if (is_windows) {
4731 var name_buffer: [HostName.max_len + 1]u16 = undefined;
4732 const name_len = std.unicode.wtf8ToWtf16Le(&name_buffer, host_name.bytes) catch
4733 unreachable; // HostName is prevalidated.
4734 name_buffer[name_len] = 0;
4735 const name_w = name_buffer[0..name_len :0];
4736
4737 var port_buffer: [8]u8 = undefined;
4738 var port_buffer_wide: [8]u16 = undefined;
4739 const port = std.fmt.bufPrint(&port_buffer, "{d}", .{options.port}) catch
4740 unreachable; // `port_buffer` is big enough for decimal u16.
4741 for (port, port_buffer_wide[0..port.len]) |byte, *wide|
4742 wide.* = std.mem.nativeToLittle(u16, byte);
4743 port_buffer_wide[port.len] = 0;
4744 const port_w = port_buffer_wide[0..port.len :0];
4745
4746 const hints: ws2_32.ADDRINFOEXW = .{
4747 .flags = .{ .NUMERICSERV = true },
4748 .family = if (options.family) |f| switch (f) {
4749 .ip4 => posix.AF.INET,
4750 .ip6 => posix.AF.INET6,
4751 } else posix.AF.UNSPEC,
4752 .socktype = posix.SOCK.STREAM,
4753 .protocol = posix.IPPROTO.TCP,
4754 .canonname = null,
4755 .addr = null,
4756 .addrlen = 0,
4757 .blob = null,
4758 .bloblen = 0,
4759 .provider = null,
4760 .next = null,
4761 };
4762 const cancel_handle: ?*windows.HANDLE = null;
4763 var res: *ws2_32.ADDRINFOEXW = undefined;
4764 const timeout: ?*ws2_32.timeval = null;
4765 while (true) {
4766 try t.checkCancel(); // TODO make requestCancel call GetAddrInfoExCancel
4767 // TODO make this append to the queue eagerly rather than blocking until
4768 // the whole thing finishes
4769 const rc: ws2_32.WinsockError = @enumFromInt(ws2_32.GetAddrInfoExW(name_w, port_w, .DNS, null, &hints, &res, timeout, null, null, cancel_handle));
4770 switch (rc) {
4771 @as(ws2_32.WinsockError, @enumFromInt(0)) => break,
4772 .EINTR => continue,
4773 .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled,
4774 .NOTINITIALISED => {
4775 try initializeWsa(t);
4776 continue;
4777 },
4778 .TRY_AGAIN => return error.NameServerFailure,
4779 .EINVAL => |err| return wsaErrorBug(err),
4780 .NO_RECOVERY => return error.NameServerFailure,
4781 .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
4782 .NOT_ENOUGH_MEMORY => return error.SystemResources,
4783 .HOST_NOT_FOUND => return error.UnknownHostName,
4784 .TYPE_NOT_FOUND => return error.ProtocolUnsupportedByAddressFamily,
4785 .ESOCKTNOSUPPORT => return error.ProtocolUnsupportedBySystem,
4786 else => |err| return windows.unexpectedWSAError(err),
4787 }
4788 }
4789 defer ws2_32.FreeAddrInfoExW(res);
4790
4791 var it: ?*ws2_32.ADDRINFOEXW = res;
4792 var canon_name: ?[*:0]const u16 = null;
4793 while (it) |info| : (it = info.next) {
4794 const addr = info.addr orelse continue;
4795 const storage: WsaAddress = .{ .any = addr.* };
4796 try resolved.putOne(t_io, .{ .address = addressFromWsa(&storage) });
4797
4798 if (info.canonname) |n| {
4799 if (canon_name == null) {
4800 canon_name = n;
4801 }
4802 }
4803 }
4804 if (canon_name) |n| {
4805 const len = std.unicode.wtf16LeToWtf8(options.canonical_name_buffer, std.mem.sliceTo(n, 0));
4806 try resolved.putOne(t_io, .{ .canonical_name = .{
4807 .bytes = options.canonical_name_buffer[0..len],
4808 } });
4809 }
4810 return;
4811 }
4812
4813 // On Linux, glibc provides getaddrinfo_a which is capable of supporting our semantics.
4814 // However, musl's POSIX-compliant getaddrinfo is not, so we bypass it.
4815
4816 if (builtin.target.isGnuLibC()) {
4817 // TODO use getaddrinfo_a / gai_cancel
4818 }
4819
4820 if (native_os == .linux) {
4821 if (options.family != .ip4) {
4822 if (IpAddress.parseIp6(name, options.port)) |addr| {
4823 try resolved.putAll(t_io, &.{
4824 .{ .address = addr },
4825 .{ .canonical_name = copyCanon(options.canonical_name_buffer, name) },
4826 });
4827 return;
4828 } else |_| {}
4829 }
4830
4831 if (options.family != .ip6) {
4832 if (IpAddress.parseIp4(name, options.port)) |addr| {
4833 try resolved.putAll(t_io, &.{
4834 .{ .address = addr },
4835 .{ .canonical_name = copyCanon(options.canonical_name_buffer, name) },
4836 });
4837 return;
4838 } else |_| {}
4839 }
4840
4841 lookupHosts(t, host_name, resolved, options) catch |err| switch (err) {
4842 error.UnknownHostName => {},
4843 else => |e| return e,
4844 };
4845
4846 // RFC 6761 Section 6.3.3
4847 // Name resolution APIs and libraries SHOULD recognize
4848 // localhost names as special and SHOULD always return the IP
4849 // loopback address for address queries and negative responses
4850 // for all other query types.
4851
4852 // Check for equal to "localhost(.)" or ends in ".localhost(.)"
4853 const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost";
4854 if (std.mem.endsWith(u8, name, localhost) and
4855 (name.len == localhost.len or name[name.len - localhost.len] == '.'))
4856 {
4857 var results_buffer: [3]HostName.LookupResult = undefined;
4858 var results_index: usize = 0;
4859 if (options.family != .ip4) {
4860 results_buffer[results_index] = .{ .address = .{ .ip6 = .loopback(options.port) } };
4861 results_index += 1;
4862 }
4863 if (options.family != .ip6) {
4864 results_buffer[results_index] = .{ .address = .{ .ip4 = .loopback(options.port) } };
4865 results_index += 1;
4866 }
4867 const canon_name = "localhost";
4868 const canon_name_dest = options.canonical_name_buffer[0..canon_name.len];
4869 canon_name_dest.* = canon_name.*;
4870 results_buffer[results_index] = .{ .canonical_name = .{ .bytes = canon_name_dest } };
4871 results_index += 1;
4872 try resolved.putAll(t_io, results_buffer[0..results_index]);
4873 return;
4874 }
4875
4876 return lookupDnsSearch(t, host_name, resolved, options);
4877 }
4878
4879 if (native_os == .openbsd) {
4880 // TODO use getaddrinfo_async / asr_abort
4881 }
4882
4883 if (native_os == .freebsd) {
4884 // TODO use dnsres_getaddrinfo
4885 }
4886
4887 if (native_os.isDarwin()) {
4888 // TODO use CFHostStartInfoResolution / CFHostCancelInfoResolution
4889 }
4890
4891 if (builtin.link_libc) {
4892 // This operating system lacks a way to resolve asynchronously. We are
4893 // stuck with getaddrinfo.
4894 var name_buffer: [HostName.max_len + 1]u8 = undefined;
4895 @memcpy(name_buffer[0..host_name.bytes.len], host_name.bytes);
4896 name_buffer[host_name.bytes.len] = 0;
4897 const name_c = name_buffer[0..host_name.bytes.len :0];
4898
4899 var port_buffer: [8]u8 = undefined;
4900 const port_c = std.fmt.bufPrintZ(&port_buffer, "{d}", .{options.port}) catch unreachable;
4901
4902 const hints: posix.addrinfo = .{
4903 .flags = .{ .NUMERICSERV = true },
4904 .family = posix.AF.UNSPEC,
4905 .socktype = posix.SOCK.STREAM,
4906 .protocol = posix.IPPROTO.TCP,
4907 .canonname = null,
4908 .addr = null,
4909 .addrlen = 0,
4910 .next = null,
4911 };
4912 var res: ?*posix.addrinfo = null;
4913 while (true) {
4914 try t.checkCancel();
4915 switch (posix.system.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res)) {
4916 @as(posix.system.EAI, @enumFromInt(0)) => break,
4917 .ADDRFAMILY => return error.AddressFamilyUnsupported,
4918 .AGAIN => return error.NameServerFailure,
4919 .FAIL => return error.NameServerFailure,
4920 .FAMILY => return error.AddressFamilyUnsupported,
4921 .MEMORY => return error.SystemResources,
4922 .NODATA => return error.UnknownHostName,
4923 .NONAME => return error.UnknownHostName,
4924 .SYSTEM => switch (posix.errno(-1)) {
4925 .INTR => continue,
4926 .CANCELED => return error.Canceled,
4927 else => |e| return posix.unexpectedErrno(e),
4928 },
4929 else => return error.Unexpected,
4930 }
4931 }
4932 defer if (res) |some| posix.system.freeaddrinfo(some);
4933
4934 var it = res;
4935 var canon_name: ?[*:0]const u8 = null;
4936 while (it) |info| : (it = info.next) {
4937 const addr = info.addr orelse continue;
4938 const storage: PosixAddress = .{ .any = addr.* };
4939 try resolved.putOne(t_io, .{ .address = addressFromPosix(&storage) });
4940
4941 if (info.canonname) |n| {
4942 if (canon_name == null) {
4943 canon_name = n;
4944 }
4945 }
4946 }
4947 if (canon_name) |n| {
4948 try resolved.putOne(t_io, .{
4949 .canonical_name = copyCanon(options.canonical_name_buffer, std.mem.sliceTo(n, 0)),
4950 });
4951 }
4952 return;
4953 }
4954
4955 return error.OptionUnsupported;
4956}
4957
4958pub const PosixAddress = extern union {
4959 any: posix.sockaddr,
4960 in: posix.sockaddr.in,
4961 in6: posix.sockaddr.in6,
4962};
4963
4964const UnixAddress = extern union {
4965 any: posix.sockaddr,
4966 un: posix.sockaddr.un,
4967};
4968
4969const WsaAddress = extern union {
4970 any: ws2_32.sockaddr,
4971 in: ws2_32.sockaddr.in,
4972 in6: ws2_32.sockaddr.in6,
4973 un: ws2_32.sockaddr.un,
4974};
4975
4976pub fn posixAddressFamily(a: *const IpAddress) posix.sa_family_t {
4977 return switch (a.*) {
4978 .ip4 => posix.AF.INET,
4979 .ip6 => posix.AF.INET6,
4980 };
4981}
4982
4983pub fn addressFromPosix(posix_address: *const PosixAddress) IpAddress {
4984 return switch (posix_address.any.family) {
4985 posix.AF.INET => .{ .ip4 = address4FromPosix(&posix_address.in) },
4986 posix.AF.INET6 => .{ .ip6 = address6FromPosix(&posix_address.in6) },
4987 else => .{ .ip4 = .loopback(0) },
4988 };
4989}
4990
4991fn addressFromWsa(wsa_address: *const WsaAddress) IpAddress {
4992 return switch (wsa_address.any.family) {
4993 posix.AF.INET => .{ .ip4 = address4FromWsa(&wsa_address.in) },
4994 posix.AF.INET6 => .{ .ip6 = address6FromWsa(&wsa_address.in6) },
4995 else => .{ .ip4 = .loopback(0) },
4996 };
4997}
4998
4999pub fn addressToPosix(a: *const IpAddress, storage: *PosixAddress) posix.socklen_t {
5000 return switch (a.*) {
5001 .ip4 => |ip4| {
5002 storage.in = address4ToPosix(ip4);
5003 return @sizeOf(posix.sockaddr.in);
5004 },
5005 .ip6 => |*ip6| {
5006 storage.in6 = address6ToPosix(ip6);
5007 return @sizeOf(posix.sockaddr.in6);
5008 },
5009 };
5010}
5011
5012fn addressToWsa(a: *const IpAddress, storage: *WsaAddress) i32 {
5013 return switch (a.*) {
5014 .ip4 => |ip4| {
5015 storage.in = address4ToPosix(ip4);
5016 return @sizeOf(posix.sockaddr.in);
5017 },
5018 .ip6 => |*ip6| {
5019 storage.in6 = address6ToPosix(ip6);
5020 return @sizeOf(posix.sockaddr.in6);
5021 },
5022 };
5023}
5024
5025fn addressUnixToPosix(a: *const net.UnixAddress, storage: *UnixAddress) posix.socklen_t {
5026 @memcpy(storage.un.path[0..a.path.len], a.path);
5027 storage.un.family = posix.AF.UNIX;
5028 storage.un.path[a.path.len] = 0;
5029 return @sizeOf(posix.sockaddr.un);
5030}
5031
5032fn addressUnixToWsa(a: *const net.UnixAddress, storage: *WsaAddress) i32 {
5033 @memcpy(storage.un.path[0..a.path.len], a.path);
5034 storage.un.family = posix.AF.UNIX;
5035 storage.un.path[a.path.len] = 0;
5036 return @sizeOf(posix.sockaddr.un);
5037}
5038
5039fn address4FromPosix(in: *const posix.sockaddr.in) net.Ip4Address {
5040 return .{
5041 .port = std.mem.bigToNative(u16, in.port),
5042 .bytes = @bitCast(in.addr),
5043 };
5044}
5045
5046fn address6FromPosix(in6: *const posix.sockaddr.in6) net.Ip6Address {
5047 return .{
5048 .port = std.mem.bigToNative(u16, in6.port),
5049 .bytes = in6.addr,
5050 .flow = in6.flowinfo,
5051 .interface = .{ .index = in6.scope_id },
5052 };
5053}
5054
5055fn address4FromWsa(in: *const ws2_32.sockaddr.in) net.Ip4Address {
5056 return .{
5057 .port = std.mem.bigToNative(u16, in.port),
5058 .bytes = @bitCast(in.addr),
5059 };
5060}
5061
5062fn address6FromWsa(in6: *const ws2_32.sockaddr.in6) net.Ip6Address {
5063 return .{
5064 .port = std.mem.bigToNative(u16, in6.port),
5065 .bytes = in6.addr,
5066 .flow = in6.flowinfo,
5067 .interface = .{ .index = in6.scope_id },
5068 };
5069}
5070
5071fn address4ToPosix(a: net.Ip4Address) posix.sockaddr.in {
5072 return .{
5073 .port = std.mem.nativeToBig(u16, a.port),
5074 .addr = @bitCast(a.bytes),
5075 };
5076}
5077
5078fn address6ToPosix(a: *const net.Ip6Address) posix.sockaddr.in6 {
5079 return .{
5080 .port = std.mem.nativeToBig(u16, a.port),
5081 .flowinfo = a.flow,
5082 .addr = a.bytes,
5083 .scope_id = a.interface.index,
5084 };
5085}
5086
5087pub fn errnoBug(err: posix.E) Io.UnexpectedError {
5088 if (is_debug) std.debug.panic("programmer bug caused syscall error: {t}", .{err});
5089 return error.Unexpected;
5090}
5091
5092fn wsaErrorBug(err: ws2_32.WinsockError) Io.UnexpectedError {
5093 if (is_debug) std.debug.panic("programmer bug caused syscall error: {t}", .{err});
5094 return error.Unexpected;
5095}
5096
5097pub fn posixSocketMode(mode: net.Socket.Mode) u32 {
5098 return switch (mode) {
5099 .stream => posix.SOCK.STREAM,
5100 .dgram => posix.SOCK.DGRAM,
5101 .seqpacket => posix.SOCK.SEQPACKET,
5102 .raw => posix.SOCK.RAW,
5103 .rdm => posix.SOCK.RDM,
5104 };
5105}
5106
5107pub fn posixProtocol(protocol: ?net.Protocol) u32 {
5108 return @intFromEnum(protocol orelse return 0);
5109}
5110
5111fn recoverableOsBugDetected() void {
5112 if (is_debug) unreachable;
5113}
5114
5115fn clockToPosix(clock: Io.Clock) posix.clockid_t {
5116 return switch (clock) {
5117 .real => posix.CLOCK.REALTIME,
5118 .awake => switch (native_os) {
5119 .macos, .ios, .watchos, .tvos => posix.CLOCK.UPTIME_RAW,
5120 else => posix.CLOCK.MONOTONIC,
5121 },
5122 .boot => switch (native_os) {
5123 .macos, .ios, .watchos, .tvos => posix.CLOCK.MONOTONIC_RAW,
5124 // On freebsd derivatives, use MONOTONIC_FAST as currently there's
5125 // no precision tradeoff.
5126 .freebsd, .dragonfly => posix.CLOCK.MONOTONIC_FAST,
5127 // On linux, use BOOTTIME instead of MONOTONIC as it ticks while
5128 // suspended.
5129 .linux => posix.CLOCK.BOOTTIME,
5130 // On other posix systems, MONOTONIC is generally the fastest and
5131 // ticks while suspended.
5132 else => posix.CLOCK.MONOTONIC,
5133 },
5134 .cpu_process => posix.CLOCK.PROCESS_CPUTIME_ID,
5135 .cpu_thread => posix.CLOCK.THREAD_CPUTIME_ID,
5136 };
5137}
5138
5139fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t {
5140 return switch (clock) {
5141 .real => .REALTIME,
5142 .awake => .MONOTONIC,
5143 .boot => .MONOTONIC,
5144 .cpu_process => .PROCESS_CPUTIME_ID,
5145 .cpu_thread => .THREAD_CPUTIME_ID,
5146 };
5147}
5148
5149fn statFromLinux(stx: *const std.os.linux.Statx) Io.File.Stat {
5150 const atime = stx.atime;
5151 const mtime = stx.mtime;
5152 const ctime = stx.ctime;
5153 return .{
5154 .inode = stx.ino,
5155 .size = stx.size,
5156 .mode = stx.mode,
5157 .kind = switch (stx.mode & std.os.linux.S.IFMT) {
5158 std.os.linux.S.IFDIR => .directory,
5159 std.os.linux.S.IFCHR => .character_device,
5160 std.os.linux.S.IFBLK => .block_device,
5161 std.os.linux.S.IFREG => .file,
5162 std.os.linux.S.IFIFO => .named_pipe,
5163 std.os.linux.S.IFLNK => .sym_link,
5164 std.os.linux.S.IFSOCK => .unix_domain_socket,
5165 else => .unknown,
5166 },
5167 .atime = .{ .nanoseconds = @intCast(@as(i128, atime.sec) * std.time.ns_per_s + atime.nsec) },
5168 .mtime = .{ .nanoseconds = @intCast(@as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec) },
5169 .ctime = .{ .nanoseconds = @intCast(@as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec) },
5170 };
5171}
5172
5173fn statFromPosix(st: *const posix.Stat) Io.File.Stat {
5174 const atime = st.atime();
5175 const mtime = st.mtime();
5176 const ctime = st.ctime();
5177 return .{
5178 .inode = st.ino,
5179 .size = @bitCast(st.size),
5180 .mode = st.mode,
5181 .kind = k: {
5182 const m = st.mode & posix.S.IFMT;
5183 switch (m) {
5184 posix.S.IFBLK => break :k .block_device,
5185 posix.S.IFCHR => break :k .character_device,
5186 posix.S.IFDIR => break :k .directory,
5187 posix.S.IFIFO => break :k .named_pipe,
5188 posix.S.IFLNK => break :k .sym_link,
5189 posix.S.IFREG => break :k .file,
5190 posix.S.IFSOCK => break :k .unix_domain_socket,
5191 else => {},
5192 }
5193 if (native_os == .illumos) switch (m) {
5194 posix.S.IFDOOR => break :k .door,
5195 posix.S.IFPORT => break :k .event_port,
5196 else => {},
5197 };
5198
5199 break :k .unknown;
5200 },
5201 .atime = timestampFromPosix(&atime),
5202 .mtime = timestampFromPosix(&mtime),
5203 .ctime = timestampFromPosix(&ctime),
5204 };
5205}
5206
5207fn statFromWasi(st: *const std.os.wasi.filestat_t) Io.File.Stat {
5208 return .{
5209 .inode = st.ino,
5210 .size = @bitCast(st.size),
5211 .mode = 0,
5212 .kind = switch (st.filetype) {
5213 .BLOCK_DEVICE => .block_device,
5214 .CHARACTER_DEVICE => .character_device,
5215 .DIRECTORY => .directory,
5216 .SYMBOLIC_LINK => .sym_link,
5217 .REGULAR_FILE => .file,
5218 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
5219 else => .unknown,
5220 },
5221 .atime = .fromNanoseconds(st.atim),
5222 .mtime = .fromNanoseconds(st.mtim),
5223 .ctime = .fromNanoseconds(st.ctim),
5224 };
5225}
5226
5227fn timestampFromPosix(timespec: *const posix.timespec) Io.Timestamp {
5228 return .{ .nanoseconds = @intCast(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec) };
5229}
5230
5231fn timestampToPosix(nanoseconds: i96) posix.timespec {
5232 return .{
5233 .sec = @intCast(@divFloor(nanoseconds, std.time.ns_per_s)),
5234 .nsec = @intCast(@mod(nanoseconds, std.time.ns_per_s)),
5235 };
5236}
5237
5238fn pathToPosix(file_path: []const u8, buffer: *[posix.PATH_MAX]u8) Io.Dir.PathNameError![:0]u8 {
5239 if (std.mem.containsAtLeastScalar2(u8, file_path, 0, 1)) return error.BadPathName;
5240 // >= rather than > to make room for the null byte
5241 if (file_path.len >= buffer.len) return error.NameTooLong;
5242 @memcpy(buffer[0..file_path.len], file_path);
5243 buffer[file_path.len] = 0;
5244 return buffer[0..file_path.len :0];
5245}
5246
5247fn lookupDnsSearch(
5248 t: *Threaded,
5249 host_name: HostName,
5250 resolved: *Io.Queue(HostName.LookupResult),
5251 options: HostName.LookupOptions,
5252) HostName.LookupError!void {
5253 const t_io = io(t);
5254 const rc = HostName.ResolvConf.init(t_io) catch return error.ResolvConfParseFailed;
5255
5256 // Count dots, suppress search when >=ndots or name ends in
5257 // a dot, which is an explicit request for global scope.
5258 const dots = std.mem.countScalar(u8, host_name.bytes, '.');
5259 const search_len = if (dots >= rc.ndots or std.mem.endsWith(u8, host_name.bytes, ".")) 0 else rc.search_len;
5260 const search = rc.search_buffer[0..search_len];
5261
5262 var canon_name = host_name.bytes;
5263
5264 // Strip final dot for canon, fail if multiple trailing dots.
5265 if (std.mem.endsWith(u8, canon_name, ".")) canon_name.len -= 1;
5266 if (std.mem.endsWith(u8, canon_name, ".")) return error.UnknownHostName;
5267
5268 // Name with search domain appended is set up in `canon_name`. This
5269 // both provides the desired default canonical name (if the requested
5270 // name is not a CNAME record) and serves as a buffer for passing the
5271 // full requested name to `lookupDns`.
5272 @memcpy(options.canonical_name_buffer[0..canon_name.len], canon_name);
5273 options.canonical_name_buffer[canon_name.len] = '.';
5274 var it = std.mem.tokenizeAny(u8, search, " \t");
5275 while (it.next()) |token| {
5276 @memcpy(options.canonical_name_buffer[canon_name.len + 1 ..][0..token.len], token);
5277 const lookup_canon_name = options.canonical_name_buffer[0 .. canon_name.len + 1 + token.len];
5278 if (lookupDns(t, lookup_canon_name, &rc, resolved, options)) |result| {
5279 return result;
5280 } else |err| switch (err) {
5281 error.UnknownHostName => continue,
5282 else => |e| return e,
5283 }
5284 }
5285
5286 const lookup_canon_name = options.canonical_name_buffer[0..canon_name.len];
5287 return lookupDns(t, lookup_canon_name, &rc, resolved, options);
5288}
5289
5290fn lookupDns(
5291 t: *Threaded,
5292 lookup_canon_name: []const u8,
5293 rc: *const HostName.ResolvConf,
5294 resolved: *Io.Queue(HostName.LookupResult),
5295 options: HostName.LookupOptions,
5296) HostName.LookupError!void {
5297 const t_io = io(t);
5298 const family_records: [2]struct { af: IpAddress.Family, rr: HostName.DnsRecord } = .{
5299 .{ .af = .ip6, .rr = .A },
5300 .{ .af = .ip4, .rr = .AAAA },
5301 };
5302 var query_buffers: [2][280]u8 = undefined;
5303 var answer_buffer: [2 * 512]u8 = undefined;
5304 var queries_buffer: [2][]const u8 = undefined;
5305 var answers_buffer: [2][]const u8 = undefined;
5306 var nq: usize = 0;
5307 var answer_buffer_i: usize = 0;
5308
5309 for (family_records) |fr| {
5310 if (options.family != fr.af) {
5311 const entropy = std.crypto.random.array(u8, 2);
5312 const len = writeResolutionQuery(&query_buffers[nq], 0, lookup_canon_name, 1, fr.rr, entropy);
5313 queries_buffer[nq] = query_buffers[nq][0..len];
5314 nq += 1;
5315 }
5316 }
5317
5318 var ip4_mapped_buffer: [HostName.ResolvConf.max_nameservers]IpAddress = undefined;
5319 const ip4_mapped = ip4_mapped_buffer[0..rc.nameservers_len];
5320 var any_ip6 = false;
5321 for (rc.nameservers(), ip4_mapped) |*ns, *m| {
5322 m.* = .{ .ip6 = .fromAny(ns.*) };
5323 any_ip6 = any_ip6 or ns.* == .ip6;
5324 }
5325 var socket = s: {
5326 if (any_ip6) ip6: {
5327 const ip6_addr: IpAddress = .{ .ip6 = .unspecified(0) };
5328 const socket = ip6_addr.bind(t_io, .{ .ip6_only = true, .mode = .dgram }) catch |err| switch (err) {
5329 error.AddressFamilyUnsupported => break :ip6,
5330 else => |e| return e,
5331 };
5332 break :s socket;
5333 }
5334 any_ip6 = false;
5335 const ip4_addr: IpAddress = .{ .ip4 = .unspecified(0) };
5336 const socket = try ip4_addr.bind(t_io, .{ .mode = .dgram });
5337 break :s socket;
5338 };
5339 defer socket.close(t_io);
5340
5341 const mapped_nameservers = if (any_ip6) ip4_mapped else rc.nameservers();
5342 const queries = queries_buffer[0..nq];
5343 const answers = answers_buffer[0..queries.len];
5344 var answers_remaining = answers.len;
5345 for (answers) |*answer| answer.len = 0;
5346
5347 // boot clock is chosen because time the computer is suspended should count
5348 // against time spent waiting for external messages to arrive.
5349 const clock: Io.Clock = .boot;
5350 var now_ts = try clock.now(t_io);
5351 const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds));
5352 const attempt_duration: Io.Duration = .{
5353 .nanoseconds = (std.time.ns_per_s / rc.attempts) * @as(i96, rc.timeout_seconds),
5354 };
5355
5356 send: while (now_ts.nanoseconds < final_ts.nanoseconds) : (now_ts = try clock.now(t_io)) {
5357 const max_messages = queries_buffer.len * HostName.ResolvConf.max_nameservers;
5358 {
5359 var message_buffer: [max_messages]Io.net.OutgoingMessage = undefined;
5360 var message_i: usize = 0;
5361 for (queries, answers) |query, *answer| {
5362 if (answer.len != 0) continue;
5363 for (mapped_nameservers) |*ns| {
5364 message_buffer[message_i] = .{
5365 .address = ns,
5366 .data_ptr = query.ptr,
5367 .data_len = query.len,
5368 };
5369 message_i += 1;
5370 }
5371 }
5372 _ = netSendPosix(t, socket.handle, message_buffer[0..message_i], .{});
5373 }
5374
5375 const timeout: Io.Timeout = .{ .deadline = .{
5376 .raw = now_ts.addDuration(attempt_duration),
5377 .clock = clock,
5378 } };
5379
5380 while (true) {
5381 var message_buffer: [max_messages]Io.net.IncomingMessage = @splat(.init);
5382 const buf = answer_buffer[answer_buffer_i..];
5383 const recv_err, const recv_n = socket.receiveManyTimeout(t_io, &message_buffer, buf, .{}, timeout);
5384 for (message_buffer[0..recv_n]) |*received_message| {
5385 const reply = received_message.data;
5386 // Ignore non-identifiable packets.
5387 if (reply.len < 4) continue;
5388
5389 // Ignore replies from addresses we didn't send to.
5390 const ns = for (mapped_nameservers) |*ns| {
5391 if (received_message.from.eql(ns)) break ns;
5392 } else {
5393 continue;
5394 };
5395
5396 // Find which query this answer goes with, if any.
5397 const query, const answer = for (queries, answers) |query, *answer| {
5398 if (reply[0] == query[0] and reply[1] == query[1]) break .{ query, answer };
5399 } else {
5400 continue;
5401 };
5402 if (answer.len != 0) continue;
5403
5404 // Only accept positive or negative responses; retry immediately on
5405 // server failure, and ignore all other codes such as refusal.
5406 switch (reply[3] & 15) {
5407 0, 3 => {
5408 answer.* = reply;
5409 answer_buffer_i += reply.len;
5410 answers_remaining -= 1;
5411 if (answer_buffer.len - answer_buffer_i == 0) break :send;
5412 if (answers_remaining == 0) break :send;
5413 },
5414 2 => {
5415 var retry_message: Io.net.OutgoingMessage = .{
5416 .address = ns,
5417 .data_ptr = query.ptr,
5418 .data_len = query.len,
5419 };
5420 _ = netSendPosix(t, socket.handle, (&retry_message)[0..1], .{});
5421 continue;
5422 },
5423 else => continue,
5424 }
5425 }
5426 if (recv_err) |err| switch (err) {
5427 error.Canceled => return error.Canceled,
5428 error.Timeout => continue :send,
5429 else => continue,
5430 };
5431 }
5432 } else {
5433 return error.NameServerFailure;
5434 }
5435
5436 var addresses_len: usize = 0;
5437 var canonical_name: ?HostName = null;
5438
5439 for (answers) |answer| {
5440 var it = HostName.DnsResponse.init(answer) catch {
5441 // Here we could potentially add diagnostics to the results queue.
5442 continue;
5443 };
5444 while (it.next() catch {
5445 // Here we could potentially add diagnostics to the results queue.
5446 continue;
5447 }) |record| switch (record.rr) {
5448 .A => {
5449 const data = record.packet[record.data_off..][0..record.data_len];
5450 if (data.len != 4) return error.InvalidDnsARecord;
5451 try resolved.putOne(t_io, .{ .address = .{ .ip4 = .{
5452 .bytes = data[0..4].*,
5453 .port = options.port,
5454 } } });
5455 addresses_len += 1;
5456 },
5457 .AAAA => {
5458 const data = record.packet[record.data_off..][0..record.data_len];
5459 if (data.len != 16) return error.InvalidDnsAAAARecord;
5460 try resolved.putOne(t_io, .{ .address = .{ .ip6 = .{
5461 .bytes = data[0..16].*,
5462 .port = options.port,
5463 } } });
5464 addresses_len += 1;
5465 },
5466 .CNAME => {
5467 _, canonical_name = HostName.expand(record.packet, record.data_off, options.canonical_name_buffer) catch
5468 return error.InvalidDnsCnameRecord;
5469 },
5470 _ => continue,
5471 };
5472 }
5473
5474 try resolved.putOne(t_io, .{ .canonical_name = canonical_name orelse .{ .bytes = lookup_canon_name } });
5475 if (addresses_len == 0) return error.NameServerFailure;
5476}
5477
5478fn lookupHosts(
5479 t: *Threaded,
5480 host_name: HostName,
5481 resolved: *Io.Queue(HostName.LookupResult),
5482 options: HostName.LookupOptions,
5483) !void {
5484 const t_io = io(t);
5485 const file = Io.File.openAbsolute(t_io, "/etc/hosts", .{}) catch |err| switch (err) {
5486 error.FileNotFound,
5487 error.NotDir,
5488 error.AccessDenied,
5489 => return error.UnknownHostName,
5490
5491 error.Canceled => |e| return e,
5492
5493 else => {
5494 // Here we could add more detailed diagnostics to the results queue.
5495 return error.DetectingNetworkConfigurationFailed;
5496 },
5497 };
5498 defer file.close(t_io);
5499
5500 var line_buf: [512]u8 = undefined;
5501 var file_reader = file.reader(t_io, &line_buf);
5502 return lookupHostsReader(t, host_name, resolved, options, &file_reader.interface) catch |err| switch (err) {
5503 error.ReadFailed => switch (file_reader.err.?) {
5504 error.Canceled => |e| return e,
5505 else => {
5506 // Here we could add more detailed diagnostics to the results queue.
5507 return error.DetectingNetworkConfigurationFailed;
5508 },
5509 },
5510 error.Canceled => |e| return e,
5511 error.UnknownHostName => |e| return e,
5512 };
5513}
5514
5515fn lookupHostsReader(
5516 t: *Threaded,
5517 host_name: HostName,
5518 resolved: *Io.Queue(HostName.LookupResult),
5519 options: HostName.LookupOptions,
5520 reader: *Io.Reader,
5521) error{ ReadFailed, Canceled, UnknownHostName }!void {
5522 const t_io = io(t);
5523 var addresses_len: usize = 0;
5524 var canonical_name: ?HostName = null;
5525 while (true) {
5526 const line = reader.takeDelimiterExclusive('\n') catch |err| switch (err) {
5527 error.StreamTooLong => {
5528 // Skip lines that are too long.
5529 _ = reader.discardDelimiterInclusive('\n') catch |e| switch (e) {
5530 error.EndOfStream => break,
5531 error.ReadFailed => return error.ReadFailed,
5532 };
5533 continue;
5534 },
5535 error.ReadFailed => return error.ReadFailed,
5536 error.EndOfStream => break,
5537 };
5538 reader.toss(1);
5539 var split_it = std.mem.splitScalar(u8, line, '#');
5540 const no_comment_line = split_it.first();
5541
5542 var line_it = std.mem.tokenizeAny(u8, no_comment_line, " \t");
5543 const ip_text = line_it.next() orelse continue;
5544 var first_name_text: ?[]const u8 = null;
5545 while (line_it.next()) |name_text| {
5546 if (std.mem.eql(u8, name_text, host_name.bytes)) {
5547 if (first_name_text == null) first_name_text = name_text;
5548 break;
5549 }
5550 } else continue;
5551
5552 if (canonical_name == null) {
5553 if (HostName.init(first_name_text.?)) |name_text| {
5554 if (name_text.bytes.len <= options.canonical_name_buffer.len) {
5555 const canonical_name_dest = options.canonical_name_buffer[0..name_text.bytes.len];
5556 @memcpy(canonical_name_dest, name_text.bytes);
5557 canonical_name = .{ .bytes = canonical_name_dest };
5558 }
5559 } else |_| {}
5560 }
5561
5562 if (options.family != .ip6) {
5563 if (IpAddress.parseIp4(ip_text, options.port)) |addr| {
5564 try resolved.putOne(t_io, .{ .address = addr });
5565 addresses_len += 1;
5566 } else |_| {}
5567 }
5568 if (options.family != .ip4) {
5569 if (IpAddress.parseIp6(ip_text, options.port)) |addr| {
5570 try resolved.putOne(t_io, .{ .address = addr });
5571 addresses_len += 1;
5572 } else |_| {}
5573 }
5574 }
5575
5576 if (canonical_name) |canon_name| try resolved.putOne(t_io, .{ .canonical_name = canon_name });
5577 if (addresses_len == 0) return error.UnknownHostName;
5578}
5579
5580/// Writes DNS resolution query packet data to `w`; at most 280 bytes.
5581fn writeResolutionQuery(q: *[280]u8, op: u4, dname: []const u8, class: u8, ty: HostName.DnsRecord, entropy: [2]u8) usize {
5582 // This implementation is ported from musl libc.
5583 // A more idiomatic "ziggy" implementation would be welcome.
5584 var name = dname;
5585 if (std.mem.endsWith(u8, name, ".")) name.len -= 1;
5586 assert(name.len <= 253);
5587 const n = 17 + name.len + @intFromBool(name.len != 0);
5588
5589 // Construct query template - ID will be filled later
5590 q[0..2].* = entropy;
5591 @memset(q[2..n], 0);
5592 q[2] = @as(u8, op) * 8 + 1;
5593 q[5] = 1;
5594 @memcpy(q[13..][0..name.len], name);
5595 var i: usize = 13;
5596 var j: usize = undefined;
5597 while (q[i] != 0) : (i = j + 1) {
5598 j = i;
5599 while (q[j] != 0 and q[j] != '.') : (j += 1) {}
5600 // TODO determine the circumstances for this and whether or
5601 // not this should be an error.
5602 if (j - i - 1 > 62) unreachable;
5603 q[i - 1] = @intCast(j - i);
5604 }
5605 q[i + 1] = @intFromEnum(ty);
5606 q[i + 3] = class;
5607 return n;
5608}
5609
5610fn copyCanon(canonical_name_buffer: *[HostName.max_len]u8, name: []const u8) HostName {
5611 const dest = canonical_name_buffer[0..name.len];
5612 @memcpy(dest, name);
5613 return .{ .bytes = dest };
5614}
5615
5616/// Darwin XNU 7195.50.7.100.1 introduced __ulock_wait2 and migrated code paths (notably pthread_cond_t) towards it:
5617/// https://github.com/apple/darwin-xnu/commit/d4061fb0260b3ed486147341b72468f836ed6c8f#diff-08f993cc40af475663274687b7c326cc6c3031e0db3ac8de7b24624610616be6
5618///
5619/// This XNU version appears to correspond to 11.0.1:
5620/// https://kernelshaman.blogspot.com/2021/01/building-xnu-for-macos-big-sur-1101.html
5621///
5622/// ulock_wait() uses 32-bit micro-second timeouts where 0 = INFINITE or no-timeout
5623/// ulock_wait2() uses 64-bit nano-second timeouts (with the same convention)
5624const darwin_supports_ulock_wait2 = builtin.os.version_range.semver.min.major >= 11;
5625
5626fn futexWait(t: *Threaded, ptr: *const std.atomic.Value(u32), expect: u32) Io.Cancelable!void {
5627 @branchHint(.cold);
5628
5629 if (builtin.cpu.arch.isWasm()) {
5630 comptime assert(builtin.cpu.has(.wasm, .atomics));
5631 try t.checkCancel();
5632 const timeout: i64 = -1;
5633 const signed_expect: i32 = @bitCast(expect);
5634 const result = asm volatile (
5635 \\local.get %[ptr]
5636 \\local.get %[expected]
5637 \\local.get %[timeout]
5638 \\memory.atomic.wait32 0
5639 \\local.set %[ret]
5640 : [ret] "=r" (-> u32),
5641 : [ptr] "r" (&ptr.raw),
5642 [expected] "r" (signed_expect),
5643 [timeout] "r" (timeout),
5644 );
5645 switch (result) {
5646 0 => {}, // ok
5647 1 => {}, // expected != loaded
5648 2 => assert(!is_debug), // timeout
5649 else => assert(!is_debug),
5650 }
5651 } else switch (native_os) {
5652 .linux => {
5653 const linux = std.os.linux;
5654 try t.checkCancel();
5655 const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, null);
5656 if (is_debug) switch (linux.E.init(rc)) {
5657 .SUCCESS => {}, // notified by `wake()`
5658 .INTR => {}, // gives caller a chance to check cancellation
5659 .AGAIN => {}, // ptr.* != expect
5660 .INVAL => {}, // possibly timeout overflow
5661 .TIMEDOUT => unreachable,
5662 .FAULT => unreachable, // ptr was invalid
5663 else => unreachable,
5664 };
5665 },
5666 .driverkit, .ios, .macos, .tvos, .visionos, .watchos => {
5667 const c = std.c;
5668 const flags: c.UL = .{
5669 .op = .COMPARE_AND_WAIT,
5670 .NO_ERRNO = true,
5671 };
5672 try t.checkCancel();
5673 const status = if (darwin_supports_ulock_wait2)
5674 c.__ulock_wait2(flags, ptr, expect, 0, 0)
5675 else
5676 c.__ulock_wait(flags, ptr, expect, 0);
5677
5678 if (status >= 0) return;
5679
5680 if (is_debug) switch (@as(c.E, @enumFromInt(-status))) {
5681 .INTR => {}, // spurious wake
5682 // Address of the futex was paged out. This is unlikely, but possible in theory, and
5683 // pthread/libdispatch on darwin bother to handle it. In this case we'll return
5684 // without waiting, but the caller should retry anyway.
5685 .FAULT => {},
5686 .TIMEDOUT => unreachable,
5687 else => unreachable,
5688 };
5689 },
5690 .windows => {
5691 try t.checkCancel();
5692 switch (windows.ntdll.RtlWaitOnAddress(ptr, &expect, @sizeOf(@TypeOf(expect)), null)) {
5693 .SUCCESS => {},
5694 .CANCELLED => return error.Canceled,
5695 else => recoverableOsBugDetected(),
5696 }
5697 },
5698 .freebsd => {
5699 const flags = @intFromEnum(std.c.UMTX_OP.WAIT_UINT_PRIVATE);
5700 try t.checkCancel();
5701 const rc = std.c._umtx_op(@intFromPtr(&ptr.raw), flags, @as(c_ulong, expect), 0, 0);
5702 if (is_debug) switch (posix.errno(rc)) {
5703 .SUCCESS => {},
5704 .FAULT => unreachable, // one of the args points to invalid memory
5705 .INVAL => unreachable, // arguments should be correct
5706 .TIMEDOUT => unreachable, // no timeout provided
5707 .INTR => {}, // spurious wake
5708 else => unreachable,
5709 };
5710 },
5711 else => @compileError("unimplemented: futexWait"),
5712 }
5713}
5714
5715pub fn futexWaitUncancelable(ptr: *const std.atomic.Value(u32), expect: u32) void {
5716 @branchHint(.cold);
5717
5718 if (builtin.cpu.arch.isWasm()) {
5719 comptime assert(builtin.cpu.has(.wasm, .atomics));
5720 const timeout: i64 = -1;
5721 const signed_expect: i32 = @bitCast(expect);
5722 const result = asm volatile (
5723 \\local.get %[ptr]
5724 \\local.get %[expected]
5725 \\local.get %[timeout]
5726 \\memory.atomic.wait32 0
5727 \\local.set %[ret]
5728 : [ret] "=r" (-> u32),
5729 : [ptr] "r" (&ptr.raw),
5730 [expected] "r" (signed_expect),
5731 [timeout] "r" (timeout),
5732 );
5733 switch (result) {
5734 0 => {}, // ok
5735 1 => {}, // expected != loaded
5736 2 => recoverableOsBugDetected(), // timeout
5737 else => recoverableOsBugDetected(),
5738 }
5739 } else switch (native_os) {
5740 .linux => {
5741 const linux = std.os.linux;
5742 const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, null);
5743 switch (linux.E.init(rc)) {
5744 .SUCCESS => {}, // notified by `wake()`
5745 .INTR => {}, // gives caller a chance to check cancellation
5746 .AGAIN => {}, // ptr.* != expect
5747 .INVAL => {}, // possibly timeout overflow
5748 .TIMEDOUT => recoverableOsBugDetected(),
5749 .FAULT => recoverableOsBugDetected(), // ptr was invalid
5750 else => recoverableOsBugDetected(),
5751 }
5752 },
5753 .driverkit, .ios, .macos, .tvos, .visionos, .watchos => {
5754 const c = std.c;
5755 const flags: c.UL = .{
5756 .op = .COMPARE_AND_WAIT,
5757 .NO_ERRNO = true,
5758 };
5759 const status = if (darwin_supports_ulock_wait2)
5760 c.__ulock_wait2(flags, ptr, expect, 0, 0)
5761 else
5762 c.__ulock_wait(flags, ptr, expect, 0);
5763
5764 if (status >= 0) return;
5765
5766 switch (@as(c.E, @enumFromInt(-status))) {
5767 // Wait was interrupted by the OS or other spurious signalling.
5768 .INTR => {},
5769 // Address of the futex was paged out. This is unlikely, but possible in theory, and
5770 // pthread/libdispatch on darwin bother to handle it. In this case we'll return
5771 // without waiting, but the caller should retry anyway.
5772 .FAULT => {},
5773 .TIMEDOUT => recoverableOsBugDetected(),
5774 else => recoverableOsBugDetected(),
5775 }
5776 },
5777 .windows => {
5778 switch (windows.ntdll.RtlWaitOnAddress(ptr, &expect, @sizeOf(@TypeOf(expect)), null)) {
5779 .SUCCESS, .CANCELLED => {},
5780 else => recoverableOsBugDetected(),
5781 }
5782 },
5783 .freebsd => {
5784 const flags = @intFromEnum(std.c.UMTX_OP.WAIT_UINT_PRIVATE);
5785 const rc = std.c._umtx_op(@intFromPtr(&ptr.raw), flags, @as(c_ulong, expect), 0, 0);
5786 switch (posix.errno(rc)) {
5787 .SUCCESS => {},
5788 .INTR => {}, // spurious wake
5789 .FAULT => recoverableOsBugDetected(), // one of the args points to invalid memory
5790 .INVAL => recoverableOsBugDetected(), // arguments should be correct
5791 .TIMEDOUT => recoverableOsBugDetected(), // no timeout provided
5792 else => recoverableOsBugDetected(),
5793 }
5794 },
5795 else => @compileError("unimplemented: futexWaitUncancelable"),
5796 }
5797}
5798
5799pub fn futexWaitDurationUncancelable(ptr: *const std.atomic.Value(u32), expect: u32, timeout: Io.Duration) void {
5800 @branchHint(.cold);
5801
5802 if (native_os == .linux) {
5803 const linux = std.os.linux;
5804 var ts = timestampToPosix(timeout.toNanoseconds());
5805 const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, &ts);
5806 if (is_debug) switch (linux.E.init(rc)) {
5807 .SUCCESS => {}, // notified by `wake()`
5808 .INTR => {}, // gives caller a chance to check cancellation
5809 .AGAIN => {}, // ptr.* != expect
5810 .TIMEDOUT => {},
5811 .INVAL => {}, // possibly timeout overflow
5812 .FAULT => unreachable, // ptr was invalid
5813 else => unreachable,
5814 };
5815 return;
5816 } else {
5817 @compileError("TODO");
5818 }
5819}
5820
5821pub fn futexWake(ptr: *const std.atomic.Value(u32), max_waiters: u32) void {
5822 @branchHint(.cold);
5823
5824 if (builtin.cpu.arch.isWasm()) {
5825 comptime assert(builtin.cpu.has(.wasm, .atomics));
5826 assert(max_waiters != 0);
5827 const woken_count = asm volatile (
5828 \\local.get %[ptr]
5829 \\local.get %[waiters]
5830 \\memory.atomic.notify 0
5831 \\local.set %[ret]
5832 : [ret] "=r" (-> u32),
5833 : [ptr] "r" (&ptr.raw),
5834 [waiters] "r" (max_waiters),
5835 );
5836 _ = woken_count; // can be 0 when linker flag 'shared-memory' is not enabled
5837 } else switch (native_os) {
5838 .linux => {
5839 const linux = std.os.linux;
5840 switch (linux.E.init(linux.futex_3arg(
5841 &ptr.raw,
5842 .{ .cmd = .WAKE, .private = true },
5843 @min(max_waiters, std.math.maxInt(i32)),
5844 ))) {
5845 .SUCCESS => return, // successful wake up
5846 .INVAL => return, // invalid futex_wait() on ptr done elsewhere
5847 .FAULT => return, // pointer became invalid while doing the wake
5848 else => return recoverableOsBugDetected(), // deadlock due to operating system bug
5849 }
5850 },
5851 .driverkit, .ios, .macos, .tvos, .visionos, .watchos => {
5852 const c = std.c;
5853 const flags: c.UL = .{
5854 .op = .COMPARE_AND_WAIT,
5855 .NO_ERRNO = true,
5856 .WAKE_ALL = max_waiters > 1,
5857 };
5858 while (true) {
5859 const status = c.__ulock_wake(flags, ptr, 0);
5860 if (status >= 0) return;
5861 switch (@as(c.E, @enumFromInt(-status))) {
5862 .INTR, .CANCELED => continue, // spurious wake()
5863 .FAULT => unreachable, // __ulock_wake doesn't generate EFAULT according to darwin pthread_cond_t
5864 .NOENT => return, // nothing was woken up
5865 .ALREADY => unreachable, // only for UL.Op.WAKE_THREAD
5866 else => unreachable, // deadlock due to operating system bug
5867 }
5868 }
5869 },
5870 .windows => {
5871 assert(max_waiters != 0);
5872 switch (max_waiters) {
5873 1 => windows.ntdll.RtlWakeAddressSingle(ptr),
5874 else => windows.ntdll.RtlWakeAddressAll(ptr),
5875 }
5876 },
5877 .freebsd => {
5878 const rc = std.c._umtx_op(
5879 @intFromPtr(&ptr.raw),
5880 @intFromEnum(std.c.UMTX_OP.WAKE_PRIVATE),
5881 @as(c_ulong, max_waiters),
5882 0, // there is no timeout struct
5883 0, // there is no timeout struct pointer
5884 );
5885 switch (posix.errno(rc)) {
5886 .SUCCESS => {},
5887 .FAULT => {}, // it's ok if the ptr doesn't point to valid memory
5888 .INVAL => unreachable, // arguments should be correct
5889 else => unreachable, // deadlock due to operating system bug
5890 }
5891 },
5892 else => @compileError("unimplemented: futexWake"),
5893 }
5894}
5895
5896/// A thread-safe logical boolean value which can be `set` and `unset`.
5897///
5898/// It can also block threads until the value is set with cancelation via timed
5899/// waits. Statically initializable; four bytes on all targets.
5900pub const ResetEvent = switch (native_os) {
5901 .netbsd => ResetEventPosix,
5902 else => ResetEventFutex,
5903};
5904
5905/// A `ResetEvent` implementation based on futexes.
5906const ResetEventFutex = enum(u32) {
5907 unset = 0,
5908 waiting = 1,
5909 is_set = 2,
5910
5911 /// Returns whether the logical boolean is `set`.
5912 ///
5913 /// Once `reset` is called, this returns false until the next `set`.
5914 ///
5915 /// The memory accesses before the `set` can be said to happen before
5916 /// `isSet` returns true.
5917 pub fn isSet(ref: *const ResetEventFutex) bool {
5918 if (builtin.single_threaded) return switch (ref.*) {
5919 .unset => false,
5920 .waiting => unreachable,
5921 .is_set => true,
5922 };
5923 // Acquire barrier ensures memory accesses before `set` happen before
5924 // returning true.
5925 return @atomicLoad(ResetEventFutex, ref, .acquire) == .is_set;
5926 }
5927
5928 /// Blocks the calling thread until `set` is called.
5929 ///
5930 /// This is effectively a more efficient version of `while (!isSet()) {}`.
5931 ///
5932 /// The memory accesses before the `set` can be said to happen before `wait` returns.
5933 pub fn wait(ref: *ResetEventFutex, t: *Threaded) Io.Cancelable!void {
5934 if (builtin.single_threaded) switch (ref.*) {
5935 .unset => unreachable, // Deadlock, no other threads to wake us up.
5936 .waiting => unreachable, // Invalid state.
5937 .is_set => return,
5938 };
5939 // Try to set the state from `unset` to `waiting` to indicate to the
5940 // `set` thread that others are blocked on the ResetEventFutex. Avoid using
5941 // any strict barriers until we know the ResetEventFutex is set.
5942 var state = @atomicLoad(ResetEventFutex, ref, .acquire);
5943 if (state == .is_set) {
5944 @branchHint(.likely);
5945 return;
5946 }
5947 if (state == .unset) {
5948 state = @cmpxchgStrong(ResetEventFutex, ref, state, .waiting, .acquire, .acquire) orelse .waiting;
5949 }
5950 while (state == .waiting) {
5951 try futexWait(t, @ptrCast(ref), @intFromEnum(ResetEventFutex.waiting));
5952 state = @atomicLoad(ResetEventFutex, ref, .acquire);
5953 }
5954 assert(state == .is_set);
5955 }
5956
5957 /// Same as `wait` except uninterruptible.
5958 pub fn waitUncancelable(ref: *ResetEventFutex) void {
5959 if (builtin.single_threaded) switch (ref.*) {
5960 .unset => unreachable, // Deadlock, no other threads to wake us up.
5961 .waiting => unreachable, // Invalid state.
5962 .is_set => return,
5963 };
5964 // Try to set the state from `unset` to `waiting` to indicate to the
5965 // `set` thread that others are blocked on the ResetEventFutex. Avoid using
5966 // any strict barriers until we know the ResetEventFutex is set.
5967 var state = @atomicLoad(ResetEventFutex, ref, .acquire);
5968 if (state == .is_set) {
5969 @branchHint(.likely);
5970 return;
5971 }
5972 if (state == .unset) {
5973 state = @cmpxchgStrong(ResetEventFutex, ref, state, .waiting, .acquire, .acquire) orelse .waiting;
5974 }
5975 while (state == .waiting) {
5976 futexWaitUncancelable(@ptrCast(ref), @intFromEnum(ResetEventFutex.waiting));
5977 state = @atomicLoad(ResetEventFutex, ref, .acquire);
5978 }
5979 assert(state == .is_set);
5980 }
5981
5982 /// Marks the logical boolean as `set` and unblocks any threads in `wait`
5983 /// or `timedWait` to observe the new state.
5984 ///
5985 /// The logical boolean stays `set` until `reset` is called, making future
5986 /// `set` calls do nothing semantically.
5987 ///
5988 /// The memory accesses before `set` can be said to happen before `isSet`
5989 /// returns true or `wait`/`timedWait` return successfully.
5990 pub fn set(ref: *ResetEventFutex) void {
5991 if (builtin.single_threaded) {
5992 ref.* = .is_set;
5993 return;
5994 }
5995 if (@atomicRmw(ResetEventFutex, ref, .Xchg, .is_set, .release) == .waiting) {
5996 futexWake(@ptrCast(ref), std.math.maxInt(u32));
5997 }
5998 }
5999
6000 /// Unmarks the ResetEventFutex as if `set` was never called.
6001 ///
6002 /// Assumes no threads are blocked in `wait` or `timedWait`. Concurrent
6003 /// calls to `set`, `isSet` and `reset` are allowed.
6004 pub fn reset(ref: *ResetEventFutex) void {
6005 if (builtin.single_threaded) {
6006 ref.* = .unset;
6007 return;
6008 }
6009 @atomicStore(ResetEventFutex, ref, .unset, .monotonic);
6010 }
6011};
6012
6013/// A `ResetEvent` implementation based on pthreads API.
6014const ResetEventPosix = struct {
6015 cond: std.c.pthread_cond_t,
6016 mutex: std.c.pthread_mutex_t,
6017 state: ResetEventFutex,
6018
6019 pub const unset: ResetEventPosix = .{
6020 .cond = std.c.PTHREAD_COND_INITIALIZER,
6021 .mutex = std.c.PTHREAD_MUTEX_INITIALIZER,
6022 .state = .unset,
6023 };
6024
6025 pub fn isSet(rep: *const ResetEventPosix) bool {
6026 if (builtin.single_threaded) return switch (rep.state) {
6027 .unset => false,
6028 .waiting => unreachable,
6029 .is_set => true,
6030 };
6031 return @atomicLoad(ResetEventFutex, &rep.state, .acquire) == .is_set;
6032 }
6033
6034 pub fn wait(rep: *ResetEventPosix, t: *Threaded) Io.Cancelable!void {
6035 if (builtin.single_threaded) switch (rep.*) {
6036 .unset => unreachable, // Deadlock, no other threads to wake us up.
6037 .waiting => unreachable, // Invalid state.
6038 .is_set => return,
6039 };
6040 assert(std.c.pthread_mutex_lock(&rep.mutex) == .SUCCESS);
6041 defer assert(std.c.pthread_mutex_unlock(&rep.mutex) == .SUCCESS);
6042 sw: switch (rep.state) {
6043 .unset => {
6044 rep.state = .waiting;
6045 continue :sw .waiting;
6046 },
6047 .waiting => {
6048 try t.checkCancel();
6049 assert(std.c.pthread_cond_wait(&rep.cond, &rep.mutex) == .SUCCESS);
6050 continue :sw rep.state;
6051 },
6052 .is_set => return,
6053 }
6054 }
6055
6056 pub fn waitUncancelable(rep: *ResetEventPosix) void {
6057 if (builtin.single_threaded) switch (rep.*) {
6058 .unset => unreachable, // Deadlock, no other threads to wake us up.
6059 .waiting => unreachable, // Invalid state.
6060 .is_set => return,
6061 };
6062 assert(std.c.pthread_mutex_lock(&rep.mutex) == .SUCCESS);
6063 defer assert(std.c.pthread_mutex_unlock(&rep.mutex) == .SUCCESS);
6064 sw: switch (rep.state) {
6065 .unset => {
6066 rep.state = .waiting;
6067 continue :sw .waiting;
6068 },
6069 .waiting => {
6070 assert(std.c.pthread_cond_wait(&rep.cond, &rep.mutex) == .SUCCESS);
6071 continue :sw rep.state;
6072 },
6073 .is_set => return,
6074 }
6075 }
6076
6077 pub fn set(rep: *ResetEventPosix) void {
6078 if (builtin.single_threaded) {
6079 rep.* = .is_set;
6080 return;
6081 }
6082 if (@atomicRmw(ResetEventFutex, &rep.state, .Xchg, .is_set, .release) == .waiting) {
6083 assert(std.c.pthread_cond_broadcast(&rep.cond) == .SUCCESS);
6084 }
6085 }
6086
6087 pub fn reset(rep: *ResetEventPosix) void {
6088 if (builtin.single_threaded) {
6089 rep.* = .unset;
6090 return;
6091 }
6092 @atomicStore(ResetEventFutex, &rep.state, .unset, .monotonic);
6093 }
6094};
6095
6096fn closeSocketWindows(s: ws2_32.SOCKET) void {
6097 const rc = ws2_32.closesocket(s);
6098 if (is_debug) switch (rc) {
6099 0 => {},
6100 ws2_32.SOCKET_ERROR => switch (ws2_32.WSAGetLastError()) {
6101 else => recoverableOsBugDetected(),
6102 },
6103 else => recoverableOsBugDetected(),
6104 };
6105}
6106
6107const Wsa = struct {
6108 status: Status = .uninitialized,
6109 mutex: Io.Mutex = .init,
6110 init_error: ?Wsa.InitError = null,
6111
6112 const Status = enum { uninitialized, initialized, failure };
6113
6114 const InitError = error{
6115 ProcessFdQuotaExceeded,
6116 NetworkDown,
6117 VersionUnsupported,
6118 BlockingOperationInProgress,
6119 } || Io.UnexpectedError;
6120};
6121
6122fn initializeWsa(t: *Threaded) error{NetworkDown}!void {
6123 const t_io = io(t);
6124 const wsa = &t.wsa;
6125 wsa.mutex.lockUncancelable(t_io);
6126 defer wsa.mutex.unlock(t_io);
6127 switch (wsa.status) {
6128 .uninitialized => {
6129 var wsa_data: ws2_32.WSADATA = undefined;
6130 const minor_version = 2;
6131 const major_version = 2;
6132 switch (ws2_32.WSAStartup((@as(windows.WORD, minor_version) << 8) | major_version, &wsa_data)) {
6133 0 => {
6134 wsa.status = .initialized;
6135 return;
6136 },
6137 else => |err_int| switch (@as(ws2_32.WinsockError, @enumFromInt(@as(u16, @intCast(err_int))))) {
6138 .SYSNOTREADY => wsa.init_error = error.NetworkDown,
6139 .VERNOTSUPPORTED => wsa.init_error = error.VersionUnsupported,
6140 .EINPROGRESS => wsa.init_error = error.BlockingOperationInProgress,
6141 .EPROCLIM => wsa.init_error = error.ProcessFdQuotaExceeded,
6142 else => |err| wsa.init_error = windows.unexpectedWSAError(err),
6143 },
6144 }
6145 },
6146 .initialized => return,
6147 .failure => {},
6148 }
6149 return error.NetworkDown;
6150}
6151
6152fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {}
6153
6154test {
6155 _ = @import("Threaded/test.zig");
6156}
lib/std/Io/Threaded/test.zig created+58
......@@ -0,0 +1,58 @@
1const builtin = @import("builtin");
2
3const std = @import("std");
4const Io = std.Io;
5const testing = std.testing;
6const assert = std.debug.assert;
7
8test "concurrent vs main prevents deadlock via oversubscription" {
9 var threaded: Io.Threaded = .init(std.testing.allocator);
10 defer threaded.deinit();
11 const io = threaded.io();
12
13 threaded.cpu_count = 1;
14
15 var queue: Io.Queue(u8) = .init(&.{});
16
17 var putter = io.concurrent(put, .{ io, &queue }) catch |err| switch (err) {
18 error.ConcurrencyUnavailable => {
19 try testing.expect(builtin.single_threaded);
20 return;
21 },
22 };
23 defer putter.cancel(io);
24
25 try testing.expectEqual(42, queue.getOneUncancelable(io));
26}
27
28fn put(io: Io, queue: *Io.Queue(u8)) void {
29 queue.putOneUncancelable(io, 42);
30}
31
32fn get(io: Io, queue: *Io.Queue(u8)) void {
33 assert(queue.getOneUncancelable(io) == 42);
34}
35
36test "concurrent vs concurrent prevents deadlock via oversubscription" {
37 var threaded: Io.Threaded = .init(std.testing.allocator);
38 defer threaded.deinit();
39 const io = threaded.io();
40
41 threaded.cpu_count = 1;
42
43 var queue: Io.Queue(u8) = .init(&.{});
44
45 var putter = io.concurrent(put, .{ io, &queue }) catch |err| switch (err) {
46 error.ConcurrencyUnavailable => {
47 try testing.expect(builtin.single_threaded);
48 return;
49 },
50 };
51 defer putter.cancel(io);
52
53 var getter = try io.concurrent(get, .{ io, &queue });
54 defer getter.cancel(io);
55
56 getter.await(io);
57 putter.await(io);
58}
lib/std/Io/Writer.zig+10-4
......@@ -5,7 +5,7 @@ const Writer = @This();
55const std = @import("../std.zig");
66const assert = std.debug.assert;
77const Limit = std.Io.Limit;
8const File = std.fs.File;
8const File = std.Io.File;
99const testing = std.testing;
1010const Allocator = std.mem.Allocator;
1111const ArrayList = std.ArrayList;
......@@ -2827,6 +2827,8 @@ pub const Allocating = struct {
28272827};
28282828
28292829test "discarding sendFile" {
2830 const io = testing.io;
2831
28302832 var tmp_dir = testing.tmpDir(.{});
28312833 defer tmp_dir.cleanup();
28322834
......@@ -2837,7 +2839,7 @@ test "discarding sendFile" {
28372839 try file_writer.interface.writeByte('h');
28382840 try file_writer.interface.flush();
28392841
2840 var file_reader = file_writer.moveToReader();
2842 var file_reader = file_writer.moveToReader(io);
28412843 try file_reader.seekTo(0);
28422844
28432845 var w_buffer: [256]u8 = undefined;
......@@ -2847,6 +2849,8 @@ test "discarding sendFile" {
28472849}
28482850
28492851test "allocating sendFile" {
2852 const io = testing.io;
2853
28502854 var tmp_dir = testing.tmpDir(.{});
28512855 defer tmp_dir.cleanup();
28522856
......@@ -2857,7 +2861,7 @@ test "allocating sendFile" {
28572861 try file_writer.interface.writeAll("abcd");
28582862 try file_writer.interface.flush();
28592863
2860 var file_reader = file_writer.moveToReader();
2864 var file_reader = file_writer.moveToReader(io);
28612865 try file_reader.seekTo(0);
28622866 try file_reader.interface.fill(2);
28632867
......@@ -2869,6 +2873,8 @@ test "allocating sendFile" {
28692873}
28702874
28712875test sendFileReading {
2876 const io = testing.io;
2877
28722878 var tmp_dir = testing.tmpDir(.{});
28732879 defer tmp_dir.cleanup();
28742880
......@@ -2879,7 +2885,7 @@ test sendFileReading {
28792885 try file_writer.interface.writeAll("abcd");
28802886 try file_writer.interface.flush();
28812887
2882 var file_reader = file_writer.moveToReader();
2888 var file_reader = file_writer.moveToReader(io);
28832889 try file_reader.seekTo(0);
28842890 try file_reader.interface.fill(2);
28852891
lib/std/Io/net.zig created+1379
......@@ -0,0 +1,1379 @@
1const builtin = @import("builtin");
2const native_os = builtin.os.tag;
3const std = @import("../std.zig");
4const Io = std.Io;
5const assert = std.debug.assert;
6
7pub const HostName = @import("net/HostName.zig");
8
9/// Source of truth: Internet Assigned Numbers Authority (IANA)
10pub const Protocol = enum(u32) {
11 hopopts = 0,
12 icmp = 1,
13 igmp = 2,
14 ipip = 4,
15 tcp = 6,
16 egp = 8,
17 pup = 12,
18 udp = 17,
19 idp = 22,
20 tp = 29,
21 dccp = 33,
22 ipv6 = 41,
23 routing = 43,
24 fragment = 44,
25 rsvp = 46,
26 gre = 47,
27 esp = 50,
28 ah = 51,
29 icmpv6 = 58,
30 none = 59,
31 dstopts = 60,
32 mtp = 92,
33 beetph = 94,
34 encap = 98,
35 pim = 103,
36 comp = 108,
37 sctp = 132,
38 mh = 135,
39 udplite = 136,
40 mpls = 137,
41 ethernet = 143,
42 raw = 255,
43 mptcp = 262,
44};
45
46/// Windows 10 added support for unix sockets in build 17063, redstone 4 is the
47/// first release to support them.
48pub const has_unix_sockets = switch (native_os) {
49 .windows => builtin.os.version_range.windows.isAtLeast(.win10_rs4) orelse false,
50 .wasi => false,
51 else => true,
52};
53
54pub const default_kernel_backlog = 128;
55
56pub const IpAddress = union(enum) {
57 ip4: Ip4Address,
58 ip6: Ip6Address,
59
60 pub const Family = @typeInfo(IpAddress).@"union".tag_type.?;
61
62 pub const ParseLiteralError = error{ InvalidAddress, InvalidPort };
63
64 /// Parse an IP address which may include a port.
65 ///
66 /// For IPv4, this is written `address:port`.
67 ///
68 /// For IPv6, RFC 3986 defines this as an "IP literal", and the port is
69 /// differentiated from the address by surrounding the address part in
70 /// brackets "[addr]:port". Even if the port is not given, the brackets are
71 /// mandatory.
72 pub fn parseLiteral(text: []const u8) ParseLiteralError!IpAddress {
73 if (text.len == 0) return error.InvalidAddress;
74 if (text[0] == '[') {
75 const addr_end = std.mem.findScalar(u8, text, ']') orelse
76 return error.InvalidAddress;
77 const addr_text = text[1..addr_end];
78 const port: u16 = p: {
79 if (addr_end == text.len - 1) break :p 0;
80 if (text[addr_end + 1] != ':') return error.InvalidAddress;
81 break :p std.fmt.parseInt(u16, text[addr_end + 2 ..], 10) catch return error.InvalidPort;
82 };
83 return parseIp6(addr_text, port) catch error.InvalidAddress;
84 }
85 if (std.mem.findScalar(u8, text, ':')) |i| {
86 const addr = Ip4Address.parse(text[0..i], 0) catch return error.InvalidAddress;
87 return .{ .ip4 = .{
88 .bytes = addr.bytes,
89 .port = std.fmt.parseInt(u16, text[i + 1 ..], 10) catch return error.InvalidPort,
90 } };
91 }
92 return parseIp4(text, 0) catch error.InvalidAddress;
93 }
94
95 /// Parse the given IP address string into an `IpAddress` value.
96 ///
97 /// This is a pure function but it cannot handle IPv6 addresses that have
98 /// scope ids ("%foo" at the end). To also handle those, `resolve` must be
99 /// called instead.
100 pub fn parse(text: []const u8, port: u16) !IpAddress {
101 if (parseIp4(text, port)) |ip4| return ip4 else |err| switch (err) {
102 error.Overflow,
103 error.InvalidEnd,
104 error.InvalidCharacter,
105 error.Incomplete,
106 error.NonCanonical,
107 => {},
108 }
109
110 return parseIp6(text, port);
111 }
112
113 pub fn parseIp4(text: []const u8, port: u16) Ip4Address.ParseError!IpAddress {
114 return .{ .ip4 = try Ip4Address.parse(text, port) };
115 }
116
117 /// This is a pure function but it cannot handle IPv6 addresses that have
118 /// scope ids ("%foo" at the end). To also handle those, `resolveIp6` must be
119 /// called instead.
120 pub fn parseIp6(text: []const u8, port: u16) Ip6Address.ParseError!IpAddress {
121 return .{ .ip6 = try Ip6Address.parse(text, port) };
122 }
123
124 /// This function requires an `Io` parameter because it must query the operating
125 /// system to convert interface name to index. For example, in
126 /// "fe80::e0e:76ff:fed4:cf22%eno1", "eno1" must be resolved to an index by
127 /// creating a socket and then using an `ioctl` syscall.
128 ///
129 /// For a pure function that cannot handle scopes, see `parse`.
130 pub fn resolve(io: Io, text: []const u8, port: u16) !IpAddress {
131 if (parseIp4(text, port)) |ip4| return ip4 else |err| switch (err) {
132 error.Overflow,
133 error.InvalidEnd,
134 error.InvalidCharacter,
135 error.Incomplete,
136 error.NonCanonical,
137 => {},
138 }
139
140 return resolveIp6(io, text, port);
141 }
142
143 pub fn resolveIp6(io: Io, text: []const u8, port: u16) Ip6Address.ResolveError!IpAddress {
144 return .{ .ip6 = try Ip6Address.resolve(io, text, port) };
145 }
146
147 /// Returns the port in native endian.
148 pub fn getPort(a: IpAddress) u16 {
149 return switch (a) {
150 inline .ip4, .ip6 => |x| x.port,
151 };
152 }
153
154 /// `port` is native-endian.
155 pub fn setPort(a: *IpAddress, port: u16) void {
156 switch (a) {
157 inline .ip4, .ip6 => |*x| x.port = port,
158 }
159 }
160
161 /// Includes the optional scope ("%foo" at the end) in IPv6 addresses.
162 ///
163 /// See `format` for an alternative that omits scopes and does
164 /// not require an `Io` parameter.
165 pub fn formatResolved(a: IpAddress, io: Io, w: *Io.Writer) Ip6Address.FormatError!void {
166 switch (a) {
167 .ip4 => |x| return x.format(w),
168 .ip6 => |x| return x.formatResolved(io, w),
169 }
170 }
171
172 /// See `formatResolved` for an alternative that additionally prints the optional
173 /// scope at the end of IPv6 addresses and requires an `Io` parameter.
174 pub fn format(a: IpAddress, w: *Io.Writer) Io.Writer.Error!void {
175 switch (a) {
176 inline .ip4, .ip6 => |x| return x.format(w),
177 }
178 }
179
180 pub fn eql(a: *const IpAddress, b: *const IpAddress) bool {
181 return switch (a.*) {
182 .ip4 => |a_ip4| switch (b.*) {
183 .ip4 => |b_ip4| a_ip4.eql(b_ip4),
184 else => false,
185 },
186 .ip6 => |a_ip6| switch (b.*) {
187 .ip6 => |b_ip6| a_ip6.eql(b_ip6),
188 else => false,
189 },
190 };
191 }
192
193 pub const ListenError = error{
194 /// The address is already taken. Can occur when bound port is 0 but
195 /// all ephemeral ports are already in use.
196 AddressInUse,
197 /// A nonexistent interface was requested or the requested address was not local.
198 AddressUnavailable,
199 /// The local network interface used to reach the destination is offline.
200 NetworkDown,
201 /// Insufficient memory or other resource internal to the operating system.
202 SystemResources,
203 /// Per-process limit on the number of open file descriptors has been reached.
204 ProcessFdQuotaExceeded,
205 /// System-wide limit on the total number of open files has been reached.
206 SystemFdQuotaExceeded,
207 /// The requested address family (IPv4 or IPv6) is not supported by the operating system.
208 AddressFamilyUnsupported,
209 ProtocolUnsupportedBySystem,
210 ProtocolUnsupportedByAddressFamily,
211 SocketModeUnsupported,
212 /// One of the `ListenOptions` is not supported by the Io
213 /// implementation.
214 OptionUnsupported,
215 } || Io.UnexpectedError || Io.Cancelable;
216
217 pub const ListenOptions = struct {
218 /// How many connections the kernel will accept on the application's behalf.
219 /// If more than this many connections pool in the kernel, clients will start
220 /// seeing "Connection refused".
221 kernel_backlog: u31 = default_kernel_backlog,
222 /// Sets SO_REUSEADDR and SO_REUSEPORT on POSIX.
223 /// Sets SO_REUSEADDR on Windows, which is roughly equivalent.
224 reuse_address: bool = false,
225 /// Only connection-oriented modes may be used here, which includes:
226 /// * `Socket.Mode.stream`
227 /// * `Socket.Mode.seqpacket`
228 mode: Socket.Mode = .stream,
229 /// Only connection-oriented protocols may be used here, which includes:
230 /// * `Protocol.tcp`
231 /// * `Protocol.tp`
232 /// * `Protocol.dccp`
233 /// * `Protocol.sctp`
234 protocol: Protocol = .tcp,
235 };
236
237 /// Waits for a TCP connection. When using this API, `bind` does not need
238 /// to be called. The returned `Server` has an open `stream`.
239 pub fn listen(address: IpAddress, io: Io, options: ListenOptions) ListenError!Server {
240 return io.vtable.netListenIp(io.userdata, address, options);
241 }
242
243 pub const BindError = error{
244 /// The address is already taken. Can occur when bound port is 0 but
245 /// all ephemeral ports are already in use.
246 AddressInUse,
247 /// A nonexistent interface was requested or the requested address was not local.
248 AddressUnavailable,
249 /// The address is not valid for the address family of socket.
250 AddressFamilyUnsupported,
251 /// Insufficient memory or other resource internal to the operating system.
252 SystemResources,
253 /// The local network interface used to reach the destination is offline.
254 NetworkDown,
255 ProtocolUnsupportedBySystem,
256 ProtocolUnsupportedByAddressFamily,
257 /// Per-process limit on the number of open file descriptors has been reached.
258 ProcessFdQuotaExceeded,
259 /// System-wide limit on the total number of open files has been reached.
260 SystemFdQuotaExceeded,
261 SocketModeUnsupported,
262 /// One of the `BindOptions` is not supported by the Io
263 /// implementation.
264 OptionUnsupported,
265 } || Io.UnexpectedError || Io.Cancelable;
266
267 pub const BindOptions = struct {
268 /// The socket is restricted to sending and receiving IPv6 packets only.
269 /// In this case, an IPv4 and an IPv6 application can bind to a single port
270 /// at the same time.
271 ip6_only: bool = false,
272 mode: Socket.Mode,
273 protocol: ?Protocol = null,
274 };
275
276 /// Associates an address with a `Socket` which can be used to receive UDP
277 /// packets and other kinds of non-streaming messages. See `listen` for a
278 /// streaming alternative.
279 ///
280 /// One bound `Socket` can be used to receive messages from multiple
281 /// different addresses.
282 pub fn bind(address: *const IpAddress, io: Io, options: BindOptions) BindError!Socket {
283 return io.vtable.netBindIp(io.userdata, address, options);
284 }
285
286 pub const ConnectError = error{
287 AddressUnavailable,
288 AddressFamilyUnsupported,
289 /// Insufficient memory or other resource internal to the operating system.
290 SystemResources,
291 ConnectionPending,
292 ConnectionRefused,
293 ConnectionResetByPeer,
294 HostUnreachable,
295 NetworkUnreachable,
296 Timeout,
297 /// One of the `ConnectOptions` is not supported by the Io
298 /// implementation.
299 OptionUnsupported,
300 /// Per-process limit on the number of open file descriptors has been reached.
301 ProcessFdQuotaExceeded,
302 /// System-wide limit on the total number of open files has been reached.
303 SystemFdQuotaExceeded,
304 ProtocolUnsupportedBySystem,
305 ProtocolUnsupportedByAddressFamily,
306 SocketModeUnsupported,
307 /// The user tried to connect to a broadcast address without having the socket broadcast flag enabled or
308 /// the connection request failed because of a local firewall rule.
309 AccessDenied,
310 /// Non-blocking was requested and the operation cannot return immediately.
311 WouldBlock,
312 NetworkDown,
313 } || Io.Timeout.Error || Io.UnexpectedError || Io.Cancelable;
314
315 pub const ConnectOptions = struct {
316 mode: Socket.Mode,
317 protocol: ?Protocol = null,
318 timeout: Io.Timeout = .none,
319 };
320
321 /// Initiates a connection-oriented network stream.
322 pub fn connect(address: IpAddress, io: Io, options: ConnectOptions) ConnectError!Stream {
323 return io.vtable.netConnectIp(io.userdata, &address, options);
324 }
325};
326
327/// An IPv4 address in binary memory layout.
328pub const Ip4Address = struct {
329 bytes: [4]u8,
330 port: u16,
331
332 pub fn loopback(port: u16) Ip4Address {
333 return .{
334 .bytes = .{ 127, 0, 0, 1 },
335 .port = port,
336 };
337 }
338
339 pub fn unspecified(port: u16) Ip4Address {
340 return .{
341 .bytes = .{ 0, 0, 0, 0 },
342 .port = port,
343 };
344 }
345
346 pub const ParseError = error{
347 Overflow,
348 InvalidEnd,
349 InvalidCharacter,
350 Incomplete,
351 NonCanonical,
352 };
353
354 pub fn parse(buffer: []const u8, port: u16) ParseError!Ip4Address {
355 var bytes: [4]u8 = @splat(0);
356 var index: u8 = 0;
357 var saw_any_digits = false;
358 var has_zero_prefix = false;
359 for (buffer) |c| switch (c) {
360 '.' => {
361 if (!saw_any_digits) return error.InvalidCharacter;
362 if (index == 3) return error.InvalidEnd;
363 index += 1;
364 saw_any_digits = false;
365 has_zero_prefix = false;
366 },
367 '0'...'9' => {
368 if (c == '0' and !saw_any_digits) {
369 has_zero_prefix = true;
370 } else if (has_zero_prefix) {
371 return error.NonCanonical;
372 }
373 saw_any_digits = true;
374 bytes[index] = try std.math.mul(u8, bytes[index], 10);
375 bytes[index] = try std.math.add(u8, bytes[index], c - '0');
376 },
377 else => return error.InvalidCharacter,
378 };
379 if (index == 3 and saw_any_digits) return .{
380 .bytes = bytes,
381 .port = port,
382 };
383 return error.Incomplete;
384 }
385
386 pub fn format(a: Ip4Address, w: *Io.Writer) Io.Writer.Error!void {
387 const bytes = &a.bytes;
388 try w.print("{d}.{d}.{d}.{d}:{d}", .{ bytes[0], bytes[1], bytes[2], bytes[3], a.port });
389 }
390
391 pub fn eql(a: Ip4Address, b: Ip4Address) bool {
392 const a_int: u32 = @bitCast(a.bytes);
393 const b_int: u32 = @bitCast(b.bytes);
394 return a.port == b.port and a_int == b_int;
395 }
396};
397
398/// An IPv6 address in binary memory layout.
399pub const Ip6Address = struct {
400 /// Native endian
401 port: u16,
402 /// Big endian
403 bytes: [16]u8,
404 flow: u32 = 0,
405 interface: Interface = .none,
406
407 pub const Policy = struct {
408 addr: [16]u8,
409 len: u8,
410 mask: u8,
411 prec: u8,
412 label: u8,
413 };
414
415 pub fn loopback(port: u16) Ip6Address {
416 return .{
417 .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
418 .port = port,
419 };
420 }
421
422 pub fn unspecified(port: u16) Ip6Address {
423 return .{
424 .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
425 .port = port,
426 };
427 }
428
429 /// Constructs an IPv4-mapped IPv6 address.
430 pub fn fromIp4(ip4: Ip4Address) Ip6Address {
431 const b = &ip4.bytes;
432 return .{
433 .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, b[0], b[1], b[2], b[3] },
434 .port = ip4.port,
435 };
436 }
437
438 /// Given an `IpAddress`, converts it to an `Ip6Address` directly, or via
439 /// constructing an IPv4-mapped IPv6 address.
440 pub fn fromAny(addr: IpAddress) Ip6Address {
441 return switch (addr) {
442 .ip4 => |ip4| fromIp4(ip4),
443 .ip6 => |ip6| ip6,
444 };
445 }
446
447 /// An IPv6 address but with `Interface` as a name rather than index.
448 pub const Unresolved = struct {
449 /// Big endian
450 bytes: [16]u8,
451 /// Has not been checked to be a valid native interface name.
452 /// Externally managed memory.
453 interface_name: ?[]const u8,
454
455 pub const Parsed = union(enum) {
456 success: Unresolved,
457 invalid_byte: usize,
458 incomplete,
459 junk_after_end: usize,
460 interface_name_oversized: usize,
461 invalid_ip4_mapping: usize,
462 overflow: usize,
463 };
464
465 pub fn parse(text: []const u8) Parsed {
466 if (text.len < 2) return .incomplete;
467 const ip4_prefix = "::ffff:";
468 if (std.ascii.startsWithIgnoreCase(text, ip4_prefix)) {
469 const parsed = Ip4Address.parse(text[ip4_prefix.len..], 0) catch
470 return .{ .invalid_ip4_mapping = ip4_prefix.len };
471 const b = parsed.bytes;
472 return .{ .success = .{
473 .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, b[0], b[1], b[2], b[3] },
474 .interface_name = null,
475 } };
476 }
477 // Has to be u16 elements to handle 3-digit hex numbers from compression.
478 var parts: [8]u16 = @splat(0);
479 var parts_i: u8 = 0;
480 var text_i: u8 = 0;
481 var digit_i: u8 = 0;
482 var compress_start: ?u8 = null;
483 var interface_name_text: ?[]const u8 = null;
484 const State = union(enum) { digit, end };
485 state: switch (State.digit) {
486 .digit => c: switch (text[text_i]) {
487 'a'...'f' => |c| {
488 const digit = c - 'a' + 10;
489 parts[parts_i] = (std.math.mul(u16, parts[parts_i], 16) catch return .{
490 .overflow = text_i,
491 }) + digit;
492 if (digit_i == 4) return .{ .invalid_byte = text_i };
493 digit_i += 1;
494 text_i += 1;
495 if (text.len - text_i == 0) {
496 parts_i += 1;
497 continue :state .end;
498 }
499 continue :c text[text_i];
500 },
501 'A'...'F' => |c| continue :c c - 'A' + 'a',
502 '0'...'9' => |c| {
503 const digit = c - '0';
504 parts[parts_i] = (std.math.mul(u16, parts[parts_i], 16) catch return .{
505 .overflow = text_i,
506 }) + digit;
507 if (digit_i == 4) return .{ .invalid_byte = text_i };
508 digit_i += 1;
509 text_i += 1;
510 if (text.len - text_i == 0) {
511 parts_i += 1;
512 continue :state .end;
513 }
514 continue :c text[text_i];
515 },
516 ':' => {
517 if (digit_i == 0) {
518 if (compress_start != null) return .{ .invalid_byte = text_i };
519 if (text_i == 0) {
520 text_i += 1;
521 if (text[text_i] != ':') return .{ .invalid_byte = text_i };
522 assert(parts_i == 0);
523 }
524 compress_start = parts_i;
525 text_i += 1;
526 if (text.len - text_i == 0) continue :state .end;
527 continue :c text[text_i];
528 } else {
529 parts_i += 1;
530 if (parts.len - parts_i == 0) continue :state .end;
531 digit_i = 0;
532 text_i += 1;
533 if (text.len - text_i == 0) return .incomplete;
534 continue :c text[text_i];
535 }
536 },
537 '%' => {
538 if (digit_i == 0) return .{ .invalid_byte = text_i };
539 parts_i += 1;
540 text_i += 1;
541 const name = text[text_i..];
542 if (name.len == 0) return .incomplete;
543 interface_name_text = name;
544 text_i = @intCast(text.len);
545 continue :state .end;
546 },
547 else => return .{ .invalid_byte = text_i },
548 },
549 .end => {
550 if (text.len - text_i != 0) return .{ .junk_after_end = text_i };
551 const remaining = parts.len - parts_i;
552 if (compress_start) |s| {
553 const src = parts[s..parts_i];
554 @memmove(parts[parts.len - src.len ..], src);
555 @memset(parts[s..][0..remaining], 0);
556 } else {
557 if (remaining != 0) return .incomplete;
558 }
559
560 // Workaround that can be removed when this proposal is
561 // implemented https://github.com/ziglang/zig/issues/19755
562 if ((comptime @import("builtin").cpu.arch.endian()) != .big) {
563 for (&parts) |*part| part.* = @byteSwap(part.*);
564 }
565
566 return .{ .success = .{
567 .bytes = @bitCast(parts),
568 .interface_name = interface_name_text,
569 } };
570 },
571 }
572 }
573
574 pub const FromAddressError = Interface.NameError;
575
576 pub fn fromAddress(a: *const Ip6Address, io: Io) FromAddressError!Unresolved {
577 if (a.interface.isNone()) return .{
578 .bytes = a.bytes,
579 .interface_name = null,
580 };
581 return .{
582 .bytes = a.bytes,
583 .interface_name = try a.interface.name(io),
584 };
585 }
586
587 pub fn format(u: *const Unresolved, w: *Io.Writer) Io.Writer.Error!void {
588 const bytes = &u.bytes;
589 if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) {
590 try w.print("::ffff:{d}.{d}.{d}.{d}", .{ bytes[12], bytes[13], bytes[14], bytes[15] });
591 } else {
592 const parts: [8]u16 = .{
593 std.mem.readInt(u16, bytes[0..2], .big),
594 std.mem.readInt(u16, bytes[2..4], .big),
595 std.mem.readInt(u16, bytes[4..6], .big),
596 std.mem.readInt(u16, bytes[6..8], .big),
597 std.mem.readInt(u16, bytes[8..10], .big),
598 std.mem.readInt(u16, bytes[10..12], .big),
599 std.mem.readInt(u16, bytes[12..14], .big),
600 std.mem.readInt(u16, bytes[14..16], .big),
601 };
602
603 // Find the longest zero run
604 var longest_start: usize = 8;
605 var longest_len: usize = 0;
606 var current_start: usize = 0;
607 var current_len: usize = 0;
608
609 for (parts, 0..) |part, i| {
610 if (part == 0) {
611 if (current_len == 0) {
612 current_start = i;
613 }
614 current_len += 1;
615 if (current_len > longest_len) {
616 longest_start = current_start;
617 longest_len = current_len;
618 }
619 } else {
620 current_len = 0;
621 }
622 }
623
624 // Only compress if the longest zero run is 2 or more
625 if (longest_len < 2) {
626 longest_start = 8;
627 longest_len = 0;
628 }
629
630 var i: usize = 0;
631 var abbrv = false;
632 while (i < parts.len) : (i += 1) {
633 if (i == longest_start) {
634 // Emit "::" for the longest zero run
635 if (!abbrv) {
636 try w.writeAll(if (i == 0) "::" else ":");
637 abbrv = true;
638 }
639 i += longest_len - 1; // Skip the compressed range
640 continue;
641 }
642 if (abbrv) {
643 abbrv = false;
644 }
645 try w.print("{x}", .{parts[i]});
646 if (i != parts.len - 1) {
647 try w.writeAll(":");
648 }
649 }
650 }
651 if (u.interface_name) |n| try w.print("%{s}", .{n});
652 }
653 };
654
655 pub const ParseError = error{
656 /// If this is returned, more detailed diagnostics can be obtained by
657 /// calling `Ip6Address.Parsed.init`.
658 ParseFailed,
659 /// If this is returned, the IPv6 address had a scope id on it ("%foo"
660 /// at the end) which requires calling `resolve`.
661 UnresolvedScope,
662 };
663
664 /// This is a pure function but it cannot handle IPv6 addresses that have
665 /// scope ids ("%foo" at the end). To also handle those, `resolve` must be
666 /// called instead, or the lower level `Unresolved` API may be used.
667 pub fn parse(buffer: []const u8, port: u16) ParseError!Ip6Address {
668 switch (Unresolved.parse(buffer)) {
669 .success => |p| return .{
670 .bytes = p.bytes,
671 .port = port,
672 .interface = if (p.interface_name != null) return error.UnresolvedScope else .none,
673 },
674 else => return error.ParseFailed,
675 }
676 return .{ .ip6 = try Ip6Address.parse(buffer, port) };
677 }
678
679 pub const ResolveError = error{
680 /// If this is returned, more detailed diagnostics can be obtained by
681 /// calling the `Parsed.init` function.
682 ParseFailed,
683 /// The interface name is longer than the host operating system supports.
684 NameTooLong,
685 } || Interface.Name.ResolveError;
686
687 /// This function requires an `Io` parameter because it must query the operating
688 /// system to convert interface name to index. For example, in
689 /// "fe80::e0e:76ff:fed4:cf22%eno1", "eno1" must be resolved to an index by
690 /// creating a socket and then using an `ioctl` syscall.
691 pub fn resolve(io: Io, buffer: []const u8, port: u16) ResolveError!Ip6Address {
692 return switch (Unresolved.parse(buffer)) {
693 .success => |p| return .{
694 .bytes = p.bytes,
695 .port = port,
696 .interface = i: {
697 const text = p.interface_name orelse break :i .none;
698 const name: Interface.Name = try .fromSlice(text);
699 break :i try name.resolve(io);
700 },
701 },
702 else => return error.ParseFailed,
703 };
704 }
705
706 pub const FormatError = Io.Writer.Error || Unresolved.FromAddressError;
707
708 /// Includes the optional scope ("%foo" at the end).
709 ///
710 /// See `format` for an alternative that omits scopes and does
711 /// not require an `Io` parameter.
712 pub fn formatResolved(a: Ip6Address, io: Io, w: *Io.Writer) FormatError!void {
713 const u: Unresolved = try .fromAddress(io);
714 try w.print("[{f}]:{d}", .{ u, a.port });
715 }
716
717 /// See `formatResolved` for an alternative that additionally prints the optional
718 /// scope at the end of addresses and requires an `Io` parameter.
719 pub fn format(a: Ip6Address, w: *Io.Writer) Io.Writer.Error!void {
720 const u: Unresolved = .{
721 .bytes = a.bytes,
722 .interface_name = null,
723 };
724 try w.print("[{f}]:{d}", .{ u, a.port });
725 }
726
727 pub fn eql(a: Ip6Address, b: Ip6Address) bool {
728 return a.port == b.port and std.mem.eql(u8, &a.bytes, &b.bytes);
729 }
730
731 pub fn isMultiCast(a: Ip6Address) bool {
732 return a.bytes[0] == 0xff;
733 }
734
735 pub fn isLinkLocal(a: Ip6Address) bool {
736 const b = &a.bytes;
737 return b[0] == 0xfe and (b[1] & 0xc0) == 0x80;
738 }
739
740 pub fn isLoopBack(a: Ip6Address) bool {
741 const b = &a.bytes;
742 return b[0] == 0 and b[1] == 0 and
743 b[2] == 0 and
744 b[12] == 0 and b[13] == 0 and
745 b[14] == 0 and b[15] == 1;
746 }
747
748 pub fn isSiteLocal(a: Ip6Address) bool {
749 const b = &a.bytes;
750 return b[0] == 0xfe and (b[1] & 0xc0) == 0xc0;
751 }
752
753 pub fn policy(a: Ip6Address) *const Policy {
754 const b = &a.bytes;
755 for (&defined_policies) |*p| {
756 if (!std.mem.eql(u8, b[0..p.len], p.addr[0..p.len])) continue;
757 if ((b[p.len] & p.mask) != p.addr[p.len]) continue;
758 return p;
759 }
760 unreachable;
761 }
762
763 pub fn scope(a: Ip6Address) u8 {
764 if (isMultiCast(a)) return a.bytes[1] & 15;
765 if (isLinkLocal(a)) return 2;
766 if (isLoopBack(a)) return 2;
767 if (isSiteLocal(a)) return 5;
768 return 14;
769 }
770
771 const defined_policies = [_]Policy{
772 .{
773 .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01".*,
774 .len = 15,
775 .mask = 0xff,
776 .prec = 50,
777 .label = 0,
778 },
779 .{
780 .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00".*,
781 .len = 11,
782 .mask = 0xff,
783 .prec = 35,
784 .label = 4,
785 },
786 .{
787 .addr = "\x20\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
788 .len = 1,
789 .mask = 0xff,
790 .prec = 30,
791 .label = 2,
792 },
793 .{
794 .addr = "\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
795 .len = 3,
796 .mask = 0xff,
797 .prec = 5,
798 .label = 5,
799 },
800 .{
801 .addr = "\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
802 .len = 0,
803 .mask = 0xfe,
804 .prec = 3,
805 .label = 13,
806 },
807 // These are deprecated and/or returned to the address
808 // pool, so despite the RFC, treating them as special
809 // is probably wrong.
810 // { "", 11, 0xff, 1, 3 },
811 // { "\xfe\xc0", 1, 0xc0, 1, 11 },
812 // { "\x3f\xfe", 1, 0xff, 1, 12 },
813 // Last rule must match all addresses to stop loop.
814 .{
815 .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
816 .len = 0,
817 .mask = 0,
818 .prec = 40,
819 .label = 1,
820 },
821 };
822};
823
824pub const UnixAddress = struct {
825 path: []const u8,
826
827 pub const max_len = 108;
828
829 pub const InitError = error{NameTooLong};
830
831 pub fn init(p: []const u8) InitError!UnixAddress {
832 if (p.len > max_len) return error.NameTooLong;
833 return .{ .path = p };
834 }
835
836 pub const ListenError = error{
837 AddressFamilyUnsupported,
838 AddressInUse,
839 NetworkDown,
840 SystemResources,
841 SymLinkLoop,
842 FileNotFound,
843 NotDir,
844 ReadOnlyFileSystem,
845 ProcessFdQuotaExceeded,
846 SystemFdQuotaExceeded,
847 AccessDenied,
848 PermissionDenied,
849 AddressUnavailable,
850 } || Io.Cancelable || Io.UnexpectedError;
851
852 pub const ListenOptions = struct {
853 /// How many connections the kernel will accept on the application's behalf.
854 /// If more than this many connections pool in the kernel, clients will start
855 /// seeing "Connection refused".
856 kernel_backlog: u31 = default_kernel_backlog,
857 };
858
859 pub fn listen(ua: *const UnixAddress, io: Io, options: ListenOptions) ListenError!Server {
860 assert(ua.path.len <= max_len);
861 return .{ .socket = .{
862 .handle = try io.vtable.netListenUnix(io.userdata, ua, options),
863 .address = .{ .ip4 = .loopback(0) },
864 } };
865 }
866
867 pub const ConnectError = error{
868 SystemResources,
869 ProcessFdQuotaExceeded,
870 SystemFdQuotaExceeded,
871 AddressFamilyUnsupported,
872 ProtocolUnsupportedBySystem,
873 ProtocolUnsupportedByAddressFamily,
874 SocketModeUnsupported,
875 AccessDenied,
876 PermissionDenied,
877 SymLinkLoop,
878 FileNotFound,
879 NotDir,
880 ReadOnlyFileSystem,
881 WouldBlock,
882 NetworkDown,
883 } || Io.Cancelable || Io.UnexpectedError;
884
885 pub fn connect(ua: *const UnixAddress, io: Io) ConnectError!Stream {
886 assert(ua.path.len <= max_len);
887 return .{ .socket = .{
888 .handle = try io.vtable.netConnectUnix(io.userdata, ua),
889 .address = .{ .ip4 = .loopback(0) },
890 } };
891 }
892};
893
894pub const ReceiveFlags = packed struct(u8) {
895 oob: bool = false,
896 peek: bool = false,
897 trunc: bool = false,
898 _: u5 = 0,
899};
900
901pub const IncomingMessage = struct {
902 /// Populated by receive functions.
903 from: IpAddress,
904 /// Populated by receive functions, points into the caller-supplied buffer.
905 data: []u8,
906 /// Supplied by caller before calling receive functions; mutated by receive
907 /// functions.
908 control: []u8,
909 /// Populated by receive functions.
910 flags: Flags,
911
912 /// Useful for initializing before calling `receiveManyTimeout`.
913 pub const init: IncomingMessage = .{
914 .from = undefined,
915 .data = undefined,
916 .control = &.{},
917 .flags = undefined,
918 };
919
920 pub const Flags = packed struct(u8) {
921 /// indicates end-of-record; the data returned completed a record
922 /// (generally used with sockets of type SOCK_SEQPACKET).
923 eor: bool,
924 /// indicates that the trailing portion of a datagram was discarded
925 /// because the datagram was larger than the buffer supplied.
926 trunc: bool,
927 /// indicates that some control data was discarded due to lack of
928 /// space in the buffer for ancil‐ lary data.
929 ctrunc: bool,
930 /// indicates expedited or out-of-band data was received.
931 oob: bool,
932 /// indicates that no data was received but an extended error from the
933 /// socket error queue.
934 errqueue: bool,
935 _: u3 = 0,
936 };
937};
938
939pub const OutgoingMessage = struct {
940 address: *const IpAddress,
941 data_ptr: [*]const u8,
942 /// Initialized with how many bytes of `data_ptr` to send. After sending
943 /// succeeds, replaced with how many bytes were actually sent.
944 data_len: usize,
945 control: []const u8 = &.{},
946};
947
948pub const SendFlags = packed struct(u8) {
949 confirm: bool = false,
950 dont_route: bool = false,
951 eor: bool = false,
952 oob: bool = false,
953 fastopen: bool = false,
954 _: u3 = 0,
955};
956
957pub const Interface = struct {
958 /// Value 0 indicates `none`.
959 index: u32,
960
961 pub const none: Interface = .{ .index = 0 };
962
963 pub const Name = struct {
964 bytes: [max_len:0]u8,
965
966 pub const max_len = if (@TypeOf(std.posix.IFNAMESIZE) == void) 0 else std.posix.IFNAMESIZE - 1;
967
968 pub fn toSlice(n: *const Name) []const u8 {
969 return std.mem.sliceTo(&n.bytes, 0);
970 }
971
972 pub fn fromSlice(bytes: []const u8) error{NameTooLong}!Name {
973 if (bytes.len > max_len) return error.NameTooLong;
974 return .fromSliceUnchecked(bytes);
975 }
976
977 /// Asserts bytes.len fits in `max_len`.
978 pub fn fromSliceUnchecked(bytes: []const u8) Name {
979 assert(bytes.len <= max_len);
980 var result: Name = undefined;
981 @memcpy(result.bytes[0..bytes.len], bytes);
982 result.bytes[bytes.len] = 0;
983 return result;
984 }
985
986 pub const ResolveError = error{
987 InterfaceNotFound,
988 AccessDenied,
989 SystemResources,
990 } || Io.UnexpectedError || Io.Cancelable;
991
992 /// Corresponds to "if_nametoindex" in libc.
993 pub fn resolve(n: *const Name, io: Io) ResolveError!Interface {
994 return io.vtable.netInterfaceNameResolve(io.userdata, n);
995 }
996 };
997
998 pub const NameError = Io.UnexpectedError || Io.Cancelable;
999
1000 /// Asserts not `none`.
1001 ///
1002 /// Corresponds to "if_indextoname" in libc.
1003 pub fn name(i: Interface, io: Io) NameError!Name {
1004 assert(i.index != 0);
1005 return io.vtable.netInterfaceName(io.userdata, i);
1006 }
1007
1008 pub fn isNone(i: Interface) bool {
1009 return i.index == 0;
1010 }
1011};
1012
1013/// An open port with unspecified protocol.
1014pub const Socket = struct {
1015 handle: Handle,
1016 /// Contains the resolved ephemeral port number if requested.
1017 address: IpAddress,
1018
1019 pub const Mode = enum {
1020 /// Provides sequenced, reliable, two-way, connection-based byte
1021 /// streams. An out-of-band data transmission mechanism may be
1022 /// supported.
1023 stream,
1024 /// Supports datagrams (connectionless, unreliable messages of a fixed
1025 /// maximum length).
1026 dgram,
1027 /// Provides a sequenced, reliable, two-way connection-based data
1028 /// transmission path for datagrams of fixed maximum length; a consumer
1029 /// is required to read an entire packet with each input system call.
1030 seqpacket,
1031 /// Provides raw network protocol access.
1032 raw,
1033 /// Provides a reliable datagram layer that does not guarantee ordering.
1034 rdm,
1035 };
1036
1037 /// Underlying platform-defined type which may or may not be
1038 /// interchangeable with a file system file descriptor.
1039 pub const Handle = switch (native_os) {
1040 .windows => std.os.windows.ws2_32.SOCKET,
1041 else => std.posix.fd_t,
1042 };
1043
1044 /// Leaves `address` in a valid state.
1045 pub fn close(s: *const Socket, io: Io) void {
1046 io.vtable.netClose(io.userdata, s.handle);
1047 }
1048
1049 pub const SendError = error{
1050 /// The socket type requires that message be sent atomically, and the
1051 /// size of the message to be sent made this impossible. The message
1052 /// was not transmitted, or was partially transmitted.
1053 MessageOversize,
1054 /// The output queue for a network interface was full. This generally indicates that the
1055 /// interface has stopped sending, but may be caused by transient congestion. (Normally,
1056 /// this does not occur in Linux. Packets are just silently dropped when a device queue
1057 /// overflows.)
1058 ///
1059 /// This is also caused when there is not enough kernel memory available.
1060 SystemResources,
1061 /// No route to network.
1062 NetworkUnreachable,
1063 /// Network reached but no route to host.
1064 HostUnreachable,
1065 /// The local network interface used to reach the destination is offline.
1066 NetworkDown,
1067 /// The destination address is not listening. Can still occur for
1068 /// connectionless messages.
1069 ConnectionRefused,
1070 /// Operating system or protocol does not support the address family.
1071 AddressFamilyUnsupported,
1072 /// Another TCP Fast Open is already in progress.
1073 FastOpenAlreadyInProgress,
1074 /// Network session was unexpectedly closed by recipient.
1075 ConnectionResetByPeer,
1076 /// Local end has been shut down on a connection-oriented socket, or
1077 /// the socket was never connected.
1078 SocketUnconnected,
1079 /// An attempt was made to send to a network/broadcast address as
1080 /// though it was a unicast address.
1081 AccessDenied,
1082 } || Io.UnexpectedError || Io.Cancelable;
1083
1084 /// Transfers `data` to `dest`, connectionless, in one packet.
1085 pub fn send(s: *const Socket, io: Io, dest: *const IpAddress, data: []const u8) SendError!void {
1086 var message: OutgoingMessage = .{ .address = dest, .data_ptr = data.ptr, .data_len = data.len };
1087 const err, const n = io.vtable.netSend(io.userdata, s.handle, (&message)[0..1], .{});
1088 if (n != 1) return err.?;
1089 if (message.data_len != data.len) return error.MessageOversize;
1090 }
1091
1092 pub fn sendMany(s: *const Socket, io: Io, messages: []OutgoingMessage, flags: SendFlags) SendError!void {
1093 return io.vtable.netSend(io.userdata, s.handle, messages, flags);
1094 }
1095
1096 pub const ReceiveError = error{
1097 /// Insufficient memory or other resource internal to the operating system.
1098 SystemResources,
1099 /// Per-process limit on the number of open file descriptors has been reached.
1100 ProcessFdQuotaExceeded,
1101 /// System-wide limit on the total number of open files has been reached.
1102 SystemFdQuotaExceeded,
1103 /// Local end has been shut down on a connection-oriented socket, or
1104 /// the socket was never connected.
1105 SocketUnconnected,
1106 /// The socket type requires that message be sent atomically, and the
1107 /// size of the message to be sent made this impossible. The message
1108 /// was not transmitted, or was partially transmitted.
1109 MessageOversize,
1110 /// Network connection was unexpectedly closed by sender.
1111 ConnectionResetByPeer,
1112 /// The local network interface used to reach the destination is offline.
1113 NetworkDown,
1114 } || Io.UnexpectedError || Io.Cancelable;
1115
1116 /// Waits for data. Connectionless.
1117 ///
1118 /// See also:
1119 /// * `receiveTimeout`
1120 pub fn receive(s: *const Socket, io: Io, buffer: []u8) ReceiveError!IncomingMessage {
1121 var message: IncomingMessage = undefined;
1122 assert(1 == try io.vtable.netReceive(io.userdata, s.handle, (&message)[0..1], buffer, .{}, .none));
1123 return message;
1124 }
1125
1126 pub const ReceiveTimeoutError = ReceiveError || Io.Timeout.Error;
1127
1128 /// Waits for data. Connectionless.
1129 ///
1130 /// Returns `error.Timeout` if no message arrives early enough.
1131 ///
1132 /// See also:
1133 /// * `receive`
1134 /// * `receiveManyTimeout`
1135 pub fn receiveTimeout(
1136 s: *const Socket,
1137 io: Io,
1138 buffer: []u8,
1139 timeout: Io.Timeout,
1140 ) ReceiveTimeoutError!IncomingMessage {
1141 var message: IncomingMessage = undefined;
1142 assert(1 == try io.vtable.netReceive(io.userdata, s.handle, (&message)[0..1], buffer, .{}, timeout));
1143 return message;
1144 }
1145
1146 /// Waits until at least one message is delivered, possibly returning more
1147 /// than one message. Connectionless.
1148 ///
1149 /// Returns number of messages received, or `error.Timeout` if no message
1150 /// arrives early enough.
1151 ///
1152 /// See also:
1153 /// * `receive`
1154 /// * `receiveTimeout`
1155 pub fn receiveManyTimeout(
1156 s: *const Socket,
1157 io: Io,
1158 /// Function assumes each element has initialized `control` field.
1159 /// Initializing with `IncomingMessage.init` may be helpful.
1160 message_buffer: []IncomingMessage,
1161 data_buffer: []u8,
1162 flags: ReceiveFlags,
1163 timeout: Io.Timeout,
1164 ) struct { ?ReceiveTimeoutError, usize } {
1165 return io.vtable.netReceive(io.userdata, s.handle, message_buffer, data_buffer, flags, timeout);
1166 }
1167};
1168
1169/// An open socket connection with a network protocol that guarantees
1170/// sequencing, delivery, and prevents repetition. Typically TCP or UNIX domain
1171/// socket.
1172pub const Stream = struct {
1173 socket: Socket,
1174
1175 const max_iovecs_len = 8;
1176
1177 pub fn close(s: *const Stream, io: Io) void {
1178 io.vtable.netClose(io.userdata, s.socket.handle);
1179 }
1180
1181 pub const Reader = struct {
1182 io: Io,
1183 interface: Io.Reader,
1184 stream: Stream,
1185 err: ?Error,
1186
1187 pub const Error = error{
1188 SystemResources,
1189 ConnectionResetByPeer,
1190 Timeout,
1191 SocketUnconnected,
1192 /// The file descriptor does not hold the required rights to read
1193 /// from it.
1194 AccessDenied,
1195 NetworkDown,
1196 } || Io.Cancelable || Io.UnexpectedError;
1197
1198 pub fn init(stream: Stream, io: Io, buffer: []u8) Reader {
1199 return .{
1200 .io = io,
1201 .interface = .{
1202 .vtable = &.{
1203 .stream = streamImpl,
1204 .readVec = readVec,
1205 },
1206 .buffer = buffer,
1207 .seek = 0,
1208 .end = 0,
1209 },
1210 .stream = stream,
1211 .err = null,
1212 };
1213 }
1214
1215 fn streamImpl(io_r: *Io.Reader, io_w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
1216 const dest = limit.slice(try io_w.writableSliceGreedy(1));
1217 var data: [1][]u8 = .{dest};
1218 const n = try readVec(io_r, &data);
1219 io_w.advance(n);
1220 return n;
1221 }
1222
1223 fn readVec(io_r: *Io.Reader, data: [][]u8) Io.Reader.Error!usize {
1224 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
1225 const io = r.io;
1226 var iovecs_buffer: [max_iovecs_len][]u8 = undefined;
1227 const dest_n, const data_size = try io_r.writableVector(&iovecs_buffer, data);
1228 const dest = iovecs_buffer[0..dest_n];
1229 assert(dest[0].len > 0);
1230 const n = io.vtable.netRead(io.userdata, r.stream.socket.handle, dest) catch |err| {
1231 r.err = err;
1232 return error.ReadFailed;
1233 };
1234 if (n == 0) {
1235 return error.EndOfStream;
1236 }
1237 if (n > data_size) {
1238 r.interface.end += n - data_size;
1239 return data_size;
1240 }
1241 return n;
1242 }
1243 };
1244
1245 pub const Writer = struct {
1246 io: Io,
1247 interface: Io.Writer,
1248 stream: Stream,
1249 err: ?Error = null,
1250
1251 pub const Error = error{
1252 /// Another TCP Fast Open is already in progress.
1253 FastOpenAlreadyInProgress,
1254 /// Network session was unexpectedly closed by recipient.
1255 ConnectionResetByPeer,
1256 /// The output queue for a network interface was full. This generally indicates that the
1257 /// interface has stopped sending, but may be caused by transient congestion. (Normally,
1258 /// this does not occur in Linux. Packets are just silently dropped when a device queue
1259 /// overflows.)
1260 ///
1261 /// This is also caused when there is not enough kernel memory available.
1262 SystemResources,
1263 /// No route to network.
1264 NetworkUnreachable,
1265 /// Network reached but no route to host.
1266 HostUnreachable,
1267 /// The local network interface used to reach the destination is down.
1268 NetworkDown,
1269 /// The destination address is not listening.
1270 ConnectionRefused,
1271 /// The passed address didn't have the correct address family in its sa_family field.
1272 AddressFamilyUnsupported,
1273 /// Local end has been shut down on a connection-oriented socket, or
1274 /// the socket was never connected.
1275 SocketUnconnected,
1276 SocketNotBound,
1277 } || Io.UnexpectedError || Io.Cancelable;
1278
1279 pub fn init(stream: Stream, io: Io, buffer: []u8) Writer {
1280 return .{
1281 .io = io,
1282 .stream = stream,
1283 .interface = .{
1284 .vtable = &.{ .drain = drain },
1285 .buffer = buffer,
1286 },
1287 };
1288 }
1289
1290 fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
1291 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
1292 const io = w.io;
1293 const buffered = io_w.buffered();
1294 const handle = w.stream.socket.handle;
1295 const n = io.vtable.netWrite(io.userdata, handle, buffered, data, splat) catch |err| {
1296 w.err = err;
1297 return error.WriteFailed;
1298 };
1299 return io_w.consume(n);
1300 }
1301 };
1302
1303 pub fn reader(stream: Stream, io: Io, buffer: []u8) Reader {
1304 return .init(stream, io, buffer);
1305 }
1306
1307 pub fn writer(stream: Stream, io: Io, buffer: []u8) Writer {
1308 return .init(stream, io, buffer);
1309 }
1310};
1311
1312pub const Server = struct {
1313 socket: Socket,
1314
1315 pub fn deinit(s: *Server, io: Io) void {
1316 s.socket.close(io);
1317 s.* = undefined;
1318 }
1319
1320 pub const AcceptError = error{
1321 /// The per-process limit on the number of open file descriptors has been reached.
1322 ProcessFdQuotaExceeded,
1323 /// The system-wide limit on the total number of open files has been reached.
1324 SystemFdQuotaExceeded,
1325 /// Not enough free memory. This often means that the memory allocation is limited
1326 /// by the socket buffer limits, not by the system memory.
1327 SystemResources,
1328 /// The network subsystem has failed.
1329 NetworkDown,
1330 /// No connection is already queued and ready to be accepted, and
1331 /// the socket is configured as non-blocking.
1332 WouldBlock,
1333 /// An incoming connection was indicated, but was subsequently terminated by the
1334 /// remote peer prior to accepting the call.
1335 ConnectionAborted,
1336 /// Firewall rules forbid connection.
1337 BlockedByFirewall,
1338 ProtocolFailure,
1339 } || Io.UnexpectedError || Io.Cancelable;
1340
1341 /// Blocks until a client connects to the server.
1342 pub fn accept(s: *Server, io: Io) AcceptError!Stream {
1343 return io.vtable.netAccept(io.userdata, s.socket.handle);
1344 }
1345};
1346
1347test "parsing IPv6 addresses" {
1348 try testIp6Parse("fe80::e0e:76ff:fed4:cf22%eno1");
1349 try testIp6Parse("2001:db8::1");
1350 try testIp6ParseTransform("2001:db8::1", "2001:0db8:0000:0000:0000:0000:0000:0001");
1351 try testIp6Parse("::1");
1352 try testIp6Parse("::");
1353 try testIp6Parse("fe80::1");
1354 try testIp6Parse("fe80::abcd:ef12%3");
1355 try testIp6Parse("ff02::");
1356 try testIp6Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
1357}
1358
1359fn testIp6Parse(input: []const u8) !void {
1360 return testIp6ParseTransform(input, input);
1361}
1362
1363fn testIp6ParseTransform(expected: []const u8, input: []const u8) !void {
1364 const ua = switch (Ip6Address.Unresolved.parse(input)) {
1365 .success => |p| p,
1366 else => |x| {
1367 std.debug.print("failed to parse \"{s}\": {any}\n", .{ input, x });
1368 return error.TestFailed;
1369 },
1370 };
1371 var buffer: [100]u8 = undefined;
1372 const result = try std.fmt.bufPrint(&buffer, "{f}", .{ua});
1373 try std.testing.expectEqualStrings(expected, result);
1374}
1375
1376test {
1377 _ = HostName;
1378 _ = @import("net/test.zig");
1379}
lib/std/Io/net/HostName.zig created+433
......@@ -0,0 +1,433 @@
1//! An already-validated host name. A valid host name:
2//! * Has length less than or equal to `max_len`.
3//! * Is valid UTF-8.
4//! * Lacks ASCII characters other than alphanumeric, '-', and '.'.
5const HostName = @This();
6
7const builtin = @import("builtin");
8const native_os = builtin.os.tag;
9
10const std = @import("../../std.zig");
11const Io = std.Io;
12const IpAddress = Io.net.IpAddress;
13const Ip6Address = Io.net.Ip6Address;
14const assert = std.debug.assert;
15const Stream = Io.net.Stream;
16
17/// Externally managed memory. Already checked to be valid.
18bytes: []const u8,
19
20pub const max_len = 255;
21
22pub const ValidateError = error{
23 NameTooLong,
24 InvalidHostName,
25};
26
27pub fn validate(bytes: []const u8) ValidateError!void {
28 if (bytes.len > max_len) return error.NameTooLong;
29 if (!std.unicode.utf8ValidateSlice(bytes)) return error.InvalidHostName;
30 for (bytes) |byte| {
31 if (!std.ascii.isAscii(byte) or byte == '.' or byte == '-' or std.ascii.isAlphanumeric(byte)) {
32 continue;
33 }
34 return error.InvalidHostName;
35 }
36}
37
38pub fn init(bytes: []const u8) ValidateError!HostName {
39 try validate(bytes);
40 return .{ .bytes = bytes };
41}
42
43pub fn sameParentDomain(parent_host: HostName, child_host: HostName) bool {
44 const parent_bytes = parent_host.bytes;
45 const child_bytes = child_host.bytes;
46 if (!std.ascii.endsWithIgnoreCase(child_bytes, parent_bytes)) return false;
47 if (child_bytes.len == parent_bytes.len) return true;
48 if (parent_bytes.len > child_bytes.len) return false;
49 return child_bytes[child_bytes.len - parent_bytes.len - 1] == '.';
50}
51
52test sameParentDomain {
53 try std.testing.expect(!sameParentDomain(try .init("foo.com"), try .init("bar.com")));
54 try std.testing.expect(sameParentDomain(try .init("foo.com"), try .init("foo.com")));
55 try std.testing.expect(sameParentDomain(try .init("foo.com"), try .init("bar.foo.com")));
56 try std.testing.expect(!sameParentDomain(try .init("bar.foo.com"), try .init("foo.com")));
57}
58
59/// Domain names are case-insensitive (RFC 5890, Section 2.3.2.4)
60pub fn eql(a: HostName, b: HostName) bool {
61 return std.ascii.eqlIgnoreCase(a.bytes, b.bytes);
62}
63
64pub const LookupOptions = struct {
65 port: u16,
66 canonical_name_buffer: *[max_len]u8,
67 /// `null` means either.
68 family: ?IpAddress.Family = null,
69};
70
71pub const LookupError = error{
72 UnknownHostName,
73 ResolvConfParseFailed,
74 InvalidDnsARecord,
75 InvalidDnsAAAARecord,
76 InvalidDnsCnameRecord,
77 NameServerFailure,
78 /// Failed to open or read "/etc/hosts" or "/etc/resolv.conf".
79 DetectingNetworkConfigurationFailed,
80} || Io.Clock.Error || IpAddress.BindError || Io.Cancelable;
81
82pub const LookupResult = union(enum) {
83 address: IpAddress,
84 canonical_name: HostName,
85 end: LookupError!void,
86};
87
88/// Adds any number of `IpAddress` into resolved, exactly one canonical_name,
89/// and then always finishes by adding one `LookupResult.end` entry.
90///
91/// Guaranteed not to block if provided queue has capacity at least 16.
92pub fn lookup(
93 host_name: HostName,
94 io: Io,
95 resolved: *Io.Queue(LookupResult),
96 options: LookupOptions,
97) void {
98 return io.vtable.netLookup(io.userdata, host_name, resolved, options);
99}
100
101pub const ExpandError = error{InvalidDnsPacket} || ValidateError;
102
103/// Decompresses a DNS name.
104///
105/// Returns number of bytes consumed from `packet` starting at `i`,
106/// along with the expanded `HostName`.
107///
108/// Asserts `buffer` is has length at least `max_len`.
109pub fn expand(noalias packet: []const u8, start_i: usize, noalias dest_buffer: []u8) ExpandError!struct { usize, HostName } {
110 const dest = dest_buffer[0..max_len];
111
112 var i = start_i;
113 var dest_i: usize = 0;
114 var len: ?usize = null;
115
116 // Detect reference loop using an iteration counter.
117 for (0..packet.len / 2) |_| {
118 if (i >= packet.len) return error.InvalidDnsPacket;
119
120 const c = packet[i];
121 if ((c & 0xc0) != 0) {
122 if (i + 1 >= packet.len) return error.InvalidDnsPacket;
123 const j: usize = (@as(usize, c & 0x3F) << 8) | packet[i + 1];
124 if (j >= packet.len) return error.InvalidDnsPacket;
125 if (len == null) len = (i + 2) - start_i;
126 i = j;
127 } else if (c != 0) {
128 if (dest_i != 0) {
129 dest[dest_i] = '.';
130 dest_i += 1;
131 }
132 const label_len: usize = c;
133 if (i + 1 + label_len > packet.len) return error.InvalidDnsPacket;
134 if (dest_i + label_len + 1 > dest.len) return error.InvalidDnsPacket;
135 @memcpy(dest[dest_i..][0..label_len], packet[i + 1 ..][0..label_len]);
136 dest_i += label_len;
137 i += 1 + label_len;
138 } else {
139 dest[dest_i] = 0;
140 dest_i += 1;
141 return .{
142 len orelse i - start_i + 1,
143 try .init(dest[0..dest_i]),
144 };
145 }
146 }
147 return error.InvalidDnsPacket;
148}
149
150pub const DnsRecord = enum(u8) {
151 A = 1,
152 CNAME = 5,
153 AAAA = 28,
154 _,
155};
156
157pub const DnsResponse = struct {
158 bytes: []const u8,
159 bytes_index: u32,
160 answers_remaining: u16,
161
162 pub const Answer = struct {
163 rr: DnsRecord,
164 packet: []const u8,
165 data_off: u32,
166 data_len: u16,
167 };
168
169 pub const Error = error{InvalidDnsPacket};
170
171 pub fn init(r: []const u8) Error!DnsResponse {
172 if (r.len < 12) return error.InvalidDnsPacket;
173 if ((r[3] & 15) != 0) return .{ .bytes = r, .bytes_index = 3, .answers_remaining = 0 };
174 var i: u32 = 12;
175 var query_count = std.mem.readInt(u16, r[4..6], .big);
176 while (query_count != 0) : (query_count -= 1) {
177 while (i < r.len and r[i] -% 1 < 127) i += 1;
178 if (r.len - i < 6) return error.InvalidDnsPacket;
179 i = i + 5 + @intFromBool(r[i] != 0);
180 }
181 return .{
182 .bytes = r,
183 .bytes_index = i,
184 .answers_remaining = std.mem.readInt(u16, r[6..8], .big),
185 };
186 }
187
188 pub fn next(dr: *DnsResponse) Error!?Answer {
189 if (dr.answers_remaining == 0) return null;
190 dr.answers_remaining -= 1;
191 const r = dr.bytes;
192 var i = dr.bytes_index;
193 while (i < r.len and r[i] -% 1 < 127) i += 1;
194 if (r.len - i < 12) return error.InvalidDnsPacket;
195 i = i + 1 + @intFromBool(r[i] != 0);
196 const len = std.mem.readInt(u16, r[i + 8 ..][0..2], .big);
197 if (i + 10 + len > r.len) return error.InvalidDnsPacket;
198 defer dr.bytes_index = i + 10 + len;
199 return .{
200 .rr = @enumFromInt(r[i + 1]),
201 .packet = r,
202 .data_off = i + 10,
203 .data_len = len,
204 };
205 }
206};
207
208pub const ConnectError = LookupError || IpAddress.ConnectError;
209
210pub fn connect(
211 host_name: HostName,
212 io: Io,
213 port: u16,
214 options: IpAddress.ConnectOptions,
215) ConnectError!Stream {
216 var connect_many_buffer: [32]ConnectManyResult = undefined;
217 var connect_many_queue: Io.Queue(ConnectManyResult) = .init(&connect_many_buffer);
218
219 var connect_many = io.async(connectMany, .{ host_name, io, port, &connect_many_queue, options });
220 var saw_end = false;
221 defer {
222 connect_many.cancel(io);
223 if (!saw_end) while (true) switch (connect_many_queue.getOneUncancelable(io)) {
224 .connection => |loser| if (loser) |s| s.close(io) else |_| continue,
225 .end => break,
226 };
227 }
228
229 var aggregate_error: ConnectError = error.UnknownHostName;
230
231 while (connect_many_queue.getOne(io)) |result| switch (result) {
232 .connection => |connection| if (connection) |stream| return stream else |err| switch (err) {
233 error.SystemResources,
234 error.OptionUnsupported,
235 error.ProcessFdQuotaExceeded,
236 error.SystemFdQuotaExceeded,
237 error.Canceled,
238 => |e| return e,
239
240 error.WouldBlock => return error.Unexpected,
241
242 else => |e| aggregate_error = e,
243 },
244 .end => |end| {
245 saw_end = true;
246 try end;
247 return aggregate_error;
248 },
249 } else |err| switch (err) {
250 error.Canceled => |e| return e,
251 }
252}
253
254pub const ConnectManyResult = union(enum) {
255 connection: IpAddress.ConnectError!Stream,
256 end: ConnectError!void,
257};
258
259/// Asynchronously establishes a connection to all IP addresses associated with
260/// a host name, adding them to a results queue upon completion.
261pub fn connectMany(
262 host_name: HostName,
263 io: Io,
264 port: u16,
265 results: *Io.Queue(ConnectManyResult),
266 options: IpAddress.ConnectOptions,
267) void {
268 var canonical_name_buffer: [max_len]u8 = undefined;
269 var lookup_buffer: [32]HostName.LookupResult = undefined;
270 var lookup_queue: Io.Queue(LookupResult) = .init(&lookup_buffer);
271 var group: Io.Group = .init;
272 defer group.cancel(io);
273
274 group.async(io, lookup, .{ host_name, io, &lookup_queue, .{
275 .port = port,
276 .canonical_name_buffer = &canonical_name_buffer,
277 } });
278
279 while (lookup_queue.getOne(io)) |dns_result| switch (dns_result) {
280 .address => |address| group.async(io, enqueueConnection, .{ address, io, results, options }),
281 .canonical_name => continue,
282 .end => |lookup_result| {
283 group.wait(io);
284 results.putOneUncancelable(io, .{ .end = lookup_result });
285 return;
286 },
287 } else |err| switch (err) {
288 error.Canceled => |e| {
289 group.cancel(io);
290 results.putOneUncancelable(io, .{ .end = e });
291 },
292 }
293}
294
295fn enqueueConnection(
296 address: IpAddress,
297 io: Io,
298 queue: *Io.Queue(ConnectManyResult),
299 options: IpAddress.ConnectOptions,
300) void {
301 queue.putOneUncancelable(io, .{ .connection = address.connect(io, options) });
302}
303
304pub const ResolvConf = struct {
305 attempts: u32,
306 ndots: u32,
307 timeout_seconds: u32,
308 nameservers_buffer: [max_nameservers]IpAddress,
309 nameservers_len: usize,
310 search_buffer: [max_len]u8,
311 search_len: usize,
312
313 /// According to resolv.conf(5) there is a maximum of 3 nameservers in this
314 /// file.
315 pub const max_nameservers = 3;
316
317 /// Returns `error.StreamTooLong` if a line is longer than 512 bytes.
318 pub fn init(io: Io) !ResolvConf {
319 var rc: ResolvConf = .{
320 .nameservers_buffer = undefined,
321 .nameservers_len = 0,
322 .search_buffer = undefined,
323 .search_len = 0,
324 .ndots = 1,
325 .timeout_seconds = 5,
326 .attempts = 2,
327 };
328
329 const file = Io.File.openAbsolute(io, "/etc/resolv.conf", .{}) catch |err| switch (err) {
330 error.FileNotFound,
331 error.NotDir,
332 error.AccessDenied,
333 => {
334 try addNumeric(&rc, io, "127.0.0.1", 53);
335 return rc;
336 },
337
338 else => |e| return e,
339 };
340 defer file.close(io);
341
342 var line_buf: [512]u8 = undefined;
343 var file_reader = file.reader(io, &line_buf);
344 parse(&rc, io, &file_reader.interface) catch |err| switch (err) {
345 error.ReadFailed => return file_reader.err.?,
346 else => |e| return e,
347 };
348 return rc;
349 }
350
351 const Directive = enum { options, nameserver, domain, search };
352 const Option = enum { ndots, attempts, timeout };
353
354 pub fn parse(rc: *ResolvConf, io: Io, reader: *Io.Reader) !void {
355 while (reader.takeSentinel('\n')) |line_with_comment| {
356 const line = line: {
357 var split = std.mem.splitScalar(u8, line_with_comment, '#');
358 break :line split.first();
359 };
360 var line_it = std.mem.tokenizeAny(u8, line, " \t");
361
362 const token = line_it.next() orelse continue;
363 switch (std.meta.stringToEnum(Directive, token) orelse continue) {
364 .options => while (line_it.next()) |sub_tok| {
365 var colon_it = std.mem.splitScalar(u8, sub_tok, ':');
366 const name = colon_it.first();
367 const value_txt = colon_it.next() orelse continue;
368 const value = std.fmt.parseInt(u8, value_txt, 10) catch |err| switch (err) {
369 error.Overflow => 255,
370 error.InvalidCharacter => continue,
371 };
372 switch (std.meta.stringToEnum(Option, name) orelse continue) {
373 .ndots => rc.ndots = @min(value, 15),
374 .attempts => rc.attempts = @min(value, 10),
375 .timeout => rc.timeout_seconds = @min(value, 60),
376 }
377 },
378 .nameserver => {
379 const ip_txt = line_it.next() orelse continue;
380 try addNumeric(rc, io, ip_txt, 53);
381 },
382 .domain, .search => {
383 const rest = line_it.rest();
384 @memcpy(rc.search_buffer[0..rest.len], rest);
385 rc.search_len = rest.len;
386 },
387 }
388 } else |err| switch (err) {
389 error.EndOfStream => if (reader.bufferedLen() != 0) return error.EndOfStream,
390 else => |e| return e,
391 }
392
393 if (rc.nameservers_len == 0) {
394 try addNumeric(rc, io, "127.0.0.1", 53);
395 }
396 }
397
398 fn addNumeric(rc: *ResolvConf, io: Io, name: []const u8, port: u16) !void {
399 if (rc.nameservers_len < rc.nameservers_buffer.len) {
400 rc.nameservers_buffer[rc.nameservers_len] = try .resolve(io, name, port);
401 rc.nameservers_len += 1;
402 }
403 }
404
405 pub fn nameservers(rc: *const ResolvConf) []const IpAddress {
406 return rc.nameservers_buffer[0..rc.nameservers_len];
407 }
408};
409
410test ResolvConf {
411 const input =
412 \\# Generated by resolvconf
413 \\nameserver 1.0.0.1
414 \\nameserver 1.1.1.1
415 \\nameserver fe80::e0e:76ff:fed4:cf22
416 \\options edns0
417 \\
418 ;
419 var reader: Io.Reader = .fixed(input);
420
421 var rc: ResolvConf = .{
422 .nameservers_buffer = undefined,
423 .nameservers_len = 0,
424 .search_buffer = undefined,
425 .search_len = 0,
426 .ndots = 1,
427 .timeout_seconds = 5,
428 .attempts = 2,
429 };
430
431 try rc.parse(std.testing.io, &reader);
432 try std.testing.expectEqual(3, rc.nameservers().len);
433}
lib/std/Io/net/test.zig created+345
......@@ -0,0 +1,345 @@
1const builtin = @import("builtin");
2
3const std = @import("std");
4const Io = std.Io;
5const net = std.Io.net;
6const mem = std.mem;
7const testing = std.testing;
8
9test "parse and render IP addresses at comptime" {
10 comptime {
11 const ipv6addr = net.IpAddress.parse("::1", 0) catch unreachable;
12 try testing.expectFmt("[::1]:0", "{f}", .{ipv6addr});
13
14 const ipv4addr = net.IpAddress.parse("127.0.0.1", 0) catch unreachable;
15 try testing.expectFmt("127.0.0.1:0", "{f}", .{ipv4addr});
16
17 try testing.expectError(error.ParseFailed, net.IpAddress.parse("::123.123.123.123", 0));
18 try testing.expectError(error.ParseFailed, net.IpAddress.parse("127.01.0.1", 0));
19 }
20}
21
22test "format IPv6 address with no zero runs" {
23 const addr = try net.IpAddress.parseIp6("2001:db8:1:2:3:4:5:6", 0);
24 try testing.expectFmt("[2001:db8:1:2:3:4:5:6]:0", "{f}", .{addr});
25}
26
27test "parse IPv6 addresses and check compressed form" {
28 try testing.expectFmt("[2001:db8::1:0:0:2]:0", "{f}", .{
29 try net.IpAddress.parseIp6("2001:0db8:0000:0000:0001:0000:0000:0002", 0),
30 });
31 try testing.expectFmt("[2001:db8::1:2]:0", "{f}", .{
32 try net.IpAddress.parseIp6("2001:0db8:0000:0000:0000:0000:0001:0002", 0),
33 });
34 try testing.expectFmt("[2001:db8:1:0:1::2]:0", "{f}", .{
35 try net.IpAddress.parseIp6("2001:0db8:0001:0000:0001:0000:0000:0002", 0),
36 });
37}
38
39test "parse IPv6 address, check raw bytes" {
40 const expected_raw: [16]u8 = .{
41 0x20, 0x01, 0x0d, 0xb8, // 2001:db8
42 0x00, 0x00, 0x00, 0x00, // :0000:0000
43 0x00, 0x01, 0x00, 0x00, // :0001:0000
44 0x00, 0x00, 0x00, 0x02, // :0000:0002
45 };
46 const addr = try net.IpAddress.parseIp6("2001:db8:0000:0000:0001:0000:0000:0002", 0);
47 try testing.expectEqualSlices(u8, &expected_raw, &addr.ip6.bytes);
48}
49
50test "parse and render IPv6 addresses" {
51 try testParseAndRenderIp6Address("FF01:0:0:0:0:0:0:FB", "ff01::fb");
52 try testParseAndRenderIp6Address("FF01::Fb", "ff01::fb");
53 try testParseAndRenderIp6Address("::1", "::1");
54 try testParseAndRenderIp6Address("::", "::");
55 try testParseAndRenderIp6Address("1::", "1::");
56 try testParseAndRenderIp6Address("2001:db8::", "2001:db8::");
57 try testParseAndRenderIp6Address("::1234:5678", "::1234:5678");
58 try testParseAndRenderIp6Address("2001:db8::1234:5678", "2001:db8::1234:5678");
59 try testParseAndRenderIp6Address("FF01::FB%1234", "ff01::fb%1234");
60 try testParseAndRenderIp6Address("::ffff:123.5.123.5", "::ffff:123.5.123.5");
61 try testParseAndRenderIp6Address("ff01::fb%12345678901234", "ff01::fb%12345678901234");
62}
63
64fn testParseAndRenderIp6Address(input: []const u8, expected_output: []const u8) !void {
65 var buffer: [100]u8 = undefined;
66 const parsed = net.Ip6Address.Unresolved.parse(input);
67 const actual_printed = try std.fmt.bufPrint(&buffer, "{f}", .{parsed.success});
68 try testing.expectEqualStrings(expected_output, actual_printed);
69}
70
71test "IPv6 address parse failures" {
72 try testing.expectError(error.ParseFailed, net.IpAddress.parseIp6(":::", 0));
73
74 const Unresolved = net.Ip6Address.Unresolved;
75
76 try testing.expectEqual(Unresolved.Parsed{ .invalid_byte = 2 }, Unresolved.parse(":::"));
77 try testing.expectEqual(Unresolved.Parsed{ .overflow = 4 }, Unresolved.parse("FF001::FB"));
78 try testing.expectEqual(Unresolved.Parsed{ .invalid_byte = 9 }, Unresolved.parse("FF01::Fb:zig"));
79 try testing.expectEqual(Unresolved.Parsed{ .junk_after_end = 19 }, Unresolved.parse("FF01:0:0:0:0:0:0:FB:"));
80 try testing.expectEqual(Unresolved.Parsed.incomplete, Unresolved.parse("FF01:"));
81 try testing.expectEqual(Unresolved.Parsed{ .invalid_byte = 5 }, Unresolved.parse("::123.123.123.123"));
82 try testing.expectEqual(Unresolved.Parsed.incomplete, Unresolved.parse("1"));
83 try testing.expectEqual(Unresolved.Parsed.incomplete, Unresolved.parse("ff01::fb%"));
84}
85
86test "invalid but parseable IPv6 scope ids" {
87 const io = testing.io;
88
89 if (builtin.os.tag != .linux and comptime !builtin.os.tag.isDarwin()) {
90 return error.SkipZigTest; // TODO
91 }
92
93 try testing.expectError(error.InterfaceNotFound, net.IpAddress.resolveIp6(io, "ff01::fb%123s45678901234", 0));
94}
95
96test "parse and render IPv4 addresses" {
97 var buffer: [18]u8 = undefined;
98 for ([_][]const u8{
99 "0.0.0.0",
100 "255.255.255.255",
101 "1.2.3.4",
102 "123.255.0.91",
103 "127.0.0.1",
104 }) |ip| {
105 const addr = net.IpAddress.parseIp4(ip, 0) catch unreachable;
106 var newIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr}) catch unreachable;
107 try testing.expect(std.mem.eql(u8, ip, newIp[0 .. newIp.len - 2]));
108 }
109
110 try testing.expectError(error.Overflow, net.IpAddress.parseIp4("256.0.0.1", 0));
111 try testing.expectError(error.InvalidCharacter, net.IpAddress.parseIp4("x.0.0.1", 0));
112 try testing.expectError(error.InvalidEnd, net.IpAddress.parseIp4("127.0.0.1.1", 0));
113 try testing.expectError(error.Incomplete, net.IpAddress.parseIp4("127.0.0.", 0));
114 try testing.expectError(error.InvalidCharacter, net.IpAddress.parseIp4("100..0.1", 0));
115 try testing.expectError(error.NonCanonical, net.IpAddress.parseIp4("127.01.0.1", 0));
116}
117
118test "resolve DNS" {
119 if (builtin.os.tag == .wasi) return error.SkipZigTest;
120
121 const io = testing.io;
122
123 // Resolve localhost, this should not fail.
124 {
125 const localhost_v4 = try net.IpAddress.parse("127.0.0.1", 80);
126 const localhost_v6 = try net.IpAddress.parse("::2", 80);
127
128 var canonical_name_buffer: [net.HostName.max_len]u8 = undefined;
129 var results_buffer: [32]net.HostName.LookupResult = undefined;
130 var results: Io.Queue(net.HostName.LookupResult) = .init(&results_buffer);
131
132 net.HostName.lookup(try .init("localhost"), io, &results, .{
133 .port = 80,
134 .canonical_name_buffer = &canonical_name_buffer,
135 });
136
137 var addresses_found: usize = 0;
138
139 while (results.getOne(io)) |result| switch (result) {
140 .address => |address| {
141 if (address.eql(&localhost_v4) or address.eql(&localhost_v6))
142 addresses_found += 1;
143 },
144 .canonical_name => |canonical_name| try testing.expectEqualStrings("localhost", canonical_name.bytes),
145 .end => |end| {
146 try end;
147 break;
148 },
149 } else |err| return err;
150
151 try testing.expect(addresses_found != 0);
152 }
153
154 {
155 // The tests are required to work even when there is no Internet connection,
156 // so some of these errors we must accept and skip the test.
157 var canonical_name_buffer: [net.HostName.max_len]u8 = undefined;
158 var results_buffer: [16]net.HostName.LookupResult = undefined;
159 var results: Io.Queue(net.HostName.LookupResult) = .init(&results_buffer);
160
161 net.HostName.lookup(try .init("example.com"), io, &results, .{
162 .port = 80,
163 .canonical_name_buffer = &canonical_name_buffer,
164 });
165
166 while (results.getOne(io)) |result| switch (result) {
167 .address => {},
168 .canonical_name => {},
169 .end => |end| {
170 end catch |err| switch (err) {
171 error.UnknownHostName => return error.SkipZigTest,
172 error.NameServerFailure => return error.SkipZigTest,
173 else => return err,
174 };
175 break;
176 },
177 } else |err| return err;
178 }
179}
180
181test "listen on a port, send bytes, receive bytes" {
182 if (builtin.single_threaded) return error.SkipZigTest;
183 if (builtin.os.tag == .wasi) return error.SkipZigTest;
184
185 const io = testing.io;
186
187 // Try only the IPv4 variant as some CI builders have no IPv6 localhost
188 // configured.
189 const localhost: net.IpAddress = .{ .ip4 = .loopback(0) };
190
191 var server = try localhost.listen(io, .{});
192 defer server.deinit(io);
193
194 const S = struct {
195 fn clientFn(server_address: net.IpAddress) !void {
196 var stream = try server_address.connect(io, .{ .mode = .stream });
197 defer stream.close(io);
198
199 var stream_writer = stream.writer(io, &.{});
200 try stream_writer.interface.writeAll("Hello world!");
201 }
202 };
203
204 const t = try std.Thread.spawn(.{}, S.clientFn, .{server.socket.address});
205 defer t.join();
206
207 var stream = try server.accept(io);
208 defer stream.close(io);
209 var buf: [16]u8 = undefined;
210 var stream_reader = stream.reader(io, &.{});
211 const n = try stream_reader.interface.readSliceShort(&buf);
212
213 try testing.expectEqual(@as(usize, 12), n);
214 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
215}
216
217test "listen on an in use port" {
218 if (builtin.os.tag != .linux and comptime !builtin.os.tag.isDarwin() and builtin.os.tag != .windows) {
219 // TODO build abstractions for other operating systems
220 return error.SkipZigTest;
221 }
222
223 const io = testing.io;
224
225 const localhost: net.IpAddress = .{ .ip4 = .loopback(0) };
226
227 var server1 = try localhost.listen(io, .{ .reuse_address = true });
228 defer server1.deinit(io);
229
230 var server2 = try server1.socket.address.listen(io, .{ .reuse_address = true });
231 defer server2.deinit(io);
232}
233
234fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyerror!void {
235 if (builtin.os.tag == .wasi) return error.SkipZigTest;
236
237 const connection = try net.tcpConnectToHost(allocator, name, port);
238 defer connection.close();
239
240 var buf: [100]u8 = undefined;
241 const len = try connection.read(&buf);
242 const msg = buf[0..len];
243 try testing.expect(mem.eql(u8, msg, "hello from server\n"));
244}
245
246fn testClient(addr: net.IpAddress) anyerror!void {
247 if (builtin.os.tag == .wasi) return error.SkipZigTest;
248
249 const socket_file = try net.tcpConnectToAddress(addr);
250 defer socket_file.close();
251
252 var buf: [100]u8 = undefined;
253 const len = try socket_file.read(&buf);
254 const msg = buf[0..len];
255 try testing.expect(mem.eql(u8, msg, "hello from server\n"));
256}
257
258fn testServer(server: *net.Server) anyerror!void {
259 if (builtin.os.tag == .wasi) return error.SkipZigTest;
260
261 const io = testing.io;
262
263 var stream = try server.accept(io);
264 var writer = stream.writer(io, &.{});
265 try writer.interface.print("hello from server\n", .{});
266}
267
268test "listen on a unix socket, send bytes, receive bytes" {
269 if (builtin.single_threaded) return error.SkipZigTest;
270 if (!net.has_unix_sockets) return error.SkipZigTest;
271
272 const io = testing.io;
273
274 const socket_path = try generateFileName("socket.unix");
275 defer testing.allocator.free(socket_path);
276
277 const socket_addr = try net.UnixAddress.init(socket_path);
278 defer std.fs.cwd().deleteFile(socket_path) catch {};
279
280 var server = try socket_addr.listen(io, .{});
281 defer server.socket.close(io);
282
283 const S = struct {
284 fn clientFn(path: []const u8) !void {
285 const server_path: net.UnixAddress = try .init(path);
286 var stream = try server_path.connect(io);
287 defer stream.close(io);
288
289 var stream_writer = stream.writer(io, &.{});
290 try stream_writer.interface.writeAll("Hello world!");
291 }
292 };
293
294 const t = try std.Thread.spawn(.{}, S.clientFn, .{socket_path});
295 defer t.join();
296
297 var stream = try server.accept(io);
298 defer stream.close(io);
299 var buf: [16]u8 = undefined;
300 var stream_reader = stream.reader(io, &.{});
301 const n = try stream_reader.interface.readSliceShort(&buf);
302
303 try testing.expectEqual(@as(usize, 12), n);
304 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
305}
306
307fn generateFileName(base_name: []const u8) ![]const u8 {
308 const random_bytes_count = 12;
309 const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count);
310 var random_bytes: [12]u8 = undefined;
311 std.crypto.random.bytes(&random_bytes);
312 var sub_path: [sub_path_len]u8 = undefined;
313 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
314 return std.fmt.allocPrint(testing.allocator, "{s}-{s}", .{ sub_path[0..], base_name });
315}
316
317test "non-blocking tcp server" {
318 if (builtin.os.tag == .wasi) return error.SkipZigTest;
319 if (true) {
320 // https://github.com/ziglang/zig/issues/18315
321 return error.SkipZigTest;
322 }
323
324 const io = testing.io;
325
326 const localhost: net.IpAddress = .{ .ip4 = .loopback(0) };
327 var server = localhost.listen(io, .{ .force_nonblocking = true });
328 defer server.deinit(io);
329
330 const accept_err = server.accept(io);
331 try testing.expectError(error.WouldBlock, accept_err);
332
333 const socket_file = try net.tcpConnectToAddress(server.socket.address);
334 defer socket_file.close();
335
336 var stream = try server.accept(io);
337 defer stream.close(io);
338 var writer = stream.writer(io, .{});
339 try writer.interface.print("hello from server\n", .{});
340
341 var buf: [100]u8 = undefined;
342 const len = try socket_file.read(&buf);
343 const msg = buf[0..len];
344 try testing.expect(mem.eql(u8, msg, "hello from server\n"));
345}
lib/std/Io/test.zig+105-18
......@@ -1,21 +1,28 @@
1const builtin = @import("builtin");
2const native_endian = builtin.cpu.arch.endian();
3
14const std = @import("std");
2const DefaultPrng = std.Random.DefaultPrng;
5const Io = std.Io;
6const testing = std.testing;
37const expect = std.testing.expect;
48const expectEqual = std.testing.expectEqual;
59const expectError = std.testing.expectError;
10const DefaultPrng = std.Random.DefaultPrng;
611const mem = std.mem;
712const fs = std.fs;
813const File = std.fs.File;
9const native_endian = @import("builtin").target.cpu.arch.endian();
14const assert = std.debug.assert;
1015
1116const tmpDir = std.testing.tmpDir;
1217
1318test "write a file, read it, then delete it" {
19 const io = testing.io;
20
1421 var tmp = tmpDir(.{});
1522 defer tmp.cleanup();
1623
1724 var data: [1024]u8 = undefined;
18 var prng = DefaultPrng.init(std.testing.random_seed);
25 var prng = DefaultPrng.init(testing.random_seed);
1926 const random = prng.random();
2027 random.bytes(data[0..]);
2128 const tmp_file_name = "temp_test_file.txt";
......@@ -45,9 +52,9 @@ test "write a file, read it, then delete it" {
4552 try expectEqual(expected_file_size, file_size);
4653
4754 var file_buffer: [1024]u8 = undefined;
48 var file_reader = file.reader(&file_buffer);
49 const contents = try file_reader.interface.allocRemaining(std.testing.allocator, .limited(2 * 1024));
50 defer std.testing.allocator.free(contents);
55 var file_reader = file.reader(io, &file_buffer);
56 const contents = try file_reader.interface.allocRemaining(testing.allocator, .limited(2 * 1024));
57 defer testing.allocator.free(contents);
5158
5259 try expect(mem.eql(u8, contents[0.."begin".len], "begin"));
5360 try expect(mem.eql(u8, contents["begin".len .. contents.len - "end".len], &data));
......@@ -89,18 +96,18 @@ test "setEndPos" {
8996 defer file.close();
9097
9198 // Verify that the file size changes and the file offset is not moved
92 try std.testing.expect((try file.getEndPos()) == 0);
93 try std.testing.expect((try file.getPos()) == 0);
99 try expect((try file.getEndPos()) == 0);
100 try expect((try file.getPos()) == 0);
94101 try file.setEndPos(8192);
95 try std.testing.expect((try file.getEndPos()) == 8192);
96 try std.testing.expect((try file.getPos()) == 0);
102 try expect((try file.getEndPos()) == 8192);
103 try expect((try file.getPos()) == 0);
97104 try file.seekTo(100);
98105 try file.setEndPos(4096);
99 try std.testing.expect((try file.getEndPos()) == 4096);
100 try std.testing.expect((try file.getPos()) == 100);
106 try expect((try file.getEndPos()) == 4096);
107 try expect((try file.getPos()) == 100);
101108 try file.setEndPos(0);
102 try std.testing.expect((try file.getEndPos()) == 0);
103 try std.testing.expect((try file.getPos()) == 100);
109 try expect((try file.getEndPos()) == 0);
110 try expect((try file.getPos()) == 100);
104111}
105112
106113test "updateTimes" {
......@@ -114,10 +121,90 @@ test "updateTimes" {
114121 const stat_old = try file.stat();
115122 // Set atime and mtime to 5s before
116123 try file.updateTimes(
117 stat_old.atime - 5 * std.time.ns_per_s,
118 stat_old.mtime - 5 * std.time.ns_per_s,
124 stat_old.atime.subDuration(.fromSeconds(5)),
125 stat_old.mtime.subDuration(.fromSeconds(5)),
119126 );
120127 const stat_new = try file.stat();
121 try expect(stat_new.atime < stat_old.atime);
122 try expect(stat_new.mtime < stat_old.mtime);
128 try expect(stat_new.atime.nanoseconds < stat_old.atime.nanoseconds);
129 try expect(stat_new.mtime.nanoseconds < stat_old.mtime.nanoseconds);
130}
131
132test "Group" {
133 const io = testing.io;
134
135 var group: Io.Group = .init;
136 var results: [2]usize = undefined;
137
138 group.async(io, count, .{ 1, 10, &results[0] });
139 group.async(io, count, .{ 20, 30, &results[1] });
140
141 group.wait(io);
142
143 try testing.expectEqualSlices(usize, &.{ 45, 245 }, &results);
144}
145
146fn count(a: usize, b: usize, result: *usize) void {
147 var sum: usize = 0;
148 for (a..b) |i| {
149 sum += i;
150 }
151 result.* = sum;
152}
153
154test "Group cancellation" {
155 const io = testing.io;
156
157 var group: Io.Group = .init;
158 var results: [2]usize = undefined;
159
160 group.async(io, sleep, .{ io, &results[0] });
161 group.async(io, sleep, .{ io, &results[1] });
162
163 group.cancel(io);
164
165 try testing.expectEqualSlices(usize, &.{ 1, 1 }, &results);
166}
167
168fn sleep(io: Io, result: *usize) void {
169 // TODO when cancellation race bug is fixed, make this timeout much longer so that
170 // it causes the unit test to be failed if not cancelled.
171 io.sleep(.fromMilliseconds(1), .awake) catch {};
172 result.* = 1;
173}
174
175test "select" {
176 const io = testing.io;
177
178 var queue: Io.Queue(u8) = .init(&.{});
179
180 var get_a = io.concurrent(Io.Queue(u8).getOne, .{ &queue, io }) catch |err| switch (err) {
181 error.ConcurrencyUnavailable => {
182 try testing.expect(builtin.single_threaded);
183 return;
184 },
185 };
186 defer if (get_a.cancel(io)) |_| {} else |_| @panic("fail");
187
188 var get_b = try io.concurrent(Io.Queue(u8).getOne, .{ &queue, io });
189 defer if (get_b.cancel(io)) |_| {} else |_| @panic("fail");
190
191 var timeout = io.async(Io.sleep, .{ io, .fromMilliseconds(1), .awake });
192 defer timeout.cancel(io) catch {};
193
194 switch (try io.select(.{
195 .get_a = &get_a,
196 .get_b = &get_b,
197 .timeout = &timeout,
198 })) {
199 .get_a => return error.TestFailure,
200 .get_b => return error.TestFailure,
201 .timeout => {
202 // Unblock the queues to avoid making this unit test depend on
203 // cancellation.
204 queue.putOneUncancelable(io, 1);
205 queue.putOneUncancelable(io, 1);
206 try testing.expectEqual(1, try get_a.await(io));
207 try testing.expectEqual(1, try get_b.await(io));
208 },
209 }
123210}
lib/std/Progress.zig+5-7
......@@ -392,7 +392,7 @@ var global_progress: Progress = .{
392392 .terminal = undefined,
393393 .terminal_mode = .off,
394394 .update_thread = null,
395 .redraw_event = .{},
395 .redraw_event = .unset,
396396 .refresh_rate_ns = undefined,
397397 .initial_delay_ns = undefined,
398398 .rows = 0,
......@@ -493,7 +493,7 @@ pub fn start(options: Options) Node {
493493 .mask = posix.sigemptyset(),
494494 .flags = (posix.SA.SIGINFO | posix.SA.RESTART),
495495 };
496 posix.sigaction(posix.SIG.WINCH, &act, null);
496 posix.sigaction(.WINCH, &act, null);
497497 }
498498
499499 if (switch (global_progress.terminal_mode) {
......@@ -523,9 +523,7 @@ pub fn setStatus(new_status: Status) void {
523523
524524/// Returns whether a resize is needed to learn the terminal size.
525525fn wait(timeout_ns: u64) bool {
526 const resize_flag = if (global_progress.redraw_event.timedWait(timeout_ns)) |_|
527 true
528 else |err| switch (err) {
526 const resize_flag = if (global_progress.redraw_event.timedWait(timeout_ns)) |_| true else |err| switch (err) {
529527 error.Timeout => false,
530528 };
531529 global_progress.redraw_event.reset();
......@@ -1537,10 +1535,10 @@ fn maybeUpdateSize(resize_flag: bool) void {
15371535 }
15381536}
15391537
1540fn handleSigWinch(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
1538fn handleSigWinch(sig: posix.SIG, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
15411539 _ = info;
15421540 _ = ctx_ptr;
1543 assert(sig == posix.SIG.WINCH);
1541 assert(sig == .WINCH);
15441542 global_progress.redraw_event.set();
15451543}
15461544
lib/std/Random.zig+6
......@@ -58,6 +58,12 @@ pub fn bytes(r: Random, buf: []u8) void {
5858 r.fillFn(r.ptr, buf);
5959}
6060
61pub fn array(r: Random, comptime E: type, comptime N: usize) [N]E {
62 var result: [N]E = undefined;
63 bytes(r, &result);
64 return result;
65}
66
6167pub fn boolean(r: Random) bool {
6268 return r.int(u1) != 0;
6369}
lib/std/Target/Query.zig+7-5
......@@ -612,6 +612,8 @@ fn versionEqualOpt(a: ?SemanticVersion, b: ?SemanticVersion) bool {
612612}
613613
614614test parse {
615 const io = std.testing.io;
616
615617 if (builtin.target.isGnuLibC()) {
616618 var query = try Query.parse(.{});
617619 query.setGnuLibCVersion(2, 1, 1);
......@@ -654,7 +656,7 @@ test parse {
654656 .arch_os_abi = "x86_64-linux-gnu",
655657 .cpu_features = "x86_64-sse-sse2-avx-cx8",
656658 });
657 const target = try std.zig.system.resolveTargetQuery(query);
659 const target = try std.zig.system.resolveTargetQuery(io, query);
658660
659661 try std.testing.expect(target.os.tag == .linux);
660662 try std.testing.expect(target.abi == .gnu);
......@@ -679,7 +681,7 @@ test parse {
679681 .arch_os_abi = "arm-linux-musleabihf",
680682 .cpu_features = "generic+v8a",
681683 });
682 const target = try std.zig.system.resolveTargetQuery(query);
684 const target = try std.zig.system.resolveTargetQuery(io, query);
683685
684686 try std.testing.expect(target.os.tag == .linux);
685687 try std.testing.expect(target.abi == .musleabihf);
......@@ -696,7 +698,7 @@ test parse {
696698 .arch_os_abi = "aarch64-linux.3.10...4.4.1-gnu.2.27",
697699 .cpu_features = "generic+v8a",
698700 });
699 const target = try std.zig.system.resolveTargetQuery(query);
701 const target = try std.zig.system.resolveTargetQuery(io, query);
700702
701703 try std.testing.expect(target.cpu.arch == .aarch64);
702704 try std.testing.expect(target.os.tag == .linux);
......@@ -719,7 +721,7 @@ test parse {
719721 const query = try Query.parse(.{
720722 .arch_os_abi = "aarch64-linux.3.10...4.4.1-android.30",
721723 });
722 const target = try std.zig.system.resolveTargetQuery(query);
724 const target = try std.zig.system.resolveTargetQuery(io, query);
723725
724726 try std.testing.expect(target.cpu.arch == .aarch64);
725727 try std.testing.expect(target.os.tag == .linux);
......@@ -740,7 +742,7 @@ test parse {
740742 const query = try Query.parse(.{
741743 .arch_os_abi = "x86-windows.xp...win8-msvc",
742744 });
743 const target = try std.zig.system.resolveTargetQuery(query);
745 const target = try std.zig.system.resolveTargetQuery(io, query);
744746
745747 try std.testing.expect(target.cpu.arch == .x86);
746748 try std.testing.expect(target.os.tag == .windows);
lib/std/Thread.zig+235-67
......@@ -10,9 +10,9 @@ const target = builtin.target;
1010const native_os = builtin.os.tag;
1111const posix = std.posix;
1212const windows = std.os.windows;
13const testing = std.testing;
1314
1415pub const Futex = @import("Thread/Futex.zig");
15pub const ResetEvent = @import("Thread/ResetEvent.zig");
1616pub const Mutex = @import("Thread/Mutex.zig");
1717pub const Semaphore = @import("Thread/Semaphore.zig");
1818pub const Condition = @import("Thread/Condition.zig");
......@@ -22,81 +22,122 @@ pub const WaitGroup = @import("Thread/WaitGroup.zig");
2222
2323pub const use_pthreads = native_os != .windows and native_os != .wasi and builtin.link_libc;
2424
25/// Spurious wakeups are possible and no precision of timing is guaranteed.
26pub fn sleep(nanoseconds: u64) void {
27 if (builtin.os.tag == .windows) {
28 const big_ms_from_ns = nanoseconds / std.time.ns_per_ms;
29 const ms = math.cast(windows.DWORD, big_ms_from_ns) orelse math.maxInt(windows.DWORD);
30 windows.kernel32.Sleep(ms);
31 return;
25/// A thread-safe logical boolean value which can be `set` and `unset`.
26///
27/// It can also block threads until the value is set with cancelation via timed
28/// waits. Statically initializable; four bytes on all targets.
29pub const ResetEvent = enum(u32) {
30 unset = 0,
31 waiting = 1,
32 is_set = 2,
33
34 /// Returns whether the logical boolean is `set`.
35 ///
36 /// Once `reset` is called, this returns false until the next `set`.
37 ///
38 /// The memory accesses before the `set` can be said to happen before
39 /// `isSet` returns true.
40 pub fn isSet(re: *const ResetEvent) bool {
41 if (builtin.single_threaded) return switch (re.*) {
42 .unset => false,
43 .waiting => unreachable,
44 .is_set => true,
45 };
46 // Acquire barrier ensures memory accesses before `set` happen before
47 // returning true.
48 return @atomicLoad(ResetEvent, re, .acquire) == .is_set;
3249 }
3350
34 if (builtin.os.tag == .wasi) {
35 const w = std.os.wasi;
36 const userdata: w.userdata_t = 0x0123_45678;
37 const clock: w.subscription_clock_t = .{
38 .id = .MONOTONIC,
39 .timeout = nanoseconds,
40 .precision = 0,
41 .flags = 0,
51 /// Blocks the calling thread until `set` is called.
52 ///
53 /// This is effectively a more efficient version of `while (!isSet()) {}`.
54 ///
55 /// The memory accesses before the `set` can be said to happen before `wait` returns.
56 pub fn wait(re: *ResetEvent) void {
57 if (builtin.single_threaded) switch (re.*) {
58 .unset => unreachable, // Deadlock, no other threads to wake us up.
59 .waiting => unreachable, // Invalid state.
60 .is_set => return,
4261 };
43 const in: w.subscription_t = .{
44 .userdata = userdata,
45 .u = .{
46 .tag = .CLOCK,
47 .u = .{ .clock = clock },
48 },
62 if (!re.isSet()) return timedWaitInner(re, null) catch |err| switch (err) {
63 error.Timeout => unreachable, // No timeout specified.
4964 };
50
51 var event: w.event_t = undefined;
52 var nevents: usize = undefined;
53 _ = w.poll_oneoff(&in, &event, 1, &nevents);
54 return;
5565 }
5666
57 if (builtin.os.tag == .uefi) {
58 const boot_services = std.os.uefi.system_table.boot_services.?;
59 const us_from_ns = nanoseconds / std.time.ns_per_us;
60 const us = math.cast(usize, us_from_ns) orelse math.maxInt(usize);
61 boot_services.stall(us) catch unreachable;
62 return;
67 /// Blocks the calling thread until `set` is called, or until the
68 /// corresponding timeout expires, returning `error.Timeout`.
69 ///
70 /// This is effectively a more efficient version of `while (!isSet()) {}`.
71 ///
72 /// The memory accesses before the set() can be said to happen before
73 /// timedWait() returns without error.
74 pub fn timedWait(re: *ResetEvent, timeout_ns: u64) error{Timeout}!void {
75 if (builtin.single_threaded) switch (re.*) {
76 .unset => return error.Timeout,
77 .waiting => unreachable, // Invalid state.
78 .is_set => return,
79 };
80 if (!re.isSet()) return timedWaitInner(re, timeout_ns);
6381 }
6482
65 const s = nanoseconds / std.time.ns_per_s;
66 const ns = nanoseconds % std.time.ns_per_s;
83 fn timedWaitInner(re: *ResetEvent, timeout: ?u64) error{Timeout}!void {
84 @branchHint(.cold);
6785
68 // Newer kernel ports don't have old `nanosleep()` and `clock_nanosleep()` has been around
69 // since Linux 2.6 and glibc 2.1 anyway.
70 if (builtin.os.tag == .linux) {
71 const linux = std.os.linux;
86 // Try to set the state from `unset` to `waiting` to indicate to the
87 // `set` thread that others are blocked on the ResetEvent. Avoid using
88 // any strict barriers until we know the ResetEvent is set.
89 var state = @atomicLoad(ResetEvent, re, .acquire);
90 if (state == .unset) {
91 state = @cmpxchgStrong(ResetEvent, re, state, .waiting, .acquire, .acquire) orelse .waiting;
92 }
7293
73 var req: linux.timespec = .{
74 .sec = std.math.cast(linux.time_t, s) orelse std.math.maxInt(linux.time_t),
75 .nsec = std.math.cast(linux.time_t, ns) orelse std.math.maxInt(linux.time_t),
76 };
77 var rem: linux.timespec = undefined;
94 // Wait until the ResetEvent is set since the state is waiting.
95 if (state == .waiting) {
96 var futex_deadline = Futex.Deadline.init(timeout);
97 while (true) {
98 const wait_result = futex_deadline.wait(@ptrCast(re), @intFromEnum(ResetEvent.waiting));
7899
79 while (true) {
80 switch (linux.E.init(linux.clock_nanosleep(.MONOTONIC, .{ .ABSTIME = false }, &req, &rem))) {
81 .SUCCESS => return,
82 .INTR => {
83 req = rem;
84 continue;
85 },
86 .FAULT => unreachable,
87 .INVAL => unreachable,
88 .OPNOTSUPP => unreachable,
89 else => return,
100 // Check if the ResetEvent was set before possibly reporting error.Timeout below.
101 state = @atomicLoad(ResetEvent, re, .acquire);
102 if (state != .waiting) break;
103
104 try wait_result;
90105 }
91106 }
107
108 assert(state == .is_set);
92109 }
93110
94 posix.nanosleep(s, ns);
95}
111 /// Marks the logical boolean as `set` and unblocks any threads in `wait`
112 /// or `timedWait` to observe the new state.
113 ///
114 /// The logical boolean stays `set` until `reset` is called, making future
115 /// `set` calls do nothing semantically.
116 ///
117 /// The memory accesses before `set` can be said to happen before `isSet`
118 /// returns true or `wait`/`timedWait` return successfully.
119 pub fn set(re: *ResetEvent) void {
120 if (builtin.single_threaded) {
121 re.* = .is_set;
122 return;
123 }
124 if (@atomicRmw(ResetEvent, re, .Xchg, .is_set, .release) == .waiting) {
125 Futex.wake(@ptrCast(re), std.math.maxInt(u32));
126 }
127 }
96128
97test sleep {
98 sleep(1);
99}
129 /// Unmarks the ResetEvent as if `set` was never called.
130 ///
131 /// Assumes no threads are blocked in `wait` or `timedWait`. Concurrent
132 /// calls to `set`, `isSet` and `reset` are allowed.
133 pub fn reset(re: *ResetEvent) void {
134 if (builtin.single_threaded) {
135 re.* = .unset;
136 return;
137 }
138 @atomicStore(ResetEvent, re, .unset, .monotonic);
139 }
140};
100141
101142const Thread = @This();
102143const Impl = if (native_os == .windows)
......@@ -130,6 +171,7 @@ pub const SetNameError = error{
130171 NameTooLong,
131172 Unsupported,
132173 Unexpected,
174 InvalidWtf8,
133175} || posix.PrctlError || posix.WriteError || std.fs.File.OpenError || std.fmt.BufPrintError;
134176
135177pub fn setName(self: Thread, name: []const u8) SetNameError!void {
......@@ -277,10 +319,13 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co
277319 var buf: [32]u8 = undefined;
278320 const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});
279321
322 var threaded: std.Io.Threaded = .init_single_threaded;
323 const io = threaded.ioBasic();
324
280325 const file = try std.fs.cwd().openFile(path, .{});
281326 defer file.close();
282327
283 var file_reader = file.readerStreaming(&.{});
328 var file_reader = file.readerStreaming(io, &.{});
284329 const data_len = file_reader.interface.readSliceShort(buffer_ptr[0 .. max_name_len + 1]) catch |err| switch (err) {
285330 error.ReadFailed => return file_reader.err.?,
286331 };
......@@ -385,6 +430,8 @@ pub const CpuCountError = error{
385430};
386431
387432/// Returns the platforms view on the number of logical CPU cores available.
433///
434/// Returned value guaranteed to be >= 1.
388435pub fn getCpuCount() CpuCountError!usize {
389436 return try Impl.getCpuCount();
390437}
......@@ -963,7 +1010,7 @@ const WasiThreadImpl = struct {
9631010 @call(.auto, f, w.args) catch |err| {
9641011 std.debug.print("error: {s}\n", .{@errorName(err)});
9651012 if (@errorReturnTrace()) |trace| {
966 std.debug.dumpStackTrace(trace.*);
1013 std.debug.dumpStackTrace(trace);
9671014 }
9681015 };
9691016 },
......@@ -1652,9 +1699,9 @@ test "setName, getName" {
16521699 if (builtin.single_threaded) return error.SkipZigTest;
16531700
16541701 const Context = struct {
1655 start_wait_event: ResetEvent = .{},
1656 test_done_event: ResetEvent = .{},
1657 thread_done_event: ResetEvent = .{},
1702 start_wait_event: ResetEvent = .unset,
1703 test_done_event: ResetEvent = .unset,
1704 thread_done_event: ResetEvent = .unset,
16581705
16591706 done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
16601707 thread: Thread = undefined,
......@@ -1721,7 +1768,7 @@ test join {
17211768 if (builtin.single_threaded) return error.SkipZigTest;
17221769
17231770 var value: usize = 0;
1724 var event = ResetEvent{};
1771 var event: ResetEvent = .unset;
17251772
17261773 const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });
17271774 thread.join();
......@@ -1733,7 +1780,7 @@ test detach {
17331780 if (builtin.single_threaded) return error.SkipZigTest;
17341781
17351782 var value: usize = 0;
1736 var event = ResetEvent{};
1783 var event: ResetEvent = .unset;
17371784
17381785 const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });
17391786 thread.detach();
......@@ -1778,3 +1825,124 @@ fn testTls() !void {
17781825 x += 1;
17791826 if (x != 1235) return error.TlsBadEndValue;
17801827}
1828
1829test "ResetEvent smoke test" {
1830 var event: ResetEvent = .unset;
1831 try testing.expectEqual(false, event.isSet());
1832
1833 // make sure the event gets set
1834 event.set();
1835 try testing.expectEqual(true, event.isSet());
1836
1837 // make sure the event gets unset again
1838 event.reset();
1839 try testing.expectEqual(false, event.isSet());
1840
1841 // waits should timeout as there's no other thread to set the event
1842 try testing.expectError(error.Timeout, event.timedWait(0));
1843 try testing.expectError(error.Timeout, event.timedWait(std.time.ns_per_ms));
1844
1845 // set the event again and make sure waits complete
1846 event.set();
1847 event.wait();
1848 try event.timedWait(std.time.ns_per_ms);
1849 try testing.expectEqual(true, event.isSet());
1850}
1851
1852test "ResetEvent signaling" {
1853 // This test requires spawning threads
1854 if (builtin.single_threaded) {
1855 return error.SkipZigTest;
1856 }
1857
1858 const Context = struct {
1859 in: ResetEvent = .unset,
1860 out: ResetEvent = .unset,
1861 value: usize = 0,
1862
1863 fn input(self: *@This()) !void {
1864 // wait for the value to become 1
1865 self.in.wait();
1866 self.in.reset();
1867 try testing.expectEqual(self.value, 1);
1868
1869 // bump the value and wake up output()
1870 self.value = 2;
1871 self.out.set();
1872
1873 // wait for output to receive 2, bump the value and wake us up with 3
1874 self.in.wait();
1875 self.in.reset();
1876 try testing.expectEqual(self.value, 3);
1877
1878 // bump the value and wake up output() for it to see 4
1879 self.value = 4;
1880 self.out.set();
1881 }
1882
1883 fn output(self: *@This()) !void {
1884 // start with 0 and bump the value for input to see 1
1885 try testing.expectEqual(self.value, 0);
1886 self.value = 1;
1887 self.in.set();
1888
1889 // wait for input to receive 1, bump the value to 2 and wake us up
1890 self.out.wait();
1891 self.out.reset();
1892 try testing.expectEqual(self.value, 2);
1893
1894 // bump the value to 3 for input to see (rhymes)
1895 self.value = 3;
1896 self.in.set();
1897
1898 // wait for input to bump the value to 4 and receive no more (rhymes)
1899 self.out.wait();
1900 self.out.reset();
1901 try testing.expectEqual(self.value, 4);
1902 }
1903 };
1904
1905 var ctx = Context{};
1906
1907 const thread = try std.Thread.spawn(.{}, Context.output, .{&ctx});
1908 defer thread.join();
1909
1910 try ctx.input();
1911}
1912
1913test "ResetEvent broadcast" {
1914 // This test requires spawning threads
1915 if (builtin.single_threaded) {
1916 return error.SkipZigTest;
1917 }
1918
1919 const num_threads = 10;
1920 const Barrier = struct {
1921 event: ResetEvent = .unset,
1922 counter: std.atomic.Value(usize) = std.atomic.Value(usize).init(num_threads),
1923
1924 fn wait(self: *@This()) void {
1925 if (self.counter.fetchSub(1, .acq_rel) == 1) {
1926 self.event.set();
1927 }
1928 }
1929 };
1930
1931 const Context = struct {
1932 start_barrier: Barrier = .{},
1933 finish_barrier: Barrier = .{},
1934
1935 fn run(self: *@This()) void {
1936 self.start_barrier.wait();
1937 self.finish_barrier.wait();
1938 }
1939 };
1940
1941 var ctx = Context{};
1942 var threads: [num_threads - 1]std.Thread = undefined;
1943
1944 for (&threads) |*t| t.* = try std.Thread.spawn(.{}, Context.run, .{&ctx});
1945 defer for (threads) |t| t.join();
1946
1947 ctx.run();
1948}
lib/std/Thread/Condition.zig+7-8
......@@ -123,14 +123,9 @@ const SingleThreadedImpl = struct {
123123 fn wait(self: *Impl, mutex: *Mutex, timeout: ?u64) error{Timeout}!void {
124124 _ = self;
125125 _ = mutex;
126
127126 // There are no other threads to wake us up.
128127 // So if we wait without a timeout we would never wake up.
129 const timeout_ns = timeout orelse {
130 unreachable; // deadlock detected
131 };
132
133 std.Thread.sleep(timeout_ns);
128 assert(timeout != null); // Deadlock detected.
134129 return error.Timeout;
135130 }
136131
......@@ -323,6 +318,8 @@ test "wait and signal" {
323318 return error.SkipZigTest;
324319 }
325320
321 const io = testing.io;
322
326323 const num_threads = 4;
327324
328325 const MultiWait = struct {
......@@ -348,7 +345,7 @@ test "wait and signal" {
348345 }
349346
350347 while (true) {
351 std.Thread.sleep(100 * std.time.ns_per_ms);
348 try std.Io.Clock.Duration.sleep(.{ .clock = .awake, .raw = .fromMilliseconds(100) }, io);
352349
353350 multi_wait.mutex.lock();
354351 defer multi_wait.mutex.unlock();
......@@ -368,6 +365,8 @@ test signal {
368365 return error.SkipZigTest;
369366 }
370367
368 const io = testing.io;
369
371370 const num_threads = 4;
372371
373372 const SignalTest = struct {
......@@ -405,7 +404,7 @@ test signal {
405404 }
406405
407406 while (true) {
408 std.Thread.sleep(10 * std.time.ns_per_ms);
407 try std.Io.Clock.Duration.sleep(.{ .clock = .awake, .raw = .fromMilliseconds(10) }, io);
409408
410409 signal_test.mutex.lock();
411410 defer signal_test.mutex.unlock();
lib/std/Thread/Futex.zig+1-1
......@@ -116,7 +116,7 @@ const SingleThreadedImpl = struct {
116116 unreachable; // deadlock detected
117117 };
118118
119 std.Thread.sleep(delay);
119 _ = delay;
120120 return error.Timeout;
121121 }
122122
lib/std/Thread/ResetEvent.zig deleted-278
......@@ -1,278 +0,0 @@
1//! ResetEvent is a thread-safe bool which can be set to true/false ("set"/"unset").
2//! It can also block threads until the "bool" is set with cancellation via timed waits.
3//! ResetEvent can be statically initialized and is at most `@sizeOf(u64)` large.
4
5const std = @import("../std.zig");
6const builtin = @import("builtin");
7const ResetEvent = @This();
8
9const os = std.os;
10const assert = std.debug.assert;
11const testing = std.testing;
12const Futex = std.Thread.Futex;
13
14impl: Impl = .{},
15
16/// Returns if the ResetEvent was set().
17/// Once reset() is called, this returns false until the next set().
18/// The memory accesses before the set() can be said to happen before isSet() returns true.
19pub fn isSet(self: *const ResetEvent) bool {
20 return self.impl.isSet();
21}
22
23/// Block's the callers thread until the ResetEvent is set().
24/// This is effectively a more efficient version of `while (!isSet()) {}`.
25/// The memory accesses before the set() can be said to happen before wait() returns.
26pub fn wait(self: *ResetEvent) void {
27 self.impl.wait(null) catch |err| switch (err) {
28 error.Timeout => unreachable, // no timeout provided so we shouldn't have timed-out
29 };
30}
31
32/// Block's the callers thread until the ResetEvent is set(), or until the corresponding timeout expires.
33/// If the timeout expires before the ResetEvent is set, `error.Timeout` is returned.
34/// This is effectively a more efficient version of `while (!isSet()) {}`.
35/// The memory accesses before the set() can be said to happen before timedWait() returns without error.
36pub fn timedWait(self: *ResetEvent, timeout_ns: u64) error{Timeout}!void {
37 return self.impl.wait(timeout_ns);
38}
39
40/// Marks the ResetEvent as "set" and unblocks any threads in `wait()` or `timedWait()` to observe the new state.
41/// The ResetEvent says "set" until reset() is called, making future set() calls do nothing semantically.
42/// The memory accesses before set() can be said to happen before isSet() returns true or wait()/timedWait() return successfully.
43pub fn set(self: *ResetEvent) void {
44 self.impl.set();
45}
46
47/// Unmarks the ResetEvent from its "set" state if set() was called previously.
48/// It is undefined behavior is reset() is called while threads are blocked in wait() or timedWait().
49/// Concurrent calls to set(), isSet() and reset() are allowed.
50pub fn reset(self: *ResetEvent) void {
51 self.impl.reset();
52}
53
54const Impl = if (builtin.single_threaded)
55 SingleThreadedImpl
56else
57 FutexImpl;
58
59const SingleThreadedImpl = struct {
60 is_set: bool = false,
61
62 fn isSet(self: *const Impl) bool {
63 return self.is_set;
64 }
65
66 fn wait(self: *Impl, timeout: ?u64) error{Timeout}!void {
67 if (self.isSet()) {
68 return;
69 }
70
71 // There are no other threads to wake us up.
72 // So if we wait without a timeout we would never wake up.
73 const timeout_ns = timeout orelse {
74 unreachable; // deadlock detected
75 };
76
77 std.Thread.sleep(timeout_ns);
78 return error.Timeout;
79 }
80
81 fn set(self: *Impl) void {
82 self.is_set = true;
83 }
84
85 fn reset(self: *Impl) void {
86 self.is_set = false;
87 }
88};
89
90const FutexImpl = struct {
91 state: std.atomic.Value(u32) = std.atomic.Value(u32).init(unset),
92
93 const unset = 0;
94 const waiting = 1;
95 const is_set = 2;
96
97 fn isSet(self: *const Impl) bool {
98 // Acquire barrier ensures memory accesses before set() happen before we return true.
99 return self.state.load(.acquire) == is_set;
100 }
101
102 fn wait(self: *Impl, timeout: ?u64) error{Timeout}!void {
103 // Outline the slow path to allow isSet() to be inlined
104 if (!self.isSet()) {
105 return self.waitUntilSet(timeout);
106 }
107 }
108
109 fn waitUntilSet(self: *Impl, timeout: ?u64) error{Timeout}!void {
110 @branchHint(.cold);
111
112 // Try to set the state from `unset` to `waiting` to indicate
113 // to the set() thread that others are blocked on the ResetEvent.
114 // We avoid using any strict barriers until the end when we know the ResetEvent is set.
115 var state = self.state.load(.acquire);
116 if (state == unset) {
117 state = self.state.cmpxchgStrong(state, waiting, .acquire, .acquire) orelse waiting;
118 }
119
120 // Wait until the ResetEvent is set since the state is waiting.
121 if (state == waiting) {
122 var futex_deadline = Futex.Deadline.init(timeout);
123 while (true) {
124 const wait_result = futex_deadline.wait(&self.state, waiting);
125
126 // Check if the ResetEvent was set before possibly reporting error.Timeout below.
127 state = self.state.load(.acquire);
128 if (state != waiting) {
129 break;
130 }
131
132 try wait_result;
133 }
134 }
135
136 assert(state == is_set);
137 }
138
139 fn set(self: *Impl) void {
140 // Quick check if the ResetEvent is already set before doing the atomic swap below.
141 // set() could be getting called quite often and multiple threads calling swap() increases contention unnecessarily.
142 if (self.state.load(.monotonic) == is_set) {
143 return;
144 }
145
146 // Mark the ResetEvent as set and unblock all waiters waiting on it if any.
147 // Release barrier ensures memory accesses before set() happen before the ResetEvent is observed to be "set".
148 if (self.state.swap(is_set, .release) == waiting) {
149 Futex.wake(&self.state, std.math.maxInt(u32));
150 }
151 }
152
153 fn reset(self: *Impl) void {
154 self.state.store(unset, .monotonic);
155 }
156};
157
158test "smoke test" {
159 // make sure the event is unset
160 var event = ResetEvent{};
161 try testing.expectEqual(false, event.isSet());
162
163 // make sure the event gets set
164 event.set();
165 try testing.expectEqual(true, event.isSet());
166
167 // make sure the event gets unset again
168 event.reset();
169 try testing.expectEqual(false, event.isSet());
170
171 // waits should timeout as there's no other thread to set the event
172 try testing.expectError(error.Timeout, event.timedWait(0));
173 try testing.expectError(error.Timeout, event.timedWait(std.time.ns_per_ms));
174
175 // set the event again and make sure waits complete
176 event.set();
177 event.wait();
178 try event.timedWait(std.time.ns_per_ms);
179 try testing.expectEqual(true, event.isSet());
180}
181
182test "signaling" {
183 // This test requires spawning threads
184 if (builtin.single_threaded) {
185 return error.SkipZigTest;
186 }
187
188 const Context = struct {
189 in: ResetEvent = .{},
190 out: ResetEvent = .{},
191 value: usize = 0,
192
193 fn input(self: *@This()) !void {
194 // wait for the value to become 1
195 self.in.wait();
196 self.in.reset();
197 try testing.expectEqual(self.value, 1);
198
199 // bump the value and wake up output()
200 self.value = 2;
201 self.out.set();
202
203 // wait for output to receive 2, bump the value and wake us up with 3
204 self.in.wait();
205 self.in.reset();
206 try testing.expectEqual(self.value, 3);
207
208 // bump the value and wake up output() for it to see 4
209 self.value = 4;
210 self.out.set();
211 }
212
213 fn output(self: *@This()) !void {
214 // start with 0 and bump the value for input to see 1
215 try testing.expectEqual(self.value, 0);
216 self.value = 1;
217 self.in.set();
218
219 // wait for input to receive 1, bump the value to 2 and wake us up
220 self.out.wait();
221 self.out.reset();
222 try testing.expectEqual(self.value, 2);
223
224 // bump the value to 3 for input to see (rhymes)
225 self.value = 3;
226 self.in.set();
227
228 // wait for input to bump the value to 4 and receive no more (rhymes)
229 self.out.wait();
230 self.out.reset();
231 try testing.expectEqual(self.value, 4);
232 }
233 };
234
235 var ctx = Context{};
236
237 const thread = try std.Thread.spawn(.{}, Context.output, .{&ctx});
238 defer thread.join();
239
240 try ctx.input();
241}
242
243test "broadcast" {
244 // This test requires spawning threads
245 if (builtin.single_threaded) {
246 return error.SkipZigTest;
247 }
248
249 const num_threads = 10;
250 const Barrier = struct {
251 event: ResetEvent = .{},
252 counter: std.atomic.Value(usize) = std.atomic.Value(usize).init(num_threads),
253
254 fn wait(self: *@This()) void {
255 if (self.counter.fetchSub(1, .acq_rel) == 1) {
256 self.event.set();
257 }
258 }
259 };
260
261 const Context = struct {
262 start_barrier: Barrier = .{},
263 finish_barrier: Barrier = .{},
264
265 fn run(self: *@This()) void {
266 self.start_barrier.wait();
267 self.finish_barrier.wait();
268 }
269 };
270
271 var ctx = Context{};
272 var threads: [num_threads - 1]std.Thread = undefined;
273
274 for (&threads) |*t| t.* = try std.Thread.spawn(.{}, Context.run, .{&ctx});
275 defer for (threads) |t| t.join();
276
277 ctx.run();
278}
lib/std/Thread/WaitGroup.zig+20-9
......@@ -7,11 +7,15 @@ const is_waiting: usize = 1 << 0;
77const one_pending: usize = 1 << 1;
88
99state: std.atomic.Value(usize) = std.atomic.Value(usize).init(0),
10event: std.Thread.ResetEvent = .{},
10event: std.Thread.ResetEvent = .unset,
1111
1212pub fn start(self: *WaitGroup) void {
13 const state = self.state.fetchAdd(one_pending, .monotonic);
14 assert((state / one_pending) < (std.math.maxInt(usize) / one_pending));
13 return startStateless(&self.state);
14}
15
16pub fn startStateless(state: *std.atomic.Value(usize)) void {
17 const prev_state = state.fetchAdd(one_pending, .monotonic);
18 assert((prev_state / one_pending) < (std.math.maxInt(usize) / one_pending));
1519}
1620
1721pub fn startMany(self: *WaitGroup, n: usize) void {
......@@ -28,13 +32,20 @@ pub fn finish(self: *WaitGroup) void {
2832 }
2933}
3034
31pub fn wait(self: *WaitGroup) void {
32 const state = self.state.fetchAdd(is_waiting, .acquire);
33 assert(state & is_waiting == 0);
35pub fn finishStateless(state: *std.atomic.Value(usize), event: *std.Thread.ResetEvent) void {
36 const prev_state = state.fetchSub(one_pending, .acq_rel);
37 assert((prev_state / one_pending) > 0);
38 if (prev_state == (one_pending | is_waiting)) event.set();
39}
3440
35 if ((state / one_pending) > 0) {
36 self.event.wait();
37 }
41pub fn wait(wg: *WaitGroup) void {
42 return waitStateless(&wg.state, &wg.event);
43}
44
45pub fn waitStateless(state: *std.atomic.Value(usize), event: *std.Thread.ResetEvent) void {
46 const prev_state = state.fetchAdd(is_waiting, .acquire);
47 assert(prev_state & is_waiting == 0);
48 if ((prev_state / one_pending) > 0) event.wait();
3849}
3950
4051pub fn reset(self: *WaitGroup) void {
lib/std/Uri.zig+36-16
......@@ -1,45 +1,48 @@
1//! Uniform Resource Identifier (URI) parsing roughly adhering to <https://tools.ietf.org/html/rfc3986>.
2//! Does not do perfect grammar and character class checking, but should be robust against URIs in the wild.
1//! Uniform Resource Identifier (URI) parsing roughly adhering to
2//! <https://tools.ietf.org/html/rfc3986>. Does not do perfect grammar and
3//! character class checking, but should be robust against URIs in the wild.
34
45const std = @import("std.zig");
56const testing = std.testing;
67const Uri = @This();
78const Allocator = std.mem.Allocator;
89const Writer = std.Io.Writer;
10const HostName = std.Io.net.HostName;
911
1012scheme: []const u8,
1113user: ?Component = null,
1214password: ?Component = null,
15/// If non-null, already validated.
1316host: ?Component = null,
1417port: ?u16 = null,
1518path: Component = Component.empty,
1619query: ?Component = null,
1720fragment: ?Component = null,
1821
19pub const host_name_max = 255;
22pub const GetHostError = error{UriMissingHost};
2023
2124/// Returned value may point into `buffer` or be the original string.
2225///
23/// Suggested buffer length: `host_name_max`.
24///
2526/// See also:
2627/// * `getHostAlloc`
27pub fn getHost(uri: Uri, buffer: []u8) error{ UriMissingHost, UriHostTooLong }![]const u8 {
28pub fn getHost(uri: Uri, buffer: *[HostName.max_len]u8) GetHostError!HostName {
2829 const component = uri.host orelse return error.UriMissingHost;
29 return component.toRaw(buffer) catch |err| switch (err) {
30 error.NoSpaceLeft => return error.UriHostTooLong,
30 const bytes = component.toRaw(buffer) catch |err| switch (err) {
31 error.NoSpaceLeft => unreachable, // `host` already validated.
3132 };
33 return .{ .bytes = bytes };
3234}
3335
36pub const GetHostAllocError = GetHostError || error{OutOfMemory};
37
3438/// Returned value may point into `buffer` or be the original string.
3539///
3640/// See also:
3741/// * `getHost`
38pub fn getHostAlloc(uri: Uri, arena: Allocator) error{ UriMissingHost, UriHostTooLong, OutOfMemory }![]const u8 {
42pub fn getHostAlloc(uri: Uri, arena: Allocator) GetHostAllocError!HostName {
3943 const component = uri.host orelse return error.UriMissingHost;
40 const result = try component.toRawMaybeAlloc(arena);
41 if (result.len > host_name_max) return error.UriHostTooLong;
42 return result;
44 const bytes = try component.toRawMaybeAlloc(arena);
45 return .{ .bytes = bytes };
4346}
4447
4548pub const Component = union(enum) {
......@@ -194,7 +197,12 @@ pub fn percentDecodeInPlace(buffer: []u8) []u8 {
194197 return percentDecodeBackwards(buffer, buffer);
195198}
196199
197pub const ParseError = error{ UnexpectedCharacter, InvalidFormat, InvalidPort };
200pub const ParseError = error{
201 UnexpectedCharacter,
202 InvalidFormat,
203 InvalidPort,
204 InvalidHostName,
205};
198206
199207/// Parses the URI or returns an error. This function is not compliant, but is required to parse
200208/// some forms of URIs in the wild, such as HTTP Location headers.
......@@ -397,7 +405,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE
397405 .scheme = new_parsed.scheme,
398406 .user = new_parsed.user,
399407 .password = new_parsed.password,
400 .host = new_parsed.host,
408 .host = try validateHostComponent(new_parsed.host),
401409 .port = new_parsed.port,
402410 .path = remove_dot_segments(new_path),
403411 .query = new_parsed.query,
......@@ -408,7 +416,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE
408416 .scheme = base.scheme,
409417 .user = new_parsed.user,
410418 .password = new_parsed.password,
411 .host = host,
419 .host = try validateHostComponent(host),
412420 .port = new_parsed.port,
413421 .path = remove_dot_segments(new_path),
414422 .query = new_parsed.query,
......@@ -430,7 +438,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE
430438 .scheme = base.scheme,
431439 .user = base.user,
432440 .password = base.password,
433 .host = base.host,
441 .host = try validateHostComponent(base.host),
434442 .port = base.port,
435443 .path = path,
436444 .query = query,
......@@ -438,6 +446,18 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE
438446 };
439447}
440448
449fn validateHostComponent(optional_component: ?Component) error{InvalidHostName}!?Component {
450 const component = optional_component orelse return null;
451 switch (component) {
452 .raw => |raw| HostName.validate(raw) catch return error.InvalidHostName,
453 .percent_encoded => |encoded| {
454 // TODO validate decoded name instead
455 HostName.validate(encoded) catch return error.InvalidHostName;
456 },
457 }
458 return component;
459}
460
441461/// In-place implementation of RFC 3986, Section 5.2.4.
442462fn remove_dot_segments(path: []u8) Component {
443463 var in_i: usize = 0;
lib/std/builtin.zig-13
......@@ -37,19 +37,6 @@ pub const subsystem: ?std.Target.SubSystem = blk: {
3737pub const StackTrace = struct {
3838 index: usize,
3939 instruction_addresses: []usize,
40
41 pub fn format(st: *const StackTrace, writer: *std.Io.Writer) std.Io.Writer.Error!void {
42 // TODO: re-evaluate whether to use format() methods at all.
43 // Until then, avoid an error when using GeneralPurposeAllocator with WebAssembly
44 // where it tries to call detectTTYConfig here.
45 if (builtin.os.tag == .freestanding) return;
46
47 // TODO: why on earth are we using stderr's ttyconfig?
48 // If we want colored output, we should just make a formatter out of `writeStackTrace`.
49 const tty_config = std.Io.tty.detectConfig(.stderr());
50 try writer.writeAll("\n");
51 try std.debug.writeStackTrace(st, writer, tty_config);
52 }
5340};
5441
5542/// This data structure is used by the Zig language code generation and
lib/std/c.zig+423-413
......@@ -1,12 +1,14 @@
1const std = @import("std");
21const builtin = @import("builtin");
2const native_abi = builtin.abi;
3const native_arch = builtin.cpu.arch;
4const native_os = builtin.os.tag;
5const native_endian = builtin.cpu.arch.endian();
6
7const std = @import("std");
38const c = @This();
49const maxInt = std.math.maxInt;
510const assert = std.debug.assert;
611const page_size = std.heap.page_size_min;
7const native_abi = builtin.abi;
8const native_arch = builtin.cpu.arch;
9const native_os = builtin.os.tag;
1012const linux = std.os.linux;
1113const emscripten = std.os.emscripten;
1214const wasi = std.os.wasi;
......@@ -2587,25 +2589,24 @@ pub const SHUT = switch (native_os) {
25872589
25882590/// Signal types
25892591pub const SIG = switch (native_os) {
2590 .linux => linux.SIG,
2591 .emscripten => emscripten.SIG,
2592 .windows => struct {
2592 .linux, .emscripten => linux.SIG,
2593 .windows => enum(u32) {
25932594 /// interrupt
2594 pub const INT = 2;
2595 INT = 2,
25952596 /// illegal instruction - invalid function image
2596 pub const ILL = 4;
2597 ILL = 4,
25972598 /// floating point exception
2598 pub const FPE = 8;
2599 FPE = 8,
25992600 /// segment violation
2600 pub const SEGV = 11;
2601 SEGV = 11,
26012602 /// Software termination signal from kill
2602 pub const TERM = 15;
2603 TERM = 15,
26032604 /// Ctrl-Break sequence
2604 pub const BREAK = 21;
2605 BREAK = 21,
26052606 /// abnormal termination triggered by abort call
2606 pub const ABRT = 22;
2607 ABRT = 22,
26072608 /// SIGABRT compatible with other platforms, same as SIGABRT
2608 pub const ABRT_COMPAT = 6;
2609 ABRT_COMPAT = 6,
26092610
26102611 // Signal action codes
26112612 /// default signal action
......@@ -2621,7 +2622,7 @@ pub const SIG = switch (native_os) {
26212622 /// Signal error value (returned by signal call on error)
26222623 pub const ERR = -1;
26232624 },
2624 .macos, .ios, .tvos, .watchos, .visionos => struct {
2625 .macos, .ios, .tvos, .watchos, .visionos => enum(u32) {
26252626 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
26262627 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
26272628 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);
......@@ -2633,113 +2634,74 @@ pub const SIG = switch (native_os) {
26332634 pub const UNBLOCK = 2;
26342635 /// set specified signal set
26352636 pub const SETMASK = 3;
2637
2638 pub const IOT: SIG = .ABRT;
2639 pub const POLL: SIG = .EMT;
2640
26362641 /// hangup
2637 pub const HUP = 1;
2642 HUP = 1,
26382643 /// interrupt
2639 pub const INT = 2;
2644 INT = 2,
26402645 /// quit
2641 pub const QUIT = 3;
2646 QUIT = 3,
26422647 /// illegal instruction (not reset when caught)
2643 pub const ILL = 4;
2648 ILL = 4,
26442649 /// trace trap (not reset when caught)
2645 pub const TRAP = 5;
2650 TRAP = 5,
26462651 /// abort()
2647 pub const ABRT = 6;
2648 /// pollable event ([XSR] generated, not supported)
2649 pub const POLL = 7;
2650 /// compatibility
2651 pub const IOT = ABRT;
2652 ABRT = 6,
26522653 /// EMT instruction
2653 pub const EMT = 7;
2654 EMT = 7,
26542655 /// floating point exception
2655 pub const FPE = 8;
2656 FPE = 8,
26562657 /// kill (cannot be caught or ignored)
2657 pub const KILL = 9;
2658 KILL = 9,
26582659 /// bus error
2659 pub const BUS = 10;
2660 BUS = 10,
26602661 /// segmentation violation
2661 pub const SEGV = 11;
2662 SEGV = 11,
26622663 /// bad argument to system call
2663 pub const SYS = 12;
2664 SYS = 12,
26642665 /// write on a pipe with no one to read it
2665 pub const PIPE = 13;
2666 PIPE = 13,
26662667 /// alarm clock
2667 pub const ALRM = 14;
2668 ALRM = 14,
26682669 /// software termination signal from kill
2669 pub const TERM = 15;
2670 TERM = 15,
26702671 /// urgent condition on IO channel
2671 pub const URG = 16;
2672 URG = 16,
26722673 /// sendable stop signal not from tty
2673 pub const STOP = 17;
2674 STOP = 17,
26742675 /// stop signal from tty
2675 pub const TSTP = 18;
2676 TSTP = 18,
26762677 /// continue a stopped process
2677 pub const CONT = 19;
2678 CONT = 19,
26782679 /// to parent on child stop or exit
2679 pub const CHLD = 20;
2680 CHLD = 20,
26802681 /// to readers pgrp upon background tty read
2681 pub const TTIN = 21;
2682 TTIN = 21,
26822683 /// like TTIN for output if (tp->t_local&LTOSTOP)
2683 pub const TTOU = 22;
2684 TTOU = 22,
26842685 /// input/output possible signal
2685 pub const IO = 23;
2686 IO = 23,
26862687 /// exceeded CPU time limit
2687 pub const XCPU = 24;
2688 XCPU = 24,
26882689 /// exceeded file size limit
2689 pub const XFSZ = 25;
2690 XFSZ = 25,
26902691 /// virtual time alarm
2691 pub const VTALRM = 26;
2692 VTALRM = 26,
26922693 /// profiling time alarm
2693 pub const PROF = 27;
2694 PROF = 27,
26942695 /// window size changes
2695 pub const WINCH = 28;
2696 WINCH = 28,
26962697 /// information request
2697 pub const INFO = 29;
2698 INFO = 29,
26982699 /// user defined signal 1
2699 pub const USR1 = 30;
2700 USR1 = 30,
27002701 /// user defined signal 2
2701 pub const USR2 = 31;
2702 USR2 = 31,
27022703 },
2703 .freebsd => struct {
2704 pub const HUP = 1;
2705 pub const INT = 2;
2706 pub const QUIT = 3;
2707 pub const ILL = 4;
2708 pub const TRAP = 5;
2709 pub const ABRT = 6;
2710 pub const IOT = ABRT;
2711 pub const EMT = 7;
2712 pub const FPE = 8;
2713 pub const KILL = 9;
2714 pub const BUS = 10;
2715 pub const SEGV = 11;
2716 pub const SYS = 12;
2717 pub const PIPE = 13;
2718 pub const ALRM = 14;
2719 pub const TERM = 15;
2720 pub const URG = 16;
2721 pub const STOP = 17;
2722 pub const TSTP = 18;
2723 pub const CONT = 19;
2724 pub const CHLD = 20;
2725 pub const TTIN = 21;
2726 pub const TTOU = 22;
2727 pub const IO = 23;
2728 pub const XCPU = 24;
2729 pub const XFSZ = 25;
2730 pub const VTALRM = 26;
2731 pub const PROF = 27;
2732 pub const WINCH = 28;
2733 pub const INFO = 29;
2734 pub const USR1 = 30;
2735 pub const USR2 = 31;
2736 pub const THR = 32;
2737 pub const LWP = THR;
2738 pub const LIBRT = 33;
2739
2740 pub const RTMIN = 65;
2741 pub const RTMAX = 126;
2742
2704 .freebsd => enum(u32) {
27432705 pub const BLOCK = 1;
27442706 pub const UNBLOCK = 2;
27452707 pub const SETMASK = 3;
......@@ -2763,8 +2725,48 @@ pub const SIG = switch (native_os) {
27632725 pub inline fn VALID(sig: usize) usize {
27642726 return sig <= MAXSIG and sig > 0;
27652727 }
2728
2729 pub const IOT: SIG = .ABRT;
2730 pub const LWP: SIG = .THR;
2731
2732 pub const RTMIN = 65;
2733 pub const RTMAX = 126;
2734
2735 HUP = 1,
2736 INT = 2,
2737 QUIT = 3,
2738 ILL = 4,
2739 TRAP = 5,
2740 ABRT = 6,
2741 EMT = 7,
2742 FPE = 8,
2743 KILL = 9,
2744 BUS = 10,
2745 SEGV = 11,
2746 SYS = 12,
2747 PIPE = 13,
2748 ALRM = 14,
2749 TERM = 15,
2750 URG = 16,
2751 STOP = 17,
2752 TSTP = 18,
2753 CONT = 19,
2754 CHLD = 20,
2755 TTIN = 21,
2756 TTOU = 22,
2757 IO = 23,
2758 XCPU = 24,
2759 XFSZ = 25,
2760 VTALRM = 26,
2761 PROF = 27,
2762 WINCH = 28,
2763 INFO = 29,
2764 USR1 = 30,
2765 USR2 = 31,
2766 THR = 32,
2767 LIBRT = 33,
27662768 },
2767 .illumos => struct {
2769 .illumos => enum(u32) {
27682770 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
27692771 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
27702772 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);
......@@ -2773,54 +2775,9 @@ pub const SIG = switch (native_os) {
27732775 pub const WORDS = 4;
27742776 pub const MAXSIG = 75;
27752777
2776 pub const SIG_BLOCK = 1;
2777 pub const SIG_UNBLOCK = 2;
2778 pub const SIG_SETMASK = 3;
2779
2780 pub const HUP = 1;
2781 pub const INT = 2;
2782 pub const QUIT = 3;
2783 pub const ILL = 4;
2784 pub const TRAP = 5;
2785 pub const IOT = 6;
2786 pub const ABRT = 6;
2787 pub const EMT = 7;
2788 pub const FPE = 8;
2789 pub const KILL = 9;
2790 pub const BUS = 10;
2791 pub const SEGV = 11;
2792 pub const SYS = 12;
2793 pub const PIPE = 13;
2794 pub const ALRM = 14;
2795 pub const TERM = 15;
2796 pub const USR1 = 16;
2797 pub const USR2 = 17;
2798 pub const CLD = 18;
2799 pub const CHLD = 18;
2800 pub const PWR = 19;
2801 pub const WINCH = 20;
2802 pub const URG = 21;
2803 pub const POLL = 22;
2804 pub const IO = .POLL;
2805 pub const STOP = 23;
2806 pub const TSTP = 24;
2807 pub const CONT = 25;
2808 pub const TTIN = 26;
2809 pub const TTOU = 27;
2810 pub const VTALRM = 28;
2811 pub const PROF = 29;
2812 pub const XCPU = 30;
2813 pub const XFSZ = 31;
2814 pub const WAITING = 32;
2815 pub const LWP = 33;
2816 pub const FREEZE = 34;
2817 pub const THAW = 35;
2818 pub const CANCEL = 36;
2819 pub const LOST = 37;
2820 pub const XRES = 38;
2821 pub const JVM1 = 39;
2822 pub const JVM2 = 40;
2823 pub const INFO = 41;
2778 pub const BLOCK = 1;
2779 pub const UNBLOCK = 2;
2780 pub const SETMASK = 3;
28242781
28252782 pub const RTMIN = 42;
28262783 pub const RTMAX = 74;
......@@ -2837,8 +2794,54 @@ pub const SIG = switch (native_os) {
28372794 pub inline fn VALID(sig: usize) usize {
28382795 return sig <= MAXSIG and sig > 0;
28392796 }
2797
2798 pub const POLL: SIG = .IO;
2799
2800 HUP = 1,
2801 INT = 2,
2802 QUIT = 3,
2803 ILL = 4,
2804 TRAP = 5,
2805 IOT = 6,
2806 ABRT = 6,
2807 EMT = 7,
2808 FPE = 8,
2809 KILL = 9,
2810 BUS = 10,
2811 SEGV = 11,
2812 SYS = 12,
2813 PIPE = 13,
2814 ALRM = 14,
2815 TERM = 15,
2816 USR1 = 16,
2817 USR2 = 17,
2818 CLD = 18,
2819 CHLD = 18,
2820 PWR = 19,
2821 WINCH = 20,
2822 URG = 21,
2823 IO = 22,
2824 STOP = 23,
2825 TSTP = 24,
2826 CONT = 25,
2827 TTIN = 26,
2828 TTOU = 27,
2829 VTALRM = 28,
2830 PROF = 29,
2831 XCPU = 30,
2832 XFSZ = 31,
2833 WAITING = 32,
2834 LWP = 33,
2835 FREEZE = 34,
2836 THAW = 35,
2837 CANCEL = 36,
2838 LOST = 37,
2839 XRES = 38,
2840 JVM1 = 39,
2841 JVM2 = 40,
2842 INFO = 41,
28402843 },
2841 .netbsd => struct {
2844 .netbsd => enum(u32) {
28422845 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
28432846 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);
28442847 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
......@@ -2850,40 +2853,6 @@ pub const SIG = switch (native_os) {
28502853 pub const UNBLOCK = 2;
28512854 pub const SETMASK = 3;
28522855
2853 pub const HUP = 1;
2854 pub const INT = 2;
2855 pub const QUIT = 3;
2856 pub const ILL = 4;
2857 pub const TRAP = 5;
2858 pub const ABRT = 6;
2859 pub const IOT = ABRT;
2860 pub const EMT = 7;
2861 pub const FPE = 8;
2862 pub const KILL = 9;
2863 pub const BUS = 10;
2864 pub const SEGV = 11;
2865 pub const SYS = 12;
2866 pub const PIPE = 13;
2867 pub const ALRM = 14;
2868 pub const TERM = 15;
2869 pub const URG = 16;
2870 pub const STOP = 17;
2871 pub const TSTP = 18;
2872 pub const CONT = 19;
2873 pub const CHLD = 20;
2874 pub const TTIN = 21;
2875 pub const TTOU = 22;
2876 pub const IO = 23;
2877 pub const XCPU = 24;
2878 pub const XFSZ = 25;
2879 pub const VTALRM = 26;
2880 pub const PROF = 27;
2881 pub const WINCH = 28;
2882 pub const INFO = 29;
2883 pub const USR1 = 30;
2884 pub const USR2 = 31;
2885 pub const PWR = 32;
2886
28872856 pub const RTMIN = 33;
28882857 pub const RTMAX = 63;
28892858
......@@ -2899,8 +2868,43 @@ pub const SIG = switch (native_os) {
28992868 pub inline fn VALID(sig: usize) usize {
29002869 return sig <= MAXSIG and sig > 0;
29012870 }
2871
2872 pub const IOT: SIG = .ABRT;
2873
2874 HUP = 1,
2875 INT = 2,
2876 QUIT = 3,
2877 ILL = 4,
2878 TRAP = 5,
2879 ABRT = 6,
2880 EMT = 7,
2881 FPE = 8,
2882 KILL = 9,
2883 BUS = 10,
2884 SEGV = 11,
2885 SYS = 12,
2886 PIPE = 13,
2887 ALRM = 14,
2888 TERM = 15,
2889 URG = 16,
2890 STOP = 17,
2891 TSTP = 18,
2892 CONT = 19,
2893 CHLD = 20,
2894 TTIN = 21,
2895 TTOU = 22,
2896 IO = 23,
2897 XCPU = 24,
2898 XFSZ = 25,
2899 VTALRM = 26,
2900 PROF = 27,
2901 WINCH = 28,
2902 INFO = 29,
2903 USR1 = 30,
2904 USR2 = 31,
2905 PWR = 32,
29022906 },
2903 .dragonfly => struct {
2907 .dragonfly => enum(u32) {
29042908 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
29052909 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);
29062910 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
......@@ -2909,137 +2913,140 @@ pub const SIG = switch (native_os) {
29092913 pub const UNBLOCK = 2;
29102914 pub const SETMASK = 3;
29112915
2912 pub const IOT = ABRT;
2913 pub const HUP = 1;
2914 pub const INT = 2;
2915 pub const QUIT = 3;
2916 pub const ILL = 4;
2917 pub const TRAP = 5;
2918 pub const ABRT = 6;
2919 pub const EMT = 7;
2920 pub const FPE = 8;
2921 pub const KILL = 9;
2922 pub const BUS = 10;
2923 pub const SEGV = 11;
2924 pub const SYS = 12;
2925 pub const PIPE = 13;
2926 pub const ALRM = 14;
2927 pub const TERM = 15;
2928 pub const URG = 16;
2929 pub const STOP = 17;
2930 pub const TSTP = 18;
2931 pub const CONT = 19;
2932 pub const CHLD = 20;
2933 pub const TTIN = 21;
2934 pub const TTOU = 22;
2935 pub const IO = 23;
2936 pub const XCPU = 24;
2937 pub const XFSZ = 25;
2938 pub const VTALRM = 26;
2939 pub const PROF = 27;
2940 pub const WINCH = 28;
2941 pub const INFO = 29;
2942 pub const USR1 = 30;
2943 pub const USR2 = 31;
2944 pub const THR = 32;
2945 pub const CKPT = 33;
2946 pub const CKPTEXIT = 34;
2947
29482916 pub const WORDS = 4;
2949 },
2950 .haiku => struct {
2917
2918 pub const IOT: SIG = .ABRT;
2919
2920 HUP = 1,
2921 INT = 2,
2922 QUIT = 3,
2923 ILL = 4,
2924 TRAP = 5,
2925 ABRT = 6,
2926 EMT = 7,
2927 FPE = 8,
2928 KILL = 9,
2929 BUS = 10,
2930 SEGV = 11,
2931 SYS = 12,
2932 PIPE = 13,
2933 ALRM = 14,
2934 TERM = 15,
2935 URG = 16,
2936 STOP = 17,
2937 TSTP = 18,
2938 CONT = 19,
2939 CHLD = 20,
2940 TTIN = 21,
2941 TTOU = 22,
2942 IO = 23,
2943 XCPU = 24,
2944 XFSZ = 25,
2945 VTALRM = 26,
2946 PROF = 27,
2947 WINCH = 28,
2948 INFO = 29,
2949 USR1 = 30,
2950 USR2 = 31,
2951 THR = 32,
2952 CKPT = 33,
2953 CKPTEXIT = 34,
2954 },
2955 .haiku => enum(u32) {
29512956 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
29522957 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);
29532958 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
29542959
29552960 pub const HOLD: ?Sigaction.handler_fn = @ptrFromInt(3);
29562961
2957 pub const HUP = 1;
2958 pub const INT = 2;
2959 pub const QUIT = 3;
2960 pub const ILL = 4;
2961 pub const CHLD = 5;
2962 pub const ABRT = 6;
2963 pub const IOT = ABRT;
2964 pub const PIPE = 7;
2965 pub const FPE = 8;
2966 pub const KILL = 9;
2967 pub const STOP = 10;
2968 pub const SEGV = 11;
2969 pub const CONT = 12;
2970 pub const TSTP = 13;
2971 pub const ALRM = 14;
2972 pub const TERM = 15;
2973 pub const TTIN = 16;
2974 pub const TTOU = 17;
2975 pub const USR1 = 18;
2976 pub const USR2 = 19;
2977 pub const WINCH = 20;
2978 pub const KILLTHR = 21;
2979 pub const TRAP = 22;
2980 pub const POLL = 23;
2981 pub const PROF = 24;
2982 pub const SYS = 25;
2983 pub const URG = 26;
2984 pub const VTALRM = 27;
2985 pub const XCPU = 28;
2986 pub const XFSZ = 29;
2987 pub const BUS = 30;
2988 pub const RESERVED1 = 31;
2989 pub const RESERVED2 = 32;
2990
29912962 pub const BLOCK = 1;
29922963 pub const UNBLOCK = 2;
29932964 pub const SETMASK = 3;
2965
2966 pub const IOT: SIG = .ABRT;
2967
2968 HUP = 1,
2969 INT = 2,
2970 QUIT = 3,
2971 ILL = 4,
2972 CHLD = 5,
2973 ABRT = 6,
2974 PIPE = 7,
2975 FPE = 8,
2976 KILL = 9,
2977 STOP = 10,
2978 SEGV = 11,
2979 CONT = 12,
2980 TSTP = 13,
2981 ALRM = 14,
2982 TERM = 15,
2983 TTIN = 16,
2984 TTOU = 17,
2985 USR1 = 18,
2986 USR2 = 19,
2987 WINCH = 20,
2988 KILLTHR = 21,
2989 TRAP = 22,
2990 POLL = 23,
2991 PROF = 24,
2992 SYS = 25,
2993 URG = 26,
2994 VTALRM = 27,
2995 XCPU = 28,
2996 XFSZ = 29,
2997 BUS = 30,
2998 RESERVED1 = 31,
2999 RESERVED2 = 32,
29943000 },
2995 .openbsd => struct {
3001 .openbsd => enum(u32) {
29963002 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
29973003 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);
29983004 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
29993005 pub const CATCH: ?Sigaction.handler_fn = @ptrFromInt(2);
30003006 pub const HOLD: ?Sigaction.handler_fn = @ptrFromInt(3);
30013007
3002 pub const HUP = 1;
3003 pub const INT = 2;
3004 pub const QUIT = 3;
3005 pub const ILL = 4;
3006 pub const TRAP = 5;
3007 pub const ABRT = 6;
3008 pub const IOT = ABRT;
3009 pub const EMT = 7;
3010 pub const FPE = 8;
3011 pub const KILL = 9;
3012 pub const BUS = 10;
3013 pub const SEGV = 11;
3014 pub const SYS = 12;
3015 pub const PIPE = 13;
3016 pub const ALRM = 14;
3017 pub const TERM = 15;
3018 pub const URG = 16;
3019 pub const STOP = 17;
3020 pub const TSTP = 18;
3021 pub const CONT = 19;
3022 pub const CHLD = 20;
3023 pub const TTIN = 21;
3024 pub const TTOU = 22;
3025 pub const IO = 23;
3026 pub const XCPU = 24;
3027 pub const XFSZ = 25;
3028 pub const VTALRM = 26;
3029 pub const PROF = 27;
3030 pub const WINCH = 28;
3031 pub const INFO = 29;
3032 pub const USR1 = 30;
3033 pub const USR2 = 31;
3034 pub const PWR = 32;
3035
30363008 pub const BLOCK = 1;
30373009 pub const UNBLOCK = 2;
30383010 pub const SETMASK = 3;
3011
3012 pub const IOT: SIG = .ABRT;
3013
3014 HUP = 1,
3015 INT = 2,
3016 QUIT = 3,
3017 ILL = 4,
3018 TRAP = 5,
3019 ABRT = 6,
3020 EMT = 7,
3021 FPE = 8,
3022 KILL = 9,
3023 BUS = 10,
3024 SEGV = 11,
3025 SYS = 12,
3026 PIPE = 13,
3027 ALRM = 14,
3028 TERM = 15,
3029 URG = 16,
3030 STOP = 17,
3031 TSTP = 18,
3032 CONT = 19,
3033 CHLD = 20,
3034 TTIN = 21,
3035 TTOU = 22,
3036 IO = 23,
3037 XCPU = 24,
3038 XFSZ = 25,
3039 VTALRM = 26,
3040 PROF = 27,
3041 WINCH = 28,
3042 INFO = 29,
3043 USR1 = 30,
3044 USR2 = 31,
3045 PWR = 32,
30393046 },
30403047 // https://github.com/SerenityOS/serenity/blob/046c23f567a17758d762a33bdf04bacbfd088f9f/Kernel/API/POSIX/signal.h
30413048 // https://github.com/SerenityOS/serenity/blob/046c23f567a17758d762a33bdf04bacbfd088f9f/Kernel/API/POSIX/signal_numbers.h
3042 .serenity => struct {
3049 .serenity => enum(u32) {
30433050 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
30443051 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
30453052 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);
......@@ -3048,39 +3055,39 @@ pub const SIG = switch (native_os) {
30483055 pub const UNBLOCK = 2;
30493056 pub const SETMASK = 3;
30503057
3051 pub const INVAL = 0;
3052 pub const HUP = 1;
3053 pub const INT = 2;
3054 pub const QUIT = 3;
3055 pub const ILL = 4;
3056 pub const TRAP = 5;
3057 pub const ABRT = 6;
3058 pub const BUS = 7;
3059 pub const FPE = 8;
3060 pub const KILL = 9;
3061 pub const USR1 = 10;
3062 pub const SEGV = 11;
3063 pub const USR2 = 12;
3064 pub const PIPE = 13;
3065 pub const ALRM = 14;
3066 pub const TERM = 15;
3067 pub const STKFLT = 16;
3068 pub const CHLD = 17;
3069 pub const CONT = 18;
3070 pub const STOP = 19;
3071 pub const TSTP = 20;
3072 pub const TTIN = 21;
3073 pub const TTOU = 22;
3074 pub const URG = 23;
3075 pub const XCPU = 24;
3076 pub const XFSZ = 25;
3077 pub const VTALRM = 26;
3078 pub const PROF = 27;
3079 pub const WINCH = 28;
3080 pub const IO = 29;
3081 pub const INFO = 30;
3082 pub const SYS = 31;
3083 pub const CANCEL = 32;
3058 INVAL = 0,
3059 HUP = 1,
3060 INT = 2,
3061 QUIT = 3,
3062 ILL = 4,
3063 TRAP = 5,
3064 ABRT = 6,
3065 BUS = 7,
3066 FPE = 8,
3067 KILL = 9,
3068 USR1 = 10,
3069 SEGV = 11,
3070 USR2 = 12,
3071 PIPE = 13,
3072 ALRM = 14,
3073 TERM = 15,
3074 STKFLT = 16,
3075 CHLD = 17,
3076 CONT = 18,
3077 STOP = 19,
3078 TSTP = 20,
3079 TTIN = 21,
3080 TTOU = 22,
3081 URG = 23,
3082 XCPU = 24,
3083 XFSZ = 25,
3084 VTALRM = 26,
3085 PROF = 27,
3086 WINCH = 28,
3087 IO = 29,
3088 INFO = 30,
3089 SYS = 31,
3090 CANCEL = 32,
30843091 },
30853092 else => void,
30863093};
......@@ -3117,8 +3124,8 @@ pub const SYS = switch (native_os) {
31173124
31183125/// A common format for the Sigaction struct across a variety of Linux flavors.
31193126const common_linux_Sigaction = extern struct {
3120 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;
3121 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;
3127 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3128 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
31223129
31233130 handler: extern union {
31243131 handler: ?handler_fn,
......@@ -3139,8 +3146,8 @@ pub const Sigaction = switch (native_os) {
31393146 => if (builtin.target.abi.isMusl())
31403147 common_linux_Sigaction
31413148 else if (builtin.target.ptrBitWidth() == 64) extern struct {
3142 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;
3143 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;
3149 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3150 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
31443151
31453152 flags: c_uint,
31463153 handler: extern union {
......@@ -3150,8 +3157,8 @@ pub const Sigaction = switch (native_os) {
31503157 mask: sigset_t,
31513158 restorer: ?*const fn () callconv(.c) void = null,
31523159 } else extern struct {
3153 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;
3154 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;
3160 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3161 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
31553162
31563163 flags: c_uint,
31573164 handler: extern union {
......@@ -3163,8 +3170,8 @@ pub const Sigaction = switch (native_os) {
31633170 __resv: [1]c_int = .{0},
31643171 },
31653172 .s390x => if (builtin.abi == .gnu) extern struct {
3166 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;
3167 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;
3173 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3174 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
31683175
31693176 handler: extern union {
31703177 handler: ?handler_fn,
......@@ -3179,8 +3186,8 @@ pub const Sigaction = switch (native_os) {
31793186 },
31803187 .emscripten => emscripten.Sigaction,
31813188 .netbsd, .macos, .ios, .tvos, .watchos, .visionos => extern struct {
3182 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;
3183 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;
3189 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3190 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
31843191
31853192 handler: extern union {
31863193 handler: ?handler_fn,
......@@ -3190,8 +3197,8 @@ pub const Sigaction = switch (native_os) {
31903197 flags: c_uint,
31913198 },
31923199 .dragonfly, .freebsd => extern struct {
3193 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;
3194 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;
3200 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3201 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
31953202
31963203 /// signal handler
31973204 handler: extern union {
......@@ -3204,8 +3211,8 @@ pub const Sigaction = switch (native_os) {
32043211 mask: sigset_t,
32053212 },
32063213 .illumos => extern struct {
3207 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;
3208 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;
3214 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3215 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
32093216
32103217 /// signal options
32113218 flags: c_uint,
......@@ -3218,8 +3225,8 @@ pub const Sigaction = switch (native_os) {
32183225 mask: sigset_t,
32193226 },
32203227 .haiku => extern struct {
3221 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;
3222 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;
3228 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3229 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
32233230
32243231 /// signal handler
32253232 handler: extern union {
......@@ -3237,8 +3244,8 @@ pub const Sigaction = switch (native_os) {
32373244 userdata: *allowzero anyopaque = undefined,
32383245 },
32393246 .openbsd => extern struct {
3240 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;
3241 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;
3247 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3248 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
32423249
32433250 /// signal handler
32443251 handler: extern union {
......@@ -3252,8 +3259,8 @@ pub const Sigaction = switch (native_os) {
32523259 },
32533260 // https://github.com/SerenityOS/serenity/blob/ec492a1a0819e6239ea44156825c4ee7234ca3db/Kernel/API/POSIX/signal.h#L39-L46
32543261 .serenity => extern struct {
3255 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;
3256 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;
3262 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3263 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
32573264
32583265 handler: extern union {
32593266 handler: ?handler_fn,
......@@ -4087,8 +4094,9 @@ pub const linger = switch (native_os) {
40874094 },
40884095 else => void,
40894096};
4097
40904098pub const msghdr = switch (native_os) {
4091 .linux => linux.msghdr,
4099 .linux => if (@bitSizeOf(usize) > @bitSizeOf(i32) and builtin.abi.isMusl()) posix_msghdr else linux.msghdr,
40924100 .openbsd,
40934101 .emscripten,
40944102 .dragonfly,
......@@ -4102,36 +4110,28 @@ pub const msghdr = switch (native_os) {
41024110 .tvos,
41034111 .visionos,
41044112 .watchos,
4105 => extern struct {
4106 /// optional address
4107 name: ?*sockaddr,
4108 /// size of address
4109 namelen: socklen_t,
4110 /// scatter/gather array
4111 iov: [*]iovec,
4112 /// # elements in iov
4113 iovlen: i32,
4114 /// ancillary data
4115 control: ?*anyopaque,
4116 /// ancillary data buffer len
4117 controllen: socklen_t,
4118 /// flags on received message
4119 flags: i32,
4120 },
4121 // https://github.com/SerenityOS/serenity/blob/ac44ec5ebc707f9dd0c3d4759a1e17e91db5d74f/Kernel/API/POSIX/sys/socket.h#L74-L82
4122 .serenity => extern struct {
4123 name: ?*anyopaque,
4124 namelen: socklen_t,
4125 iov: [*]iovec,
4126 iovlen: c_int,
4127 control: ?*anyopaque,
4128 controllen: socklen_t,
4129 flags: c_int,
4130 },
4113 .serenity, // https://github.com/SerenityOS/serenity/blob/ac44ec5ebc707f9dd0c3d4759a1e17e91db5d74f/Kernel/API/POSIX/sys/socket.h#L74-L82
4114 => posix_msghdr,
41314115 else => void,
41324116};
4117
4118/// https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_socket.h.html
4119const posix_msghdr = extern struct {
4120 name: ?*sockaddr,
4121 namelen: socklen_t,
4122 iov: [*]iovec,
4123 pad0: if (@sizeOf(usize) == 8 and native_endian == .big) u32 else u0 = 0,
4124 iovlen: u32,
4125 pad1: if (@sizeOf(usize) == 8 and native_endian == .little) u32 else u0 = 0,
4126 control: ?*anyopaque,
4127 pad2: if (@sizeOf(usize) == 8 and native_endian == .big) u32 else u0 = 0,
4128 controllen: socklen_t,
4129 pad3: if (@sizeOf(usize) == 8 and native_endian == .little) u32 else u0 = 0,
4130 flags: u32,
4131};
4132
41334133pub const msghdr_const = switch (native_os) {
4134 .linux => linux.msghdr_const,
4134 .linux => if (@bitSizeOf(usize) > @bitSizeOf(i32) and builtin.abi.isMusl()) posix_msghdr_const else linux.msghdr_const,
41354135 .openbsd,
41364136 .emscripten,
41374137 .dragonfly,
......@@ -4145,36 +4145,37 @@ pub const msghdr_const = switch (native_os) {
41454145 .tvos,
41464146 .visionos,
41474147 .watchos,
4148 => extern struct {
4149 /// optional address
4150 name: ?*const sockaddr,
4151 /// size of address
4152 namelen: socklen_t,
4153 /// scatter/gather array
4154 iov: [*]const iovec_const,
4155 /// # elements in iov
4156 iovlen: u32,
4157 /// ancillary data
4158 control: ?*const anyopaque,
4159 /// ancillary data buffer len
4160 controllen: socklen_t,
4161 /// flags on received message
4162 flags: i32,
4163 },
4164 .serenity => extern struct {
4165 name: ?*const anyopaque,
4166 namelen: socklen_t,
4167 iov: [*]const iovec_const,
4168 iovlen: c_uint,
4169 control: ?*const anyopaque,
4170 controllen: socklen_t,
4171 flags: c_int,
4172 },
4148 .serenity,
4149 => posix_msghdr_const,
41734150 else => void,
41744151};
4152
4153const posix_msghdr_const = extern struct {
4154 name: ?*const sockaddr,
4155 namelen: socklen_t,
4156 iov: [*]const iovec_const,
4157 pad0: if (@sizeOf(usize) == 8 and native_endian == .big) u32 else u0 = 0,
4158 iovlen: u32,
4159 pad1: if (@sizeOf(usize) == 8 and native_endian == .little) u32 else u0 = 0,
4160 control: ?*const anyopaque,
4161 pad2: if (@sizeOf(usize) == 8 and native_endian == .big) u32 else u0 = 0,
4162 controllen: socklen_t,
4163 pad3: if (@sizeOf(usize) == 8 and native_endian == .little) u32 else u0 = 0,
4164 flags: u32,
4165};
4166
4167pub const mmsghdr = switch (native_os) {
4168 .linux => linux.mmsghdr,
4169 else => extern struct {
4170 hdr: msghdr,
4171 len: u32,
4172 },
4173};
4174
41754175pub const cmsghdr = switch (native_os) {
4176 .linux => if (@bitSizeOf(usize) > @bitSizeOf(i32) and builtin.abi.isMusl()) posix_cmsghdr else linux.cmsghdr,
41764177 // https://github.com/emscripten-core/emscripten/blob/96371ed7888fc78c040179f4d4faa82a6a07a116/system/lib/libc/musl/include/sys/socket.h#L44
4177 .linux, .emscripten => linux.cmsghdr,
4178 .emscripten => linux.cmsghdr,
41784179 // https://github.com/freebsd/freebsd-src/blob/b197d2abcb6895d78bc9df8404e374397aa44748/sys/sys/socket.h#L492
41794180 .freebsd,
41804181 // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/107c0518337ba90e7fa49e74845d8d44320c9a6d/sys/sys/socket.h#L452
......@@ -4196,13 +4197,19 @@ pub const cmsghdr = switch (native_os) {
41964197 .tvos,
41974198 .visionos,
41984199 .watchos,
4199 => extern struct {
4200 len: socklen_t,
4201 level: c_int,
4202 type: c_int,
4203 },
4200 => posix_cmsghdr,
4201
42044202 else => void,
42054203};
4204
4205const posix_cmsghdr = extern struct {
4206 pad0: if (@sizeOf(usize) == 8 and native_endian == .big) u32 else u0 = 0,
4207 len: socklen_t,
4208 pad1: if (@sizeOf(usize) == 8 and native_endian == .little) u32 else u0 = 0,
4209 level: c_int,
4210 type: c_int,
4211};
4212
42064213pub const nfds_t = switch (native_os) {
42074214 .linux => linux.nfds_t,
42084215 .emscripten => emscripten.nfds_t,
......@@ -4443,7 +4450,7 @@ pub const siginfo_t = switch (native_os) {
44434450 .linux => linux.siginfo_t,
44444451 .emscripten => emscripten.siginfo_t,
44454452 .driverkit, .macos, .ios, .tvos, .watchos, .visionos => extern struct {
4446 signo: c_int,
4453 signo: SIG,
44474454 errno: c_int,
44484455 code: c_int,
44494456 pid: pid_t,
......@@ -4459,7 +4466,7 @@ pub const siginfo_t = switch (native_os) {
44594466 },
44604467 .freebsd => extern struct {
44614468 // Signal number.
4462 signo: c_int,
4469 signo: SIG,
44634470 // Errno association.
44644471 errno: c_int,
44654472 /// Signal code.
......@@ -4502,7 +4509,7 @@ pub const siginfo_t = switch (native_os) {
45024509 },
45034510 },
45044511 .illumos => extern struct {
4505 signo: c_int,
4512 signo: SIG,
45064513 code: c_int,
45074514 errno: c_int,
45084515 // 64bit architectures insert 4bytes of padding here, this is done by
......@@ -4559,7 +4566,7 @@ pub const siginfo_t = switch (native_os) {
45594566 info: netbsd._ksiginfo,
45604567 },
45614568 .dragonfly => extern struct {
4562 signo: c_int,
4569 signo: SIG,
45634570 errno: c_int,
45644571 code: c_int,
45654572 pid: c_int,
......@@ -4571,7 +4578,7 @@ pub const siginfo_t = switch (native_os) {
45714578 __spare__: [7]c_int,
45724579 },
45734580 .haiku => extern struct {
4574 signo: i32,
4581 signo: SIG,
45754582 code: i32,
45764583 errno: i32,
45774584
......@@ -4580,7 +4587,7 @@ pub const siginfo_t = switch (native_os) {
45804587 addr: *allowzero anyopaque,
45814588 },
45824589 .openbsd => extern struct {
4583 signo: c_int,
4590 signo: SIG,
45844591 code: c_int,
45854592 errno: c_int,
45864593 data: extern union {
......@@ -4615,7 +4622,7 @@ pub const siginfo_t = switch (native_os) {
46154622 },
46164623 // https://github.com/SerenityOS/serenity/blob/ec492a1a0819e6239ea44156825c4ee7234ca3db/Kernel/API/POSIX/signal.h#L27-L37
46174624 .serenity => extern struct {
4618 signo: c_int,
4625 signo: SIG,
46194626 code: c_int,
46204627 errno: c_int,
46214628 pid: pid_t,
......@@ -6865,7 +6872,7 @@ pub const IFNAMESIZE = switch (native_os) {
68656872 // https://github.com/SerenityOS/serenity/blob/9882848e0bf783dfc8e8a6d887a848d70d9c58f4/Kernel/API/POSIX/net/if.h#L50
68666873 .openbsd, .dragonfly, .netbsd, .freebsd, .macos, .ios, .tvos, .watchos, .visionos, .serenity => 16,
68676874 .illumos => 32,
6868 else => void,
6875 else => {},
68696876};
68706877
68716878pub const stack_t = switch (native_os) {
......@@ -10591,7 +10598,7 @@ pub extern "c" fn lseek(fd: fd_t, offset: off_t, whence: whence_t) off_t;
1059110598pub extern "c" fn open(path: [*:0]const u8, oflag: O, ...) c_int;
1059210599pub extern "c" fn openat(fd: c_int, path: [*:0]const u8, oflag: O, ...) c_int;
1059310600pub extern "c" fn ftruncate(fd: c_int, length: off_t) c_int;
10594pub extern "c" fn raise(sig: c_int) c_int;
10601pub extern "c" fn raise(sig: SIG) c_int;
1059510602pub extern "c" fn read(fd: fd_t, buf: [*]u8, nbyte: usize) isize;
1059610603pub extern "c" fn readv(fd: c_int, iov: [*]const iovec, iovcnt: c_uint) isize;
1059710604pub extern "c" fn pread(fd: fd_t, buf: [*]u8, nbyte: usize, offset: off_t) isize;
......@@ -10683,6 +10690,7 @@ pub extern "c" fn sendto(
1068310690 addrlen: socklen_t,
1068410691) isize;
1068510692pub extern "c" fn sendmsg(sockfd: fd_t, msg: *const msghdr_const, flags: u32) isize;
10693pub extern "c" fn sendmmsg(sockfd: fd_t, msgvec: [*]mmsghdr, n: c_uint, flags: u32) c_int;
1068610694
1068710695pub extern "c" fn recv(
1068810696 sockfd: fd_t,
......@@ -10708,7 +10716,7 @@ pub const recvmsg = switch (native_os) {
1070810716 else => private.recvmsg,
1070910717};
1071010718
10711pub extern "c" fn kill(pid: pid_t, sig: c_int) c_int;
10719pub extern "c" fn kill(pid: pid_t, sig: SIG) c_int;
1071210720
1071310721pub extern "c" fn setuid(uid: uid_t) c_int;
1071410722pub extern "c" fn setgid(gid: gid_t) c_int;
......@@ -10772,6 +10780,8 @@ pub const pthread_setname_np = switch (native_os) {
1077210780};
1077310781
1077410782pub extern "c" fn pthread_getname_np(thread: pthread_t, name: [*:0]u8, len: usize) c_int;
10783pub extern "c" fn pthread_kill(pthread_t, signal: SIG) c_int;
10784
1077510785pub const pthread_threadid_np = switch (native_os) {
1077610786 .macos, .ios, .tvos, .watchos, .visionos => private.pthread_threadid_np,
1077710787 else => {},
......@@ -10876,13 +10886,13 @@ pub extern "c" fn dn_expand(
1087610886 length: c_int,
1087710887) c_int;
1087810888
10879pub const PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{};
10889pub const PTHREAD_MUTEX_INITIALIZER: pthread_mutex_t = .{};
1088010890pub extern "c" fn pthread_mutex_lock(mutex: *pthread_mutex_t) E;
1088110891pub extern "c" fn pthread_mutex_unlock(mutex: *pthread_mutex_t) E;
1088210892pub extern "c" fn pthread_mutex_trylock(mutex: *pthread_mutex_t) E;
1088310893pub extern "c" fn pthread_mutex_destroy(mutex: *pthread_mutex_t) E;
1088410894
10885pub const PTHREAD_COND_INITIALIZER = pthread_cond_t{};
10895pub const PTHREAD_COND_INITIALIZER: pthread_cond_t = .{};
1088610896pub extern "c" fn pthread_cond_wait(noalias cond: *pthread_cond_t, noalias mutex: *pthread_mutex_t) E;
1088710897pub extern "c" fn pthread_cond_timedwait(noalias cond: *pthread_cond_t, noalias mutex: *pthread_mutex_t, noalias abstime: *const timespec) E;
1088810898pub extern "c" fn pthread_cond_signal(cond: *pthread_cond_t) E;
......@@ -11363,12 +11373,12 @@ const private = struct {
1136311373 extern "c" fn recvmsg(sockfd: fd_t, msg: *msghdr, flags: u32) isize;
1136411374 extern "c" fn sched_yield() c_int;
1136511375 extern "c" fn sendfile(out_fd: fd_t, in_fd: fd_t, offset: ?*off_t, count: usize) isize;
11366 extern "c" fn sigaction(sig: c_int, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) c_int;
11367 extern "c" fn sigdelset(set: ?*sigset_t, signo: c_int) c_int;
11368 extern "c" fn sigaddset(set: ?*sigset_t, signo: c_int) c_int;
11376 extern "c" fn sigaction(sig: SIG, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) c_int;
11377 extern "c" fn sigdelset(set: ?*sigset_t, signo: SIG) c_int;
11378 extern "c" fn sigaddset(set: ?*sigset_t, signo: SIG) c_int;
1136911379 extern "c" fn sigfillset(set: ?*sigset_t) c_int;
1137011380 extern "c" fn sigemptyset(set: ?*sigset_t) c_int;
11371 extern "c" fn sigismember(set: ?*const sigset_t, signo: c_int) c_int;
11381 extern "c" fn sigismember(set: ?*const sigset_t, signo: SIG) c_int;
1137211382 extern "c" fn sigprocmask(how: c_int, noalias set: ?*const sigset_t, noalias oset: ?*sigset_t) c_int;
1137311383 extern "c" fn socket(domain: c_uint, sock_type: c_uint, protocol: c_uint) c_int;
1137411384 extern "c" fn socketpair(domain: c_uint, sock_type: c_uint, protocol: c_uint, sv: *[2]fd_t) c_int;
......@@ -11420,7 +11430,7 @@ const private = struct {
1142011430 extern "c" fn __libc_thr_yield() c_int;
1142111431 extern "c" fn __msync13(addr: *align(page_size) const anyopaque, len: usize, flags: c_int) c_int;
1142211432 extern "c" fn __nanosleep50(rqtp: *const timespec, rmtp: ?*timespec) c_int;
11423 extern "c" fn __sigaction14(sig: c_int, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) c_int;
11433 extern "c" fn __sigaction14(sig: SIG, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) c_int;
1142411434 extern "c" fn __sigemptyset14(set: ?*sigset_t) c_int;
1142511435 extern "c" fn __sigfillset14(set: ?*sigset_t) c_int;
1142611436 extern "c" fn __sigprocmask14(how: c_int, noalias set: ?*const sigset_t, noalias oset: ?*sigset_t) c_int;
lib/std/crypto/Certificate/Bundle.zig+65-49
......@@ -4,6 +4,20 @@
44//! concatenated together in the `bytes` array. The `map` field contains an
55//! index from the DER-encoded subject name to the index of the containing
66//! certificate within `bytes`.
7const Bundle = @This();
8const builtin = @import("builtin");
9
10const std = @import("../../std.zig");
11const Io = std.Io;
12const assert = std.debug.assert;
13const fs = std.fs;
14const mem = std.mem;
15const crypto = std.crypto;
16const Allocator = std.mem.Allocator;
17const Certificate = std.crypto.Certificate;
18const der = Certificate.der;
19
20const base64 = std.base64.standard.decoderWithIgnore(" \t\r\n");
721
822/// The key is the contents slice of the subject.
923map: std.HashMapUnmanaged(der.Element.Slice, u32, MapContext, std.hash_map.default_max_load_percentage) = .empty,
......@@ -56,18 +70,18 @@ pub const RescanError = RescanLinuxError || RescanMacError || RescanWithPathErro
5670/// file system standard locations for certificates.
5771/// For operating systems that do not have standard CA installations to be
5872/// found, this function clears the set of certificates.
59pub fn rescan(cb: *Bundle, gpa: Allocator) RescanError!void {
73pub fn rescan(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanError!void {
6074 switch (builtin.os.tag) {
61 .linux => return rescanLinux(cb, gpa),
62 .macos => return rescanMac(cb, gpa),
63 .freebsd, .openbsd => return rescanWithPath(cb, gpa, "/etc/ssl/cert.pem"),
64 .netbsd => return rescanWithPath(cb, gpa, "/etc/openssl/certs/ca-certificates.crt"),
65 .dragonfly => return rescanWithPath(cb, gpa, "/usr/local/etc/ssl/cert.pem"),
66 .illumos => return rescanWithPath(cb, gpa, "/etc/ssl/cacert.pem"),
67 .haiku => return rescanWithPath(cb, gpa, "/boot/system/data/ssl/CARootCertificates.pem"),
75 .linux => return rescanLinux(cb, gpa, io, now),
76 .macos => return rescanMac(cb, gpa, io, now),
77 .freebsd, .openbsd => return rescanWithPath(cb, gpa, io, now, "/etc/ssl/cert.pem"),
78 .netbsd => return rescanWithPath(cb, gpa, io, now, "/etc/openssl/certs/ca-certificates.crt"),
79 .dragonfly => return rescanWithPath(cb, gpa, io, now, "/usr/local/etc/ssl/cert.pem"),
80 .illumos => return rescanWithPath(cb, gpa, io, now, "/etc/ssl/cacert.pem"),
81 .haiku => return rescanWithPath(cb, gpa, io, now, "/boot/system/data/ssl/CARootCertificates.pem"),
6882 // https://github.com/SerenityOS/serenity/blob/222acc9d389bc6b490d4c39539761b043a4bfcb0/Ports/ca-certificates/package.sh#L19
69 .serenity => return rescanWithPath(cb, gpa, "/etc/ssl/certs/ca-certificates.crt"),
70 .windows => return rescanWindows(cb, gpa),
83 .serenity => return rescanWithPath(cb, gpa, io, now, "/etc/ssl/certs/ca-certificates.crt"),
84 .windows => return rescanWindows(cb, gpa, io, now),
7185 else => {},
7286 }
7387}
......@@ -77,7 +91,7 @@ const RescanMacError = @import("Bundle/macos.zig").RescanMacError;
7791
7892const RescanLinuxError = AddCertsFromFilePathError || AddCertsFromDirPathError;
7993
80fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {
94fn rescanLinux(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanLinuxError!void {
8195 // Possible certificate files; stop after finding one.
8296 const cert_file_paths = [_][]const u8{
8397 "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
......@@ -100,7 +114,7 @@ fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {
100114
101115 scan: {
102116 for (cert_file_paths) |cert_file_path| {
103 if (addCertsFromFilePathAbsolute(cb, gpa, cert_file_path)) |_| {
117 if (addCertsFromFilePathAbsolute(cb, gpa, io, now, cert_file_path)) |_| {
104118 break :scan;
105119 } else |err| switch (err) {
106120 error.FileNotFound => continue,
......@@ -109,7 +123,7 @@ fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {
109123 }
110124
111125 for (cert_dir_paths) |cert_dir_path| {
112 addCertsFromDirPathAbsolute(cb, gpa, cert_dir_path) catch |err| switch (err) {
126 addCertsFromDirPathAbsolute(cb, gpa, io, now, cert_dir_path) catch |err| switch (err) {
113127 error.FileNotFound => continue,
114128 else => |e| return e,
115129 };
......@@ -121,19 +135,21 @@ fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {
121135
122136const RescanWithPathError = AddCertsFromFilePathError;
123137
124fn rescanWithPath(cb: *Bundle, gpa: Allocator, cert_file_path: []const u8) RescanWithPathError!void {
138fn rescanWithPath(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp, cert_file_path: []const u8) RescanWithPathError!void {
125139 cb.bytes.clearRetainingCapacity();
126140 cb.map.clearRetainingCapacity();
127 try addCertsFromFilePathAbsolute(cb, gpa, cert_file_path);
141 try addCertsFromFilePathAbsolute(cb, gpa, io, now, cert_file_path);
128142 cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
129143}
130144
131145const RescanWindowsError = Allocator.Error || ParseCertError || std.posix.UnexpectedError || error{FileNotFound};
132146
133fn rescanWindows(cb: *Bundle, gpa: Allocator) RescanWindowsError!void {
147fn rescanWindows(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanWindowsError!void {
134148 cb.bytes.clearRetainingCapacity();
135149 cb.map.clearRetainingCapacity();
136150
151 _ = io;
152
137153 const w = std.os.windows;
138154 const GetLastError = w.GetLastError;
139155 const root = [4:0]u16{ 'R', 'O', 'O', 'T' };
......@@ -143,7 +159,7 @@ fn rescanWindows(cb: *Bundle, gpa: Allocator) RescanWindowsError!void {
143159 };
144160 defer _ = w.crypt32.CertCloseStore(store, 0);
145161
146 const now_sec = std.time.timestamp();
162 const now_sec = now.toSeconds();
147163
148164 var ctx = w.crypt32.CertEnumCertificatesInStore(store, null);
149165 while (ctx) |context| : (ctx = w.crypt32.CertEnumCertificatesInStore(store, ctx)) {
......@@ -160,28 +176,31 @@ pub const AddCertsFromDirPathError = fs.File.OpenError || AddCertsFromDirError;
160176pub fn addCertsFromDirPath(
161177 cb: *Bundle,
162178 gpa: Allocator,
179 io: Io,
163180 dir: fs.Dir,
164181 sub_dir_path: []const u8,
165182) AddCertsFromDirPathError!void {
166183 var iterable_dir = try dir.openDir(sub_dir_path, .{ .iterate = true });
167184 defer iterable_dir.close();
168 return addCertsFromDir(cb, gpa, iterable_dir);
185 return addCertsFromDir(cb, gpa, io, iterable_dir);
169186}
170187
171188pub fn addCertsFromDirPathAbsolute(
172189 cb: *Bundle,
173190 gpa: Allocator,
191 io: Io,
192 now: Io.Timestamp,
174193 abs_dir_path: []const u8,
175194) AddCertsFromDirPathError!void {
176195 assert(fs.path.isAbsolute(abs_dir_path));
177196 var iterable_dir = try fs.openDirAbsolute(abs_dir_path, .{ .iterate = true });
178197 defer iterable_dir.close();
179 return addCertsFromDir(cb, gpa, iterable_dir);
198 return addCertsFromDir(cb, gpa, io, now, iterable_dir);
180199}
181200
182201pub const AddCertsFromDirError = AddCertsFromFilePathError;
183202
184pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.Dir) AddCertsFromDirError!void {
203pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp, iterable_dir: fs.Dir) AddCertsFromDirError!void {
185204 var it = iterable_dir.iterate();
186205 while (try it.next()) |entry| {
187206 switch (entry.kind) {
......@@ -189,32 +208,37 @@ pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.Dir) AddCer
189208 else => continue,
190209 }
191210
192 try addCertsFromFilePath(cb, gpa, iterable_dir, entry.name);
211 try addCertsFromFilePath(cb, gpa, io, now, iterable_dir.adaptToNewApi(), entry.name);
193212 }
194213}
195214
196pub const AddCertsFromFilePathError = fs.File.OpenError || AddCertsFromFileError;
215pub const AddCertsFromFilePathError = fs.File.OpenError || AddCertsFromFileError || Io.Clock.Error;
197216
198217pub fn addCertsFromFilePathAbsolute(
199218 cb: *Bundle,
200219 gpa: Allocator,
220 io: Io,
221 now: Io.Timestamp,
201222 abs_file_path: []const u8,
202223) AddCertsFromFilePathError!void {
203 assert(fs.path.isAbsolute(abs_file_path));
204224 var file = try fs.openFileAbsolute(abs_file_path, .{});
205225 defer file.close();
206 return addCertsFromFile(cb, gpa, file);
226 var file_reader = file.reader(io, &.{});
227 return addCertsFromFile(cb, gpa, &file_reader, now.toSeconds());
207228}
208229
209230pub fn addCertsFromFilePath(
210231 cb: *Bundle,
211232 gpa: Allocator,
212 dir: fs.Dir,
233 io: Io,
234 now: Io.Timestamp,
235 dir: Io.Dir,
213236 sub_file_path: []const u8,
214237) AddCertsFromFilePathError!void {
215 var file = try dir.openFile(sub_file_path, .{});
216 defer file.close();
217 return addCertsFromFile(cb, gpa, file);
238 var file = try dir.openFile(io, sub_file_path, .{});
239 defer file.close(io);
240 var file_reader = file.reader(io, &.{});
241 return addCertsFromFile(cb, gpa, &file_reader, now.toSeconds());
218242}
219243
220244pub const AddCertsFromFileError = Allocator.Error ||
......@@ -222,10 +246,10 @@ pub const AddCertsFromFileError = Allocator.Error ||
222246 fs.File.ReadError ||
223247 ParseCertError ||
224248 std.base64.Error ||
225 error{ CertificateAuthorityBundleTooBig, MissingEndCertificateMarker };
249 error{ CertificateAuthorityBundleTooBig, MissingEndCertificateMarker, Streaming };
226250
227pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) AddCertsFromFileError!void {
228 const size = try file.getEndPos();
251pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file_reader: *Io.File.Reader, now_sec: i64) AddCertsFromFileError!void {
252 const size = try file_reader.getSize();
229253
230254 // We borrow `bytes` as a temporary buffer for the base64-encoded data.
231255 // This is possible by computing the decoded length and reserving the space
......@@ -236,14 +260,14 @@ pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) AddCertsFrom
236260 try cb.bytes.ensureUnusedCapacity(gpa, needed_capacity);
237261 const end_reserved: u32 = @intCast(cb.bytes.items.len + decoded_size_upper_bound);
238262 const buffer = cb.bytes.allocatedSlice()[end_reserved..];
239 const end_index = try file.readAll(buffer);
263 const end_index = file_reader.interface.readSliceShort(buffer) catch |err| switch (err) {
264 error.ReadFailed => return file_reader.err.?,
265 };
240266 const encoded_bytes = buffer[0..end_index];
241267
242268 const begin_marker = "-----BEGIN CERTIFICATE-----";
243269 const end_marker = "-----END CERTIFICATE-----";
244270
245 const now_sec = std.time.timestamp();
246
247271 var start_index: usize = 0;
248272 while (mem.indexOfPos(u8, encoded_bytes, start_index, begin_marker)) |begin_marker_start| {
249273 const cert_start = begin_marker_start + begin_marker.len;
......@@ -288,19 +312,6 @@ pub fn parseCert(cb: *Bundle, gpa: Allocator, decoded_start: u32, now_sec: i64)
288312 }
289313}
290314
291const builtin = @import("builtin");
292const std = @import("../../std.zig");
293const assert = std.debug.assert;
294const fs = std.fs;
295const mem = std.mem;
296const crypto = std.crypto;
297const Allocator = std.mem.Allocator;
298const Certificate = std.crypto.Certificate;
299const der = Certificate.der;
300const Bundle = @This();
301
302const base64 = std.base64.standard.decoderWithIgnore(" \t\r\n");
303
304315const MapContext = struct {
305316 cb: *const Bundle,
306317
......@@ -321,8 +332,13 @@ const MapContext = struct {
321332test "scan for OS-provided certificates" {
322333 if (builtin.os.tag == .wasi) return error.SkipZigTest;
323334
335 const io = std.testing.io;
336 const gpa = std.testing.allocator;
337
324338 var bundle: Bundle = .{};
325 defer bundle.deinit(std.testing.allocator);
339 defer bundle.deinit(gpa);
340
341 const now = try Io.Clock.real.now(io);
326342
327 try bundle.rescan(std.testing.allocator);
343 try bundle.rescan(gpa, io, now);
328344}
lib/std/crypto/Certificate/Bundle/macos.zig+6-6
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23const assert = std.debug.assert;
34const fs = std.fs;
45const mem = std.mem;
......@@ -7,7 +8,7 @@ const Bundle = @import("../Bundle.zig");
78
89pub const RescanMacError = Allocator.Error || fs.File.OpenError || fs.File.ReadError || fs.File.SeekError || Bundle.ParseCertError || error{EndOfStream};
910
10pub fn rescanMac(cb: *Bundle, gpa: Allocator) RescanMacError!void {
11pub fn rescanMac(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanMacError!void {
1112 cb.bytes.clearRetainingCapacity();
1213 cb.map.clearRetainingCapacity();
1314
......@@ -16,6 +17,7 @@ pub fn rescanMac(cb: *Bundle, gpa: Allocator) RescanMacError!void {
1617 "/Library/Keychains/System.keychain",
1718 };
1819
20 _ = io; // TODO migrate file system to use std.Io
1921 for (keychain_paths) |keychain_path| {
2022 const bytes = std.fs.cwd().readFileAlloc(keychain_path, gpa, .limited(std.math.maxInt(u32))) catch |err| switch (err) {
2123 error.StreamTooLong => return error.FileTooBig,
......@@ -23,8 +25,8 @@ pub fn rescanMac(cb: *Bundle, gpa: Allocator) RescanMacError!void {
2325 };
2426 defer gpa.free(bytes);
2527
26 var reader: std.Io.Reader = .fixed(bytes);
27 scanReader(cb, gpa, &reader) catch |err| switch (err) {
28 var reader: Io.Reader = .fixed(bytes);
29 scanReader(cb, gpa, &reader, now.toSeconds()) catch |err| switch (err) {
2830 error.ReadFailed => unreachable, // prebuffered
2931 else => |e| return e,
3032 };
......@@ -33,7 +35,7 @@ pub fn rescanMac(cb: *Bundle, gpa: Allocator) RescanMacError!void {
3335 cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
3436}
3537
36fn scanReader(cb: *Bundle, gpa: Allocator, reader: *std.Io.Reader) !void {
38fn scanReader(cb: *Bundle, gpa: Allocator, reader: *Io.Reader, now_sec: i64) !void {
3739 const db_header = try reader.takeStruct(ApplDbHeader, .big);
3840 assert(mem.eql(u8, &db_header.signature, "kych"));
3941
......@@ -49,8 +51,6 @@ fn scanReader(cb: *Bundle, gpa: Allocator, reader: *std.Io.Reader) !void {
4951 table_list[table_idx] = try reader.takeInt(u32, .big);
5052 }
5153
52 const now_sec = std.time.timestamp();
53
5454 for (table_list) |table_offset| {
5555 reader.seek = db_header.schema_offset + table_offset;
5656
lib/std/crypto/tls/Client.zig+12-8
......@@ -105,6 +105,14 @@ pub const Options = struct {
105105 /// Verify that the server certificate is authorized by a given ca bundle.
106106 bundle: Certificate.Bundle,
107107 },
108 write_buffer: []u8,
109 read_buffer: []u8,
110 /// Cryptographically secure random bytes. The pointer is not captured; data is only
111 /// read during `init`.
112 entropy: *const [176]u8,
113 /// Current time according to the wall clock / calendar, in seconds.
114 realtime_now_seconds: i64,
115
108116 /// If non-null, ssl secrets are logged to this stream. Creating such a log file allows
109117 /// other programs with access to that file to decrypt all traffic over this connection.
110118 ///
......@@ -120,8 +128,6 @@ pub const Options = struct {
120128 /// application layer itself verifies that the amount of data received equals
121129 /// the amount of data expected, such as HTTP with the Content-Length header.
122130 allow_truncation_attacks: bool = false,
123 write_buffer: []u8,
124 read_buffer: []u8,
125131 /// Populated when `error.TlsAlert` is returned from `init`.
126132 alert: ?*tls.Alert = null,
127133};
......@@ -189,14 +195,12 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
189195 };
190196 const host_len: u16 = @intCast(host.len);
191197
192 var random_buffer: [176]u8 = undefined;
193 crypto.random.bytes(&random_buffer);
194 const client_hello_rand = random_buffer[0..32].*;
198 const client_hello_rand = options.entropy[0..32].*;
195199 var key_seq: u64 = 0;
196200 var server_hello_rand: [32]u8 = undefined;
197 const legacy_session_id = random_buffer[32..64].*;
201 const legacy_session_id = options.entropy[32..64].*;
198202
199 var key_share = KeyShare.init(random_buffer[64..176].*) catch |err| switch (err) {
203 var key_share = KeyShare.init(options.entropy[64..176].*) catch |err| switch (err) {
200204 // Only possible to happen if the seed is all zeroes.
201205 error.IdentityElement => return error.InsufficientEntropy,
202206 };
......@@ -321,7 +325,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
321325 var handshake_cipher: tls.HandshakeCipher = undefined;
322326 var main_cert_pub_key: CertificatePublicKey = undefined;
323327 var tls12_negotiated_group: ?tls.NamedGroup = null;
324 const now_sec = std.time.timestamp();
328 const now_sec = options.realtime_now_seconds;
325329
326330 var cleartext_fragment_start: usize = 0;
327331 var cleartext_fragment_end: usize = 0;
lib/std/debug.zig+49-24
......@@ -1,4 +1,7 @@
11const std = @import("std.zig");
2const Io = std.Io;
3const Writer = std.Io.Writer;
4const tty = std.Io.tty;
25const math = std.math;
36const mem = std.mem;
47const posix = std.posix;
......@@ -7,12 +10,11 @@ const testing = std.testing;
710const Allocator = mem.Allocator;
811const File = std.fs.File;
912const windows = std.os.windows;
10const Writer = std.Io.Writer;
11const tty = std.Io.tty;
1213
1314const builtin = @import("builtin");
1415const native_arch = builtin.cpu.arch;
1516const native_os = builtin.os.tag;
17const StackTrace = std.builtin.StackTrace;
1618
1719const root = @import("root");
1820
......@@ -82,6 +84,7 @@ pub const SelfInfoError = error{
8284 /// The required debug info could not be read from disk due to some IO error.
8385 ReadFailed,
8486 OutOfMemory,
87 Canceled,
8588 Unexpected,
8689};
8790
......@@ -544,7 +547,7 @@ pub fn defaultPanic(
544547 stderr.print("panic: ", .{}) catch break :trace;
545548 } else {
546549 const current_thread_id = std.Thread.getCurrentId();
547 stderr.print("thread {} panic: ", .{current_thread_id}) catch break :trace;
550 stderr.print("thread {d} panic: ", .{current_thread_id}) catch break :trace;
548551 }
549552 stderr.print("{s}\n", .{msg}) catch break :trace;
550553
......@@ -606,8 +609,8 @@ pub const StackUnwindOptions = struct {
606609/// the given buffer, so `addr_buf` must have a lifetime at least equal to the `StackTrace`.
607610///
608611/// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it.
609pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) std.builtin.StackTrace {
610 const empty_trace: std.builtin.StackTrace = .{ .index = 0, .instruction_addresses = &.{} };
612pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) StackTrace {
613 const empty_trace: StackTrace = .{ .index = 0, .instruction_addresses = &.{} };
611614 if (!std.options.allow_stack_tracing) return empty_trace;
612615 var it = StackIterator.init(options.context) catch return empty_trace;
613616 defer it.deinit();
......@@ -645,6 +648,9 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
645648///
646649/// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing.
647650pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_config: tty.Config) Writer.Error!void {
651 var threaded: Io.Threaded = .init_single_threaded;
652 const io = threaded.ioBasic();
653
648654 if (!std.options.allow_stack_tracing) {
649655 tty_config.setColor(writer, .dim) catch {};
650656 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
......@@ -691,6 +697,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
691697 error.UnsupportedDebugInfo => "unwind info unsupported",
692698 error.ReadFailed => "filesystem error",
693699 error.OutOfMemory => "out of memory",
700 error.Canceled => "operation canceled",
694701 error.Unexpected => "unexpected error",
695702 };
696703 if (it.stratOk(options.allow_unsafe_unwind)) {
......@@ -728,7 +735,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
728735 }
729736 // `ret_addr` is the return address, which is *after* the function call.
730737 // Subtract 1 to get an address *in* the function call for a better source location.
731 try printSourceAtAddress(di_gpa, di, writer, ret_addr -| StackIterator.ra_call_offset, tty_config);
738 try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, tty_config);
732739 printed_any_frame = true;
733740 },
734741 };
......@@ -752,14 +759,29 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
752759 };
753760}
754761
762pub const FormatStackTrace = struct {
763 stack_trace: StackTrace,
764 tty_config: tty.Config,
765
766 pub fn format(context: @This(), writer: *Io.Writer) Io.Writer.Error!void {
767 try writer.writeAll("\n");
768 try writeStackTrace(&context.stack_trace, writer, context.tty_config);
769 }
770};
771
755772/// Write a previously captured stack trace to `writer`, annotated with source locations.
756pub fn writeStackTrace(st: *const std.builtin.StackTrace, writer: *Writer, tty_config: tty.Config) Writer.Error!void {
773pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, tty_config: tty.Config) Writer.Error!void {
757774 if (!std.options.allow_stack_tracing) {
758775 tty_config.setColor(writer, .dim) catch {};
759776 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
760777 tty_config.setColor(writer, .reset) catch {};
761778 return;
762779 }
780 // We use an independent Io implementation here in case there was a problem
781 // with the application's Io implementation itself.
782 var threaded: Io.Threaded = .init_single_threaded;
783 const io = threaded.ioBasic();
784
763785 // Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if
764786 // `st` is `@errorReturnTrace()` and errors are encountered while writing the stack trace.
765787 const n_frames = st.index;
......@@ -777,7 +799,7 @@ pub fn writeStackTrace(st: *const std.builtin.StackTrace, writer: *Writer, tty_c
777799 for (st.instruction_addresses[0..captured_frames]) |ret_addr| {
778800 // `ret_addr` is the return address, which is *after* the function call.
779801 // Subtract 1 to get an address *in* the function call for a better source location.
780 try printSourceAtAddress(di_gpa, di, writer, ret_addr -| StackIterator.ra_call_offset, tty_config);
802 try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, tty_config);
781803 }
782804 if (n_frames > captured_frames) {
783805 tty_config.setColor(writer, .bold) catch {};
......@@ -786,7 +808,7 @@ pub fn writeStackTrace(st: *const std.builtin.StackTrace, writer: *Writer, tty_c
786808 }
787809}
788810/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors.
789pub fn dumpStackTrace(st: *const std.builtin.StackTrace) void {
811pub fn dumpStackTrace(st: *const StackTrace) void {
790812 const tty_config = tty.detectConfig(.stderr());
791813 const stderr = lockStderrWriter(&.{});
792814 defer unlockStderrWriter();
......@@ -1073,13 +1095,13 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
10731095 return ptr;
10741096}
10751097
1076fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void {
1077 const symbol: Symbol = debug_info.getSymbol(gpa, address) catch |err| switch (err) {
1098fn printSourceAtAddress(gpa: Allocator, io: Io, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void {
1099 const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) {
10781100 error.MissingDebugInfo,
10791101 error.UnsupportedDebugInfo,
10801102 error.InvalidDebugInfo,
10811103 => .unknown,
1082 error.ReadFailed, error.Unexpected => s: {
1104 error.ReadFailed, error.Unexpected, error.Canceled => s: {
10831105 tty_config.setColor(writer, .dim) catch {};
10841106 try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
10851107 tty_config.setColor(writer, .reset) catch {};
......@@ -1387,10 +1409,10 @@ pub fn maybeEnableSegfaultHandler() void {
13871409var windows_segfault_handle: ?windows.HANDLE = null;
13881410
13891411pub fn updateSegfaultHandler(act: ?*const posix.Sigaction) void {
1390 posix.sigaction(posix.SIG.SEGV, act, null);
1391 posix.sigaction(posix.SIG.ILL, act, null);
1392 posix.sigaction(posix.SIG.BUS, act, null);
1393 posix.sigaction(posix.SIG.FPE, act, null);
1412 posix.sigaction(.SEGV, act, null);
1413 posix.sigaction(.ILL, act, null);
1414 posix.sigaction(.BUS, act, null);
1415 posix.sigaction(.FPE, act, null);
13941416}
13951417
13961418/// Attaches a global handler for several signals which, when triggered, prints output to stderr
......@@ -1435,7 +1457,7 @@ fn resetSegfaultHandler() void {
14351457 updateSegfaultHandler(&act);
14361458}
14371459
1438fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) noreturn {
1460fn handleSegfaultPosix(sig: posix.SIG, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) noreturn {
14391461 if (use_trap_panic) @trap();
14401462 const addr: ?usize, const name: []const u8 = info: {
14411463 if (native_os == .linux and native_arch == .x86_64) {
......@@ -1447,7 +1469,7 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa
14471469 // for example when reading/writing model-specific registers
14481470 // by executing `rdmsr` or `wrmsr` in user-space (unprivileged mode).
14491471 const SI_KERNEL = 0x80;
1450 if (sig == posix.SIG.SEGV and info.code == SI_KERNEL) {
1472 if (sig == .SEGV and info.code == SI_KERNEL) {
14511473 break :info .{ null, "General protection exception" };
14521474 }
14531475 }
......@@ -1474,10 +1496,10 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa
14741496 else => comptime unreachable,
14751497 };
14761498 const name = switch (sig) {
1477 posix.SIG.SEGV => "Segmentation fault",
1478 posix.SIG.ILL => "Illegal instruction",
1479 posix.SIG.BUS => "Bus error",
1480 posix.SIG.FPE => "Arithmetic exception",
1499 .SEGV => "Segmentation fault",
1500 .ILL => "Illegal instruction",
1501 .BUS => "Bus error",
1502 .FPE => "Arithmetic exception",
14811503 else => unreachable,
14821504 };
14831505 break :info .{ addr, name };
......@@ -1579,11 +1601,14 @@ test "manage resources correctly" {
15791601 }
15801602 };
15811603 const gpa = std.testing.allocator;
1582 var discarding: std.Io.Writer.Discarding = .init(&.{});
1604 var threaded: Io.Threaded = .init_single_threaded;
1605 const io = threaded.ioBasic();
1606 var discarding: Io.Writer.Discarding = .init(&.{});
15831607 var di: SelfInfo = .init;
15841608 defer di.deinit(gpa);
15851609 try printSourceAtAddress(
15861610 gpa,
1611 io,
15871612 &di,
15881613 &discarding.writer,
15891614 S.showMyTrace(),
......@@ -1657,7 +1682,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
16571682 stderr.print("{s}:\n", .{t.notes[i]}) catch return;
16581683 var frames_array_mutable = frames_array;
16591684 const frames = mem.sliceTo(frames_array_mutable[0..], 0);
1660 const stack_trace: std.builtin.StackTrace = .{
1685 const stack_trace: StackTrace = .{
16611686 .index = frames.len,
16621687 .instruction_addresses = frames,
16631688 };
lib/std/debug/ElfFile.zig+3-1
......@@ -108,6 +108,8 @@ pub const LoadError = error{
108108 LockedMemoryLimitExceeded,
109109 ProcessFdQuotaExceeded,
110110 SystemFdQuotaExceeded,
111 Streaming,
112 Canceled,
111113 Unexpected,
112114};
113115
......@@ -408,7 +410,7 @@ fn loadInner(
408410 arena: Allocator,
409411 elf_file: std.fs.File,
410412 opt_crc: ?u32,
411) (LoadError || error{CrcMismatch})!LoadInnerResult {
413) (LoadError || error{ CrcMismatch, Streaming, Canceled })!LoadInnerResult {
412414 const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: {
413415 const file_len = std.math.cast(
414416 usize,
lib/std/debug/SelfInfo/Elf.zig+5-1
......@@ -28,7 +28,8 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
2828 if (si.unwind_cache) |cache| gpa.free(cache);
2929}
3030
31pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol {
31pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol {
32 _ = io;
3233 const module = try si.findModule(gpa, address, .exclusive);
3334 defer si.rwlock.unlock();
3435
......@@ -336,6 +337,7 @@ const Module = struct {
336337 var elf_file = load_result catch |err| switch (err) {
337338 error.OutOfMemory,
338339 error.Unexpected,
340 error.Canceled,
339341 => |e| return e,
340342
341343 error.Overflow,
......@@ -353,6 +355,7 @@ const Module = struct {
353355 error.LockedMemoryLimitExceeded,
354356 error.ProcessFdQuotaExceeded,
355357 error.SystemFdQuotaExceeded,
358 error.Streaming,
356359 => return error.ReadFailed,
357360 };
358361 errdefer elf_file.deinit(gpa);
......@@ -487,6 +490,7 @@ const DlIterContext = struct {
487490};
488491
489492const std = @import("std");
493const Io = std.Io;
490494const Allocator = std.mem.Allocator;
491495const Dwarf = std.debug.Dwarf;
492496const Error = std.debug.SelfInfoError;
lib/std/debug/SelfInfo/MachO.zig+6-1
......@@ -30,7 +30,8 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
3030 si.ofiles.deinit(gpa);
3131}
3232
33pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol {
33pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol {
34 _ = io;
3435 const module = try si.findModule(gpa, address);
3536 defer si.mutex.unlock();
3637
......@@ -117,11 +118,14 @@ pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error
117118 error.ReadFailed,
118119 error.OutOfMemory,
119120 error.Unexpected,
121 error.Canceled,
120122 => |e| return e,
123
121124 error.UnsupportedRegister,
122125 error.UnsupportedAddrSize,
123126 error.UnimplementedUserOpcode,
124127 => return error.UnsupportedDebugInfo,
128
125129 error.Overflow,
126130 error.EndOfStream,
127131 error.StreamTooLong,
......@@ -967,6 +971,7 @@ fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile {
967971}
968972
969973const std = @import("std");
974const Io = std.Io;
970975const Allocator = std.mem.Allocator;
971976const Dwarf = std.debug.Dwarf;
972977const Error = std.debug.SelfInfoError;
lib/std/debug/SelfInfo/Windows.zig+19-14
......@@ -20,11 +20,11 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
2020 module_name_arena.deinit();
2121}
2222
23pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol {
23pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol {
2424 si.mutex.lock();
2525 defer si.mutex.unlock();
2626 const module = try si.findModule(gpa, address);
27 const di = try module.getDebugInfo(gpa);
27 const di = try module.getDebugInfo(gpa, io);
2828 return di.getSymbol(gpa, address - module.base_address);
2929}
3030pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 {
......@@ -190,6 +190,7 @@ const Module = struct {
190190
191191 const DebugInfo = struct {
192192 arena: std.heap.ArenaAllocator.State,
193 io: Io,
193194 coff_image_base: u64,
194195 mapped_file: ?MappedFile,
195196 dwarf: ?Dwarf,
......@@ -209,9 +210,10 @@ const Module = struct {
209210 };
210211
211212 fn deinit(di: *DebugInfo, gpa: Allocator) void {
213 const io = di.io;
212214 if (di.dwarf) |*dwarf| dwarf.deinit(gpa);
213215 if (di.pdb) |*pdb| {
214 pdb.file_reader.file.close();
216 pdb.file_reader.file.close(io);
215217 pdb.deinit();
216218 }
217219 if (di.mapped_file) |*mf| mf.deinit();
......@@ -277,11 +279,11 @@ const Module = struct {
277279 }
278280 };
279281
280 fn getDebugInfo(module: *Module, gpa: Allocator) Error!*DebugInfo {
281 if (module.di == null) module.di = loadDebugInfo(module, gpa);
282 fn getDebugInfo(module: *Module, gpa: Allocator, io: Io) Error!*DebugInfo {
283 if (module.di == null) module.di = loadDebugInfo(module, gpa, io);
282284 return if (module.di.?) |*di| di else |err| err;
283285 }
284 fn loadDebugInfo(module: *const Module, gpa: Allocator) Error!DebugInfo {
286 fn loadDebugInfo(module: *const Module, gpa: Allocator, io: Io) Error!DebugInfo {
285287 const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address);
286288 const mapped = mapped_ptr[0..module.size];
287289 var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo;
......@@ -305,7 +307,10 @@ const Module = struct {
305307 windows.PATH_MAX_WIDE,
306308 );
307309 if (len == 0) return error.MissingDebugInfo;
308 const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
310 const name_w = name_buffer[0 .. len + 4 :0];
311 var threaded: Io.Threaded = .init_single_threaded;
312 const coff_file = threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) {
313 error.Canceled => |e| return e,
309314 error.Unexpected => |e| return e,
310315 error.FileNotFound => return error.MissingDebugInfo,
311316
......@@ -314,8 +319,6 @@ const Module = struct {
314319 error.NotDir,
315320 error.SymLinkLoop,
316321 error.NameTooLong,
317 error.InvalidUtf8,
318 error.InvalidWtf8,
319322 error.BadPathName,
320323 => return error.InvalidDebugInfo,
321324
......@@ -338,7 +341,7 @@ const Module = struct {
338341 error.FileBusy,
339342 => return error.ReadFailed,
340343 };
341 errdefer coff_file.close();
344 errdefer coff_file.close(io);
342345 var section_handle: windows.HANDLE = undefined;
343346 const create_section_rc = windows.ntdll.NtCreateSection(
344347 &section_handle,
......@@ -372,7 +375,7 @@ const Module = struct {
372375 const section_view = section_view_ptr.?[0..coff_len];
373376 coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
374377 break :mapped .{
375 .file = coff_file,
378 .file = .adaptFromNewApi(coff_file),
376379 .section_handle = section_handle,
377380 .section_view = section_view,
378381 };
......@@ -434,8 +437,8 @@ const Module = struct {
434437 };
435438 errdefer pdb_file.close();
436439
437 const pdb_reader = try arena.create(std.fs.File.Reader);
438 pdb_reader.* = pdb_file.reader(try arena.alloc(u8, 4096));
440 const pdb_reader = try arena.create(Io.File.Reader);
441 pdb_reader.* = pdb_file.reader(io, try arena.alloc(u8, 4096));
439442
440443 var pdb = Pdb.init(gpa, pdb_reader) catch |err| switch (err) {
441444 error.OutOfMemory, error.ReadFailed, error.Unexpected => |e| return e,
......@@ -473,7 +476,7 @@ const Module = struct {
473476 break :pdb pdb;
474477 };
475478 errdefer if (opt_pdb) |*pdb| {
476 pdb.file_reader.file.close();
479 pdb.file_reader.file.close(io);
477480 pdb.deinit();
478481 };
479482
......@@ -483,6 +486,7 @@ const Module = struct {
483486
484487 return .{
485488 .arena = arena_instance.state,
489 .io = io,
486490 .coff_image_base = coff_image_base,
487491 .mapped_file = mapped_file,
488492 .dwarf = opt_dwarf,
......@@ -544,6 +548,7 @@ fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) error{ MissingDebug
544548}
545549
546550const std = @import("std");
551const Io = std.Io;
547552const Allocator = std.mem.Allocator;
548553const Dwarf = std.debug.Dwarf;
549554const Pdb = std.debug.Pdb;
lib/std/dynamic_library.zig+2
......@@ -137,6 +137,8 @@ const ElfDynLibError = error{
137137 ElfStringSectionNotFound,
138138 ElfSymSectionNotFound,
139139 ElfHashTableNotFound,
140 Canceled,
141 Streaming,
140142} || posix.OpenError || posix.MMapError;
141143
142144pub const ElfDynLib = struct {
lib/std/elf.zig+121-45
......@@ -1,9 +1,11 @@
11//! Executable and Linkable Format.
22
33const std = @import("std.zig");
4const Io = std.Io;
45const math = std.math;
56const mem = std.mem;
67const assert = std.debug.assert;
8const Endian = std.builtin.Endian;
79const native_endian = @import("builtin").target.cpu.arch.endian();
810
911pub const AT_NULL = 0;
......@@ -568,7 +570,7 @@ pub const ET = enum(u16) {
568570/// All integers are native endian.
569571pub const Header = struct {
570572 is_64: bool,
571 endian: std.builtin.Endian,
573 endian: Endian,
572574 os_abi: OSABI,
573575 /// The meaning of this value depends on `os_abi`.
574576 abi_version: u8,
......@@ -583,48 +585,76 @@ pub const Header = struct {
583585 shnum: u16,
584586 shstrndx: u16,
585587
586 pub fn iterateProgramHeaders(h: Header, file_reader: *std.fs.File.Reader) ProgramHeaderIterator {
588 pub fn iterateProgramHeaders(h: *const Header, file_reader: *Io.File.Reader) ProgramHeaderIterator {
587589 return .{
588 .elf_header = h,
590 .is_64 = h.is_64,
591 .endian = h.endian,
592 .phnum = h.phnum,
593 .phoff = h.phoff,
589594 .file_reader = file_reader,
590595 };
591596 }
592597
593 pub fn iterateProgramHeadersBuffer(h: Header, buf: []const u8) ProgramHeaderBufferIterator {
598 pub fn iterateProgramHeadersBuffer(h: *const Header, buf: []const u8) ProgramHeaderBufferIterator {
594599 return .{
595 .elf_header = h,
600 .is_64 = h.is_64,
601 .endian = h.endian,
602 .phnum = h.phnum,
603 .phoff = h.phoff,
596604 .buf = buf,
597605 };
598606 }
599607
600 pub fn iterateSectionHeaders(h: Header, file_reader: *std.fs.File.Reader) SectionHeaderIterator {
608 pub fn iterateSectionHeaders(h: *const Header, file_reader: *Io.File.Reader) SectionHeaderIterator {
601609 return .{
602 .elf_header = h,
610 .is_64 = h.is_64,
611 .endian = h.endian,
612 .shnum = h.shnum,
613 .shoff = h.shoff,
603614 .file_reader = file_reader,
604615 };
605616 }
606617
607 pub fn iterateSectionHeadersBuffer(h: Header, buf: []const u8) SectionHeaderBufferIterator {
618 pub fn iterateSectionHeadersBuffer(h: *const Header, buf: []const u8) SectionHeaderBufferIterator {
608619 return .{
609 .elf_header = h,
620 .is_64 = h.is_64,
621 .endian = h.endian,
622 .shnum = h.shnum,
623 .shoff = h.shoff,
610624 .buf = buf,
611625 };
612626 }
613627
614 pub const ReadError = std.Io.Reader.Error || error{
628 pub fn iterateDynamicSection(
629 h: *const Header,
630 file_reader: *Io.File.Reader,
631 offset: u64,
632 size: u64,
633 ) DynamicSectionIterator {
634 return .{
635 .is_64 = h.is_64,
636 .endian = h.endian,
637 .offset = offset,
638 .end_offset = offset + size,
639 .file_reader = file_reader,
640 };
641 }
642
643 pub const ReadError = Io.Reader.Error || error{
615644 InvalidElfMagic,
616645 InvalidElfVersion,
617646 InvalidElfClass,
618647 InvalidElfEndian,
619648 };
620649
621 pub fn read(r: *std.Io.Reader) ReadError!Header {
650 /// If this function fails, seek position of `r` is unchanged.
651 pub fn read(r: *Io.Reader) ReadError!Header {
622652 const buf = try r.peek(@sizeOf(Elf64_Ehdr));
623653
624654 if (!mem.eql(u8, buf[0..4], MAGIC)) return error.InvalidElfMagic;
625655 if (buf[EI.VERSION] != 1) return error.InvalidElfVersion;
626656
627 const endian: std.builtin.Endian = switch (buf[EI.DATA]) {
657 const endian: Endian = switch (buf[EI.DATA]) {
628658 ELFDATA2LSB => .little,
629659 ELFDATA2MSB => .big,
630660 else => return error.InvalidElfEndian,
......@@ -637,7 +667,7 @@ pub const Header = struct {
637667 };
638668 }
639669
640 pub fn init(hdr: anytype, endian: std.builtin.Endian) Header {
670 pub fn init(hdr: anytype, endian: Endian) Header {
641671 // Converting integers to exhaustive enums using `@enumFromInt` could cause a panic.
642672 comptime assert(!@typeInfo(OSABI).@"enum".is_exhaustive);
643673 return .{
......@@ -664,46 +694,54 @@ pub const Header = struct {
664694};
665695
666696pub const ProgramHeaderIterator = struct {
667 elf_header: Header,
668 file_reader: *std.fs.File.Reader,
697 is_64: bool,
698 endian: Endian,
699 phnum: u16,
700 phoff: u64,
701
702 file_reader: *Io.File.Reader,
669703 index: usize = 0,
670704
671705 pub fn next(it: *ProgramHeaderIterator) !?Elf64_Phdr {
672 if (it.index >= it.elf_header.phnum) return null;
706 if (it.index >= it.phnum) return null;
673707 defer it.index += 1;
674708
675 const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Phdr) else @sizeOf(Elf32_Phdr);
676 const offset = it.elf_header.phoff + size * it.index;
709 const size: u64 = if (it.is_64) @sizeOf(Elf64_Phdr) else @sizeOf(Elf32_Phdr);
710 const offset = it.phoff + size * it.index;
677711 try it.file_reader.seekTo(offset);
678712
679 return takePhdr(&it.file_reader.interface, it.elf_header);
713 return try takeProgramHeader(&it.file_reader.interface, it.is_64, it.endian);
680714 }
681715};
682716
683717pub const ProgramHeaderBufferIterator = struct {
684 elf_header: Header,
718 is_64: bool,
719 endian: Endian,
720 phnum: u16,
721 phoff: u64,
722
685723 buf: []const u8,
686724 index: usize = 0,
687725
688726 pub fn next(it: *ProgramHeaderBufferIterator) !?Elf64_Phdr {
689 if (it.index >= it.elf_header.phnum) return null;
727 if (it.index >= it.phnum) return null;
690728 defer it.index += 1;
691729
692 const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Phdr) else @sizeOf(Elf32_Phdr);
693 const offset = it.elf_header.phoff + size * it.index;
694 var reader = std.Io.Reader.fixed(it.buf[offset..]);
730 const size: u64 = if (it.is_64) @sizeOf(Elf64_Phdr) else @sizeOf(Elf32_Phdr);
731 const offset = it.phoff + size * it.index;
732 var reader = Io.Reader.fixed(it.buf[offset..]);
695733
696 return takePhdr(&reader, it.elf_header);
734 return try takeProgramHeader(&reader, it.is_64, it.endian);
697735 }
698736};
699737
700fn takePhdr(reader: *std.Io.Reader, elf_header: Header) !?Elf64_Phdr {
701 if (elf_header.is_64) {
702 const phdr = try reader.takeStruct(Elf64_Phdr, elf_header.endian);
738pub fn takeProgramHeader(reader: *Io.Reader, is_64: bool, endian: Endian) !Elf64_Phdr {
739 if (is_64) {
740 const phdr = try reader.takeStruct(Elf64_Phdr, endian);
703741 return phdr;
704742 }
705743
706 const phdr = try reader.takeStruct(Elf32_Phdr, elf_header.endian);
744 const phdr = try reader.takeStruct(Elf32_Phdr, endian);
707745 return .{
708746 .p_type = phdr.p_type,
709747 .p_offset = phdr.p_offset,
......@@ -717,47 +755,55 @@ fn takePhdr(reader: *std.Io.Reader, elf_header: Header) !?Elf64_Phdr {
717755}
718756
719757pub const SectionHeaderIterator = struct {
720 elf_header: Header,
721 file_reader: *std.fs.File.Reader,
758 is_64: bool,
759 endian: Endian,
760 shnum: u16,
761 shoff: u64,
762
763 file_reader: *Io.File.Reader,
722764 index: usize = 0,
723765
724766 pub fn next(it: *SectionHeaderIterator) !?Elf64_Shdr {
725 if (it.index >= it.elf_header.shnum) return null;
767 if (it.index >= it.shnum) return null;
726768 defer it.index += 1;
727769
728 const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr);
729 const offset = it.elf_header.shoff + size * it.index;
770 const size: u64 = if (it.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr);
771 const offset = it.shoff + size * it.index;
730772 try it.file_reader.seekTo(offset);
731773
732 return takeShdr(&it.file_reader.interface, it.elf_header);
774 return try takeSectionHeader(&it.file_reader.interface, it.is_64, it.endian);
733775 }
734776};
735777
736778pub const SectionHeaderBufferIterator = struct {
737 elf_header: Header,
779 is_64: bool,
780 endian: Endian,
781 shnum: u16,
782 shoff: u64,
783
738784 buf: []const u8,
739785 index: usize = 0,
740786
741787 pub fn next(it: *SectionHeaderBufferIterator) !?Elf64_Shdr {
742 if (it.index >= it.elf_header.shnum) return null;
788 if (it.index >= it.shnum) return null;
743789 defer it.index += 1;
744790
745 const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr);
746 const offset = it.elf_header.shoff + size * it.index;
791 const size: u64 = if (it.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr);
792 const offset = it.shoff + size * it.index;
747793 if (offset > it.buf.len) return error.EndOfStream;
748 var reader = std.Io.Reader.fixed(it.buf[@intCast(offset)..]);
794 var reader = Io.Reader.fixed(it.buf[@intCast(offset)..]);
749795
750 return takeShdr(&reader, it.elf_header);
796 return try takeSectionHeader(&reader, it.is_64, it.endian);
751797 }
752798};
753799
754fn takeShdr(reader: *std.Io.Reader, elf_header: Header) !?Elf64_Shdr {
755 if (elf_header.is_64) {
756 const shdr = try reader.takeStruct(Elf64_Shdr, elf_header.endian);
800pub fn takeSectionHeader(reader: *Io.Reader, is_64: bool, endian: Endian) !Elf64_Shdr {
801 if (is_64) {
802 const shdr = try reader.takeStruct(Elf64_Shdr, endian);
757803 return shdr;
758804 }
759805
760 const shdr = try reader.takeStruct(Elf32_Shdr, elf_header.endian);
806 const shdr = try reader.takeStruct(Elf32_Shdr, endian);
761807 return .{
762808 .sh_name = shdr.sh_name,
763809 .sh_type = shdr.sh_type,
......@@ -772,6 +818,36 @@ fn takeShdr(reader: *std.Io.Reader, elf_header: Header) !?Elf64_Shdr {
772818 };
773819}
774820
821pub const DynamicSectionIterator = struct {
822 is_64: bool,
823 endian: Endian,
824 offset: u64,
825 end_offset: u64,
826
827 file_reader: *Io.File.Reader,
828
829 pub fn next(it: *DynamicSectionIterator) !?Elf64_Dyn {
830 if (it.offset >= it.end_offset) return null;
831 const size: u64 = if (it.is_64) @sizeOf(Elf64_Dyn) else @sizeOf(Elf32_Dyn);
832 defer it.offset += size;
833 try it.file_reader.seekTo(it.offset);
834 return try takeDynamicSection(&it.file_reader.interface, it.is_64, it.endian);
835 }
836};
837
838pub fn takeDynamicSection(reader: *Io.Reader, is_64: bool, endian: Endian) !Elf64_Dyn {
839 if (is_64) {
840 const dyn = try reader.takeStruct(Elf64_Dyn, endian);
841 return dyn;
842 }
843
844 const dyn = try reader.takeStruct(Elf32_Dyn, endian);
845 return .{
846 .d_tag = dyn.d_tag,
847 .d_val = dyn.d_val,
848 };
849}
850
775851pub const EI = struct {
776852 pub const CLASS = 4;
777853 pub const DATA = 5;
lib/std/fs.zig+24-163
......@@ -1,14 +1,15 @@
11//! File System.
2const builtin = @import("builtin");
3const native_os = builtin.os.tag;
24
35const std = @import("std.zig");
4const builtin = @import("builtin");
6const Io = std.Io;
57const root = @import("root");
68const mem = std.mem;
79const base64 = std.base64;
810const crypto = std.crypto;
911const Allocator = std.mem.Allocator;
1012const assert = std.debug.assert;
11const native_os = builtin.os.tag;
1213const posix = std.posix;
1314const windows = std.os.windows;
1415
......@@ -97,23 +98,6 @@ pub const base64_encoder = base64.Base64Encoder.init(base64_alphabet, null);
9798/// Base64 decoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem.
9899pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null);
99100
100/// Same as `Dir.updateFile`, except asserts that both `source_path` and `dest_path`
101/// are absolute. See `Dir.updateFile` for a function that operates on both
102/// absolute and relative paths.
103/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
104/// On WASI, both paths should be encoded as valid UTF-8.
105/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
106pub fn updateFileAbsolute(
107 source_path: []const u8,
108 dest_path: []const u8,
109 args: Dir.CopyFileOptions,
110) !Dir.PrevStatus {
111 assert(path.isAbsolute(source_path));
112 assert(path.isAbsolute(dest_path));
113 const my_cwd = cwd();
114 return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, args);
115}
116
117101/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`
118102/// are absolute. See `Dir.copyFile` for a function that operates on both
119103/// absolute and relative paths.
......@@ -131,6 +115,8 @@ pub fn copyFileAbsolute(
131115 return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args);
132116}
133117
118test copyFileAbsolute {}
119
134120/// Create a new directory, based on an absolute path.
135121/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
136122/// on both absolute and relative paths.
......@@ -142,17 +128,15 @@ pub fn makeDirAbsolute(absolute_path: []const u8) !void {
142128 return posix.mkdir(absolute_path, Dir.default_mode);
143129}
144130
131test makeDirAbsolute {}
132
145133/// Same as `makeDirAbsolute` except the parameter is null-terminated.
146134pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
147135 assert(path.isAbsoluteZ(absolute_path_z));
148136 return posix.mkdirZ(absolute_path_z, Dir.default_mode);
149137}
150138
151/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 LE-encoded string.
152pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
153 assert(path.isAbsoluteWindowsW(absolute_path_w));
154 return posix.mkdirW(mem.span(absolute_path_w), Dir.default_mode);
155}
139test makeDirAbsoluteZ {}
156140
157141/// Same as `Dir.deleteDir` except the path is absolute.
158142/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
......@@ -169,12 +153,6 @@ pub fn deleteDirAbsoluteZ(dir_path: [*:0]const u8) !void {
169153 return posix.rmdirZ(dir_path);
170154}
171155
172/// Same as `deleteDirAbsolute` except the path parameter is WTF-16 and target OS is assumed Windows.
173pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void {
174 assert(path.isAbsoluteWindowsW(dir_path));
175 return posix.rmdirW(mem.span(dir_path));
176}
177
178156/// Same as `Dir.rename` except the paths are absolute.
179157/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
180158/// On WASI, both paths should be encoded as valid UTF-8.
......@@ -192,13 +170,6 @@ pub fn renameAbsoluteZ(old_path: [*:0]const u8, new_path: [*:0]const u8) !void {
192170 return posix.renameZ(old_path, new_path);
193171}
194172
195/// Same as `renameAbsolute` except the path parameters are WTF-16 and target OS is assumed Windows.
196pub fn renameAbsoluteW(old_path: [*:0]const u16, new_path: [*:0]const u16) !void {
197 assert(path.isAbsoluteWindowsW(old_path));
198 assert(path.isAbsoluteWindowsW(new_path));
199 return posix.renameW(old_path, new_path);
200}
201
202173/// Same as `Dir.rename`, except `new_sub_path` is relative to `new_dir`
203174pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void {
204175 return posix.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path);
......@@ -209,15 +180,7 @@ pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_su
209180 return posix.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);
210181}
211182
212/// Same as `rename` except the parameters are WTF16LE, NT prefixed.
213/// This function is Windows-only.
214pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_path_w: []const u16) !void {
215 return posix.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w, windows.TRUE);
216}
217
218/// Returns a handle to the current working directory. It is not opened with iteration capability.
219/// Closing the returned `Dir` is checked illegal behavior. Iterating over the result is illegal behavior.
220/// On POSIX targets, this function is comptime-callable.
183/// Deprecated in favor of `Io.Dir.cwd`.
221184pub fn cwd() Dir {
222185 if (native_os == .windows) {
223186 return .{ .fd = windows.peb().ProcessParameters.CurrentDirectory.Handle };
......@@ -251,12 +214,6 @@ pub fn openDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenOptions)
251214 assert(path.isAbsoluteZ(absolute_path_c));
252215 return cwd().openDirZ(absolute_path_c, flags);
253216}
254/// Same as `openDirAbsolute` but the path parameter is null-terminated.
255pub fn openDirAbsoluteW(absolute_path_c: [*:0]const u16, flags: Dir.OpenOptions) File.OpenError!Dir {
256 assert(path.isAbsoluteWindowsW(absolute_path_c));
257 return cwd().openDirW(absolute_path_c, flags);
258}
259
260217/// Opens a file for reading or writing, without attempting to create a new file, based on an absolute path.
261218/// Call `File.close` to release the resource.
262219/// Asserts that the path is absolute. See `Dir.openFile` for a function that
......@@ -271,18 +228,6 @@ pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.O
271228 return cwd().openFile(absolute_path, flags);
272229}
273230
274/// Same as `openFileAbsolute` but the path parameter is null-terminated.
275pub fn openFileAbsoluteZ(absolute_path_c: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File {
276 assert(path.isAbsoluteZ(absolute_path_c));
277 return cwd().openFileZ(absolute_path_c, flags);
278}
279
280/// Same as `openFileAbsolute` but the path parameter is WTF-16-encoded.
281pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File {
282 assert(path.isAbsoluteWindowsWTF16(absolute_path_w));
283 return cwd().openFileW(absolute_path_w, flags);
284}
285
286231/// Test accessing `path`.
287232/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
288233/// For example, instead of testing if a file exists and then opening it, just
......@@ -291,21 +236,10 @@ pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) Fi
291236/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
292237/// On WASI, `absolute_path` should be encoded as valid UTF-8.
293238/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.
294pub fn accessAbsolute(absolute_path: []const u8, flags: File.OpenFlags) Dir.AccessError!void {
239pub fn accessAbsolute(absolute_path: []const u8, flags: Io.Dir.AccessOptions) Dir.AccessError!void {
295240 assert(path.isAbsolute(absolute_path));
296241 try cwd().access(absolute_path, flags);
297242}
298/// Same as `accessAbsolute` but the path parameter is null-terminated.
299pub fn accessAbsoluteZ(absolute_path: [*:0]const u8, flags: File.OpenFlags) Dir.AccessError!void {
300 assert(path.isAbsoluteZ(absolute_path));
301 try cwd().accessZ(absolute_path, flags);
302}
303/// Same as `accessAbsolute` but the path parameter is WTF-16 encoded.
304pub fn accessAbsoluteW(absolute_path: [*:0]const u16, flags: File.OpenFlags) Dir.AccessError!void {
305 assert(path.isAbsoluteWindowsW(absolute_path));
306 try cwd().accessW(absolute_path, flags);
307}
308
309243/// Creates, opens, or overwrites a file with write access, based on an absolute path.
310244/// Call `File.close` to release the resource.
311245/// Asserts that the path is absolute. See `Dir.createFile` for a function that
......@@ -320,18 +254,6 @@ pub fn createFileAbsolute(absolute_path: []const u8, flags: File.CreateFlags) Fi
320254 return cwd().createFile(absolute_path, flags);
321255}
322256
323/// Same as `createFileAbsolute` but the path parameter is null-terminated.
324pub fn createFileAbsoluteZ(absolute_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File {
325 assert(path.isAbsoluteZ(absolute_path_c));
326 return cwd().createFileZ(absolute_path_c, flags);
327}
328
329/// Same as `createFileAbsolute` but the path parameter is WTF-16 encoded.
330pub fn createFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.CreateFlags) File.OpenError!File {
331 assert(path.isAbsoluteWindowsW(absolute_path_w));
332 return cwd().createFileW(mem.span(absolute_path_w), flags);
333}
334
335257/// Delete a file name and possibly the file it refers to, based on an absolute path.
336258/// Asserts that the path is absolute. See `Dir.deleteFile` for a function that
337259/// operates on both absolute and relative paths.
......@@ -344,18 +266,6 @@ pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void {
344266 return cwd().deleteFile(absolute_path);
345267}
346268
347/// Same as `deleteFileAbsolute` except the parameter is null-terminated.
348pub fn deleteFileAbsoluteZ(absolute_path_c: [*:0]const u8) Dir.DeleteFileError!void {
349 assert(path.isAbsoluteZ(absolute_path_c));
350 return cwd().deleteFileZ(absolute_path_c);
351}
352
353/// Same as `deleteFileAbsolute` except the parameter is WTF-16 encoded.
354pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) Dir.DeleteFileError!void {
355 assert(path.isAbsoluteWindowsW(absolute_path_w));
356 return cwd().deleteFileW(mem.span(absolute_path_w));
357}
358
359269/// Removes a symlink, file, or directory.
360270/// This is equivalent to `Dir.deleteTree` with the base directory.
361271/// Asserts that the path is absolute. See `Dir.deleteTree` for a function that
......@@ -387,19 +297,6 @@ pub fn readLinkAbsolute(pathname: []const u8, buffer: *[max_path_bytes]u8) ![]u8
387297 return posix.readlink(pathname, buffer);
388298}
389299
390/// Windows-only. Same as `readlinkW`, except the path parameter is null-terminated, WTF16
391/// encoded.
392pub fn readlinkAbsoluteW(pathname_w: [*:0]const u16, buffer: *[max_path_bytes]u8) ![]u8 {
393 assert(path.isAbsoluteWindowsW(pathname_w));
394 return posix.readlinkW(mem.span(pathname_w), buffer);
395}
396
397/// Same as `readLink`, except the path parameter is null-terminated.
398pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[max_path_bytes]u8) ![]u8 {
399 assert(path.isAbsoluteZ(pathname_c));
400 return posix.readlinkZ(pathname_c, buffer);
401}
402
403300/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
404301/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
405302/// one; the latter case is known as a dangling link.
......@@ -437,44 +334,21 @@ pub fn symLinkAbsoluteW(
437334 return windows.CreateSymbolicLink(null, mem.span(sym_link_path_w), mem.span(target_path_w), flags.is_directory);
438335}
439336
440/// Same as `symLinkAbsolute` except the parameters are null-terminated pointers.
441/// See also `symLinkAbsolute`.
442pub fn symLinkAbsoluteZ(
443 target_path_c: [*:0]const u8,
444 sym_link_path_c: [*:0]const u8,
445 flags: Dir.SymLinkFlags,
446) !void {
447 assert(path.isAbsoluteZ(target_path_c));
448 assert(path.isAbsoluteZ(sym_link_path_c));
449 if (native_os == .windows) {
450 const target_path_w = try windows.cStrToPrefixedFileW(null, target_path_c);
451 const sym_link_path_w = try windows.cStrToPrefixedFileW(null, sym_link_path_c);
452 return windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
453 }
454 return posix.symlinkZ(target_path_c, sym_link_path_c);
455}
456
457pub const OpenSelfExeError = posix.OpenError || SelfExePathError || posix.FlockError;
337pub const OpenSelfExeError = Io.File.OpenSelfExeError;
458338
339/// Deprecated in favor of `Io.File.openSelfExe`.
459340pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
460 if (native_os == .linux or native_os == .serenity) {
461 return openFileAbsoluteZ("/proc/self/exe", flags);
462 }
463 if (native_os == .windows) {
464 // If ImagePathName is a symlink, then it will contain the path of the symlink,
465 // not the path that the symlink points to. However, because we are opening
466 // the file, we can let the openFileW call follow the symlink for us.
467 const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName;
468 const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
469 const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name);
470 return cwd().openFileW(prefixed_path_w.span(), flags);
341 if (native_os == .linux or native_os == .serenity or native_os == .windows) {
342 var threaded: Io.Threaded = .init_single_threaded;
343 const io = threaded.ioBasic();
344 return .adaptFromNewApi(try Io.File.openSelfExe(io, flags));
471345 }
472346 // Use of max_path_bytes here is valid as the resulting path is immediately
473347 // opened with no modification.
474348 var buf: [max_path_bytes]u8 = undefined;
475349 const self_exe_path = try selfExePath(&buf);
476350 buf[self_exe_path.len] = 0;
477 return openFileAbsoluteZ(buf[0..self_exe_path.len :0].ptr, flags);
351 return openFileAbsolute(buf[0..self_exe_path.len :0], flags);
478352}
479353
480354// This is `posix.ReadLinkError || posix.RealPathError` with impossible errors excluded
......@@ -515,6 +389,8 @@ pub const SelfExePathError = error{
515389 /// On Windows, the volume does not contain a recognized file system. File
516390 /// system drivers might not be loaded, or the volume may be corrupt.
517391 UnrecognizedVolume,
392
393 Canceled,
518394} || posix.SysCtlError;
519395
520396/// `selfExePath` except allocates the result on the heap.
......@@ -554,7 +430,6 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
554430
555431 var real_path_buf: [max_path_bytes]u8 = undefined;
556432 const real_path = std.posix.realpathZ(&symlink_path_buf, &real_path_buf) catch |err| switch (err) {
557 error.InvalidWtf8 => unreachable, // Windows-only
558433 error.NetworkNotFound => unreachable, // Windows-only
559434 else => |e| return e,
560435 };
......@@ -565,15 +440,11 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
565440 }
566441 switch (native_os) {
567442 .linux, .serenity => return posix.readlinkZ("/proc/self/exe", out_buffer) catch |err| switch (err) {
568 error.InvalidUtf8 => unreachable, // WASI-only
569 error.InvalidWtf8 => unreachable, // Windows-only
570443 error.UnsupportedReparsePointType => unreachable, // Windows-only
571444 error.NetworkNotFound => unreachable, // Windows-only
572445 else => |e| return e,
573446 },
574447 .illumos => return posix.readlinkZ("/proc/self/path/a.out", out_buffer) catch |err| switch (err) {
575 error.InvalidUtf8 => unreachable, // WASI-only
576 error.InvalidWtf8 => unreachable, // Windows-only
577448 error.UnsupportedReparsePointType => unreachable, // Windows-only
578449 error.NetworkNotFound => unreachable, // Windows-only
579450 else => |e| return e,
......@@ -602,7 +473,6 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
602473 // argv[0] is a path (relative or absolute): use realpath(3) directly
603474 var real_path_buf: [max_path_bytes]u8 = undefined;
604475 const real_path = posix.realpathZ(std.os.argv[0], &real_path_buf) catch |err| switch (err) {
605 error.InvalidWtf8 => unreachable, // Windows-only
606476 error.NetworkNotFound => unreachable, // Windows-only
607477 else => |e| return e,
608478 };
......@@ -645,10 +515,7 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
645515 // that the symlink points to, though, so we need to get the realpath.
646516 var pathname_w = try windows.wToPrefixedFileW(null, image_path_name);
647517
648 const wide_slice = std.fs.cwd().realpathW2(pathname_w.span(), &pathname_w.data) catch |err| switch (err) {
649 error.InvalidWtf8 => unreachable,
650 else => |e| return e,
651 };
518 const wide_slice = try std.fs.cwd().realpathW2(pathname_w.span(), &pathname_w.data);
652519
653520 const len = std.unicode.calcWtf8Len(wide_slice);
654521 if (len > out_buffer.len)
......@@ -702,16 +569,10 @@ pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {
702569}
703570
704571test {
705 if (native_os != .wasi) {
706 _ = &makeDirAbsolute;
707 _ = &makeDirAbsoluteZ;
708 _ = &copyFileAbsolute;
709 _ = &updateFileAbsolute;
710 }
711 _ = &AtomicFile;
712 _ = &Dir;
713 _ = &File;
714 _ = &path;
572 _ = AtomicFile;
573 _ = Dir;
574 _ = File;
575 _ = path;
715576 _ = @import("fs/test.zig");
716577 _ = @import("fs/get_app_data_dir.zig");
717578}
lib/std/fs/Dir.zig+103-928
......@@ -1,6 +1,11 @@
1//! Deprecated in favor of `Io.Dir`.
12const Dir = @This();
3
24const builtin = @import("builtin");
5const native_os = builtin.os.tag;
6
37const std = @import("../std.zig");
8const Io = std.Io;
49const File = std.fs.File;
510const AtomicFile = std.fs.AtomicFile;
611const base64_encoder = fs.base64_encoder;
......@@ -12,7 +17,6 @@ const Allocator = std.mem.Allocator;
1217const assert = std.debug.assert;
1318const linux = std.os.linux;
1419const windows = std.os.windows;
15const native_os = builtin.os.tag;
1620const have_flock = @TypeOf(posix.system.flock) != void;
1721
1822fd: Handle,
......@@ -32,10 +36,6 @@ const IteratorError = error{
3236 AccessDenied,
3337 PermissionDenied,
3438 SystemResources,
35 /// WASI-only. The path of an entry could not be encoded as valid UTF-8.
36 /// WASI is unable to handle paths that cannot be encoded as well-formed UTF-8.
37 /// https://github.com/WebAssembly/wasi-filesystem/issues/17#issuecomment-1430639353
38 InvalidUtf8,
3939} || posix.UnexpectedError;
4040
4141pub const Iterator = switch (native_os) {
......@@ -549,7 +549,6 @@ pub const Iterator = switch (native_os) {
549549 .INVAL => unreachable,
550550 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
551551 .NOTCAPABLE => return error.AccessDenied,
552 .ILSEQ => return error.InvalidUtf8, // An entry's name cannot be encoded as UTF-8.
553552 else => |err| return posix.unexpectedErrno(err),
554553 }
555554 if (bufused == 0) return null;
......@@ -840,517 +839,73 @@ pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker {
840839 };
841840}
842841
843pub const OpenError = error{
844 FileNotFound,
845 NotDir,
846 AccessDenied,
847 PermissionDenied,
848 SymLinkLoop,
849 ProcessFdQuotaExceeded,
850 NameTooLong,
851 SystemFdQuotaExceeded,
852 NoDevice,
853 SystemResources,
854 /// WASI-only; file paths must be valid UTF-8.
855 InvalidUtf8,
856 /// Windows-only; file paths provided by the user must be valid WTF-8.
857 /// https://wtf-8.codeberg.page/
858 InvalidWtf8,
859 BadPathName,
860 DeviceBusy,
861 /// On Windows, `\\server` or `\\server\share` was not found.
862 NetworkNotFound,
863 ProcessNotFound,
864} || posix.UnexpectedError;
842pub const OpenError = Io.Dir.OpenError;
865843
866844pub fn close(self: *Dir) void {
867845 posix.close(self.fd);
868846 self.* = undefined;
869847}
870848
871/// Opens a file for reading or writing, without attempting to create a new file.
872/// To create a new file, see `createFile`.
873/// Call `File.close` to release the resource.
874/// Asserts that the path parameter has no null bytes.
875/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
876/// On WASI, `sub_path` should be encoded as valid UTF-8.
877/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
849/// Deprecated in favor of `Io.Dir.openFile`.
878850pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
879 if (native_os == .windows) {
880 const path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
881 return self.openFileW(path_w.span(), flags);
882 }
883 if (native_os == .wasi and !builtin.link_libc) {
884 var base: std.os.wasi.rights_t = .{};
885 // POLL_FD_READWRITE only grants extra rights if the corresponding FD_READ and/or FD_WRITE
886 // is also set.
887 if (flags.isRead()) {
888 base.FD_READ = true;
889 base.FD_TELL = true;
890 base.FD_SEEK = true;
891 base.FD_FILESTAT_GET = true;
892 base.POLL_FD_READWRITE = true;
893 }
894 if (flags.isWrite()) {
895 base.FD_WRITE = true;
896 base.FD_TELL = true;
897 base.FD_SEEK = true;
898 base.FD_DATASYNC = true;
899 base.FD_FDSTAT_SET_FLAGS = true;
900 base.FD_SYNC = true;
901 base.FD_ALLOCATE = true;
902 base.FD_ADVISE = true;
903 base.FD_FILESTAT_SET_TIMES = true;
904 base.FD_FILESTAT_SET_SIZE = true;
905 base.POLL_FD_READWRITE = true;
906 }
907 const fd = try posix.openatWasi(self.fd, sub_path, .{}, .{}, .{}, base, .{});
908 return .{ .handle = fd };
909 }
910 const path_c = try posix.toPosixPath(sub_path);
911 return self.openFileZ(&path_c, flags);
912}
913
914/// Same as `openFile` but the path parameter is null-terminated.
915pub fn openFileZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File {
916 switch (native_os) {
917 .windows => {
918 const path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path);
919 return self.openFileW(path_w.span(), flags);
920 },
921 // Use the libc API when libc is linked because it implements things
922 // such as opening absolute file paths.
923 .wasi => if (!builtin.link_libc) {
924 return openFile(self, mem.sliceTo(sub_path, 0), flags);
925 },
926 else => {},
927 }
928
929 var os_flags: posix.O = switch (native_os) {
930 .wasi => .{
931 .read = flags.mode != .write_only,
932 .write = flags.mode != .read_only,
933 },
934 else => .{
935 .ACCMODE = switch (flags.mode) {
936 .read_only => .RDONLY,
937 .write_only => .WRONLY,
938 .read_write => .RDWR,
939 },
940 },
941 };
942 if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;
943 if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;
944 if (@hasField(posix.O, "NOCTTY")) os_flags.NOCTTY = !flags.allow_ctty;
945
946 // Use the O locking flags if the os supports them to acquire the lock
947 // atomically.
948 const has_flock_open_flags = @hasField(posix.O, "EXLOCK");
949 if (has_flock_open_flags) {
950 // Note that the NONBLOCK flag is removed after the openat() call
951 // is successful.
952 switch (flags.lock) {
953 .none => {},
954 .shared => {
955 os_flags.SHLOCK = true;
956 os_flags.NONBLOCK = flags.lock_nonblocking;
957 },
958 .exclusive => {
959 os_flags.EXLOCK = true;
960 os_flags.NONBLOCK = flags.lock_nonblocking;
961 },
962 }
963 }
964 const fd = try posix.openatZ(self.fd, sub_path, os_flags, 0);
965 errdefer posix.close(fd);
966
967 if (have_flock and !has_flock_open_flags and flags.lock != .none) {
968 // TODO: integrate async I/O
969 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
970 try posix.flock(fd, switch (flags.lock) {
971 .none => unreachable,
972 .shared => posix.LOCK.SH | lock_nonblocking,
973 .exclusive => posix.LOCK.EX | lock_nonblocking,
974 });
975 }
976
977 if (has_flock_open_flags and flags.lock_nonblocking) {
978 var fl_flags = posix.fcntl(fd, posix.F.GETFL, 0) catch |err| switch (err) {
979 error.FileBusy => unreachable,
980 error.Locked => unreachable,
981 error.PermissionDenied => unreachable,
982 error.DeadLock => unreachable,
983 error.LockedRegionLimitExceeded => unreachable,
984 else => |e| return e,
985 };
986 fl_flags &= ~@as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK"));
987 _ = posix.fcntl(fd, posix.F.SETFL, fl_flags) catch |err| switch (err) {
988 error.FileBusy => unreachable,
989 error.Locked => unreachable,
990 error.PermissionDenied => unreachable,
991 error.DeadLock => unreachable,
992 error.LockedRegionLimitExceeded => unreachable,
993 else => |e| return e,
994 };
995 }
996
997 return .{ .handle = fd };
851 var threaded: Io.Threaded = .init_single_threaded;
852 const io = threaded.ioBasic();
853 return .adaptFromNewApi(try Io.Dir.openFile(self.adaptToNewApi(), io, sub_path, flags));
998854}
999855
1000/// Same as `openFile` but Windows-only and the path parameter is
1001/// [WTF-16](https://wtf-8.codeberg.page/#potentially-ill-formed-utf-16) encoded.
1002pub fn openFileW(self: Dir, sub_path_w: []const u16, flags: File.OpenFlags) File.OpenError!File {
1003 const w = windows;
1004 const file: File = .{
1005 .handle = try w.OpenFile(sub_path_w, .{
1006 .dir = self.fd,
1007 .access_mask = w.SYNCHRONIZE |
1008 (if (flags.isRead()) @as(u32, w.GENERIC_READ) else 0) |
1009 (if (flags.isWrite()) @as(u32, w.GENERIC_WRITE) else 0),
1010 .creation = w.FILE_OPEN,
1011 }),
1012 };
1013 errdefer file.close();
1014 var io: w.IO_STATUS_BLOCK = undefined;
1015 const range_off: w.LARGE_INTEGER = 0;
1016 const range_len: w.LARGE_INTEGER = 1;
1017 const exclusive = switch (flags.lock) {
1018 .none => return file,
1019 .shared => false,
1020 .exclusive => true,
1021 };
1022 try w.LockFile(
1023 file.handle,
1024 null,
1025 null,
1026 null,
1027 &io,
1028 &range_off,
1029 &range_len,
1030 null,
1031 @intFromBool(flags.lock_nonblocking),
1032 @intFromBool(exclusive),
1033 );
1034 return file;
1035}
1036
1037/// Creates, opens, or overwrites a file with write access.
1038/// Call `File.close` on the result when done.
1039/// Asserts that the path parameter has no null bytes.
1040/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1041/// On WASI, `sub_path` should be encoded as valid UTF-8.
1042/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
856/// Deprecated in favor of `Io.Dir.createFile`.
1043857pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
1044 if (native_os == .windows) {
1045 const path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
1046 return self.createFileW(path_w.span(), flags);
1047 }
1048 if (native_os == .wasi) {
1049 return .{
1050 .handle = try posix.openatWasi(self.fd, sub_path, .{}, .{
1051 .CREAT = true,
1052 .TRUNC = flags.truncate,
1053 .EXCL = flags.exclusive,
1054 }, .{}, .{
1055 .FD_READ = flags.read,
1056 .FD_WRITE = true,
1057 .FD_DATASYNC = true,
1058 .FD_SEEK = true,
1059 .FD_TELL = true,
1060 .FD_FDSTAT_SET_FLAGS = true,
1061 .FD_SYNC = true,
1062 .FD_ALLOCATE = true,
1063 .FD_ADVISE = true,
1064 .FD_FILESTAT_SET_TIMES = true,
1065 .FD_FILESTAT_SET_SIZE = true,
1066 .FD_FILESTAT_GET = true,
1067 // POLL_FD_READWRITE only grants extra rights if the corresponding FD_READ and/or
1068 // FD_WRITE is also set.
1069 .POLL_FD_READWRITE = true,
1070 }, .{}),
1071 };
1072 }
1073 const path_c = try posix.toPosixPath(sub_path);
1074 return self.createFileZ(&path_c, flags);
1075}
1076
1077/// Same as `createFile` but the path parameter is null-terminated.
1078pub fn createFileZ(self: Dir, sub_path_c: [*:0]const u8, flags: File.CreateFlags) File.OpenError!File {
1079 switch (native_os) {
1080 .windows => {
1081 const path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c);
1082 return self.createFileW(path_w.span(), flags);
1083 },
1084 .wasi => {
1085 return createFile(self, mem.sliceTo(sub_path_c, 0), flags);
1086 },
1087 else => {},
1088 }
1089
1090 var os_flags: posix.O = .{
1091 .ACCMODE = if (flags.read) .RDWR else .WRONLY,
1092 .CREAT = true,
1093 .TRUNC = flags.truncate,
1094 .EXCL = flags.exclusive,
1095 };
1096 if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true;
1097 if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true;
1098
1099 // Use the O locking flags if the os supports them to acquire the lock
1100 // atomically. Note that the NONBLOCK flag is removed after the openat()
1101 // call is successful.
1102 const has_flock_open_flags = @hasField(posix.O, "EXLOCK");
1103 if (has_flock_open_flags) switch (flags.lock) {
1104 .none => {},
1105 .shared => {
1106 os_flags.SHLOCK = true;
1107 os_flags.NONBLOCK = flags.lock_nonblocking;
1108 },
1109 .exclusive => {
1110 os_flags.EXLOCK = true;
1111 os_flags.NONBLOCK = flags.lock_nonblocking;
1112 },
1113 };
1114
1115 const fd = try posix.openatZ(self.fd, sub_path_c, os_flags, flags.mode);
1116 errdefer posix.close(fd);
1117
1118 if (have_flock and !has_flock_open_flags and flags.lock != .none) {
1119 // TODO: integrate async I/O
1120 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
1121 try posix.flock(fd, switch (flags.lock) {
1122 .none => unreachable,
1123 .shared => posix.LOCK.SH | lock_nonblocking,
1124 .exclusive => posix.LOCK.EX | lock_nonblocking,
1125 });
1126 }
1127
1128 if (has_flock_open_flags and flags.lock_nonblocking) {
1129 var fl_flags = posix.fcntl(fd, posix.F.GETFL, 0) catch |err| switch (err) {
1130 error.FileBusy => unreachable,
1131 error.Locked => unreachable,
1132 error.PermissionDenied => unreachable,
1133 error.DeadLock => unreachable,
1134 error.LockedRegionLimitExceeded => unreachable,
1135 else => |e| return e,
1136 };
1137 fl_flags &= ~@as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK"));
1138 _ = posix.fcntl(fd, posix.F.SETFL, fl_flags) catch |err| switch (err) {
1139 error.FileBusy => unreachable,
1140 error.Locked => unreachable,
1141 error.PermissionDenied => unreachable,
1142 error.DeadLock => unreachable,
1143 error.LockedRegionLimitExceeded => unreachable,
1144 else => |e| return e,
1145 };
1146 }
1147
1148 return .{ .handle = fd };
858 var threaded: Io.Threaded = .init_single_threaded;
859 const io = threaded.ioBasic();
860 const new_file = try Io.Dir.createFile(self.adaptToNewApi(), io, sub_path, flags);
861 return .adaptFromNewApi(new_file);
1149862}
1150863
1151/// Same as `createFile` but Windows-only and the path parameter is
1152/// [WTF-16](https://wtf-8.codeberg.page/#potentially-ill-formed-utf-16) encoded.
1153pub fn createFileW(self: Dir, sub_path_w: []const u16, flags: File.CreateFlags) File.OpenError!File {
1154 const w = windows;
1155 const read_flag = if (flags.read) @as(u32, w.GENERIC_READ) else 0;
1156 const file: File = .{
1157 .handle = try w.OpenFile(sub_path_w, .{
1158 .dir = self.fd,
1159 .access_mask = w.SYNCHRONIZE | w.GENERIC_WRITE | read_flag,
1160 .creation = if (flags.exclusive)
1161 @as(u32, w.FILE_CREATE)
1162 else if (flags.truncate)
1163 @as(u32, w.FILE_OVERWRITE_IF)
1164 else
1165 @as(u32, w.FILE_OPEN_IF),
1166 }),
1167 };
1168 errdefer file.close();
1169 var io: w.IO_STATUS_BLOCK = undefined;
1170 const range_off: w.LARGE_INTEGER = 0;
1171 const range_len: w.LARGE_INTEGER = 1;
1172 const exclusive = switch (flags.lock) {
1173 .none => return file,
1174 .shared => false,
1175 .exclusive => true,
1176 };
1177 try w.LockFile(
1178 file.handle,
1179 null,
1180 null,
1181 null,
1182 &io,
1183 &range_off,
1184 &range_len,
1185 null,
1186 @intFromBool(flags.lock_nonblocking),
1187 @intFromBool(exclusive),
1188 );
1189 return file;
1190}
1191
1192pub const MakeError = posix.MakeDirError;
864/// Deprecated in favor of `Io.Dir.MakeError`.
865pub const MakeError = Io.Dir.MakeError;
1193866
1194/// Creates a single directory with a relative or absolute path.
1195/// To create multiple directories to make an entire path, see `makePath`.
1196/// To operate on only absolute paths, see `makeDirAbsolute`.
1197/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1198/// On WASI, `sub_path` should be encoded as valid UTF-8.
1199/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
867/// Deprecated in favor of `Io.Dir.makeDir`.
1200868pub fn makeDir(self: Dir, sub_path: []const u8) MakeError!void {
1201 try posix.mkdirat(self.fd, sub_path, default_mode);
869 var threaded: Io.Threaded = .init_single_threaded;
870 const io = threaded.ioBasic();
871 return Io.Dir.makeDir(.{ .handle = self.fd }, io, sub_path);
1202872}
1203873
1204/// Same as `makeDir`, but `sub_path` is null-terminated.
1205/// To create multiple directories to make an entire path, see `makePath`.
1206/// To operate on only absolute paths, see `makeDirAbsoluteZ`.
874/// Deprecated in favor of `Io.Dir.makeDir`.
1207875pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) MakeError!void {
1208876 try posix.mkdiratZ(self.fd, sub_path, default_mode);
1209877}
1210878
1211/// Creates a single directory with a relative or absolute null-terminated WTF-16 LE-encoded path.
1212/// To create multiple directories to make an entire path, see `makePath`.
1213/// To operate on only absolute paths, see `makeDirAbsoluteW`.
879/// Deprecated in favor of `Io.Dir.makeDir`.
1214880pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) MakeError!void {
1215881 try posix.mkdiratW(self.fd, mem.span(sub_path), default_mode);
1216882}
1217883
1218/// Calls makeDir iteratively to make an entire path
1219/// (i.e. creating any parent directories that do not exist).
1220/// Returns success if the path already exists and is a directory.
1221/// This function is not atomic, and if it returns an error, the file system may
1222/// have been modified regardless.
1223/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1224/// On WASI, `sub_path` should be encoded as valid UTF-8.
1225/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1226/// Fails on an empty path with `error.BadPathName` as that is not a path that can be created.
1227///
1228/// Paths containing `..` components are handled differently depending on the platform:
1229/// - On Windows, `..` are resolved before the path is passed to NtCreateFile, meaning
1230/// a `sub_path` like "first/../second" will resolve to "second" and only a
1231/// `./second` directory will be created.
1232/// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`,
1233/// meaning a `sub_path` like "first/../second" will create both a `./first`
1234/// and a `./second` directory.
1235pub fn makePath(self: Dir, sub_path: []const u8) (MakeError || StatFileError)!void {
884/// Deprecated in favor of `Io.Dir.makePath`.
885pub fn makePath(self: Dir, sub_path: []const u8) MakePathError!void {
1236886 _ = try self.makePathStatus(sub_path);
1237887}
1238888
1239pub const MakePathStatus = enum { existed, created };
1240/// Same as `makePath` except returns whether the path already existed or was successfully created.
1241pub fn makePathStatus(self: Dir, sub_path: []const u8) (MakeError || StatFileError)!MakePathStatus {
1242 var it = try fs.path.componentIterator(sub_path);
1243 var status: MakePathStatus = .existed;
1244 var component = it.last() orelse return error.BadPathName;
1245 while (true) {
1246 if (self.makeDir(component.path)) |_| {
1247 status = .created;
1248 } else |err| switch (err) {
1249 error.PathAlreadyExists => {
1250 // stat the file and return an error if it's not a directory
1251 // this is important because otherwise a dangling symlink
1252 // could cause an infinite loop
1253 check_dir: {
1254 // workaround for windows, see https://github.com/ziglang/zig/issues/16738
1255 const fstat = self.statFile(component.path) catch |stat_err| switch (stat_err) {
1256 error.IsDir => break :check_dir,
1257 else => |e| return e,
1258 };
1259 if (fstat.kind != .directory) return error.NotDir;
1260 }
1261 },
1262 error.FileNotFound => |e| {
1263 component = it.previous() orelse return e;
1264 continue;
1265 },
1266 else => |e| return e,
1267 }
1268 component = it.next() orelse return status;
1269 }
1270}
1271
1272/// Windows only. Calls makeOpenDirAccessMaskW iteratively to make an entire path
1273/// (i.e. creating any parent directories that do not exist).
1274/// Opens the dir if the path already exists and is a directory.
1275/// This function is not atomic, and if it returns an error, the file system may
1276/// have been modified regardless.
1277/// `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1278fn makeOpenPathAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) (MakeError || OpenError || StatFileError)!Dir {
1279 const w = windows;
1280 var it = try fs.path.componentIterator(sub_path);
1281 // If there are no components in the path, then create a dummy component with the full path.
1282 var component = it.last() orelse fs.path.NativeComponentIterator.Component{
1283 .name = "",
1284 .path = sub_path,
1285 };
889/// Deprecated in favor of `Io.Dir.MakePathStatus`.
890pub const MakePathStatus = Io.Dir.MakePathStatus;
891/// Deprecated in favor of `Io.Dir.MakePathError`.
892pub const MakePathError = Io.Dir.MakePathError;
1286893
1287 while (true) {
1288 const sub_path_w = try w.sliceToPrefixedFileW(self.fd, component.path);
1289 const is_last = it.peekNext() == null;
1290 var result = self.makeOpenDirAccessMaskW(sub_path_w.span().ptr, access_mask, .{
1291 .no_follow = no_follow,
1292 .create_disposition = if (is_last) w.FILE_OPEN_IF else w.FILE_CREATE,
1293 }) catch |err| switch (err) {
1294 error.FileNotFound => |e| {
1295 component = it.previous() orelse return e;
1296 continue;
1297 },
1298 error.PathAlreadyExists => result: {
1299 assert(!is_last);
1300 // stat the file and return an error if it's not a directory
1301 // this is important because otherwise a dangling symlink
1302 // could cause an infinite loop
1303 check_dir: {
1304 // workaround for windows, see https://github.com/ziglang/zig/issues/16738
1305 const fstat = self.statFile(component.path) catch |stat_err| switch (stat_err) {
1306 error.IsDir => break :check_dir,
1307 else => |e| return e,
1308 };
1309 if (fstat.kind != .directory) return error.NotDir;
1310 }
1311 break :result null;
1312 },
1313 else => |e| return e,
1314 };
1315
1316 component = it.next() orelse return result.?;
1317
1318 // Don't leak the intermediate file handles
1319 if (result) |*dir| {
1320 dir.close();
1321 }
1322 }
894/// Deprecated in favor of `Io.Dir.makePathStatus`.
895pub fn makePathStatus(self: Dir, sub_path: []const u8) MakePathError!MakePathStatus {
896 var threaded: Io.Threaded = .init_single_threaded;
897 const io = threaded.ioBasic();
898 return Io.Dir.makePathStatus(.{ .handle = self.fd }, io, sub_path);
1323899}
1324900
1325/// This function performs `makePath`, followed by `openDir`.
1326/// If supported by the OS, this operation is atomic. It is not atomic on
1327/// all operating systems.
1328/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1329/// On WASI, `sub_path` should be encoded as valid UTF-8.
1330/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1331pub fn makeOpenPath(self: Dir, sub_path: []const u8, open_dir_options: OpenOptions) (MakeError || OpenError || StatFileError)!Dir {
1332 return switch (native_os) {
1333 .windows => {
1334 const w = windows;
1335 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1336 w.SYNCHRONIZE | w.FILE_TRAVERSE |
1337 (if (open_dir_options.iterate) w.FILE_LIST_DIRECTORY else @as(u32, 0));
1338
1339 return self.makeOpenPathAccessMaskW(sub_path, base_flags, open_dir_options.no_follow);
1340 },
1341 else => {
1342 return self.openDir(sub_path, open_dir_options) catch |err| switch (err) {
1343 error.FileNotFound => {
1344 try self.makePath(sub_path);
1345 return self.openDir(sub_path, open_dir_options);
1346 },
1347 else => |e| return e,
1348 };
1349 },
1350 };
901/// Deprecated in favor of `Io.Dir.makeOpenPath`.
902pub fn makeOpenPath(dir: Dir, sub_path: []const u8, options: OpenOptions) Io.Dir.MakeOpenPathError!Dir {
903 var threaded: Io.Threaded = .init_single_threaded;
904 const io = threaded.ioBasic();
905 return .adaptFromNewApi(try Io.Dir.makeOpenPath(dir.adaptToNewApi(), io, sub_path, options));
1351906}
1352907
1353pub const RealPathError = posix.RealPathError;
908pub const RealPathError = posix.RealPathError || error{Canceled};
1354909
1355910/// This function returns the canonicalized absolute pathname of
1356911/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this
......@@ -1408,7 +963,6 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE
1408963 error.FileLocksNotSupported => return error.Unexpected,
1409964 error.FileBusy => return error.Unexpected,
1410965 error.WouldBlock => return error.Unexpected,
1411 error.InvalidUtf8 => unreachable, // WASI-only
1412966 else => |e| return e,
1413967 };
1414968 defer posix.close(fd);
......@@ -1510,234 +1064,14 @@ pub fn setAsCwd(self: Dir) !void {
15101064 try posix.fchdir(self.fd);
15111065}
15121066
1513pub const OpenOptions = struct {
1514 /// `true` means the opened directory can be used as the `Dir` parameter
1515 /// for functions which operate based on an open directory handle. When `false`,
1516 /// such operations are Illegal Behavior.
1517 access_sub_paths: bool = true,
1518
1519 /// `true` means the opened directory can be scanned for the files and sub-directories
1520 /// of the result. It means the `iterate` function can be called.
1521 iterate: bool = false,
1067/// Deprecated in favor of `Io.Dir.OpenOptions`.
1068pub const OpenOptions = Io.Dir.OpenOptions;
15221069
1523 /// `true` means it won't dereference the symlinks.
1524 no_follow: bool = false,
1525};
1526
1527/// Opens a directory at the given path. The directory is a system resource that remains
1528/// open until `close` is called on the result.
1529/// The directory cannot be iterated unless the `iterate` option is set to `true`.
1530///
1531/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1532/// On WASI, `sub_path` should be encoded as valid UTF-8.
1533/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1534/// Asserts that the path parameter has no null bytes.
1070/// Deprecated in favor of `Io.Dir.openDir`.
15351071pub fn openDir(self: Dir, sub_path: []const u8, args: OpenOptions) OpenError!Dir {
1536 switch (native_os) {
1537 .windows => {
1538 const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
1539 return self.openDirW(sub_path_w.span().ptr, args);
1540 },
1541 .wasi => if (!builtin.link_libc) {
1542 var base: std.os.wasi.rights_t = .{
1543 .FD_FILESTAT_GET = true,
1544 .FD_FDSTAT_SET_FLAGS = true,
1545 .FD_FILESTAT_SET_TIMES = true,
1546 };
1547 if (args.access_sub_paths) {
1548 base.FD_READDIR = true;
1549 base.PATH_CREATE_DIRECTORY = true;
1550 base.PATH_CREATE_FILE = true;
1551 base.PATH_LINK_SOURCE = true;
1552 base.PATH_LINK_TARGET = true;
1553 base.PATH_OPEN = true;
1554 base.PATH_READLINK = true;
1555 base.PATH_RENAME_SOURCE = true;
1556 base.PATH_RENAME_TARGET = true;
1557 base.PATH_FILESTAT_GET = true;
1558 base.PATH_FILESTAT_SET_SIZE = true;
1559 base.PATH_FILESTAT_SET_TIMES = true;
1560 base.PATH_SYMLINK = true;
1561 base.PATH_REMOVE_DIRECTORY = true;
1562 base.PATH_UNLINK_FILE = true;
1563 }
1564
1565 const result = posix.openatWasi(
1566 self.fd,
1567 sub_path,
1568 .{ .SYMLINK_FOLLOW = !args.no_follow },
1569 .{ .DIRECTORY = true },
1570 .{},
1571 base,
1572 base,
1573 );
1574 const fd = result catch |err| switch (err) {
1575 error.FileTooBig => unreachable, // can't happen for directories
1576 error.IsDir => unreachable, // we're setting DIRECTORY
1577 error.NoSpaceLeft => unreachable, // not setting CREAT
1578 error.PathAlreadyExists => unreachable, // not setting CREAT
1579 error.FileLocksNotSupported => unreachable, // locking folders is not supported
1580 error.WouldBlock => unreachable, // can't happen for directories
1581 error.FileBusy => unreachable, // can't happen for directories
1582 else => |e| return e,
1583 };
1584 return .{ .fd = fd };
1585 },
1586 else => {},
1587 }
1588 const sub_path_c = try posix.toPosixPath(sub_path);
1589 return self.openDirZ(&sub_path_c, args);
1590}
1591
1592/// Same as `openDir` except the parameter is null-terminated.
1593pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenOptions) OpenError!Dir {
1594 switch (native_os) {
1595 .windows => {
1596 const sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path_c);
1597 return self.openDirW(sub_path_w.span().ptr, args);
1598 },
1599 // Use the libc API when libc is linked because it implements things
1600 // such as opening absolute directory paths.
1601 .wasi => if (!builtin.link_libc) {
1602 return openDir(self, mem.sliceTo(sub_path_c, 0), args);
1603 },
1604 .haiku => {
1605 const rc = posix.system._kern_open_dir(self.fd, sub_path_c);
1606 if (rc >= 0) return .{ .fd = rc };
1607 switch (@as(posix.E, @enumFromInt(rc))) {
1608 .FAULT => unreachable,
1609 .INVAL => unreachable,
1610 .BADF => unreachable,
1611 .ACCES => return error.AccessDenied,
1612 .LOOP => return error.SymLinkLoop,
1613 .MFILE => return error.ProcessFdQuotaExceeded,
1614 .NAMETOOLONG => return error.NameTooLong,
1615 .NFILE => return error.SystemFdQuotaExceeded,
1616 .NODEV => return error.NoDevice,
1617 .NOENT => return error.FileNotFound,
1618 .NOMEM => return error.SystemResources,
1619 .NOTDIR => return error.NotDir,
1620 .PERM => return error.PermissionDenied,
1621 .BUSY => return error.DeviceBusy,
1622 else => |err| return posix.unexpectedErrno(err),
1623 }
1624 },
1625 else => {},
1626 }
1627
1628 var symlink_flags: posix.O = switch (native_os) {
1629 .wasi => .{
1630 .read = true,
1631 .NOFOLLOW = args.no_follow,
1632 .DIRECTORY = true,
1633 },
1634 else => .{
1635 .ACCMODE = .RDONLY,
1636 .NOFOLLOW = args.no_follow,
1637 .DIRECTORY = true,
1638 .CLOEXEC = true,
1639 },
1640 };
1641
1642 if (@hasField(posix.O, "PATH") and !args.iterate)
1643 symlink_flags.PATH = true;
1644
1645 return self.openDirFlagsZ(sub_path_c, symlink_flags);
1646}
1647
1648/// Same as `openDir` except the path parameter is WTF-16 LE encoded, NT-prefixed.
1649/// This function asserts the target OS is Windows.
1650pub fn openDirW(self: Dir, sub_path_w: [*:0]const u16, args: OpenOptions) OpenError!Dir {
1651 const w = windows;
1652 // TODO remove some of these flags if args.access_sub_paths is false
1653 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1654 w.SYNCHRONIZE | w.FILE_TRAVERSE;
1655 const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
1656 const dir = self.makeOpenDirAccessMaskW(sub_path_w, flags, .{
1657 .no_follow = args.no_follow,
1658 .create_disposition = w.FILE_OPEN,
1659 }) catch |err| switch (err) {
1660 error.ReadOnlyFileSystem => unreachable,
1661 error.DiskQuota => unreachable,
1662 error.NoSpaceLeft => unreachable,
1663 error.PathAlreadyExists => unreachable,
1664 error.LinkQuotaExceeded => unreachable,
1665 else => |e| return e,
1666 };
1667 return dir;
1668}
1669
1670/// Asserts `flags` has `DIRECTORY` set.
1671fn openDirFlagsZ(self: Dir, sub_path_c: [*:0]const u8, flags: posix.O) OpenError!Dir {
1672 assert(flags.DIRECTORY);
1673 const fd = posix.openatZ(self.fd, sub_path_c, flags, 0) catch |err| switch (err) {
1674 error.FileTooBig => unreachable, // can't happen for directories
1675 error.IsDir => unreachable, // we're setting DIRECTORY
1676 error.NoSpaceLeft => unreachable, // not setting CREAT
1677 error.PathAlreadyExists => unreachable, // not setting CREAT
1678 error.FileLocksNotSupported => unreachable, // locking folders is not supported
1679 error.WouldBlock => unreachable, // can't happen for directories
1680 error.FileBusy => unreachable, // can't happen for directories
1681 else => |e| return e,
1682 };
1683 return Dir{ .fd = fd };
1684}
1685
1686const MakeOpenDirAccessMaskWOptions = struct {
1687 no_follow: bool,
1688 create_disposition: u32,
1689};
1690
1691fn makeOpenDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, flags: MakeOpenDirAccessMaskWOptions) (MakeError || OpenError)!Dir {
1692 const w = windows;
1693
1694 var result = Dir{
1695 .fd = undefined,
1696 };
1697
1698 const path_len_bytes = @as(u16, @intCast(mem.sliceTo(sub_path_w, 0).len * 2));
1699 var nt_name = w.UNICODE_STRING{
1700 .Length = path_len_bytes,
1701 .MaximumLength = path_len_bytes,
1702 .Buffer = @constCast(sub_path_w),
1703 };
1704 var attr = w.OBJECT_ATTRIBUTES{
1705 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
1706 .RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w)) null else self.fd,
1707 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
1708 .ObjectName = &nt_name,
1709 .SecurityDescriptor = null,
1710 .SecurityQualityOfService = null,
1711 };
1712 const open_reparse_point: w.DWORD = if (flags.no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0;
1713 var io: w.IO_STATUS_BLOCK = undefined;
1714 const rc = w.ntdll.NtCreateFile(
1715 &result.fd,
1716 access_mask,
1717 &attr,
1718 &io,
1719 null,
1720 w.FILE_ATTRIBUTE_NORMAL,
1721 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
1722 flags.create_disposition,
1723 w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
1724 null,
1725 0,
1726 );
1727
1728 switch (rc) {
1729 .SUCCESS => return result,
1730 .OBJECT_NAME_INVALID => return error.BadPathName,
1731 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
1732 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
1733 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
1734 .NOT_A_DIRECTORY => return error.NotDir,
1735 // This can happen if the directory has 'List folder contents' permission set to 'Deny'
1736 // and the directory is trying to be opened for iteration.
1737 .ACCESS_DENIED => return error.AccessDenied,
1738 .INVALID_PARAMETER => unreachable,
1739 else => return w.unexpectedStatus(rc),
1740 }
1072 var threaded: Io.Threaded = .init_single_threaded;
1073 const io = threaded.ioBasic();
1074 return .adaptFromNewApi(try Io.Dir.openDir(.{ .handle = self.fd }, io, sub_path, args));
17411075}
17421076
17431077pub const DeleteFileError = posix.UnlinkError;
......@@ -1801,11 +1135,9 @@ pub const DeleteDirError = error{
18011135 NotDir,
18021136 SystemResources,
18031137 ReadOnlyFileSystem,
1804 /// WASI-only; file paths must be valid UTF-8.
1805 InvalidUtf8,
1806 /// Windows-only; file paths provided by the user must be valid WTF-8.
1138 /// WASI: file paths must be valid UTF-8.
1139 /// Windows: file paths provided by the user must be valid WTF-8.
18071140 /// https://wtf-8.codeberg.page/
1808 InvalidWtf8,
18091141 BadPathName,
18101142 /// On Windows, `\\server` or `\\server\share` was not found.
18111143 NetworkNotFound,
......@@ -1906,10 +1238,7 @@ pub fn symLink(
19061238 // when converting to an NT namespaced path. CreateSymbolicLink in
19071239 // symLinkW will handle the necessary conversion.
19081240 var target_path_w: windows.PathSpace = undefined;
1909 if (try std.unicode.checkWtf8ToWtf16LeOverflow(target_path, &target_path_w.data)) {
1910 return error.NameTooLong;
1911 }
1912 target_path_w.len = try std.unicode.wtf8ToWtf16Le(&target_path_w.data, target_path);
1241 target_path_w.len = try windows.wtf8ToWtf16Le(&target_path_w.data, target_path);
19131242 target_path_w.data[target_path_w.len] = 0;
19141243 // However, we need to canonicalize any path separators to `\`, since if
19151244 // the target path is relative, then it must use `\` as the path separator.
......@@ -2052,20 +1381,11 @@ pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
20521381 return windows.ReadLink(self.fd, sub_path_w, buffer);
20531382}
20541383
2055/// Read all of file contents using a preallocated buffer.
2056/// The returned slice has the same pointer as `buffer`. If the length matches `buffer.len`
2057/// the situation is ambiguous. It could either mean that the entire file was read, and
2058/// it exactly fits the buffer, or it could mean the buffer was not big enough for the
2059/// entire file.
2060/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2061/// On WASI, `file_path` should be encoded as valid UTF-8.
2062/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
1384/// Deprecated in favor of `Io.Dir.readFile`.
20631385pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
2064 var file = try self.openFile(file_path, .{});
2065 defer file.close();
2066
2067 const end_index = try file.readAll(buffer);
2068 return buffer[0..end_index];
1386 var threaded: Io.Threaded = .init_single_threaded;
1387 const io = threaded.ioBasic();
1388 return Io.Dir.readFile(.{ .handle = self.fd }, io, file_path, buffer);
20691389}
20701390
20711391pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Error || error{
......@@ -2091,7 +1411,7 @@ pub fn readFileAlloc(
20911411 /// Used to allocate the result.
20921412 gpa: Allocator,
20931413 /// If reached or exceeded, `error.StreamTooLong` is returned instead.
2094 limit: std.Io.Limit,
1414 limit: Io.Limit,
20951415) ReadFileAllocError![]u8 {
20961416 return readFileAllocOptions(dir, sub_path, gpa, limit, .of(u8), null);
20971417}
......@@ -2101,6 +1421,8 @@ pub fn readFileAlloc(
21011421///
21021422/// If the file size is already known, a better alternative is to initialize a
21031423/// `File.Reader`.
1424///
1425/// TODO move this function to Io.Dir
21041426pub fn readFileAllocOptions(
21051427 dir: Dir,
21061428 /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
......@@ -2110,13 +1432,16 @@ pub fn readFileAllocOptions(
21101432 /// Used to allocate the result.
21111433 gpa: Allocator,
21121434 /// If reached or exceeded, `error.StreamTooLong` is returned instead.
2113 limit: std.Io.Limit,
1435 limit: Io.Limit,
21141436 comptime alignment: std.mem.Alignment,
21151437 comptime sentinel: ?u8,
21161438) ReadFileAllocError!(if (sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) {
1439 var threaded: Io.Threaded = .init_single_threaded;
1440 const io = threaded.ioBasic();
1441
21171442 var file = try dir.openFile(sub_path, .{});
21181443 defer file.close();
2119 var file_reader = file.reader(&.{});
1444 var file_reader = file.reader(io, &.{});
21201445 return file_reader.interface.allocRemainingAlignedSentinel(gpa, limit, alignment, sentinel) catch |err| switch (err) {
21211446 error.ReadFailed => return file_reader.err.?,
21221447 error.OutOfMemory, error.StreamTooLong => |e| return e,
......@@ -2138,24 +1463,19 @@ pub const DeleteTreeError = error{
21381463 FileBusy,
21391464 DeviceBusy,
21401465 ProcessNotFound,
2141
21421466 /// One of the path components was not a directory.
21431467 /// This error is unreachable if `sub_path` does not contain a path separator.
21441468 NotDir,
2145
2146 /// WASI-only; file paths must be valid UTF-8.
2147 InvalidUtf8,
2148
2149 /// Windows-only; file paths provided by the user must be valid WTF-8.
1469 /// WASI: file paths must be valid UTF-8.
1470 /// Windows: file paths provided by the user must be valid WTF-8.
21501471 /// https://wtf-8.codeberg.page/
2151 InvalidWtf8,
2152
21531472 /// On Windows, file paths cannot contain these characters:
21541473 /// '/', '*', '?', '"', '<', '>', '|'
21551474 BadPathName,
2156
21571475 /// On Windows, `\\server` or `\\server\share` was not found.
21581476 NetworkNotFound,
1477
1478 Canceled,
21591479} || posix.UnexpectedError;
21601480
21611481/// Whether `sub_path` describes a symlink, file, or directory, this function
......@@ -2196,7 +1516,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
21961516 if (treat_as_dir) {
21971517 if (stack.unusedCapacitySlice().len >= 1) {
21981518 var iterable_dir = top.iter.dir.openDir(entry.name, .{
2199 .no_follow = true,
1519 .follow_symlinks = false,
22001520 .iterate = true,
22011521 }) catch |err| switch (err) {
22021522 error.NotDir => {
......@@ -2212,17 +1532,15 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
22121532 error.PermissionDenied,
22131533 error.SymLinkLoop,
22141534 error.ProcessFdQuotaExceeded,
2215 error.ProcessNotFound,
22161535 error.NameTooLong,
22171536 error.SystemFdQuotaExceeded,
22181537 error.NoDevice,
22191538 error.SystemResources,
22201539 error.Unexpected,
2221 error.InvalidUtf8,
2222 error.InvalidWtf8,
22231540 error.BadPathName,
22241541 error.NetworkNotFound,
22251542 error.DeviceBusy,
1543 error.Canceled,
22261544 => |e| return e,
22271545 };
22281546 stack.appendAssumeCapacity(.{
......@@ -2251,8 +1569,6 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
22511569
22521570 error.AccessDenied,
22531571 error.PermissionDenied,
2254 error.InvalidUtf8,
2255 error.InvalidWtf8,
22561572 error.SymLinkLoop,
22571573 error.NameTooLong,
22581574 error.SystemResources,
......@@ -2294,7 +1610,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
22941610 handle_entry: while (true) {
22951611 if (treat_as_dir) {
22961612 break :iterable_dir parent_dir.openDir(name, .{
2297 .no_follow = true,
1613 .follow_symlinks = false,
22981614 .iterate = true,
22991615 }) catch |err| switch (err) {
23001616 error.NotDir => {
......@@ -2309,18 +1625,16 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
23091625 error.AccessDenied,
23101626 error.PermissionDenied,
23111627 error.SymLinkLoop,
2312 error.ProcessNotFound,
23131628 error.ProcessFdQuotaExceeded,
23141629 error.NameTooLong,
23151630 error.SystemFdQuotaExceeded,
23161631 error.NoDevice,
23171632 error.SystemResources,
23181633 error.Unexpected,
2319 error.InvalidUtf8,
2320 error.InvalidWtf8,
23211634 error.BadPathName,
23221635 error.NetworkNotFound,
23231636 error.DeviceBusy,
1637 error.Canceled,
23241638 => |e| return e,
23251639 };
23261640 } else {
......@@ -2339,8 +1653,6 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
23391653
23401654 error.AccessDenied,
23411655 error.PermissionDenied,
2342 error.InvalidUtf8,
2343 error.InvalidWtf8,
23441656 error.SymLinkLoop,
23451657 error.NameTooLong,
23461658 error.SystemResources,
......@@ -2402,7 +1714,7 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
24021714 handle_entry: while (true) {
24031715 if (treat_as_dir) {
24041716 const new_dir = dir.openDir(entry.name, .{
2405 .no_follow = true,
1717 .follow_symlinks = false,
24061718 .iterate = true,
24071719 }) catch |err| switch (err) {
24081720 error.NotDir => {
......@@ -2417,18 +1729,16 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
24171729 error.AccessDenied,
24181730 error.PermissionDenied,
24191731 error.SymLinkLoop,
2420 error.ProcessNotFound,
24211732 error.ProcessFdQuotaExceeded,
24221733 error.NameTooLong,
24231734 error.SystemFdQuotaExceeded,
24241735 error.NoDevice,
24251736 error.SystemResources,
24261737 error.Unexpected,
2427 error.InvalidUtf8,
2428 error.InvalidWtf8,
24291738 error.BadPathName,
24301739 error.NetworkNotFound,
24311740 error.DeviceBusy,
1741 error.Canceled,
24321742 => |e| return e,
24331743 };
24341744 if (cleanup_dir_parent) |*d| d.close();
......@@ -2454,8 +1764,6 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
24541764
24551765 error.AccessDenied,
24561766 error.PermissionDenied,
2457 error.InvalidUtf8,
2458 error.InvalidWtf8,
24591767 error.SymLinkLoop,
24601768 error.NameTooLong,
24611769 error.SystemResources,
......@@ -2503,7 +1811,7 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
25031811 handle_entry: while (true) {
25041812 if (treat_as_dir) {
25051813 break :iterable_dir self.openDir(sub_path, .{
2506 .no_follow = true,
1814 .follow_symlinks = false,
25071815 .iterate = true,
25081816 }) catch |err| switch (err) {
25091817 error.NotDir => {
......@@ -2519,17 +1827,15 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
25191827 error.PermissionDenied,
25201828 error.SymLinkLoop,
25211829 error.ProcessFdQuotaExceeded,
2522 error.ProcessNotFound,
25231830 error.NameTooLong,
25241831 error.SystemFdQuotaExceeded,
25251832 error.NoDevice,
25261833 error.SystemResources,
25271834 error.Unexpected,
2528 error.InvalidUtf8,
2529 error.InvalidWtf8,
25301835 error.BadPathName,
25311836 error.DeviceBusy,
25321837 error.NetworkNotFound,
1838 error.Canceled,
25331839 => |e| return e,
25341840 };
25351841 } else {
......@@ -2545,8 +1851,6 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
25451851
25461852 error.AccessDenied,
25471853 error.PermissionDenied,
2548 error.InvalidUtf8,
2549 error.InvalidWtf8,
25501854 error.SymLinkLoop,
25511855 error.NameTooLong,
25521856 error.SystemResources,
......@@ -2582,47 +1886,14 @@ pub fn writeFile(self: Dir, options: WriteFileOptions) WriteFileError!void {
25821886 try file.writeAll(options.data);
25831887}
25841888
2585pub const AccessError = posix.AccessError;
1889/// Deprecated in favor of `Io.Dir.AccessError`.
1890pub const AccessError = Io.Dir.AccessError;
25861891
2587/// Test accessing `sub_path`.
2588/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2589/// On WASI, `sub_path` should be encoded as valid UTF-8.
2590/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
2591/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
2592/// For example, instead of testing if a file exists and then opening it, just
2593/// open it and handle the error for file not found.
2594pub fn access(self: Dir, sub_path: []const u8, flags: File.OpenFlags) AccessError!void {
2595 if (native_os == .windows) {
2596 const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);
2597 return self.accessW(sub_path_w.span().ptr, flags);
2598 }
2599 const path_c = try posix.toPosixPath(sub_path);
2600 return self.accessZ(&path_c, flags);
2601}
2602
2603/// Same as `access` except the path parameter is null-terminated.
2604pub fn accessZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) AccessError!void {
2605 if (native_os == .windows) {
2606 const sub_path_w = try windows.cStrToPrefixedFileW(self.fd, sub_path);
2607 return self.accessW(sub_path_w.span().ptr, flags);
2608 }
2609 const os_mode = switch (flags.mode) {
2610 .read_only => @as(u32, posix.F_OK),
2611 .write_only => @as(u32, posix.W_OK),
2612 .read_write => @as(u32, posix.R_OK | posix.W_OK),
2613 };
2614 const result = posix.faccessatZ(self.fd, sub_path, os_mode, 0);
2615 return result;
2616}
2617
2618/// Same as `access` except asserts the target OS is Windows and the path parameter is
2619/// * WTF-16 LE encoded
2620/// * null-terminated
2621/// * relative or has the NT namespace prefix
2622/// TODO currently this ignores `flags`.
2623pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {
2624 _ = flags;
2625 return posix.faccessatW(self.fd, sub_path_w);
1892/// Deprecated in favor of `Io.Dir.access`.
1893pub fn access(self: Dir, sub_path: []const u8, options: Io.Dir.AccessOptions) AccessError!void {
1894 var threaded: Io.Threaded = .init_single_threaded;
1895 const io = threaded.ioBasic();
1896 return Io.Dir.access(self.adaptToNewApi(), io, sub_path, options);
26261897}
26271898
26281899pub const CopyFileOptions = struct {
......@@ -2630,77 +1901,9 @@ pub const CopyFileOptions = struct {
26301901 override_mode: ?File.Mode = null,
26311902};
26321903
2633pub const PrevStatus = enum {
2634 stale,
2635 fresh,
2636};
2637
2638/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If they are equal, does nothing.
2639/// Otherwise, atomically copies `source_path` to `dest_path`. The destination file gains the mtime,
2640/// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
2641/// Returns the previous status of the file before updating.
2642/// If any of the directories do not exist for dest_path, they are created.
2643/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2644/// On WASI, both paths should be encoded as valid UTF-8.
2645/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
2646pub fn updateFile(
2647 source_dir: Dir,
2648 source_path: []const u8,
2649 dest_dir: Dir,
2650 dest_path: []const u8,
2651 options: CopyFileOptions,
2652) !PrevStatus {
2653 var src_file = try source_dir.openFile(source_path, .{});
2654 defer src_file.close();
2655
2656 const src_stat = try src_file.stat();
2657 const actual_mode = options.override_mode orelse src_stat.mode;
2658 check_dest_stat: {
2659 const dest_stat = blk: {
2660 var dest_file = dest_dir.openFile(dest_path, .{}) catch |err| switch (err) {
2661 error.FileNotFound => break :check_dest_stat,
2662 else => |e| return e,
2663 };
2664 defer dest_file.close();
2665
2666 break :blk try dest_file.stat();
2667 };
2668
2669 if (src_stat.size == dest_stat.size and
2670 src_stat.mtime == dest_stat.mtime and
2671 actual_mode == dest_stat.mode)
2672 {
2673 return PrevStatus.fresh;
2674 }
2675 }
2676
2677 if (fs.path.dirname(dest_path)) |dirname| {
2678 try dest_dir.makePath(dirname);
2679 }
2680
2681 var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available.
2682 var atomic_file = try dest_dir.atomicFile(dest_path, .{
2683 .mode = actual_mode,
2684 .write_buffer = &buffer,
2685 });
2686 defer atomic_file.deinit();
2687
2688 var src_reader: File.Reader = .initSize(src_file, &.{}, src_stat.size);
2689 const dest_writer = &atomic_file.file_writer.interface;
2690
2691 _ = dest_writer.sendFileAll(&src_reader, .unlimited) catch |err| switch (err) {
2692 error.ReadFailed => return src_reader.err.?,
2693 error.WriteFailed => return atomic_file.file_writer.err.?,
2694 };
2695 try atomic_file.flush();
2696 try atomic_file.file_writer.file.updateTimes(src_stat.atime, src_stat.mtime);
2697 try atomic_file.renameIntoPlace();
2698 return .stale;
2699}
2700
27011904pub const CopyFileError = File.OpenError || File.StatError ||
27021905 AtomicFile.InitError || AtomicFile.FinishError ||
2703 File.ReadError || File.WriteError;
1906 File.ReadError || File.WriteError || error{InvalidFileName};
27041907
27051908/// Atomically creates a new file at `dest_path` within `dest_dir` with the
27061909/// same contents as `source_path` within `source_dir`, overwriting any already
......@@ -2715,6 +1918,8 @@ pub const CopyFileError = File.OpenError || File.StatError ||
27151918/// [WTF-8](https://wtf-8.codeberg.page/). On WASI, both paths should be
27161919/// encoded as valid UTF-8. On other platforms, both paths are an opaque
27171920/// sequence of bytes with no particular encoding.
1921///
1922/// TODO move this function to Io.Dir
27181923pub fn copyFile(
27191924 source_dir: Dir,
27201925 source_path: []const u8,
......@@ -2722,11 +1927,15 @@ pub fn copyFile(
27221927 dest_path: []const u8,
27231928 options: CopyFileOptions,
27241929) CopyFileError!void {
2725 var file_reader: File.Reader = .init(try source_dir.openFile(source_path, .{}), &.{});
2726 defer file_reader.file.close();
1930 var threaded: Io.Threaded = .init_single_threaded;
1931 const io = threaded.ioBasic();
1932
1933 const file = try source_dir.openFile(source_path, .{});
1934 var file_reader: File.Reader = .init(.{ .handle = file.handle }, io, &.{});
1935 defer file_reader.file.close(io);
27271936
27281937 const mode = options.override_mode orelse blk: {
2729 const st = try file_reader.file.stat();
1938 const st = try file_reader.file.stat(io);
27301939 file_reader.size = st.size;
27311940 break :blk st.mode;
27321941 };
......@@ -2776,6 +1985,7 @@ pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions)
27761985pub const Stat = File.Stat;
27771986pub const StatError = File.StatError;
27781987
1988/// Deprecated in favor of `Io.Dir.stat`.
27791989pub fn stat(self: Dir) StatError!Stat {
27801990 const file: File = .{ .handle = self.fd };
27811991 return file.stat();
......@@ -2783,54 +1993,11 @@ pub fn stat(self: Dir) StatError!Stat {
27831993
27841994pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError;
27851995
2786/// Returns metadata for a file inside the directory.
2787///
2788/// On Windows, this requires three syscalls. On other operating systems, it
2789/// only takes one.
2790///
2791/// Symlinks are followed.
2792///
2793/// `sub_path` may be absolute, in which case `self` is ignored.
2794/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
2795/// On WASI, `sub_path` should be encoded as valid UTF-8.
2796/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
1996/// Deprecated in favor of `Io.Dir.statPath`.
27971997pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
2798 if (native_os == .windows) {
2799 var file = try self.openFile(sub_path, .{});
2800 defer file.close();
2801 return file.stat();
2802 }
2803 if (native_os == .wasi and !builtin.link_libc) {
2804 const st = try std.os.fstatat_wasi(self.fd, sub_path, .{ .SYMLINK_FOLLOW = true });
2805 return Stat.fromWasi(st);
2806 }
2807 if (native_os == .linux) {
2808 const sub_path_c = try posix.toPosixPath(sub_path);
2809 var stx = std.mem.zeroes(linux.Statx);
2810
2811 const rc = linux.statx(
2812 self.fd,
2813 &sub_path_c,
2814 linux.AT.NO_AUTOMOUNT,
2815 linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME,
2816 &stx,
2817 );
2818
2819 return switch (linux.E.init(rc)) {
2820 .SUCCESS => Stat.fromLinux(stx),
2821 .ACCES => error.AccessDenied,
2822 .BADF => unreachable,
2823 .FAULT => unreachable,
2824 .INVAL => unreachable,
2825 .LOOP => error.SymLinkLoop,
2826 .NAMETOOLONG => unreachable, // Handled by posix.toPosixPath() above.
2827 .NOENT, .NOTDIR => error.FileNotFound,
2828 .NOMEM => error.SystemResources,
2829 else => |err| posix.unexpectedErrno(err),
2830 };
2831 }
2832 const st = try posix.fstatat(self.fd, sub_path, 0);
2833 return Stat.fromPosix(st);
1998 var threaded: Io.Threaded = .init_single_threaded;
1999 const io = threaded.ioBasic();
2000 return Io.Dir.statPath(.{ .handle = self.fd }, io, sub_path, .{});
28342001}
28352002
28362003pub const ChmodError = File.ChmodError;
......@@ -2867,3 +2034,11 @@ pub fn setPermissions(self: Dir, permissions: Permissions) SetPermissionsError!v
28672034 const file: File = .{ .handle = self.fd };
28682035 try file.setPermissions(permissions);
28692036}
2037
2038pub fn adaptToNewApi(dir: Dir) Io.Dir {
2039 return .{ .handle = dir.fd };
2040}
2041
2042pub fn adaptFromNewApi(dir: Io.Dir) Dir {
2043 return .{ .fd = dir.handle };
2044}
lib/std/fs/File.zig+66-874
......@@ -1,10 +1,12 @@
1const File = @This();
2
13const builtin = @import("builtin");
2const Os = std.builtin.Os;
34const native_os = builtin.os.tag;
45const is_windows = native_os == .windows;
56
6const File = @This();
77const std = @import("../std.zig");
8const Io = std.Io;
9const Os = std.builtin.Os;
810const Allocator = std.mem.Allocator;
911const posix = std.posix;
1012const math = std.math;
......@@ -17,25 +19,12 @@ const Alignment = std.mem.Alignment;
1719/// The OS-specific file descriptor or file handle.
1820handle: Handle,
1921
20pub const Handle = posix.fd_t;
21pub const Mode = posix.mode_t;
22pub const INode = posix.ino_t;
22pub const Handle = Io.File.Handle;
23pub const Mode = Io.File.Mode;
24pub const INode = Io.File.INode;
2325pub const Uid = posix.uid_t;
2426pub const Gid = posix.gid_t;
25
26pub const Kind = enum {
27 block_device,
28 character_device,
29 directory,
30 named_pipe,
31 sym_link,
32 file,
33 unix_domain_socket,
34 whiteout,
35 door,
36 event_port,
37 unknown,
38};
27pub const Kind = Io.File.Kind;
3928
4029/// This is the default mode given to POSIX operating systems for creating
4130/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,
......@@ -43,98 +32,16 @@ pub const Kind = enum {
4332/// the `touch` command, which would correspond to `0o644`. However, POSIX
4433/// libc implementations use `0o666` inside `fopen` and then rely on the
4534/// process-scoped "umask" setting to adjust this number for file creation.
46pub const default_mode = switch (builtin.os.tag) {
47 .windows => 0,
48 .wasi => 0,
49 else => 0o666,
50};
51
52pub const OpenError = error{
53 SharingViolation,
54 PathAlreadyExists,
55 FileNotFound,
56 AccessDenied,
57 PipeBusy,
58 NoDevice,
59 NameTooLong,
60 /// WASI-only; file paths must be valid UTF-8.
61 InvalidUtf8,
62 /// Windows-only; file paths provided by the user must be valid WTF-8.
63 /// https://wtf-8.codeberg.page/
64 InvalidWtf8,
65 /// On Windows, file paths cannot contain these characters:
66 /// '/', '*', '?', '"', '<', '>', '|'
67 BadPathName,
68 Unexpected,
69 /// On Windows, `\\server` or `\\server\share` was not found.
70 NetworkNotFound,
71 ProcessNotFound,
72 /// On Windows, antivirus software is enabled by default. It can be
73 /// disabled, but Windows Update sometimes ignores the user's preference
74 /// and re-enables it. When enabled, antivirus software on Windows
75 /// intercepts file system operations and makes them significantly slower
76 /// in addition to possibly failing with this error code.
77 AntivirusInterference,
78} || posix.OpenError || posix.FlockError;
79
80pub const OpenMode = enum {
81 read_only,
82 write_only,
83 read_write,
84};
35pub const default_mode: Mode = if (Mode == u0) 0 else 0o666;
8536
86pub const Lock = enum {
87 none,
88 shared,
89 exclusive,
90};
91
92pub const OpenFlags = struct {
93 mode: OpenMode = .read_only,
94
95 /// Open the file with an advisory lock to coordinate with other processes
96 /// accessing it at the same time. An exclusive lock will prevent other
97 /// processes from acquiring a lock. A shared lock will prevent other
98 /// processes from acquiring a exclusive lock, but does not prevent
99 /// other process from getting their own shared locks.
100 ///
101 /// The lock is advisory, except on Linux in very specific circumstances[1].
102 /// This means that a process that does not respect the locking API can still get access
103 /// to the file, despite the lock.
104 ///
105 /// On these operating systems, the lock is acquired atomically with
106 /// opening the file:
107 /// * Darwin
108 /// * DragonFlyBSD
109 /// * FreeBSD
110 /// * Haiku
111 /// * NetBSD
112 /// * OpenBSD
113 /// On these operating systems, the lock is acquired via a separate syscall
114 /// after opening the file:
115 /// * Linux
116 /// * Windows
117 ///
118 /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
119 lock: Lock = .none,
120
121 /// Sets whether or not to wait until the file is locked to return. If set to true,
122 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
123 /// is available to proceed.
124 lock_nonblocking: bool = false,
125
126 /// Set this to allow the opened file to automatically become the
127 /// controlling TTY for the current process.
128 allow_ctty: bool = false,
129
130 pub fn isRead(self: OpenFlags) bool {
131 return self.mode != .write_only;
132 }
133
134 pub fn isWrite(self: OpenFlags) bool {
135 return self.mode != .read_only;
136 }
137};
37/// Deprecated in favor of `Io.File.OpenError`.
38pub const OpenError = Io.File.OpenError || error{WouldBlock};
39/// Deprecated in favor of `Io.File.OpenMode`.
40pub const OpenMode = Io.File.OpenMode;
41/// Deprecated in favor of `Io.File.Lock`.
42pub const Lock = Io.File.Lock;
43/// Deprecated in favor of `Io.File.OpenFlags`.
44pub const OpenFlags = Io.File.OpenFlags;
13845
13946pub const CreateFlags = struct {
14047 /// Whether the file will be created with read access.
......@@ -399,193 +306,15 @@ pub fn mode(self: File) ModeError!Mode {
399306 return (try self.stat()).mode;
400307}
401308
402pub const Stat = struct {
403 /// A number that the system uses to point to the file metadata. This
404 /// number is not guaranteed to be unique across time, as some file
405 /// systems may reuse an inode after its file has been deleted. Some
406 /// systems may change the inode of a file over time.
407 ///
408 /// On Linux, the inode is a structure that stores the metadata, and
409 /// the inode _number_ is what you see here: the index number of the
410 /// inode.
411 ///
412 /// The FileIndex on Windows is similar. It is a number for a file that
413 /// is unique to each filesystem.
414 inode: INode,
415 size: u64,
416 /// This is available on POSIX systems and is always 0 otherwise.
417 mode: Mode,
418 kind: Kind,
419
420 /// Last access time in nanoseconds, relative to UTC 1970-01-01.
421 atime: i128,
422 /// Last modification time in nanoseconds, relative to UTC 1970-01-01.
423 mtime: i128,
424 /// Last status/metadata change time in nanoseconds, relative to UTC 1970-01-01.
425 ctime: i128,
426
427 pub fn fromPosix(st: posix.Stat) Stat {
428 const atime = st.atime();
429 const mtime = st.mtime();
430 const ctime = st.ctime();
431 return .{
432 .inode = st.ino,
433 .size = @bitCast(st.size),
434 .mode = st.mode,
435 .kind = k: {
436 const m = st.mode & posix.S.IFMT;
437 switch (m) {
438 posix.S.IFBLK => break :k .block_device,
439 posix.S.IFCHR => break :k .character_device,
440 posix.S.IFDIR => break :k .directory,
441 posix.S.IFIFO => break :k .named_pipe,
442 posix.S.IFLNK => break :k .sym_link,
443 posix.S.IFREG => break :k .file,
444 posix.S.IFSOCK => break :k .unix_domain_socket,
445 else => {},
446 }
447 if (builtin.os.tag == .illumos) switch (m) {
448 posix.S.IFDOOR => break :k .door,
449 posix.S.IFPORT => break :k .event_port,
450 else => {},
451 };
452
453 break :k .unknown;
454 },
455 .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec,
456 .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec,
457 .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec,
458 };
459 }
460
461 pub fn fromLinux(stx: linux.Statx) Stat {
462 const atime = stx.atime;
463 const mtime = stx.mtime;
464 const ctime = stx.ctime;
465
466 return .{
467 .inode = stx.ino,
468 .size = stx.size,
469 .mode = stx.mode,
470 .kind = switch (stx.mode & linux.S.IFMT) {
471 linux.S.IFDIR => .directory,
472 linux.S.IFCHR => .character_device,
473 linux.S.IFBLK => .block_device,
474 linux.S.IFREG => .file,
475 linux.S.IFIFO => .named_pipe,
476 linux.S.IFLNK => .sym_link,
477 linux.S.IFSOCK => .unix_domain_socket,
478 else => .unknown,
479 },
480 .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec,
481 .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec,
482 .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec,
483 };
484 }
485
486 pub fn fromWasi(st: std.os.wasi.filestat_t) Stat {
487 return .{
488 .inode = st.ino,
489 .size = @bitCast(st.size),
490 .mode = 0,
491 .kind = switch (st.filetype) {
492 .BLOCK_DEVICE => .block_device,
493 .CHARACTER_DEVICE => .character_device,
494 .DIRECTORY => .directory,
495 .SYMBOLIC_LINK => .sym_link,
496 .REGULAR_FILE => .file,
497 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
498 else => .unknown,
499 },
500 .atime = st.atim,
501 .mtime = st.mtim,
502 .ctime = st.ctim,
503 };
504 }
505};
309pub const Stat = Io.File.Stat;
506310
507311pub const StatError = posix.FStatError;
508312
509313/// Returns `Stat` containing basic information about the `File`.
510/// TODO: integrate with async I/O
511314pub fn stat(self: File) StatError!Stat {
512 if (builtin.os.tag == .windows) {
513 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
514 var info: windows.FILE_ALL_INFORMATION = undefined;
515 const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
516 switch (rc) {
517 .SUCCESS => {},
518 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
519 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
520 // (name, volume name, etc) we don't care about.
521 .BUFFER_OVERFLOW => {},
522 .INVALID_PARAMETER => unreachable,
523 .ACCESS_DENIED => return error.AccessDenied,
524 else => return windows.unexpectedStatus(rc),
525 }
526 return .{
527 .inode = info.InternalInformation.IndexNumber,
528 .size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
529 .mode = 0,
530 .kind = if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) reparse_point: {
531 var tag_info: windows.FILE_ATTRIBUTE_TAG_INFO = undefined;
532 const tag_rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &tag_info, @sizeOf(windows.FILE_ATTRIBUTE_TAG_INFO), .FileAttributeTagInformation);
533 switch (tag_rc) {
534 .SUCCESS => {},
535 // INFO_LENGTH_MISMATCH and ACCESS_DENIED are the only documented possible errors
536 // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/d295752f-ce89-4b98-8553-266d37c84f0e
537 .INFO_LENGTH_MISMATCH => unreachable,
538 .ACCESS_DENIED => return error.AccessDenied,
539 else => return windows.unexpectedStatus(rc),
540 }
541 if (tag_info.ReparseTag & windows.reparse_tag_name_surrogate_bit != 0) {
542 break :reparse_point .sym_link;
543 }
544 // Unknown reparse point
545 break :reparse_point .unknown;
546 } else if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0)
547 .directory
548 else
549 .file,
550 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
551 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
552 .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime),
553 };
554 }
555
556 if (builtin.os.tag == .wasi and !builtin.link_libc) {
557 const st = try std.os.fstat_wasi(self.handle);
558 return Stat.fromWasi(st);
559 }
560
561 if (builtin.os.tag == .linux) {
562 var stx = std.mem.zeroes(linux.Statx);
563
564 const rc = linux.statx(
565 self.handle,
566 "",
567 linux.AT.EMPTY_PATH,
568 linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME,
569 &stx,
570 );
571
572 return switch (linux.E.init(rc)) {
573 .SUCCESS => Stat.fromLinux(stx),
574 .ACCES => unreachable,
575 .BADF => unreachable,
576 .FAULT => unreachable,
577 .INVAL => unreachable,
578 .LOOP => unreachable,
579 .NAMETOOLONG => unreachable,
580 .NOENT => unreachable,
581 .NOMEM => error.SystemResources,
582 .NOTDIR => unreachable,
583 else => |err| posix.unexpectedErrno(err),
584 };
585 }
586
587 const st = try posix.fstat(self.handle);
588 return Stat.fromPosix(st);
315 var threaded: Io.Threaded = .init_single_threaded;
316 const io = threaded.ioBasic();
317 return Io.File.stat(.{ .handle = self.handle }, io);
589318}
590319
591320pub const ChmodError = posix.FChmodError;
......@@ -782,9 +511,9 @@ pub const UpdateTimesError = posix.FutimensError || windows.SetFileTimeError;
782511pub fn updateTimes(
783512 self: File,
784513 /// access timestamp in nanoseconds
785 atime: i128,
514 atime: Io.Timestamp,
786515 /// last modification timestamp in nanoseconds
787 mtime: i128,
516 mtime: Io.Timestamp,
788517) UpdateTimesError!void {
789518 if (builtin.os.tag == .windows) {
790519 const atime_ft = windows.nanoSecondsToFileTime(atime);
......@@ -793,12 +522,12 @@ pub fn updateTimes(
793522 }
794523 const times = [2]posix.timespec{
795524 posix.timespec{
796 .sec = math.cast(isize, @divFloor(atime, std.time.ns_per_s)) orelse maxInt(isize),
797 .nsec = math.cast(isize, @mod(atime, std.time.ns_per_s)) orelse maxInt(isize),
525 .sec = math.cast(isize, @divFloor(atime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize),
526 .nsec = math.cast(isize, @mod(atime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize),
798527 },
799528 posix.timespec{
800 .sec = math.cast(isize, @divFloor(mtime, std.time.ns_per_s)) orelse maxInt(isize),
801 .nsec = math.cast(isize, @mod(mtime, std.time.ns_per_s)) orelse maxInt(isize),
529 .sec = math.cast(isize, @divFloor(mtime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize),
530 .nsec = math.cast(isize, @mod(mtime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize),
802531 },
803532 };
804533 try posix.futimens(self.handle, &times);
......@@ -815,17 +544,6 @@ pub fn read(self: File, buffer: []u8) ReadError!usize {
815544 return posix.read(self.handle, buffer);
816545}
817546
818/// Deprecated in favor of `Reader`.
819pub fn readAll(self: File, buffer: []u8) ReadError!usize {
820 var index: usize = 0;
821 while (index != buffer.len) {
822 const amt = try self.read(buffer[index..]);
823 if (amt == 0) break;
824 index += amt;
825 }
826 return index;
827}
828
829547/// On Windows, this function currently does alter the file pointer.
830548/// https://github.com/ziglang/zig/issues/12783
831549pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize {
......@@ -858,36 +576,6 @@ pub fn readv(self: File, iovecs: []const posix.iovec) ReadError!usize {
858576 return posix.readv(self.handle, iovecs);
859577}
860578
861/// Deprecated in favor of `Reader`.
862pub fn readvAll(self: File, iovecs: []posix.iovec) ReadError!usize {
863 if (iovecs.len == 0) return 0;
864
865 // We use the address of this local variable for all zero-length
866 // vectors so that the OS does not complain that we are giving it
867 // addresses outside the application's address space.
868 var garbage: [1]u8 = undefined;
869 for (iovecs) |*v| {
870 if (v.len == 0) v.base = &garbage;
871 }
872
873 var i: usize = 0;
874 var off: usize = 0;
875 while (true) {
876 var amt = try self.readv(iovecs[i..]);
877 var eof = amt == 0;
878 off += amt;
879 while (amt >= iovecs[i].len) {
880 amt -= iovecs[i].len;
881 i += 1;
882 if (i >= iovecs.len) return off;
883 eof = false;
884 }
885 if (eof) return off;
886 iovecs[i].base += amt;
887 iovecs[i].len -= amt;
888 }
889}
890
891579/// See https://github.com/ziglang/zig/issues/7699
892580/// On Windows, this function currently does alter the file pointer.
893581/// https://github.com/ziglang/zig/issues/12783
......@@ -901,28 +589,6 @@ pub fn preadv(self: File, iovecs: []const posix.iovec, offset: u64) PReadError!u
901589 return posix.preadv(self.handle, iovecs, offset);
902590}
903591
904/// Deprecated in favor of `Reader`.
905pub fn preadvAll(self: File, iovecs: []posix.iovec, offset: u64) PReadError!usize {
906 if (iovecs.len == 0) return 0;
907
908 var i: usize = 0;
909 var off: usize = 0;
910 while (true) {
911 var amt = try self.preadv(iovecs[i..], offset + off);
912 var eof = amt == 0;
913 off += amt;
914 while (amt >= iovecs[i].len) {
915 amt -= iovecs[i].len;
916 i += 1;
917 if (i >= iovecs.len) return off;
918 eof = false;
919 }
920 if (eof) return off;
921 iovecs[i].base += amt;
922 iovecs[i].len -= amt;
923 }
924}
925
926592pub const WriteError = posix.WriteError;
927593pub const PWriteError = posix.PWriteError;
928594
......@@ -934,7 +600,6 @@ pub fn write(self: File, bytes: []const u8) WriteError!usize {
934600 return posix.write(self.handle, bytes);
935601}
936602
937/// Deprecated in favor of `Writer`.
938603pub fn writeAll(self: File, bytes: []const u8) WriteError!void {
939604 var index: usize = 0;
940605 while (index < bytes.len) {
......@@ -942,6 +607,14 @@ pub fn writeAll(self: File, bytes: []const u8) WriteError!void {
942607 }
943608}
944609
610/// Deprecated in favor of `Writer`.
611pub fn pwriteAll(self: File, bytes: []const u8, offset: u64) PWriteError!void {
612 var index: usize = 0;
613 while (index < bytes.len) {
614 index += try self.pwrite(bytes[index..], offset + index);
615 }
616}
617
945618/// On Windows, this function currently does alter the file pointer.
946619/// https://github.com/ziglang/zig/issues/12783
947620pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize {
......@@ -952,14 +625,6 @@ pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize {
952625 return posix.pwrite(self.handle, bytes, offset);
953626}
954627
955/// Deprecated in favor of `Writer`.
956pub fn pwriteAll(self: File, bytes: []const u8, offset: u64) PWriteError!void {
957 var index: usize = 0;
958 while (index < bytes.len) {
959 index += try self.pwrite(bytes[index..], offset + index);
960 }
961}
962
963628/// See https://github.com/ziglang/zig/issues/7699
964629pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize {
965630 if (is_windows) {
......@@ -972,31 +637,6 @@ pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize {
972637 return posix.writev(self.handle, iovecs);
973638}
974639
975/// Deprecated in favor of `Writer`.
976pub fn writevAll(self: File, iovecs: []posix.iovec_const) WriteError!void {
977 if (iovecs.len == 0) return;
978
979 // We use the address of this local variable for all zero-length
980 // vectors so that the OS does not complain that we are giving it
981 // addresses outside the application's address space.
982 var garbage: [1]u8 = undefined;
983 for (iovecs) |*v| {
984 if (v.len == 0) v.base = &garbage;
985 }
986
987 var i: usize = 0;
988 while (true) {
989 var amt = try self.writev(iovecs[i..]);
990 while (amt >= iovecs[i].len) {
991 amt -= iovecs[i].len;
992 i += 1;
993 if (i >= iovecs.len) return;
994 }
995 iovecs[i].base += amt;
996 iovecs[i].len -= amt;
997 }
998}
999
1000640/// See https://github.com/ziglang/zig/issues/7699
1001641/// On Windows, this function currently does alter the file pointer.
1002642/// https://github.com/ziglang/zig/issues/12783
......@@ -1011,23 +651,6 @@ pub fn pwritev(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError
1011651}
1012652
1013653/// Deprecated in favor of `Writer`.
1014pub fn pwritevAll(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError!void {
1015 if (iovecs.len == 0) return;
1016 var i: usize = 0;
1017 var off: u64 = 0;
1018 while (true) {
1019 var amt = try self.pwritev(iovecs[i..], offset + off);
1020 off += amt;
1021 while (amt >= iovecs[i].len) {
1022 amt -= iovecs[i].len;
1023 i += 1;
1024 if (i >= iovecs.len) return;
1025 }
1026 iovecs[i].base += amt;
1027 iovecs[i].len -= amt;
1028 }
1029}
1030
1031654pub const CopyRangeError = posix.CopyFileRangeError;
1032655
1033656/// Deprecated in favor of `Writer`.
......@@ -1052,449 +675,8 @@ pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: u
1052675 return total_bytes_copied;
1053676}
1054677
1055/// Memoizes key information about a file handle such as:
1056/// * The size from calling stat, or the error that occurred therein.
1057/// * The current seek position.
1058/// * The error that occurred when trying to seek.
1059/// * Whether reading should be done positionally or streaming.
1060/// * Whether reading should be done via fd-to-fd syscalls (e.g. `sendfile`)
1061/// versus plain variants (e.g. `read`).
1062///
1063/// Fulfills the `std.Io.Reader` interface.
1064pub const Reader = struct {
1065 file: File,
1066 err: ?ReadError = null,
1067 mode: Reader.Mode = .positional,
1068 /// Tracks the true seek position in the file. To obtain the logical
1069 /// position, use `logicalPos`.
1070 pos: u64 = 0,
1071 size: ?u64 = null,
1072 size_err: ?SizeError = null,
1073 seek_err: ?Reader.SeekError = null,
1074 interface: std.Io.Reader,
1075
1076 pub const SizeError = std.os.windows.GetFileSizeError || StatError || error{
1077 /// Occurs if, for example, the file handle is a network socket and therefore does not have a size.
1078 Streaming,
1079 };
1080
1081 pub const SeekError = File.SeekError || error{
1082 /// Seeking fell back to reading, and reached the end before the requested seek position.
1083 /// `pos` remains at the end of the file.
1084 EndOfStream,
1085 /// Seeking fell back to reading, which failed.
1086 ReadFailed,
1087 };
1088
1089 pub const Mode = enum {
1090 streaming,
1091 positional,
1092 /// Avoid syscalls other than `read` and `readv`.
1093 streaming_reading,
1094 /// Avoid syscalls other than `pread` and `preadv`.
1095 positional_reading,
1096 /// Indicates reading cannot continue because of a seek failure.
1097 failure,
1098
1099 pub fn toStreaming(m: @This()) @This() {
1100 return switch (m) {
1101 .positional, .streaming => .streaming,
1102 .positional_reading, .streaming_reading => .streaming_reading,
1103 .failure => .failure,
1104 };
1105 }
1106
1107 pub fn toReading(m: @This()) @This() {
1108 return switch (m) {
1109 .positional, .positional_reading => .positional_reading,
1110 .streaming, .streaming_reading => .streaming_reading,
1111 .failure => .failure,
1112 };
1113 }
1114 };
1115
1116 pub fn initInterface(buffer: []u8) std.Io.Reader {
1117 return .{
1118 .vtable = &.{
1119 .stream = Reader.stream,
1120 .discard = Reader.discard,
1121 .readVec = Reader.readVec,
1122 },
1123 .buffer = buffer,
1124 .seek = 0,
1125 .end = 0,
1126 };
1127 }
1128
1129 pub fn init(file: File, buffer: []u8) Reader {
1130 return .{
1131 .file = file,
1132 .interface = initInterface(buffer),
1133 };
1134 }
1135
1136 pub fn initSize(file: File, buffer: []u8, size: ?u64) Reader {
1137 return .{
1138 .file = file,
1139 .interface = initInterface(buffer),
1140 .size = size,
1141 };
1142 }
1143
1144 /// Positional is more threadsafe, since the global seek position is not
1145 /// affected, but when such syscalls are not available, preemptively
1146 /// initializing in streaming mode skips a failed syscall.
1147 pub fn initStreaming(file: File, buffer: []u8) Reader {
1148 return .{
1149 .file = file,
1150 .interface = Reader.initInterface(buffer),
1151 .mode = .streaming,
1152 .seek_err = error.Unseekable,
1153 .size_err = error.Streaming,
1154 };
1155 }
1156
1157 pub fn getSize(r: *Reader) SizeError!u64 {
1158 return r.size orelse {
1159 if (r.size_err) |err| return err;
1160 if (is_windows) {
1161 if (windows.GetFileSizeEx(r.file.handle)) |size| {
1162 r.size = size;
1163 return size;
1164 } else |err| {
1165 r.size_err = err;
1166 return err;
1167 }
1168 }
1169 if (posix.Stat == void) {
1170 r.size_err = error.Streaming;
1171 return error.Streaming;
1172 }
1173 if (stat(r.file)) |st| {
1174 if (st.kind == .file) {
1175 r.size = st.size;
1176 return st.size;
1177 } else {
1178 r.mode = r.mode.toStreaming();
1179 r.size_err = error.Streaming;
1180 return error.Streaming;
1181 }
1182 } else |err| {
1183 r.size_err = err;
1184 return err;
1185 }
1186 };
1187 }
1188
1189 pub fn seekBy(r: *Reader, offset: i64) Reader.SeekError!void {
1190 switch (r.mode) {
1191 .positional, .positional_reading => {
1192 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
1193 },
1194 .streaming, .streaming_reading => {
1195 if (posix.SEEK == void) {
1196 r.seek_err = error.Unseekable;
1197 return error.Unseekable;
1198 }
1199 const seek_err = r.seek_err orelse e: {
1200 if (posix.lseek_CUR(r.file.handle, offset)) |_| {
1201 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
1202 return;
1203 } else |err| {
1204 r.seek_err = err;
1205 break :e err;
1206 }
1207 };
1208 var remaining = std.math.cast(u64, offset) orelse return seek_err;
1209 while (remaining > 0) {
1210 remaining -= discard(&r.interface, .limited64(remaining)) catch |err| {
1211 r.seek_err = err;
1212 return err;
1213 };
1214 }
1215 r.interface.seek = 0;
1216 r.interface.end = 0;
1217 },
1218 .failure => return r.seek_err.?,
1219 }
1220 }
1221
1222 pub fn seekTo(r: *Reader, offset: u64) Reader.SeekError!void {
1223 switch (r.mode) {
1224 .positional, .positional_reading => {
1225 setLogicalPos(r, offset);
1226 },
1227 .streaming, .streaming_reading => {
1228 const logical_pos = logicalPos(r);
1229 if (offset >= logical_pos) return Reader.seekBy(r, @intCast(offset - logical_pos));
1230 if (r.seek_err) |err| return err;
1231 posix.lseek_SET(r.file.handle, offset) catch |err| {
1232 r.seek_err = err;
1233 return err;
1234 };
1235 setLogicalPos(r, offset);
1236 },
1237 .failure => return r.seek_err.?,
1238 }
1239 }
1240
1241 pub fn logicalPos(r: *const Reader) u64 {
1242 return r.pos - r.interface.bufferedLen();
1243 }
1244
1245 fn setLogicalPos(r: *Reader, offset: u64) void {
1246 const logical_pos = logicalPos(r);
1247 if (offset < logical_pos or offset >= r.pos) {
1248 r.interface.seek = 0;
1249 r.interface.end = 0;
1250 r.pos = offset;
1251 } else {
1252 const logical_delta: usize = @intCast(offset - logical_pos);
1253 r.interface.seek += logical_delta;
1254 }
1255 }
1256
1257 /// Number of slices to store on the stack, when trying to send as many byte
1258 /// vectors through the underlying read calls as possible.
1259 const max_buffers_len = 16;
1260
1261 fn stream(io_reader: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize {
1262 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
1263 switch (r.mode) {
1264 .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {
1265 error.Unimplemented => {
1266 r.mode = r.mode.toReading();
1267 return 0;
1268 },
1269 else => |e| return e,
1270 },
1271 .positional_reading => {
1272 const dest = limit.slice(try w.writableSliceGreedy(1));
1273 var data: [1][]u8 = .{dest};
1274 const n = try readVecPositional(r, &data);
1275 w.advance(n);
1276 return n;
1277 },
1278 .streaming_reading => {
1279 const dest = limit.slice(try w.writableSliceGreedy(1));
1280 var data: [1][]u8 = .{dest};
1281 const n = try readVecStreaming(r, &data);
1282 w.advance(n);
1283 return n;
1284 },
1285 .failure => return error.ReadFailed,
1286 }
1287 }
1288
1289 fn readVec(io_reader: *std.Io.Reader, data: [][]u8) std.Io.Reader.Error!usize {
1290 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
1291 switch (r.mode) {
1292 .positional, .positional_reading => return readVecPositional(r, data),
1293 .streaming, .streaming_reading => return readVecStreaming(r, data),
1294 .failure => return error.ReadFailed,
1295 }
1296 }
1297
1298 fn readVecPositional(r: *Reader, data: [][]u8) std.Io.Reader.Error!usize {
1299 const io_reader = &r.interface;
1300 if (is_windows) {
1301 // Unfortunately, `ReadFileScatter` cannot be used since it
1302 // requires page alignment.
1303 if (io_reader.seek == io_reader.end) {
1304 io_reader.seek = 0;
1305 io_reader.end = 0;
1306 }
1307 const first = data[0];
1308 if (first.len >= io_reader.buffer.len - io_reader.end) {
1309 return readPositional(r, first);
1310 } else {
1311 io_reader.end += try readPositional(r, io_reader.buffer[io_reader.end..]);
1312 return 0;
1313 }
1314 }
1315 var iovecs_buffer: [max_buffers_len]posix.iovec = undefined;
1316 const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data);
1317 const dest = iovecs_buffer[0..dest_n];
1318 assert(dest[0].len > 0);
1319 const n = posix.preadv(r.file.handle, dest, r.pos) catch |err| switch (err) {
1320 error.Unseekable => {
1321 r.mode = r.mode.toStreaming();
1322 const pos = r.pos;
1323 if (pos != 0) {
1324 r.pos = 0;
1325 r.seekBy(@intCast(pos)) catch {
1326 r.mode = .failure;
1327 return error.ReadFailed;
1328 };
1329 }
1330 return 0;
1331 },
1332 else => |e| {
1333 r.err = e;
1334 return error.ReadFailed;
1335 },
1336 };
1337 if (n == 0) {
1338 r.size = r.pos;
1339 return error.EndOfStream;
1340 }
1341 r.pos += n;
1342 if (n > data_size) {
1343 io_reader.end += n - data_size;
1344 return data_size;
1345 }
1346 return n;
1347 }
1348
1349 fn readVecStreaming(r: *Reader, data: [][]u8) std.Io.Reader.Error!usize {
1350 const io_reader = &r.interface;
1351 if (is_windows) {
1352 // Unfortunately, `ReadFileScatter` cannot be used since it
1353 // requires page alignment.
1354 if (io_reader.seek == io_reader.end) {
1355 io_reader.seek = 0;
1356 io_reader.end = 0;
1357 }
1358 const first = data[0];
1359 if (first.len >= io_reader.buffer.len - io_reader.end) {
1360 return readStreaming(r, first);
1361 } else {
1362 io_reader.end += try readStreaming(r, io_reader.buffer[io_reader.end..]);
1363 return 0;
1364 }
1365 }
1366 var iovecs_buffer: [max_buffers_len]posix.iovec = undefined;
1367 const dest_n, const data_size = try io_reader.writableVectorPosix(&iovecs_buffer, data);
1368 const dest = iovecs_buffer[0..dest_n];
1369 assert(dest[0].len > 0);
1370 const n = posix.readv(r.file.handle, dest) catch |err| {
1371 r.err = err;
1372 return error.ReadFailed;
1373 };
1374 if (n == 0) {
1375 r.size = r.pos;
1376 return error.EndOfStream;
1377 }
1378 r.pos += n;
1379 if (n > data_size) {
1380 io_reader.end += n - data_size;
1381 return data_size;
1382 }
1383 return n;
1384 }
1385
1386 fn discard(io_reader: *std.Io.Reader, limit: std.Io.Limit) std.Io.Reader.Error!usize {
1387 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
1388 const file = r.file;
1389 const pos = r.pos;
1390 switch (r.mode) {
1391 .positional, .positional_reading => {
1392 const size = r.getSize() catch {
1393 r.mode = r.mode.toStreaming();
1394 return 0;
1395 };
1396 const delta = @min(@intFromEnum(limit), size - pos);
1397 r.pos = pos + delta;
1398 return delta;
1399 },
1400 .streaming, .streaming_reading => {
1401 // Unfortunately we can't seek forward without knowing the
1402 // size because the seek syscalls provided to us will not
1403 // return the true end position if a seek would exceed the
1404 // end.
1405 fallback: {
1406 if (r.size_err == null and r.seek_err == null) break :fallback;
1407 var trash_buffer: [128]u8 = undefined;
1408 if (is_windows) {
1409 const n = windows.ReadFile(file.handle, limit.slice(&trash_buffer), null) catch |err| {
1410 r.err = err;
1411 return error.ReadFailed;
1412 };
1413 if (n == 0) {
1414 r.size = pos;
1415 return error.EndOfStream;
1416 }
1417 r.pos = pos + n;
1418 return n;
1419 }
1420 var iovecs: [max_buffers_len]std.posix.iovec = undefined;
1421 var iovecs_i: usize = 0;
1422 var remaining = @intFromEnum(limit);
1423 while (remaining > 0 and iovecs_i < iovecs.len) {
1424 iovecs[iovecs_i] = .{ .base = &trash_buffer, .len = @min(trash_buffer.len, remaining) };
1425 remaining -= iovecs[iovecs_i].len;
1426 iovecs_i += 1;
1427 }
1428 const n = posix.readv(file.handle, iovecs[0..iovecs_i]) catch |err| {
1429 r.err = err;
1430 return error.ReadFailed;
1431 };
1432 if (n == 0) {
1433 r.size = pos;
1434 return error.EndOfStream;
1435 }
1436 r.pos = pos + n;
1437 return n;
1438 }
1439 const size = r.getSize() catch return 0;
1440 const n = @min(size - pos, maxInt(i64), @intFromEnum(limit));
1441 file.seekBy(n) catch |err| {
1442 r.seek_err = err;
1443 return 0;
1444 };
1445 r.pos = pos + n;
1446 return n;
1447 },
1448 .failure => return error.ReadFailed,
1449 }
1450 }
1451
1452 fn readPositional(r: *Reader, dest: []u8) std.Io.Reader.Error!usize {
1453 const n = r.file.pread(dest, r.pos) catch |err| switch (err) {
1454 error.Unseekable => {
1455 r.mode = r.mode.toStreaming();
1456 const pos = r.pos;
1457 if (pos != 0) {
1458 r.pos = 0;
1459 r.seekBy(@intCast(pos)) catch {
1460 r.mode = .failure;
1461 return error.ReadFailed;
1462 };
1463 }
1464 return 0;
1465 },
1466 else => |e| {
1467 r.err = e;
1468 return error.ReadFailed;
1469 },
1470 };
1471 if (n == 0) {
1472 r.size = r.pos;
1473 return error.EndOfStream;
1474 }
1475 r.pos += n;
1476 return n;
1477 }
1478
1479 fn readStreaming(r: *Reader, dest: []u8) std.Io.Reader.Error!usize {
1480 const n = r.file.read(dest) catch |err| {
1481 r.err = err;
1482 return error.ReadFailed;
1483 };
1484 if (n == 0) {
1485 r.size = r.pos;
1486 return error.EndOfStream;
1487 }
1488 r.pos += n;
1489 return n;
1490 }
1491
1492 pub fn atEnd(r: *Reader) bool {
1493 // Even if stat fails, size is set when end is encountered.
1494 const size = r.size orelse return false;
1495 return size - r.pos == 0;
1496 }
1497};
678/// Deprecated in favor of `Io.File.Reader`.
679pub const Reader = Io.File.Reader;
1498680
1499681pub const Writer = struct {
1500682 file: File,
......@@ -1507,7 +689,7 @@ pub const Writer = struct {
1507689 copy_file_range_err: ?CopyFileRangeError = null,
1508690 fcopyfile_err: ?FcopyfileError = null,
1509691 seek_err: ?Writer.SeekError = null,
1510 interface: std.Io.Writer,
692 interface: Io.Writer,
1511693
1512694 pub const Mode = Reader.Mode;
1513695
......@@ -1553,23 +735,25 @@ pub const Writer = struct {
1553735 };
1554736 }
1555737
1556 pub fn initInterface(buffer: []u8) std.Io.Writer {
738 pub fn initInterface(buffer: []u8) Io.Writer {
1557739 return .{
1558740 .vtable = &.{
1559741 .drain = drain,
1560742 .sendFile = switch (builtin.zig_backend) {
1561743 else => sendFile,
1562 .stage2_aarch64 => std.Io.Writer.unimplementedSendFile,
744 .stage2_aarch64 => Io.Writer.unimplementedSendFile,
1563745 },
1564746 },
1565747 .buffer = buffer,
1566748 };
1567749 }
1568750
1569 pub fn moveToReader(w: *Writer) Reader {
751 /// TODO when this logic moves from fs.File to Io.File the io parameter should be deleted
752 pub fn moveToReader(w: *Writer, io: Io) Reader {
1570753 defer w.* = undefined;
1571754 return .{
1572 .file = w.file,
755 .io = io,
756 .file = .{ .handle = w.file.handle },
1573757 .mode = w.mode,
1574758 .pos = w.pos,
1575759 .interface = Reader.initInterface(w.interface.buffer),
......@@ -1577,7 +761,7 @@ pub const Writer = struct {
1577761 };
1578762 }
1579763
1580 pub fn drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize {
764 pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
1581765 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
1582766 const handle = w.file.handle;
1583767 const buffered = io_w.buffered();
......@@ -1727,10 +911,10 @@ pub const Writer = struct {
1727911 }
1728912
1729913 pub fn sendFile(
1730 io_w: *std.Io.Writer,
1731 file_reader: *Reader,
1732 limit: std.Io.Limit,
1733 ) std.Io.Writer.FileError!usize {
914 io_w: *Io.Writer,
915 file_reader: *Io.File.Reader,
916 limit: Io.Limit,
917 ) Io.Writer.FileError!usize {
1734918 const reader_buffered = file_reader.interface.buffered();
1735919 if (reader_buffered.len >= @intFromEnum(limit))
1736920 return sendFileBuffered(io_w, file_reader, limit.slice(reader_buffered));
......@@ -1994,16 +1178,16 @@ pub const Writer = struct {
19941178 }
19951179
19961180 fn sendFileBuffered(
1997 io_w: *std.Io.Writer,
1998 file_reader: *Reader,
1181 io_w: *Io.Writer,
1182 file_reader: *Io.File.Reader,
19991183 reader_buffered: []const u8,
2000 ) std.Io.Writer.FileError!usize {
1184 ) Io.Writer.FileError!usize {
20011185 const n = try drain(io_w, &.{reader_buffered}, 1);
20021186 file_reader.seekBy(@intCast(n)) catch return error.ReadFailed;
20031187 return n;
20041188 }
20051189
2006 pub fn seekTo(w: *Writer, offset: u64) (Writer.SeekError || std.Io.Writer.Error)!void {
1190 pub fn seekTo(w: *Writer, offset: u64) (Writer.SeekError || Io.Writer.Error)!void {
20071191 try w.interface.flush();
20081192 try seekToUnbuffered(w, offset);
20091193 }
......@@ -2027,7 +1211,7 @@ pub const Writer = struct {
20271211 }
20281212 }
20291213
2030 pub const EndError = SetEndPosError || std.Io.Writer.Error;
1214 pub const EndError = SetEndPosError || Io.Writer.Error;
20311215
20321216 /// Flushes any buffered data and sets the end position of the file.
20331217 ///
......@@ -2058,15 +1242,15 @@ pub const Writer = struct {
20581242///
20591243/// Positional is more threadsafe, since the global seek position is not
20601244/// affected.
2061pub fn reader(file: File, buffer: []u8) Reader {
2062 return .init(file, buffer);
1245pub fn reader(file: File, io: Io, buffer: []u8) Reader {
1246 return .init(.{ .handle = file.handle }, io, buffer);
20631247}
20641248
20651249/// Positional is more threadsafe, since the global seek position is not
20661250/// affected, but when such syscalls are not available, preemptively
20671251/// initializing in streaming mode skips a failed syscall.
2068pub fn readerStreaming(file: File, buffer: []u8) Reader {
2069 return .initStreaming(file, buffer);
1252pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader {
1253 return .initStreaming(.{ .handle = file.handle }, io, buffer);
20701254}
20711255
20721256/// Defaults to positional reading; falls back to streaming.
......@@ -2246,3 +1430,11 @@ pub fn downgradeLock(file: File) LockError!void {
22461430 };
22471431 }
22481432}
1433
1434pub fn adaptToNewApi(file: File) Io.File {
1435 return .{ .handle = file.handle };
1436}
1437
1438pub fn adaptFromNewApi(file: Io.File) File {
1439 return .{ .handle = file.handle };
1440}
lib/std/fs/path.zig+1-1
......@@ -313,7 +313,7 @@ pub fn isAbsoluteWindowsW(path_w: [*:0]const u16) bool {
313313 return isAbsoluteWindowsImpl(u16, mem.sliceTo(path_w, 0));
314314}
315315
316pub fn isAbsoluteWindowsWTF16(path: []const u16) bool {
316pub fn isAbsoluteWindowsWtf16(path: []const u16) bool {
317317 return isAbsoluteWindowsImpl(u16, path);
318318}
319319
lib/std/fs/test.zig+91-155
......@@ -1,10 +1,12 @@
1const std = @import("../std.zig");
21const builtin = @import("builtin");
2const native_os = builtin.os.tag;
3
4const std = @import("../std.zig");
5const Io = std.Io;
36const testing = std.testing;
47const fs = std.fs;
58const mem = std.mem;
69const wasi = std.os.wasi;
7const native_os = builtin.os.tag;
810const windows = std.os.windows;
911const posix = std.posix;
1012
......@@ -73,6 +75,7 @@ const PathType = enum {
7375};
7476
7577const TestContext = struct {
78 io: Io,
7679 path_type: PathType,
7780 path_sep: u8,
7881 arena: ArenaAllocator,
......@@ -83,6 +86,7 @@ const TestContext = struct {
8386 pub fn init(path_type: PathType, path_sep: u8, allocator: mem.Allocator, transform_fn: *const PathType.TransformFn) TestContext {
8487 const tmp = tmpDir(.{ .iterate = true });
8588 return .{
89 .io = testing.io,
8690 .path_type = path_type,
8791 .path_sep = path_sep,
8892 .arena = ArenaAllocator.init(allocator),
......@@ -1319,6 +1323,8 @@ test "max file name component lengths" {
13191323}
13201324
13211325test "writev, readv" {
1326 const io = testing.io;
1327
13221328 var tmp = tmpDir(.{});
13231329 defer tmp.cleanup();
13241330
......@@ -1327,78 +1333,55 @@ test "writev, readv" {
13271333
13281334 var buf1: [line1.len]u8 = undefined;
13291335 var buf2: [line2.len]u8 = undefined;
1330 var write_vecs = [_]posix.iovec_const{
1331 .{
1332 .base = line1,
1333 .len = line1.len,
1334 },
1335 .{
1336 .base = line2,
1337 .len = line2.len,
1338 },
1339 };
1340 var read_vecs = [_]posix.iovec{
1341 .{
1342 .base = &buf2,
1343 .len = buf2.len,
1344 },
1345 .{
1346 .base = &buf1,
1347 .len = buf1.len,
1348 },
1349 };
1336 var write_vecs: [2][]const u8 = .{ line1, line2 };
1337 var read_vecs: [2][]u8 = .{ &buf2, &buf1 };
13501338
13511339 var src_file = try tmp.dir.createFile("test.txt", .{ .read = true });
13521340 defer src_file.close();
13531341
1354 try src_file.writevAll(&write_vecs);
1342 var writer = src_file.writerStreaming(&.{});
1343
1344 try writer.interface.writeVecAll(&write_vecs);
1345 try writer.interface.flush();
13551346 try testing.expectEqual(@as(u64, line1.len + line2.len), try src_file.getEndPos());
1356 try src_file.seekTo(0);
1357 const read = try src_file.readvAll(&read_vecs);
1358 try testing.expectEqual(@as(usize, line1.len + line2.len), read);
1347
1348 var reader = writer.moveToReader(io);
1349 try reader.seekTo(0);
1350 try reader.interface.readVecAll(&read_vecs);
13591351 try testing.expectEqualStrings(&buf1, "line2\n");
13601352 try testing.expectEqualStrings(&buf2, "line1\n");
1353 try testing.expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1));
13611354}
13621355
13631356test "pwritev, preadv" {
1357 const io = testing.io;
1358
13641359 var tmp = tmpDir(.{});
13651360 defer tmp.cleanup();
13661361
13671362 const line1 = "line1\n";
13681363 const line2 = "line2\n";
1369
1364 var lines: [2][]const u8 = .{ line1, line2 };
13701365 var buf1: [line1.len]u8 = undefined;
13711366 var buf2: [line2.len]u8 = undefined;
1372 var write_vecs = [_]posix.iovec_const{
1373 .{
1374 .base = line1,
1375 .len = line1.len,
1376 },
1377 .{
1378 .base = line2,
1379 .len = line2.len,
1380 },
1381 };
1382 var read_vecs = [_]posix.iovec{
1383 .{
1384 .base = &buf2,
1385 .len = buf2.len,
1386 },
1387 .{
1388 .base = &buf1,
1389 .len = buf1.len,
1390 },
1391 };
1367 var read_vecs: [2][]u8 = .{ &buf2, &buf1 };
13921368
13931369 var src_file = try tmp.dir.createFile("test.txt", .{ .read = true });
13941370 defer src_file.close();
13951371
1396 try src_file.pwritevAll(&write_vecs, 16);
1372 var writer = src_file.writer(&.{});
1373
1374 try writer.seekTo(16);
1375 try writer.interface.writeVecAll(&lines);
1376 try writer.interface.flush();
13971377 try testing.expectEqual(@as(u64, 16 + line1.len + line2.len), try src_file.getEndPos());
1398 const read = try src_file.preadvAll(&read_vecs, 16);
1399 try testing.expectEqual(@as(usize, line1.len + line2.len), read);
1378
1379 var reader = writer.moveToReader(io);
1380 try reader.seekTo(16);
1381 try reader.interface.readVecAll(&read_vecs);
14001382 try testing.expectEqualStrings(&buf1, "line2\n");
14011383 try testing.expectEqualStrings(&buf2, "line1\n");
1384 try testing.expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1));
14021385}
14031386
14041387test "setEndPos" {
......@@ -1406,6 +1389,8 @@ test "setEndPos" {
14061389 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
14071390 if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23806
14081391
1392 const io = testing.io;
1393
14091394 var tmp = tmpDir(.{});
14101395 defer tmp.cleanup();
14111396
......@@ -1416,11 +1401,13 @@ test "setEndPos" {
14161401
14171402 const initial_size = try f.getEndPos();
14181403 var buffer: [32]u8 = undefined;
1404 var reader = f.reader(io, &.{});
14191405
14201406 {
14211407 try f.setEndPos(initial_size);
14221408 try testing.expectEqual(initial_size, try f.getEndPos());
1423 try testing.expectEqual(initial_size, try f.preadAll(&buffer, 0));
1409 try reader.seekTo(0);
1410 try testing.expectEqual(initial_size, try reader.interface.readSliceShort(&buffer));
14241411 try testing.expectEqualStrings("ninebytes", buffer[0..@intCast(initial_size)]);
14251412 }
14261413
......@@ -1428,7 +1415,8 @@ test "setEndPos" {
14281415 const larger = initial_size + 4;
14291416 try f.setEndPos(larger);
14301417 try testing.expectEqual(larger, try f.getEndPos());
1431 try testing.expectEqual(larger, try f.preadAll(&buffer, 0));
1418 try reader.seekTo(0);
1419 try testing.expectEqual(larger, try reader.interface.readSliceShort(&buffer));
14321420 try testing.expectEqualStrings("ninebytes\x00\x00\x00\x00", buffer[0..@intCast(larger)]);
14331421 }
14341422
......@@ -1436,27 +1424,15 @@ test "setEndPos" {
14361424 const smaller = initial_size - 5;
14371425 try f.setEndPos(smaller);
14381426 try testing.expectEqual(smaller, try f.getEndPos());
1439 try testing.expectEqual(smaller, try f.preadAll(&buffer, 0));
1427 try reader.seekTo(0);
1428 try testing.expectEqual(smaller, try reader.interface.readSliceShort(&buffer));
14401429 try testing.expectEqualStrings("nine", buffer[0..@intCast(smaller)]);
14411430 }
14421431
14431432 try f.setEndPos(0);
14441433 try testing.expectEqual(0, try f.getEndPos());
1445 try testing.expectEqual(0, try f.preadAll(&buffer, 0));
1446
1447 // Invalid file length should error gracefully. Actual limit is host
1448 // and file-system dependent, but 1PB should fail on filesystems like
1449 // EXT4 and NTFS. But XFS or Btrfs support up to 8EiB files.
1450 f.setEndPos(0x4_0000_0000_0000) catch |err| if (err != error.FileTooBig) {
1451 return err;
1452 };
1453
1454 f.setEndPos(std.math.maxInt(u63)) catch |err| if (err != error.FileTooBig) {
1455 return err;
1456 };
1457
1458 try testing.expectError(error.FileTooBig, f.setEndPos(std.math.maxInt(u63) + 1));
1459 try testing.expectError(error.FileTooBig, f.setEndPos(std.math.maxInt(u64)));
1434 try reader.seekTo(0);
1435 try testing.expectEqual(0, try reader.interface.readSliceShort(&buffer));
14601436}
14611437
14621438test "access file" {
......@@ -1476,6 +1452,8 @@ test "access file" {
14761452}
14771453
14781454test "sendfile" {
1455 const io = testing.io;
1456
14791457 var tmp = tmpDir(.{});
14801458 defer tmp.cleanup();
14811459
......@@ -1486,21 +1464,14 @@ test "sendfile" {
14861464
14871465 const line1 = "line1\n";
14881466 const line2 = "second line\n";
1489 var vecs = [_]posix.iovec_const{
1490 .{
1491 .base = line1,
1492 .len = line1.len,
1493 },
1494 .{
1495 .base = line2,
1496 .len = line2.len,
1497 },
1498 };
1467 var vecs = [_][]const u8{ line1, line2 };
14991468
15001469 var src_file = try dir.createFile("sendfile1.txt", .{ .read = true });
15011470 defer src_file.close();
1502
1503 try src_file.writevAll(&vecs);
1471 {
1472 var fw = src_file.writer(&.{});
1473 try fw.interface.writeVecAll(&vecs);
1474 }
15041475
15051476 var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true });
15061477 defer dest_file.close();
......@@ -1513,7 +1484,7 @@ test "sendfile" {
15131484 var trailers: [2][]const u8 = .{ trailer1, trailer2 };
15141485
15151486 var written_buf: [100]u8 = undefined;
1516 var file_reader = src_file.reader(&.{});
1487 var file_reader = src_file.reader(io, &.{});
15171488 var fallback_buffer: [50]u8 = undefined;
15181489 var file_writer = dest_file.writer(&fallback_buffer);
15191490 try file_writer.interface.writeVecAll(&headers);
......@@ -1521,11 +1492,15 @@ test "sendfile" {
15211492 try testing.expectEqual(10, try file_writer.interface.sendFileAll(&file_reader, .limited(10)));
15221493 try file_writer.interface.writeVecAll(&trailers);
15231494 try file_writer.interface.flush();
1524 const amt = try dest_file.preadAll(&written_buf, 0);
1495 var fr = file_writer.moveToReader(io);
1496 try fr.seekTo(0);
1497 const amt = try fr.interface.readSliceShort(&written_buf);
15251498 try testing.expectEqualStrings("header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n", written_buf[0..amt]);
15261499}
15271500
15281501test "sendfile with buffered data" {
1502 const io = testing.io;
1503
15291504 var tmp = tmpDir(.{});
15301505 defer tmp.cleanup();
15311506
......@@ -1543,7 +1518,7 @@ test "sendfile with buffered data" {
15431518 defer dest_file.close();
15441519
15451520 var src_buffer: [32]u8 = undefined;
1546 var file_reader = src_file.reader(&src_buffer);
1521 var file_reader = src_file.reader(io, &src_buffer);
15471522
15481523 try file_reader.seekTo(0);
15491524 try file_reader.interface.fill(8);
......@@ -1554,37 +1529,14 @@ test "sendfile with buffered data" {
15541529 try std.testing.expectEqual(4, try file_writer.interface.sendFileAll(&file_reader, .limited(4)));
15551530
15561531 var written_buf: [8]u8 = undefined;
1557 const amt = try dest_file.preadAll(&written_buf, 0);
1532 var fr = file_writer.moveToReader(io);
1533 try fr.seekTo(0);
1534 const amt = try fr.interface.readSliceShort(&written_buf);
15581535
15591536 try std.testing.expectEqual(4, amt);
15601537 try std.testing.expectEqualSlices(u8, "AAAA", written_buf[0..amt]);
15611538}
15621539
1563test "copyRangeAll" {
1564 var tmp = tmpDir(.{});
1565 defer tmp.cleanup();
1566
1567 try tmp.dir.makePath("os_test_tmp");
1568
1569 var dir = try tmp.dir.openDir("os_test_tmp", .{});
1570 defer dir.close();
1571
1572 var src_file = try dir.createFile("file1.txt", .{ .read = true });
1573 defer src_file.close();
1574
1575 const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";
1576 try src_file.writeAll(data);
1577
1578 var dest_file = try dir.createFile("file2.txt", .{ .read = true });
1579 defer dest_file.close();
1580
1581 var written_buf: [100]u8 = undefined;
1582 _ = try src_file.copyRangeAll(0, dest_file, 0, data.len);
1583
1584 const amt = try dest_file.preadAll(&written_buf, 0);
1585 try testing.expectEqualStrings(data, written_buf[0..amt]);
1586}
1587
15881540test "copyFile" {
15891541 try testWithAllSupportedPathTypes(struct {
15901542 fn impl(ctx: *TestContext) !void {
......@@ -1708,8 +1660,8 @@ test "open file with exclusive lock twice, make sure second lock waits" {
17081660 }
17091661 };
17101662
1711 var started = std.Thread.ResetEvent{};
1712 var locked = std.Thread.ResetEvent{};
1663 var started: std.Thread.ResetEvent = .unset;
1664 var locked: std.Thread.ResetEvent = .unset;
17131665
17141666 const t = try std.Thread.spawn(.{}, S.checkFn, .{
17151667 &ctx.dir,
......@@ -1773,7 +1725,7 @@ test "read from locked file" {
17731725 const f = try ctx.dir.createFile(filename, .{ .read = true });
17741726 defer f.close();
17751727 var buffer: [1]u8 = undefined;
1776 _ = try f.readAll(&buffer);
1728 _ = try f.read(&buffer);
17771729 }
17781730 {
17791731 const f = try ctx.dir.createFile(filename, .{
......@@ -1785,9 +1737,9 @@ test "read from locked file" {
17851737 defer f2.close();
17861738 var buffer: [1]u8 = undefined;
17871739 if (builtin.os.tag == .windows) {
1788 try std.testing.expectError(error.LockViolation, f2.readAll(&buffer));
1740 try std.testing.expectError(error.LockViolation, f2.read(&buffer));
17891741 } else {
1790 try std.testing.expectEqual(0, f2.readAll(&buffer));
1742 try std.testing.expectEqual(0, f2.read(&buffer));
17911743 }
17921744 }
17931745 }
......@@ -1944,6 +1896,7 @@ test "'.' and '..' in fs.Dir functions" {
19441896
19451897 try testWithAllSupportedPathTypes(struct {
19461898 fn impl(ctx: *TestContext) !void {
1899 const io = ctx.io;
19471900 const subdir_path = try ctx.transformPath("./subdir");
19481901 const file_path = try ctx.transformPath("./subdir/../file");
19491902 const copy_path = try ctx.transformPath("./subdir/../copy");
......@@ -1966,8 +1919,9 @@ test "'.' and '..' in fs.Dir functions" {
19661919 try ctx.dir.deleteFile(rename_path);
19671920
19681921 try ctx.dir.writeFile(.{ .sub_path = update_path, .data = "something" });
1969 const prev_status = try ctx.dir.updateFile(file_path, ctx.dir, update_path, .{});
1970 try testing.expectEqual(fs.Dir.PrevStatus.stale, prev_status);
1922 var dir = ctx.dir.adaptToNewApi();
1923 const prev_status = try dir.updateFile(io, file_path, dir, update_path, .{});
1924 try testing.expectEqual(Io.Dir.PrevStatus.stale, prev_status);
19711925
19721926 try ctx.dir.deleteDir(subdir_path);
19731927 }
......@@ -2005,13 +1959,6 @@ test "'.' and '..' in absolute functions" {
20051959 renamed_file.close();
20061960 try fs.deleteFileAbsolute(renamed_file_path);
20071961
2008 const update_file_path = try fs.path.join(allocator, &.{ subdir_path, "../update" });
2009 const update_file = try fs.createFileAbsolute(update_file_path, .{});
2010 try update_file.writeAll("something");
2011 update_file.close();
2012 const prev_status = try fs.updateFileAbsolute(created_file_path, update_file_path, .{});
2013 try testing.expectEqual(fs.Dir.PrevStatus.stale, prev_status);
2014
20151962 try fs.deleteDirAbsolute(subdir_path);
20161963}
20171964
......@@ -2072,48 +2019,40 @@ test "delete a setAsCwd directory on Windows" {
20722019
20732020test "invalid UTF-8/WTF-8 paths" {
20742021 const expected_err = switch (native_os) {
2075 .wasi => error.InvalidUtf8,
2076 .windows => error.InvalidWtf8,
2022 .wasi => error.BadPathName,
2023 .windows => error.BadPathName,
20772024 else => return error.SkipZigTest,
20782025 };
20792026
20802027 try testWithAllSupportedPathTypes(struct {
20812028 fn impl(ctx: *TestContext) !void {
2029 const io = ctx.io;
20822030 // This is both invalid UTF-8 and WTF-8, since \xFF is an invalid start byte
20832031 const invalid_path = try ctx.transformPath("\xFF");
20842032
20852033 try testing.expectError(expected_err, ctx.dir.openFile(invalid_path, .{}));
2086 try testing.expectError(expected_err, ctx.dir.openFileZ(invalid_path, .{}));
20872034
20882035 try testing.expectError(expected_err, ctx.dir.createFile(invalid_path, .{}));
2089 try testing.expectError(expected_err, ctx.dir.createFileZ(invalid_path, .{}));
20902036
20912037 try testing.expectError(expected_err, ctx.dir.makeDir(invalid_path));
2092 try testing.expectError(expected_err, ctx.dir.makeDirZ(invalid_path));
20932038
20942039 try testing.expectError(expected_err, ctx.dir.makePath(invalid_path));
20952040 try testing.expectError(expected_err, ctx.dir.makeOpenPath(invalid_path, .{}));
20962041
20972042 try testing.expectError(expected_err, ctx.dir.openDir(invalid_path, .{}));
2098 try testing.expectError(expected_err, ctx.dir.openDirZ(invalid_path, .{}));
20992043
21002044 try testing.expectError(expected_err, ctx.dir.deleteFile(invalid_path));
2101 try testing.expectError(expected_err, ctx.dir.deleteFileZ(invalid_path));
21022045
21032046 try testing.expectError(expected_err, ctx.dir.deleteDir(invalid_path));
2104 try testing.expectError(expected_err, ctx.dir.deleteDirZ(invalid_path));
21052047
21062048 try testing.expectError(expected_err, ctx.dir.rename(invalid_path, invalid_path));
2107 try testing.expectError(expected_err, ctx.dir.renameZ(invalid_path, invalid_path));
21082049
21092050 try testing.expectError(expected_err, ctx.dir.symLink(invalid_path, invalid_path, .{}));
2110 try testing.expectError(expected_err, ctx.dir.symLinkZ(invalid_path, invalid_path, .{}));
21112051 if (native_os == .wasi) {
21122052 try testing.expectError(expected_err, ctx.dir.symLinkWasi(invalid_path, invalid_path, .{}));
21132053 }
21142054
21152055 try testing.expectError(expected_err, ctx.dir.readLink(invalid_path, &[_]u8{}));
2116 try testing.expectError(expected_err, ctx.dir.readLinkZ(invalid_path, &[_]u8{}));
21172056 if (native_os == .wasi) {
21182057 try testing.expectError(expected_err, ctx.dir.readLinkWasi(invalid_path, &[_]u8{}));
21192058 }
......@@ -2127,47 +2066,34 @@ test "invalid UTF-8/WTF-8 paths" {
21272066 try testing.expectError(expected_err, ctx.dir.writeFile(.{ .sub_path = invalid_path, .data = "" }));
21282067
21292068 try testing.expectError(expected_err, ctx.dir.access(invalid_path, .{}));
2130 try testing.expectError(expected_err, ctx.dir.accessZ(invalid_path, .{}));
21312069
2132 try testing.expectError(expected_err, ctx.dir.updateFile(invalid_path, ctx.dir, invalid_path, .{}));
2070 var dir = ctx.dir.adaptToNewApi();
2071 try testing.expectError(expected_err, dir.updateFile(io, invalid_path, dir, invalid_path, .{}));
21332072 try testing.expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, .{}));
21342073
21352074 try testing.expectError(expected_err, ctx.dir.statFile(invalid_path));
21362075
21372076 if (native_os != .wasi) {
21382077 try testing.expectError(expected_err, ctx.dir.realpath(invalid_path, &[_]u8{}));
2139 try testing.expectError(expected_err, ctx.dir.realpathZ(invalid_path, &[_]u8{}));
21402078 try testing.expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path));
21412079 }
21422080
21432081 try testing.expectError(expected_err, fs.rename(ctx.dir, invalid_path, ctx.dir, invalid_path));
2144 try testing.expectError(expected_err, fs.renameZ(ctx.dir, invalid_path, ctx.dir, invalid_path));
21452082
21462083 if (native_os != .wasi and ctx.path_type != .relative) {
2147 try testing.expectError(expected_err, fs.updateFileAbsolute(invalid_path, invalid_path, .{}));
21482084 try testing.expectError(expected_err, fs.copyFileAbsolute(invalid_path, invalid_path, .{}));
21492085 try testing.expectError(expected_err, fs.makeDirAbsolute(invalid_path));
2150 try testing.expectError(expected_err, fs.makeDirAbsoluteZ(invalid_path));
21512086 try testing.expectError(expected_err, fs.deleteDirAbsolute(invalid_path));
2152 try testing.expectError(expected_err, fs.deleteDirAbsoluteZ(invalid_path));
21532087 try testing.expectError(expected_err, fs.renameAbsolute(invalid_path, invalid_path));
2154 try testing.expectError(expected_err, fs.renameAbsoluteZ(invalid_path, invalid_path));
21552088 try testing.expectError(expected_err, fs.openDirAbsolute(invalid_path, .{}));
2156 try testing.expectError(expected_err, fs.openDirAbsoluteZ(invalid_path, .{}));
21572089 try testing.expectError(expected_err, fs.openFileAbsolute(invalid_path, .{}));
2158 try testing.expectError(expected_err, fs.openFileAbsoluteZ(invalid_path, .{}));
21592090 try testing.expectError(expected_err, fs.accessAbsolute(invalid_path, .{}));
2160 try testing.expectError(expected_err, fs.accessAbsoluteZ(invalid_path, .{}));
21612091 try testing.expectError(expected_err, fs.createFileAbsolute(invalid_path, .{}));
2162 try testing.expectError(expected_err, fs.createFileAbsoluteZ(invalid_path, .{}));
21632092 try testing.expectError(expected_err, fs.deleteFileAbsolute(invalid_path));
2164 try testing.expectError(expected_err, fs.deleteFileAbsoluteZ(invalid_path));
21652093 try testing.expectError(expected_err, fs.deleteTreeAbsolute(invalid_path));
21662094 var readlink_buf: [fs.max_path_bytes]u8 = undefined;
21672095 try testing.expectError(expected_err, fs.readLinkAbsolute(invalid_path, &readlink_buf));
2168 try testing.expectError(expected_err, fs.readLinkAbsoluteZ(invalid_path, &readlink_buf));
21692096 try testing.expectError(expected_err, fs.symLinkAbsolute(invalid_path, invalid_path, .{}));
2170 try testing.expectError(expected_err, fs.symLinkAbsoluteZ(invalid_path, invalid_path, .{}));
21712097 try testing.expectError(expected_err, fs.realpathAlloc(testing.allocator, invalid_path));
21722098 }
21732099 }
......@@ -2175,6 +2101,8 @@ test "invalid UTF-8/WTF-8 paths" {
21752101}
21762102
21772103test "read file non vectored" {
2104 const io = std.testing.io;
2105
21782106 var tmp_dir = testing.tmpDir(.{});
21792107 defer tmp_dir.cleanup();
21802108
......@@ -2188,7 +2116,7 @@ test "read file non vectored" {
21882116 try file_writer.interface.flush();
21892117 }
21902118
2191 var file_reader: std.fs.File.Reader = .init(file, &.{});
2119 var file_reader: std.Io.File.Reader = .initAdapted(file, io, &.{});
21922120
21932121 var write_buffer: [100]u8 = undefined;
21942122 var w: std.Io.Writer = .fixed(&write_buffer);
......@@ -2205,6 +2133,8 @@ test "read file non vectored" {
22052133}
22062134
22072135test "seek keeping partial buffer" {
2136 const io = std.testing.io;
2137
22082138 var tmp_dir = testing.tmpDir(.{});
22092139 defer tmp_dir.cleanup();
22102140
......@@ -2219,7 +2149,7 @@ test "seek keeping partial buffer" {
22192149 }
22202150
22212151 var read_buffer: [3]u8 = undefined;
2222 var file_reader: std.fs.File.Reader = .init(file, &read_buffer);
2152 var file_reader: Io.File.Reader = .initAdapted(file, io, &read_buffer);
22232153
22242154 try testing.expectEqual(0, file_reader.logicalPos());
22252155
......@@ -2246,13 +2176,15 @@ test "seek keeping partial buffer" {
22462176}
22472177
22482178test "seekBy" {
2179 const io = testing.io;
2180
22492181 var tmp_dir = testing.tmpDir(.{});
22502182 defer tmp_dir.cleanup();
22512183
22522184 try tmp_dir.dir.writeFile(.{ .sub_path = "blah.txt", .data = "let's test seekBy" });
22532185 const f = try tmp_dir.dir.openFile("blah.txt", .{ .mode = .read_only });
22542186 defer f.close();
2255 var reader = f.readerStreaming(&.{});
2187 var reader = f.readerStreaming(io, &.{});
22562188 try reader.seekBy(2);
22572189
22582190 var buffer: [20]u8 = undefined;
......@@ -2265,6 +2197,8 @@ test "seekTo flushes buffered data" {
22652197 var tmp = std.testing.tmpDir(.{});
22662198 defer tmp.cleanup();
22672199
2200 const io = std.testing.io;
2201
22682202 const contents = "data";
22692203
22702204 const file = try tmp.dir.createFile("seek.bin", .{ .read = true });
......@@ -2279,7 +2213,7 @@ test "seekTo flushes buffered data" {
22792213 }
22802214
22812215 var read_buffer: [16]u8 = undefined;
2282 var file_reader: std.fs.File.Reader = .init(file, &read_buffer);
2216 var file_reader: std.Io.File.Reader = .initAdapted(file, io, &read_buffer);
22832217
22842218 var buf: [4]u8 = undefined;
22852219 try file_reader.interface.readSliceAll(&buf);
......@@ -2287,6 +2221,8 @@ test "seekTo flushes buffered data" {
22872221}
22882222
22892223test "File.Writer sendfile with buffered contents" {
2224 const io = testing.io;
2225
22902226 var tmp_dir = testing.tmpDir(.{});
22912227 defer tmp_dir.cleanup();
22922228
......@@ -2298,7 +2234,7 @@ test "File.Writer sendfile with buffered contents" {
22982234 defer out.close();
22992235
23002236 var in_buf: [2]u8 = undefined;
2301 var in_r = in.reader(&in_buf);
2237 var in_r = in.reader(io, &in_buf);
23022238 _ = try in_r.getSize(); // Catch seeks past end by populating size
23032239 try in_r.interface.fill(2);
23042240
......@@ -2312,7 +2248,7 @@ test "File.Writer sendfile with buffered contents" {
23122248 var check = try tmp_dir.dir.openFile("b", .{});
23132249 defer check.close();
23142250 var check_buf: [4]u8 = undefined;
2315 var check_r = check.reader(&check_buf);
2251 var check_r = check.reader(io, &check_buf);
23162252 try testing.expectEqualStrings("abcd", try check_r.interface.take(4));
23172253 try testing.expectError(error.EndOfStream, check_r.interface.takeByte());
23182254}
lib/std/hash_map.zig+4-4
......@@ -1827,9 +1827,9 @@ test "put and remove loop in random order" {
18271827 }
18281828}
18291829
1830test "remove one million elements in random order" {
1830test "remove many elements in random order" {
18311831 const Map = AutoHashMap(u32, u32);
1832 const n = 1000 * 1000;
1832 const n = 1000 * 100;
18331833 var map = Map.init(std.heap.page_allocator);
18341834 defer map.deinit();
18351835
......@@ -2147,14 +2147,14 @@ test "getOrPut allocation failure" {
21472147 try testing.expectError(error.OutOfMemory, map.getOrPut(std.testing.failing_allocator, "hello"));
21482148}
21492149
2150test "std.hash_map rehash" {
2150test "rehash" {
21512151 var map = AutoHashMap(usize, usize).init(std.testing.allocator);
21522152 defer map.deinit();
21532153
21542154 var prng = std.Random.DefaultPrng.init(0);
21552155 const random = prng.random();
21562156
2157 const count = 6 * random.intRangeLessThan(u32, 100_000, 500_000);
2157 const count = 4 * random.intRangeLessThan(u32, 100_000, 500_000);
21582158
21592159 for (0..count) |i| {
21602160 try map.put(i, i);
lib/std/heap/debug_allocator.zig+90-19
......@@ -80,15 +80,15 @@
8080//!
8181//! Resizing and remapping are forwarded directly to the backing allocator,
8282//! except where such operations would change the category from large to small.
83const builtin = @import("builtin");
84const StackTrace = std.builtin.StackTrace;
8385
8486const std = @import("std");
85const builtin = @import("builtin");
8687const log = std.log.scoped(.gpa);
8788const math = std.math;
8889const assert = std.debug.assert;
8990const mem = std.mem;
9091const Allocator = std.mem.Allocator;
91const StackTrace = std.builtin.StackTrace;
9292
9393const default_page_size: usize = switch (builtin.os.tag) {
9494 // Makes `std.heap.PageAllocator` take the happy path.
......@@ -421,7 +421,12 @@ pub fn DebugAllocator(comptime config: Config) type {
421421 return usedBitsCount(slot_count) * @sizeOf(usize);
422422 }
423423
424 fn detectLeaksInBucket(bucket: *BucketHeader, size_class_index: usize, used_bits_count: usize) usize {
424 fn detectLeaksInBucket(
425 bucket: *BucketHeader,
426 size_class_index: usize,
427 used_bits_count: usize,
428 tty_config: std.Io.tty.Config,
429 ) usize {
425430 const size_class = @as(usize, 1) << @as(Log2USize, @intCast(size_class_index));
426431 const slot_count = slot_counts[size_class_index];
427432 var leaks: usize = 0;
......@@ -436,7 +441,13 @@ pub fn DebugAllocator(comptime config: Config) type {
436441 const stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc);
437442 const page_addr = @intFromPtr(bucket) & ~(page_size - 1);
438443 const addr = page_addr + slot_index * size_class;
439 log.err("memory address 0x{x} leaked: {f}", .{ addr, stack_trace });
444 log.err("memory address 0x{x} leaked: {f}", .{
445 addr,
446 std.debug.FormatStackTrace{
447 .stack_trace = stack_trace,
448 .tty_config = tty_config,
449 },
450 });
440451 leaks += 1;
441452 }
442453 }
......@@ -449,12 +460,14 @@ pub fn DebugAllocator(comptime config: Config) type {
449460 pub fn detectLeaks(self: *Self) usize {
450461 var leaks: usize = 0;
451462
463 const tty_config = std.Io.tty.detectConfig(.stderr());
464
452465 for (self.buckets, 0..) |init_optional_bucket, size_class_index| {
453466 var optional_bucket = init_optional_bucket;
454467 const slot_count = slot_counts[size_class_index];
455468 const used_bits_count = usedBitsCount(slot_count);
456469 while (optional_bucket) |bucket| {
457 leaks += detectLeaksInBucket(bucket, size_class_index, used_bits_count);
470 leaks += detectLeaksInBucket(bucket, size_class_index, used_bits_count, tty_config);
458471 optional_bucket = bucket.prev;
459472 }
460473 }
......@@ -464,7 +477,11 @@ pub fn DebugAllocator(comptime config: Config) type {
464477 if (config.retain_metadata and large_alloc.freed) continue;
465478 const stack_trace = large_alloc.getStackTrace(.alloc);
466479 log.err("memory address 0x{x} leaked: {f}", .{
467 @intFromPtr(large_alloc.bytes.ptr), stack_trace,
480 @intFromPtr(large_alloc.bytes.ptr),
481 std.debug.FormatStackTrace{
482 .stack_trace = stack_trace,
483 .tty_config = tty_config,
484 },
468485 });
469486 leaks += 1;
470487 }
......@@ -519,8 +536,20 @@ pub fn DebugAllocator(comptime config: Config) type {
519536 fn reportDoubleFree(ret_addr: usize, alloc_stack_trace: StackTrace, free_stack_trace: StackTrace) void {
520537 var addr_buf: [stack_n]usize = undefined;
521538 const second_free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
539 const tty_config = std.Io.tty.detectConfig(.stderr());
522540 log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{
523 alloc_stack_trace, free_stack_trace, second_free_stack_trace,
541 std.debug.FormatStackTrace{
542 .stack_trace = alloc_stack_trace,
543 .tty_config = tty_config,
544 },
545 std.debug.FormatStackTrace{
546 .stack_trace = free_stack_trace,
547 .tty_config = tty_config,
548 },
549 std.debug.FormatStackTrace{
550 .stack_trace = second_free_stack_trace,
551 .tty_config = tty_config,
552 },
524553 });
525554 }
526555
......@@ -561,11 +590,18 @@ pub fn DebugAllocator(comptime config: Config) type {
561590 if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {
562591 var addr_buf: [stack_n]usize = undefined;
563592 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
593 const tty_config = std.Io.tty.detectConfig(.stderr());
564594 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
565595 entry.value_ptr.bytes.len,
566596 old_mem.len,
567 entry.value_ptr.getStackTrace(.alloc),
568 free_stack_trace,
597 std.debug.FormatStackTrace{
598 .stack_trace = entry.value_ptr.getStackTrace(.alloc),
599 .tty_config = tty_config,
600 },
601 std.debug.FormatStackTrace{
602 .stack_trace = free_stack_trace,
603 .tty_config = tty_config,
604 },
569605 });
570606 }
571607
......@@ -667,11 +703,18 @@ pub fn DebugAllocator(comptime config: Config) type {
667703 if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {
668704 var addr_buf: [stack_n]usize = undefined;
669705 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
706 const tty_config = std.Io.tty.detectConfig(.stderr());
670707 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
671708 entry.value_ptr.bytes.len,
672709 old_mem.len,
673 entry.value_ptr.getStackTrace(.alloc),
674 free_stack_trace,
710 std.debug.FormatStackTrace{
711 .stack_trace = entry.value_ptr.getStackTrace(.alloc),
712 .tty_config = tty_config,
713 },
714 std.debug.FormatStackTrace{
715 .stack_trace = free_stack_trace,
716 .tty_config = tty_config,
717 },
675718 });
676719 }
677720
......@@ -892,19 +935,33 @@ pub fn DebugAllocator(comptime config: Config) type {
892935 var addr_buf: [stack_n]usize = undefined;
893936 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);
894937 if (old_memory.len != requested_size) {
938 const tty_config = std.Io.tty.detectConfig(.stderr());
895939 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
896940 requested_size,
897941 old_memory.len,
898 bucketStackTrace(bucket, slot_count, slot_index, .alloc),
899 free_stack_trace,
942 std.debug.FormatStackTrace{
943 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
944 .tty_config = tty_config,
945 },
946 std.debug.FormatStackTrace{
947 .stack_trace = free_stack_trace,
948 .tty_config = tty_config,
949 },
900950 });
901951 }
902952 if (alignment != slot_alignment) {
953 const tty_config = std.Io.tty.detectConfig(.stderr());
903954 log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{
904955 slot_alignment.toByteUnits(),
905956 alignment.toByteUnits(),
906 bucketStackTrace(bucket, slot_count, slot_index, .alloc),
907 free_stack_trace,
957 std.debug.FormatStackTrace{
958 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
959 .tty_config = tty_config,
960 },
961 std.debug.FormatStackTrace{
962 .stack_trace = free_stack_trace,
963 .tty_config = tty_config,
964 },
908965 });
909966 }
910967 }
......@@ -987,19 +1044,33 @@ pub fn DebugAllocator(comptime config: Config) type {
9871044 var addr_buf: [stack_n]usize = undefined;
9881045 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);
9891046 if (memory.len != requested_size) {
1047 const tty_config = std.Io.tty.detectConfig(.stderr());
9901048 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
9911049 requested_size,
9921050 memory.len,
993 bucketStackTrace(bucket, slot_count, slot_index, .alloc),
994 free_stack_trace,
1051 std.debug.FormatStackTrace{
1052 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
1053 .tty_config = tty_config,
1054 },
1055 std.debug.FormatStackTrace{
1056 .stack_trace = free_stack_trace,
1057 .tty_config = tty_config,
1058 },
9951059 });
9961060 }
9971061 if (alignment != slot_alignment) {
1062 const tty_config = std.Io.tty.detectConfig(.stderr());
9981063 log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{
9991064 slot_alignment.toByteUnits(),
10001065 alignment.toByteUnits(),
1001 bucketStackTrace(bucket, slot_count, slot_index, .alloc),
1002 free_stack_trace,
1066 std.debug.FormatStackTrace{
1067 .stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc),
1068 .tty_config = tty_config,
1069 },
1070 std.debug.FormatStackTrace{
1071 .stack_trace = free_stack_trace,
1072 .tty_config = tty_config,
1073 },
10031074 });
10041075 }
10051076 }
lib/std/http/Client.zig+104-109
......@@ -9,12 +9,13 @@ const builtin = @import("builtin");
99const testing = std.testing;
1010const http = std.http;
1111const mem = std.mem;
12const net = std.net;
1312const Uri = std.Uri;
1413const Allocator = mem.Allocator;
1514const assert = std.debug.assert;
15const Io = std.Io;
1616const Writer = std.Io.Writer;
1717const Reader = std.Io.Reader;
18const HostName = std.Io.net.HostName;
1819
1920const Client = @This();
2021
......@@ -22,6 +23,8 @@ pub const disable_tls = std.options.http_disable_tls;
2223
2324/// Used for all client allocations. Must be thread-safe.
2425allocator: Allocator,
26/// Used for opening TCP connections.
27io: Io,
2528
2629ca_bundle: if (disable_tls) void else std.crypto.Certificate.Bundle = if (disable_tls) {} else .{},
2730ca_bundle_mutex: std.Thread.Mutex = .{},
......@@ -32,9 +35,11 @@ tls_buffer_size: if (disable_tls) u0 else usize = if (disable_tls) 0 else std.cr
3235/// traffic over connections created with this `Client`.
3336ssl_key_log: ?*std.crypto.tls.Client.SslKeyLog = null,
3437
35/// When this is `true`, the next time this client performs an HTTPS request,
36/// it will first rescan the system for root certificates.
37next_https_rescan_certs: bool = true,
38/// The time used to decide whether certificates are expired.
39///
40/// When this is `null`, the next time this client performs an HTTPS request,
41/// it will first check the time and rescan the system for root certificates.
42now: ?Io.Timestamp = null,
3843
3944/// The pool of connections that can be reused (and currently in use).
4045connection_pool: ConnectionPool = .{},
......@@ -67,7 +72,7 @@ pub const ConnectionPool = struct {
6772
6873 /// The criteria for a connection to be considered a match.
6974 pub const Criteria = struct {
70 host: []const u8,
75 host: HostName,
7176 port: u16,
7277 protocol: Protocol,
7378 };
......@@ -87,7 +92,7 @@ pub const ConnectionPool = struct {
8792 if (connection.port != criteria.port) continue;
8893
8994 // Domain names are case-insensitive (RFC 5890, Section 2.3.2.4)
90 if (!std.ascii.eqlIgnoreCase(connection.host(), criteria.host)) continue;
95 if (!connection.host().eql(criteria.host)) continue;
9196
9297 pool.acquireUnsafe(connection);
9398 return connection;
......@@ -116,19 +121,19 @@ pub const ConnectionPool = struct {
116121 /// If the connection is marked as closing, it will be closed instead.
117122 ///
118123 /// Threadsafe.
119 pub fn release(pool: *ConnectionPool, connection: *Connection) void {
124 pub fn release(pool: *ConnectionPool, connection: *Connection, io: Io) void {
120125 pool.mutex.lock();
121126 defer pool.mutex.unlock();
122127
123128 pool.used.remove(&connection.pool_node);
124129
125 if (connection.closing or pool.free_size == 0) return connection.destroy();
130 if (connection.closing or pool.free_size == 0) return connection.destroy(io);
126131
127132 if (pool.free_len >= pool.free_size) {
128133 const popped: *Connection = @alignCast(@fieldParentPtr("pool_node", pool.free.popFirst().?));
129134 pool.free_len -= 1;
130135
131 popped.destroy();
136 popped.destroy(io);
132137 }
133138
134139 if (connection.proxied) {
......@@ -176,21 +181,21 @@ pub const ConnectionPool = struct {
176181 /// All future operations on the connection pool will deadlock.
177182 ///
178183 /// Threadsafe.
179 pub fn deinit(pool: *ConnectionPool) void {
184 pub fn deinit(pool: *ConnectionPool, io: Io) void {
180185 pool.mutex.lock();
181186
182187 var next = pool.free.first;
183188 while (next) |node| {
184189 const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node));
185190 next = node.next;
186 connection.destroy();
191 connection.destroy(io);
187192 }
188193
189194 next = pool.used.first;
190195 while (next) |node| {
191196 const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node));
192197 next = node.next;
193 connection.destroy();
198 connection.destroy(io);
194199 }
195200
196201 pool.* = undefined;
......@@ -225,8 +230,8 @@ pub const Protocol = enum {
225230
226231pub const Connection = struct {
227232 client: *Client,
228 stream_writer: net.Stream.Writer,
229 stream_reader: net.Stream.Reader,
233 stream_writer: Io.net.Stream.Writer,
234 stream_reader: Io.net.Stream.Reader,
230235 /// Entry in `ConnectionPool.used` or `ConnectionPool.free`.
231236 pool_node: std.DoublyLinkedList.Node,
232237 port: u16,
......@@ -240,28 +245,29 @@ pub const Connection = struct {
240245
241246 fn create(
242247 client: *Client,
243 remote_host: []const u8,
248 remote_host: HostName,
244249 port: u16,
245 stream: net.Stream,
250 stream: Io.net.Stream,
246251 ) error{OutOfMemory}!*Plain {
252 const io = client.io;
247253 const gpa = client.allocator;
248 const alloc_len = allocLen(client, remote_host.len);
254 const alloc_len = allocLen(client, remote_host.bytes.len);
249255 const base = try gpa.alignedAlloc(u8, .of(Plain), alloc_len);
250256 errdefer gpa.free(base);
251 const host_buffer = base[@sizeOf(Plain)..][0..remote_host.len];
257 const host_buffer = base[@sizeOf(Plain)..][0..remote_host.bytes.len];
252258 const socket_read_buffer = host_buffer.ptr[host_buffer.len..][0..client.read_buffer_size];
253259 const socket_write_buffer = socket_read_buffer.ptr[socket_read_buffer.len..][0..client.write_buffer_size];
254260 assert(base.ptr + alloc_len == socket_write_buffer.ptr + socket_write_buffer.len);
255 @memcpy(host_buffer, remote_host);
261 @memcpy(host_buffer, remote_host.bytes);
256262 const plain: *Plain = @ptrCast(base);
257263 plain.* = .{
258264 .connection = .{
259265 .client = client,
260 .stream_writer = stream.writer(socket_write_buffer),
261 .stream_reader = stream.reader(socket_read_buffer),
266 .stream_writer = stream.writer(io, socket_write_buffer),
267 .stream_reader = stream.reader(io, socket_read_buffer),
262268 .pool_node = .{},
263269 .port = port,
264 .host_len = @intCast(remote_host.len),
270 .host_len = @intCast(remote_host.bytes.len),
265271 .proxied = false,
266272 .closing = false,
267273 .protocol = .plain,
......@@ -281,9 +287,9 @@ pub const Connection = struct {
281287 return @sizeOf(Plain) + host_len + client.read_buffer_size + client.write_buffer_size;
282288 }
283289
284 fn host(plain: *Plain) []u8 {
290 fn host(plain: *Plain) HostName {
285291 const base: [*]u8 = @ptrCast(plain);
286 return base[@sizeOf(Plain)..][0..plain.connection.host_len];
292 return .{ .bytes = base[@sizeOf(Plain)..][0..plain.connection.host_len] };
287293 }
288294 };
289295
......@@ -291,17 +297,19 @@ pub const Connection = struct {
291297 client: std.crypto.tls.Client,
292298 connection: Connection,
293299
300 /// Asserts that `client.now` is non-null.
294301 fn create(
295302 client: *Client,
296 remote_host: []const u8,
303 remote_host: HostName,
297304 port: u16,
298 stream: net.Stream,
299 ) error{ OutOfMemory, TlsInitializationFailed }!*Tls {
305 stream: Io.net.Stream,
306 ) !*Tls {
307 const io = client.io;
300308 const gpa = client.allocator;
301 const alloc_len = allocLen(client, remote_host.len);
309 const alloc_len = allocLen(client, remote_host.bytes.len);
302310 const base = try gpa.alignedAlloc(u8, .of(Tls), alloc_len);
303311 errdefer gpa.free(base);
304 const host_buffer = base[@sizeOf(Tls)..][0..remote_host.len];
312 const host_buffer = base[@sizeOf(Tls)..][0..remote_host.bytes.len];
305313 // The TLS client wants enough buffer for the max encrypted frame
306314 // size, and the HTTP body reader wants enough buffer for the
307315 // entire HTTP header. This means we need a combined upper bound.
......@@ -311,35 +319,43 @@ pub const Connection = struct {
311319 const socket_write_buffer = tls_write_buffer.ptr[tls_write_buffer.len..][0..client.write_buffer_size];
312320 const socket_read_buffer = socket_write_buffer.ptr[socket_write_buffer.len..][0..client.tls_buffer_size];
313321 assert(base.ptr + alloc_len == socket_read_buffer.ptr + socket_read_buffer.len);
314 @memcpy(host_buffer, remote_host);
322 @memcpy(host_buffer, remote_host.bytes);
315323 const tls: *Tls = @ptrCast(base);
324 var random_buffer: [176]u8 = undefined;
325 std.crypto.random.bytes(&random_buffer);
316326 tls.* = .{
317327 .connection = .{
318328 .client = client,
319 .stream_writer = stream.writer(tls_write_buffer),
320 .stream_reader = stream.reader(socket_read_buffer),
329 .stream_writer = stream.writer(io, tls_write_buffer),
330 .stream_reader = stream.reader(io, socket_read_buffer),
321331 .pool_node = .{},
322332 .port = port,
323 .host_len = @intCast(remote_host.len),
333 .host_len = @intCast(remote_host.bytes.len),
324334 .proxied = false,
325335 .closing = false,
326336 .protocol = .tls,
327337 },
328 // TODO data race here on ca_bundle if the user sets next_https_rescan_certs to true
338 // TODO data race here on ca_bundle if the user sets `now` to null
329339 .client = std.crypto.tls.Client.init(
330 tls.connection.stream_reader.interface(),
340 &tls.connection.stream_reader.interface,
331341 &tls.connection.stream_writer.interface,
332342 .{
333 .host = .{ .explicit = remote_host },
343 .host = .{ .explicit = remote_host.bytes },
334344 .ca = .{ .bundle = client.ca_bundle },
335345 .ssl_key_log = client.ssl_key_log,
336346 .read_buffer = tls_read_buffer,
337347 .write_buffer = socket_write_buffer,
348 .entropy = &random_buffer,
349 .realtime_now_seconds = client.now.?.toSeconds(),
338350 // This is appropriate for HTTPS because the HTTP headers contain
339351 // the content length which is used to detect truncation attacks.
340352 .allow_truncation_attacks = true,
341353 },
342 ) catch return error.TlsInitializationFailed,
354 ) catch |err| switch (err) {
355 error.WriteFailed => return tls.connection.stream_writer.err.?,
356 error.ReadFailed => return tls.connection.stream_reader.err.?,
357 else => |e| return e,
358 },
343359 };
344360 return tls;
345361 }
......@@ -357,32 +373,32 @@ pub const Connection = struct {
357373 client.write_buffer_size + client.tls_buffer_size;
358374 }
359375
360 fn host(tls: *Tls) []u8 {
376 fn host(tls: *Tls) HostName {
361377 const base: [*]u8 = @ptrCast(tls);
362 return base[@sizeOf(Tls)..][0..tls.connection.host_len];
378 return .{ .bytes = base[@sizeOf(Tls)..][0..tls.connection.host_len] };
363379 }
364380 };
365381
366 pub const ReadError = std.crypto.tls.Client.ReadError || std.net.Stream.ReadError;
382 pub const ReadError = std.crypto.tls.Client.ReadError || Io.net.Stream.Reader.Error;
367383
368384 pub fn getReadError(c: *const Connection) ?ReadError {
369385 return switch (c.protocol) {
370386 .tls => {
371387 if (disable_tls) unreachable;
372388 const tls: *const Tls = @alignCast(@fieldParentPtr("connection", c));
373 return tls.client.read_err orelse c.stream_reader.getError();
389 return tls.client.read_err orelse c.stream_reader.err.?;
374390 },
375391 .plain => {
376 return c.stream_reader.getError();
392 return c.stream_reader.err.?;
377393 },
378394 };
379395 }
380396
381 fn getStream(c: *Connection) net.Stream {
382 return c.stream_reader.getStream();
397 fn getStream(c: *Connection) Io.net.Stream {
398 return c.stream_reader.stream;
383399 }
384400
385 pub fn host(c: *Connection) []u8 {
401 pub fn host(c: *Connection) HostName {
386402 return switch (c.protocol) {
387403 .tls => {
388404 if (disable_tls) unreachable;
......@@ -398,8 +414,8 @@ pub const Connection = struct {
398414
399415 /// If this is called without calling `flush` or `end`, data will be
400416 /// dropped unsent.
401 pub fn destroy(c: *Connection) void {
402 c.getStream().close();
417 pub fn destroy(c: *Connection, io: Io) void {
418 c.stream_reader.stream.close(io);
403419 switch (c.protocol) {
404420 .tls => {
405421 if (disable_tls) unreachable;
......@@ -435,7 +451,7 @@ pub const Connection = struct {
435451 const tls: *Tls = @alignCast(@fieldParentPtr("connection", c));
436452 return &tls.client.reader;
437453 },
438 .plain => c.stream_reader.interface(),
454 .plain => &c.stream_reader.interface,
439455 };
440456 }
441457
......@@ -864,6 +880,7 @@ pub const Request = struct {
864880
865881 /// Returns the request's `Connection` back to the pool of the `Client`.
866882 pub fn deinit(r: *Request) void {
883 const io = r.client.io;
867884 if (r.connection) |connection| {
868885 connection.closing = connection.closing or switch (r.reader.state) {
869886 .ready => false,
......@@ -878,7 +895,7 @@ pub const Request = struct {
878895 },
879896 else => true,
880897 };
881 r.client.connection_pool.release(connection);
898 r.client.connection_pool.release(connection, io);
882899 }
883900 r.* = undefined;
884901 }
......@@ -1180,6 +1197,7 @@ pub const Request = struct {
11801197 ///
11811198 /// `aux_buf` must outlive accesses to `Request.uri`.
11821199 fn redirect(r: *Request, head: *const Response.Head, aux_buf: *[]u8) !void {
1200 const io = r.client.io;
11831201 const new_location = head.location orelse return error.HttpRedirectLocationMissing;
11841202 if (new_location.len > aux_buf.*.len) return error.HttpRedirectLocationOversize;
11851203 const location = aux_buf.*[0..new_location.len];
......@@ -1196,19 +1214,20 @@ pub const Request = struct {
11961214 error.UnexpectedCharacter => return error.HttpRedirectLocationInvalid,
11971215 error.InvalidFormat => return error.HttpRedirectLocationInvalid,
11981216 error.InvalidPort => return error.HttpRedirectLocationInvalid,
1217 error.InvalidHostName => return error.HttpRedirectLocationInvalid,
11991218 error.NoSpaceLeft => return error.HttpRedirectLocationOversize,
12001219 };
12011220
12021221 const protocol = Protocol.fromUri(new_uri) orelse return error.UnsupportedUriScheme;
12031222 const old_connection = r.connection.?;
12041223 const old_host = old_connection.host();
1205 var new_host_name_buffer: [Uri.host_name_max]u8 = undefined;
1224 var new_host_name_buffer: [HostName.max_len]u8 = undefined;
12061225 const new_host = try new_uri.getHost(&new_host_name_buffer);
12071226 const keep_privileged_headers =
12081227 std.ascii.eqlIgnoreCase(r.uri.scheme, new_uri.scheme) and
1209 sameParentDomain(old_host, new_host);
1228 old_host.sameParentDomain(new_host);
12101229
1211 r.client.connection_pool.release(old_connection);
1230 r.client.connection_pool.release(old_connection, io);
12121231 r.connection = null;
12131232
12141233 if (!keep_privileged_headers) {
......@@ -1264,7 +1283,7 @@ pub const Request = struct {
12641283
12651284pub const Proxy = struct {
12661285 protocol: Protocol,
1267 host: []const u8,
1286 host: HostName,
12681287 authorization: ?[]const u8,
12691288 port: u16,
12701289 supports_connect: bool,
......@@ -1275,9 +1294,10 @@ pub const Proxy = struct {
12751294/// All pending requests must be de-initialized and all active connections released
12761295/// before calling this function.
12771296pub fn deinit(client: *Client) void {
1297 const io = client.io;
12781298 assert(client.connection_pool.used.first == null); // There are still active requests.
12791299
1280 client.connection_pool.deinit();
1300 client.connection_pool.deinit(io);
12811301 if (!disable_tls) client.ca_bundle.deinit(client.allocator);
12821302
12831303 client.* = undefined;
......@@ -1383,25 +1403,16 @@ pub const basic_authorization = struct {
13831403 }
13841404};
13851405
1386pub const ConnectTcpError = Allocator.Error || error{
1387 ConnectionRefused,
1388 NetworkUnreachable,
1389 ConnectionTimedOut,
1390 ConnectionResetByPeer,
1391 TemporaryNameServerFailure,
1392 NameServerFailure,
1393 UnknownHostName,
1394 HostLacksNetworkAddresses,
1395 UnexpectedConnectFailure,
1406pub const ConnectTcpError = error{
13961407 TlsInitializationFailed,
1397};
1408} || Allocator.Error || HostName.ConnectError;
13981409
13991410/// Reuses a `Connection` if one matching `host` and `port` is already open.
14001411///
14011412/// Threadsafe.
14021413pub fn connectTcp(
14031414 client: *Client,
1404 host: []const u8,
1415 host: HostName,
14051416 port: u16,
14061417 protocol: Protocol,
14071418) ConnectTcpError!*Connection {
......@@ -1409,15 +1420,17 @@ pub fn connectTcp(
14091420}
14101421
14111422pub const ConnectTcpOptions = struct {
1412 host: []const u8,
1423 host: HostName,
14131424 port: u16,
14141425 protocol: Protocol,
14151426
1416 proxied_host: ?[]const u8 = null,
1427 proxied_host: ?HostName = null,
14171428 proxied_port: ?u16 = null,
1429 timeout: Io.Timeout = .none,
14181430};
14191431
14201432pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcpError!*Connection {
1433 const io = client.io;
14211434 const host = options.host;
14221435 const port = options.port;
14231436 const protocol = options.protocol;
......@@ -1431,23 +1444,18 @@ pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcp
14311444 .protocol = protocol,
14321445 })) |conn| return conn;
14331446
1434 const stream = net.tcpConnectToHost(client.allocator, host, port) catch |err| switch (err) {
1435 error.ConnectionRefused => return error.ConnectionRefused,
1436 error.NetworkUnreachable => return error.NetworkUnreachable,
1437 error.ConnectionTimedOut => return error.ConnectionTimedOut,
1438 error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
1439 error.TemporaryNameServerFailure => return error.TemporaryNameServerFailure,
1440 error.NameServerFailure => return error.NameServerFailure,
1441 error.UnknownHostName => return error.UnknownHostName,
1442 error.HostLacksNetworkAddresses => return error.HostLacksNetworkAddresses,
1443 else => return error.UnexpectedConnectFailure,
1444 };
1445 errdefer stream.close();
1447 var stream = try host.connect(io, port, .{ .mode = .stream });
1448 errdefer stream.close(io);
14461449
14471450 switch (protocol) {
14481451 .tls => {
14491452 if (disable_tls) return error.TlsInitializationFailed;
1450 const tc = try Connection.Tls.create(client, proxied_host, proxied_port, stream);
1453 const tc = Connection.Tls.create(client, proxied_host, proxied_port, stream) catch |err| switch (err) {
1454 error.OutOfMemory => |e| return e,
1455 error.Unexpected => |e| return e,
1456 error.Canceled => |e| return e,
1457 else => return error.TlsInitializationFailed,
1458 };
14511459 client.connection_pool.addUsed(&tc.connection);
14521460 return &tc.connection;
14531461 },
......@@ -1476,7 +1484,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti
14761484 errdefer client.allocator.destroy(conn);
14771485 conn.* = .{ .data = undefined };
14781486
1479 const stream = try std.net.connectUnixSocket(path);
1487 const stream = try Io.net.connectUnixSocket(path);
14801488 errdefer stream.close();
14811489
14821490 conn.data = .{
......@@ -1501,9 +1509,10 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti
15011509pub fn connectProxied(
15021510 client: *Client,
15031511 proxy: *Proxy,
1504 proxied_host: []const u8,
1512 proxied_host: HostName,
15051513 proxied_port: u16,
15061514) !*Connection {
1515 const io = client.io;
15071516 if (!proxy.supports_connect) return error.TunnelNotSupported;
15081517
15091518 if (client.connection_pool.findConnection(.{
......@@ -1523,12 +1532,12 @@ pub fn connectProxied(
15231532 });
15241533 errdefer {
15251534 connection.closing = true;
1526 client.connection_pool.release(connection);
1535 client.connection_pool.release(connection, io);
15271536 }
15281537
15291538 var req = client.request(.CONNECT, .{
15301539 .scheme = "http",
1531 .host = .{ .raw = proxied_host },
1540 .host = .{ .raw = proxied_host.bytes },
15321541 .port = proxied_port,
15331542 }, .{
15341543 .redirect_behavior = .unhandled,
......@@ -1573,7 +1582,7 @@ pub const ConnectError = ConnectTcpError || RequestError;
15731582/// This function is threadsafe.
15741583pub fn connect(
15751584 client: *Client,
1576 host: []const u8,
1585 host: HostName,
15771586 port: u16,
15781587 protocol: Protocol,
15791588) ConnectError!*Connection {
......@@ -1583,9 +1592,7 @@ pub fn connect(
15831592 } orelse return client.connectTcp(host, port, protocol);
15841593
15851594 // Prevent proxying through itself.
1586 if (std.ascii.eqlIgnoreCase(proxy.host, host) and
1587 proxy.port == port and proxy.protocol == protocol)
1588 {
1595 if (proxy.host.eql(host) and proxy.port == port and proxy.protocol == protocol) {
15891596 return client.connectTcp(host, port, protocol);
15901597 }
15911598
......@@ -1605,7 +1612,6 @@ pub fn connect(
16051612pub const RequestError = ConnectTcpError || error{
16061613 UnsupportedUriScheme,
16071614 UriMissingHost,
1608 UriHostTooLong,
16091615 CertificateBundleLoadFailure,
16101616};
16111617
......@@ -1663,6 +1669,8 @@ pub fn request(
16631669 uri: Uri,
16641670 options: RequestOptions,
16651671) RequestError!Request {
1672 const io = client.io;
1673
16661674 if (std.debug.runtime_safety) {
16671675 for (options.extra_headers) |header| {
16681676 assert(header.name.len != 0);
......@@ -1681,20 +1689,21 @@ pub fn request(
16811689
16821690 if (protocol == .tls) {
16831691 if (disable_tls) unreachable;
1684 if (@atomicLoad(bool, &client.next_https_rescan_certs, .acquire)) {
1692 {
16851693 client.ca_bundle_mutex.lock();
16861694 defer client.ca_bundle_mutex.unlock();
16871695
1688 if (client.next_https_rescan_certs) {
1689 client.ca_bundle.rescan(client.allocator) catch
1696 if (client.now == null) {
1697 const now = try Io.Clock.real.now(io);
1698 client.now = now;
1699 client.ca_bundle.rescan(client.allocator, io, now) catch
16901700 return error.CertificateBundleLoadFailure;
1691 @atomicStore(bool, &client.next_https_rescan_certs, false, .release);
16921701 }
16931702 }
16941703 }
16951704
16961705 const connection = options.connection orelse c: {
1697 var host_name_buffer: [Uri.host_name_max]u8 = undefined;
1706 var host_name_buffer: [HostName.max_len]u8 = undefined;
16981707 const host_name = try uri.getHost(&host_name_buffer);
16991708 break :c try client.connect(host_name, uriPort(uri, protocol), protocol);
17001709 };
......@@ -1832,20 +1841,6 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {
18321841 return .{ .status = response.head.status };
18331842}
18341843
1835pub fn sameParentDomain(parent_host: []const u8, child_host: []const u8) bool {
1836 if (!std.ascii.endsWithIgnoreCase(child_host, parent_host)) return false;
1837 if (child_host.len == parent_host.len) return true;
1838 if (parent_host.len > child_host.len) return false;
1839 return child_host[child_host.len - parent_host.len - 1] == '.';
1840}
1841
1842test sameParentDomain {
1843 try testing.expect(!sameParentDomain("foo.com", "bar.com"));
1844 try testing.expect(sameParentDomain("foo.com", "foo.com"));
1845 try testing.expect(sameParentDomain("foo.com", "bar.foo.com"));
1846 try testing.expect(!sameParentDomain("bar.foo.com", "foo.com"));
1847}
1848
18491844test {
18501845 _ = Response;
18511846}
lib/std/http/Server.zig+4-4
......@@ -688,7 +688,7 @@ pub const WebSocket = struct {
688688 pub const ReadSmallTextMessageError = error{
689689 ConnectionClose,
690690 UnexpectedOpCode,
691 MessageTooBig,
691 MessageOversize,
692692 MissingMaskBit,
693693 ReadFailed,
694694 EndOfStream,
......@@ -717,15 +717,15 @@ pub const WebSocket = struct {
717717 _ => return error.UnexpectedOpCode,
718718 }
719719
720 if (!h0.fin) return error.MessageTooBig;
720 if (!h0.fin) return error.MessageOversize;
721721 if (!h1.mask) return error.MissingMaskBit;
722722
723723 const len: usize = switch (h1.payload_len) {
724724 .len16 => try in.takeInt(u16, .big),
725 .len64 => std.math.cast(usize, try in.takeInt(u64, .big)) orelse return error.MessageTooBig,
725 .len64 => std.math.cast(usize, try in.takeInt(u64, .big)) orelse return error.MessageOversize,
726726 else => @intFromEnum(h1.payload_len),
727727 };
728 if (len > in.buffer.len) return error.MessageTooBig;
728 if (len > in.buffer.len) return error.MessageOversize;
729729 const mask: u32 = @bitCast((try in.takeArray(4)).*);
730730 const payload = try in.take(len);
731731
lib/std/http/test.zig+124-91
......@@ -1,27 +1,36 @@
11const builtin = @import("builtin");
2const native_endian = builtin.cpu.arch.endian();
3
24const std = @import("std");
35const http = std.http;
46const mem = std.mem;
5const native_endian = builtin.cpu.arch.endian();
7const net = std.Io.net;
8const Io = std.Io;
69const expect = std.testing.expect;
710const expectEqual = std.testing.expectEqual;
811const expectEqualStrings = std.testing.expectEqualStrings;
912const expectError = std.testing.expectError;
1013
1114test "trailers" {
12 const test_server = try createTestServer(struct {
15 if (builtin.cpu.arch == .arm) {
16 // https://github.com/ziglang/zig/issues/25762
17 return error.SkipZigTest;
18 }
19
20 const io = std.testing.io;
21 const test_server = try createTestServer(io, struct {
1322 fn run(test_server: *TestServer) anyerror!void {
1423 const net_server = &test_server.net_server;
1524 var recv_buffer: [1024]u8 = undefined;
1625 var send_buffer: [1024]u8 = undefined;
1726 var remaining: usize = 1;
1827 while (remaining != 0) : (remaining -= 1) {
19 const connection = try net_server.accept();
20 defer connection.stream.close();
28 var stream = try net_server.accept(io);
29 defer stream.close(io);
2130
22 var connection_br = connection.stream.reader(&recv_buffer);
23 var connection_bw = connection.stream.writer(&send_buffer);
24 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);
31 var connection_br = stream.reader(io, &recv_buffer);
32 var connection_bw = stream.writer(io, &send_buffer);
33 var server = http.Server.init(&connection_br.interface, &connection_bw.interface);
2534
2635 try expectEqual(.ready, server.reader.state);
2736 var request = try server.receiveHead();
......@@ -49,7 +58,7 @@ test "trailers" {
4958
5059 const gpa = std.testing.allocator;
5160
52 var client: http.Client = .{ .allocator = gpa };
61 var client: http.Client = .{ .allocator = gpa, .io = io };
5362 defer client.deinit();
5463
5564 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/trailer", .{
......@@ -92,17 +101,18 @@ test "trailers" {
92101}
93102
94103test "HTTP server handles a chunked transfer coding request" {
95 const test_server = try createTestServer(struct {
104 const io = std.testing.io;
105 const test_server = try createTestServer(io, struct {
96106 fn run(test_server: *TestServer) anyerror!void {
97107 const net_server = &test_server.net_server;
98108 var recv_buffer: [8192]u8 = undefined;
99109 var send_buffer: [500]u8 = undefined;
100 const connection = try net_server.accept();
101 defer connection.stream.close();
110 var stream = try net_server.accept(io);
111 defer stream.close(io);
102112
103 var connection_br = connection.stream.reader(&recv_buffer);
104 var connection_bw = connection.stream.writer(&send_buffer);
105 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);
113 var connection_br = stream.reader(io, &recv_buffer);
114 var connection_bw = stream.writer(io, &send_buffer);
115 var server = http.Server.init(&connection_br.interface, &connection_bw.interface);
106116 var request = try server.receiveHead();
107117
108118 try expect(request.head.transfer_encoding == .chunked);
......@@ -136,12 +146,13 @@ test "HTTP server handles a chunked transfer coding request" {
136146 "0\r\n" ++
137147 "\r\n";
138148
139 const gpa = std.testing.allocator;
140 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
141 defer stream.close();
142 var stream_writer = stream.writer(&.{});
149 const host_name: net.HostName = try .init("127.0.0.1");
150 var stream = try host_name.connect(io, test_server.port(), .{ .mode = .stream });
151 defer stream.close(io);
152 var stream_writer = stream.writer(io, &.{});
143153 try stream_writer.interface.writeAll(request_bytes);
144154
155 const gpa = std.testing.allocator;
145156 const expected_response =
146157 "HTTP/1.1 200 OK\r\n" ++
147158 "connection: close\r\n" ++
......@@ -149,26 +160,27 @@ test "HTTP server handles a chunked transfer coding request" {
149160 "content-type: text/plain\r\n" ++
150161 "\r\n" ++
151162 "message from server!\n";
152 var stream_reader = stream.reader(&.{});
153 const response = try stream_reader.interface().allocRemaining(gpa, .limited(expected_response.len + 1));
163 var stream_reader = stream.reader(io, &.{});
164 const response = try stream_reader.interface.allocRemaining(gpa, .limited(expected_response.len + 1));
154165 defer gpa.free(response);
155166 try expectEqualStrings(expected_response, response);
156167}
157168
158169test "echo content server" {
159 const test_server = try createTestServer(struct {
170 const io = std.testing.io;
171 const test_server = try createTestServer(io, struct {
160172 fn run(test_server: *TestServer) anyerror!void {
161173 const net_server = &test_server.net_server;
162174 var recv_buffer: [1024]u8 = undefined;
163175 var send_buffer: [100]u8 = undefined;
164176
165177 accept: while (!test_server.shutting_down) {
166 const connection = try net_server.accept();
167 defer connection.stream.close();
178 var stream = try net_server.accept(io);
179 defer stream.close(io);
168180
169 var connection_br = connection.stream.reader(&recv_buffer);
170 var connection_bw = connection.stream.writer(&send_buffer);
171 var http_server = http.Server.init(connection_br.interface(), &connection_bw.interface);
181 var connection_br = stream.reader(io, &recv_buffer);
182 var connection_bw = stream.writer(io, &send_buffer);
183 var http_server = http.Server.init(&connection_br.interface, &connection_bw.interface);
172184
173185 while (http_server.reader.state == .ready) {
174186 var request = http_server.receiveHead() catch |err| switch (err) {
......@@ -235,7 +247,7 @@ test "echo content server" {
235247 defer test_server.destroy();
236248
237249 {
238 var client: http.Client = .{ .allocator = std.testing.allocator };
250 var client: http.Client = .{ .allocator = std.testing.allocator, .io = io };
239251 defer client.deinit();
240252
241253 try echoTests(&client, test_server.port());
......@@ -243,6 +255,8 @@ test "echo content server" {
243255}
244256
245257test "Server.Request.respondStreaming non-chunked, unknown content-length" {
258 const io = std.testing.io;
259
246260 if (builtin.os.tag == .windows) {
247261 // https://github.com/ziglang/zig/issues/21457
248262 return error.SkipZigTest;
......@@ -250,19 +264,19 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
250264
251265 // In this case, the response is expected to stream until the connection is
252266 // closed, indicating the end of the body.
253 const test_server = try createTestServer(struct {
267 const test_server = try createTestServer(io, struct {
254268 fn run(test_server: *TestServer) anyerror!void {
255269 const net_server = &test_server.net_server;
256270 var recv_buffer: [1000]u8 = undefined;
257271 var send_buffer: [500]u8 = undefined;
258272 var remaining: usize = 1;
259273 while (remaining != 0) : (remaining -= 1) {
260 const connection = try net_server.accept();
261 defer connection.stream.close();
274 var stream = try net_server.accept(io);
275 defer stream.close(io);
262276
263 var connection_br = connection.stream.reader(&recv_buffer);
264 var connection_bw = connection.stream.writer(&send_buffer);
265 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);
277 var connection_br = stream.reader(io, &recv_buffer);
278 var connection_bw = stream.writer(io, &send_buffer);
279 var server = http.Server.init(&connection_br.interface, &connection_bw.interface);
266280
267281 try expectEqual(.ready, server.reader.state);
268282 var request = try server.receiveHead();
......@@ -286,14 +300,15 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
286300 defer test_server.destroy();
287301
288302 const request_bytes = "GET /foo HTTP/1.1\r\n\r\n";
289 const gpa = std.testing.allocator;
290 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
291 defer stream.close();
292 var stream_writer = stream.writer(&.{});
303 const host_name: net.HostName = try .init("127.0.0.1");
304 var stream = try host_name.connect(io, test_server.port(), .{ .mode = .stream });
305 defer stream.close(io);
306 var stream_writer = stream.writer(io, &.{});
293307 try stream_writer.interface.writeAll(request_bytes);
294308
295 var stream_reader = stream.reader(&.{});
296 const response = try stream_reader.interface().allocRemaining(gpa, .unlimited);
309 var stream_reader = stream.reader(io, &.{});
310 const gpa = std.testing.allocator;
311 const response = try stream_reader.interface.allocRemaining(gpa, .unlimited);
297312 defer gpa.free(response);
298313
299314 var expected_response = std.array_list.Managed(u8).init(gpa);
......@@ -316,19 +331,21 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
316331}
317332
318333test "receiving arbitrary http headers from the client" {
319 const test_server = try createTestServer(struct {
334 const io = std.testing.io;
335
336 const test_server = try createTestServer(io, struct {
320337 fn run(test_server: *TestServer) anyerror!void {
321338 const net_server = &test_server.net_server;
322339 var recv_buffer: [666]u8 = undefined;
323340 var send_buffer: [777]u8 = undefined;
324341 var remaining: usize = 1;
325342 while (remaining != 0) : (remaining -= 1) {
326 const connection = try net_server.accept();
327 defer connection.stream.close();
343 var stream = try net_server.accept(io);
344 defer stream.close(io);
328345
329 var connection_br = connection.stream.reader(&recv_buffer);
330 var connection_bw = connection.stream.writer(&send_buffer);
331 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);
346 var connection_br = stream.reader(io, &recv_buffer);
347 var connection_bw = stream.writer(io, &send_buffer);
348 var server = http.Server.init(&connection_br.interface, &connection_bw.interface);
332349
333350 try expectEqual(.ready, server.reader.state);
334351 var request = try server.receiveHead();
......@@ -356,14 +373,15 @@ test "receiving arbitrary http headers from the client" {
356373 "CoNneCtIoN:close\r\n" ++
357374 "aoeu: asdf \r\n" ++
358375 "\r\n";
359 const gpa = std.testing.allocator;
360 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());
361 defer stream.close();
362 var stream_writer = stream.writer(&.{});
376 const host_name: net.HostName = try .init("127.0.0.1");
377 var stream = try host_name.connect(io, test_server.port(), .{ .mode = .stream });
378 defer stream.close(io);
379 var stream_writer = stream.writer(io, &.{});
363380 try stream_writer.interface.writeAll(request_bytes);
364381
365 var stream_reader = stream.reader(&.{});
366 const response = try stream_reader.interface().allocRemaining(gpa, .unlimited);
382 var stream_reader = stream.reader(io, &.{});
383 const gpa = std.testing.allocator;
384 const response = try stream_reader.interface.allocRemaining(gpa, .unlimited);
367385 defer gpa.free(response);
368386
369387 var expected_response = std.array_list.Managed(u8).init(gpa);
......@@ -376,24 +394,26 @@ test "receiving arbitrary http headers from the client" {
376394}
377395
378396test "general client/server API coverage" {
397 const io = std.testing.io;
398
379399 if (builtin.os.tag == .windows) {
380400 // This test was never passing on Windows.
381401 return error.SkipZigTest;
382402 }
383403
384 const test_server = try createTestServer(struct {
404 const test_server = try createTestServer(io, struct {
385405 fn run(test_server: *TestServer) anyerror!void {
386406 const net_server = &test_server.net_server;
387407 var recv_buffer: [1024]u8 = undefined;
388408 var send_buffer: [100]u8 = undefined;
389409
390410 outer: while (!test_server.shutting_down) {
391 var connection = try net_server.accept();
392 defer connection.stream.close();
411 var stream = try net_server.accept(io);
412 defer stream.close(io);
393413
394 var connection_br = connection.stream.reader(&recv_buffer);
395 var connection_bw = connection.stream.writer(&send_buffer);
396 var http_server = http.Server.init(connection_br.interface(), &connection_bw.interface);
414 var connection_br = stream.reader(io, &recv_buffer);
415 var connection_bw = stream.writer(io, &send_buffer);
416 var http_server = http.Server.init(&connection_br.interface, &connection_bw.interface);
397417
398418 while (http_server.reader.state == .ready) {
399419 var request = http_server.receiveHead() catch |err| switch (err) {
......@@ -401,7 +421,7 @@ test "general client/server API coverage" {
401421 else => |e| return e,
402422 };
403423
404 try handleRequest(&request, net_server.listen_address.getPort());
424 try handleRequest(&request, net_server.socket.address.getPort());
405425 }
406426 }
407427 }
......@@ -530,10 +550,10 @@ test "general client/server API coverage" {
530550 }
531551
532552 fn getUnusedTcpPort() !u16 {
533 const addr = try std.net.Address.parseIp("127.0.0.1", 0);
534 var s = try addr.listen(.{});
535 defer s.deinit();
536 return s.listen_address.in.getPort();
553 const addr = try net.IpAddress.parse("127.0.0.1", 0);
554 var s = try addr.listen(io, .{});
555 defer s.deinit(io);
556 return s.socket.address.getPort();
537557 }
538558 });
539559 defer test_server.destroy();
......@@ -541,7 +561,7 @@ test "general client/server API coverage" {
541561 const log = std.log.scoped(.client);
542562
543563 const gpa = std.testing.allocator;
544 var client: http.Client = .{ .allocator = gpa };
564 var client: http.Client = .{ .allocator = gpa, .io = io };
545565 defer client.deinit();
546566
547567 const port = test_server.port();
......@@ -867,18 +887,20 @@ test "general client/server API coverage" {
867887}
868888
869889test "Server streams both reading and writing" {
870 const test_server = try createTestServer(struct {
890 const io = std.testing.io;
891
892 const test_server = try createTestServer(io, struct {
871893 fn run(test_server: *TestServer) anyerror!void {
872894 const net_server = &test_server.net_server;
873895 var recv_buffer: [1024]u8 = undefined;
874896 var send_buffer: [777]u8 = undefined;
875897
876 const connection = try net_server.accept();
877 defer connection.stream.close();
898 var stream = try net_server.accept(io);
899 defer stream.close(io);
878900
879 var connection_br = connection.stream.reader(&recv_buffer);
880 var connection_bw = connection.stream.writer(&send_buffer);
881 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);
901 var connection_br = stream.reader(io, &recv_buffer);
902 var connection_bw = stream.writer(io, &send_buffer);
903 var server = http.Server.init(&connection_br.interface, &connection_bw.interface);
882904 var request = try server.receiveHead();
883905 var read_buffer: [100]u8 = undefined;
884906 var br = try request.readerExpectContinue(&read_buffer);
......@@ -904,7 +926,10 @@ test "Server streams both reading and writing" {
904926 });
905927 defer test_server.destroy();
906928
907 var client: http.Client = .{ .allocator = std.testing.allocator };
929 var client: http.Client = .{
930 .allocator = std.testing.allocator,
931 .io = io,
932 };
908933 defer client.deinit();
909934
910935 var redirect_buffer: [555]u8 = undefined;
......@@ -1075,36 +1100,40 @@ fn echoTests(client: *http.Client, port: u16) !void {
10751100}
10761101
10771102const TestServer = struct {
1103 io: Io,
10781104 shutting_down: bool,
10791105 server_thread: std.Thread,
1080 net_server: std.net.Server,
1106 net_server: net.Server,
10811107
10821108 fn destroy(self: *@This()) void {
1109 const io = self.io;
10831110 self.shutting_down = true;
1084 const conn = std.net.tcpConnectToAddress(self.net_server.listen_address) catch @panic("shutdown failure");
1085 conn.close();
1111 var stream = self.net_server.socket.address.connect(io, .{ .mode = .stream }) catch
1112 @panic("shutdown failure");
1113 stream.close(io);
10861114
10871115 self.server_thread.join();
1088 self.net_server.deinit();
1116 self.net_server.deinit(io);
10891117 std.testing.allocator.destroy(self);
10901118 }
10911119
10921120 fn port(self: @This()) u16 {
1093 return self.net_server.listen_address.in.getPort();
1121 return self.net_server.socket.address.getPort();
10941122 }
10951123};
10961124
1097fn createTestServer(S: type) !*TestServer {
1125fn createTestServer(io: Io, S: type) !*TestServer {
10981126 if (builtin.single_threaded) return error.SkipZigTest;
10991127 if (builtin.zig_backend == .stage2_llvm and native_endian == .big) {
11001128 // https://github.com/ziglang/zig/issues/13782
11011129 return error.SkipZigTest;
11021130 }
11031131
1104 const address = try std.net.Address.parseIp("127.0.0.1", 0);
1132 const address = try net.IpAddress.parse("127.0.0.1", 0);
11051133 const test_server = try std.testing.allocator.create(TestServer);
11061134 test_server.* = .{
1107 .net_server = try address.listen(.{ .reuse_address = true }),
1135 .io = io,
1136 .net_server = try address.listen(io, .{ .reuse_address = true }),
11081137 .shutting_down = false,
11091138 .server_thread = try std.Thread.spawn(.{}, S.run, .{test_server}),
11101139 };
......@@ -1112,18 +1141,19 @@ fn createTestServer(S: type) !*TestServer {
11121141}
11131142
11141143test "redirect to different connection" {
1115 const test_server_new = try createTestServer(struct {
1144 const io = std.testing.io;
1145 const test_server_new = try createTestServer(io, struct {
11161146 fn run(test_server: *TestServer) anyerror!void {
11171147 const net_server = &test_server.net_server;
11181148 var recv_buffer: [888]u8 = undefined;
11191149 var send_buffer: [777]u8 = undefined;
11201150
1121 const connection = try net_server.accept();
1122 defer connection.stream.close();
1151 var stream = try net_server.accept(io);
1152 defer stream.close(io);
11231153
1124 var connection_br = connection.stream.reader(&recv_buffer);
1125 var connection_bw = connection.stream.writer(&send_buffer);
1126 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);
1154 var connection_br = stream.reader(io, &recv_buffer);
1155 var connection_bw = stream.writer(io, &send_buffer);
1156 var server = http.Server.init(&connection_br.interface, &connection_bw.interface);
11271157 var request = try server.receiveHead();
11281158 try expectEqualStrings(request.head.target, "/ok");
11291159 try request.respond("good job, you pass", .{});
......@@ -1136,23 +1166,23 @@ test "redirect to different connection" {
11361166 };
11371167 global.other_port = test_server_new.port();
11381168
1139 const test_server_orig = try createTestServer(struct {
1169 const test_server_orig = try createTestServer(io, struct {
11401170 fn run(test_server: *TestServer) anyerror!void {
11411171 const net_server = &test_server.net_server;
11421172 var recv_buffer: [999]u8 = undefined;
11431173 var send_buffer: [100]u8 = undefined;
11441174
1145 const connection = try net_server.accept();
1146 defer connection.stream.close();
1175 var stream = try net_server.accept(io);
1176 defer stream.close(io);
11471177
11481178 var loc_buf: [50]u8 = undefined;
11491179 const new_loc = try std.fmt.bufPrint(&loc_buf, "http://127.0.0.1:{d}/ok", .{
11501180 global.other_port.?,
11511181 });
11521182
1153 var connection_br = connection.stream.reader(&recv_buffer);
1154 var connection_bw = connection.stream.writer(&send_buffer);
1155 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);
1183 var connection_br = stream.reader(io, &recv_buffer);
1184 var connection_bw = stream.writer(io, &send_buffer);
1185 var server = http.Server.init(&connection_br.interface, &connection_bw.interface);
11561186 var request = try server.receiveHead();
11571187 try expectEqualStrings(request.head.target, "/help");
11581188 try request.respond("", .{
......@@ -1167,7 +1197,10 @@ test "redirect to different connection" {
11671197
11681198 const gpa = std.testing.allocator;
11691199
1170 var client: http.Client = .{ .allocator = gpa };
1200 var client: http.Client = .{
1201 .allocator = gpa,
1202 .io = io,
1203 };
11711204 defer client.deinit();
11721205
11731206 var loc_buf: [100]u8 = undefined;
lib/std/mem.zig+46-23
......@@ -1678,6 +1678,7 @@ test "indexOfPos empty needle" {
16781678/// needle.len must be > 0
16791679/// does not count overlapping needles
16801680pub fn count(comptime T: type, haystack: []const T, needle: []const T) usize {
1681 if (needle.len == 1) return countScalar(T, haystack, needle[0]);
16811682 assert(needle.len > 0);
16821683 var i: usize = 0;
16831684 var found: usize = 0;
......@@ -1704,9 +1705,9 @@ test count {
17041705 try testing.expect(count(u8, "owowowu", "owowu") == 1);
17051706}
17061707
1707/// Returns the number of needles inside the haystack
1708pub fn countScalar(comptime T: type, haystack: []const T, needle: T) usize {
1709 const n = haystack.len;
1708/// Returns the number of times `element` appears in a slice of memory.
1709pub fn countScalar(comptime T: type, list: []const T, element: T) usize {
1710 const n = list.len;
17101711 var i: usize = 0;
17111712 var found: usize = 0;
17121713
......@@ -1716,16 +1717,16 @@ pub fn countScalar(comptime T: type, haystack: []const T, needle: T) usize {
17161717 if (std.simd.suggestVectorLength(T)) |block_size| {
17171718 const Block = @Vector(block_size, T);
17181719
1719 const letter_mask: Block = @splat(needle);
1720 const letter_mask: Block = @splat(element);
17201721 while (n - i >= block_size) : (i += block_size) {
1721 const haystack_block: Block = haystack[i..][0..block_size].*;
1722 const haystack_block: Block = list[i..][0..block_size].*;
17221723 found += std.simd.countTrues(letter_mask == haystack_block);
17231724 }
17241725 }
17251726 }
17261727
1727 for (haystack[i..n]) |item| {
1728 found += @intFromBool(item == needle);
1728 for (list[i..n]) |item| {
1729 found += @intFromBool(item == element);
17291730 }
17301731
17311732 return found;
......@@ -1735,6 +1736,7 @@ test countScalar {
17351736 try testing.expectEqual(0, countScalar(u8, "", 'h'));
17361737 try testing.expectEqual(1, countScalar(u8, "h", 'h'));
17371738 try testing.expectEqual(2, countScalar(u8, "hh", 'h'));
1739 try testing.expectEqual(2, countScalar(u8, "ahhb", 'h'));
17381740 try testing.expectEqual(3, countScalar(u8, " abcabc abc", 'b'));
17391741}
17401742
......@@ -1744,6 +1746,7 @@ test countScalar {
17441746//
17451747/// See also: `containsAtLeastScalar`
17461748pub fn containsAtLeast(comptime T: type, haystack: []const T, expected_count: usize, needle: []const T) bool {
1749 if (needle.len == 1) return containsAtLeastScalar(T, haystack, expected_count, needle[0]);
17471750 assert(needle.len > 0);
17481751 if (expected_count == 0) return true;
17491752
......@@ -1774,32 +1777,52 @@ test containsAtLeast {
17741777 try testing.expect(!containsAtLeast(u8, " radar radar ", 3, "radar"));
17751778}
17761779
1777/// Returns true if the haystack contains expected_count or more needles
1778//
1779/// See also: `containsAtLeast`
1780pub fn containsAtLeastScalar(comptime T: type, haystack: []const T, expected_count: usize, needle: T) bool {
1781 if (expected_count == 0) return true;
1780/// Deprecated in favor of `containsAtLeastScalar2`.
1781pub fn containsAtLeastScalar(comptime T: type, list: []const T, minimum: usize, element: T) bool {
1782 return containsAtLeastScalar2(T, list, element, minimum);
1783}
17821784
1785/// Returns true if `element` appears at least `minimum` number of times in `list`.
1786//
1787/// Related:
1788/// * `containsAtLeast`
1789/// * `countScalar`
1790pub fn containsAtLeastScalar2(comptime T: type, list: []const T, element: T, minimum: usize) bool {
1791 const n = list.len;
1792 var i: usize = 0;
17831793 var found: usize = 0;
17841794
1785 for (haystack) |item| {
1786 if (item == needle) {
1787 found += 1;
1788 if (found == expected_count) return true;
1795 if (use_vectors_for_comparison and
1796 (@typeInfo(T) == .int or @typeInfo(T) == .float) and std.math.isPowerOfTwo(@bitSizeOf(T)))
1797 {
1798 if (std.simd.suggestVectorLength(T)) |block_size| {
1799 const Block = @Vector(block_size, T);
1800
1801 const letter_mask: Block = @splat(element);
1802 while (n - i >= block_size) : (i += block_size) {
1803 const haystack_block: Block = list[i..][0..block_size].*;
1804 found += std.simd.countTrues(letter_mask == haystack_block);
1805 if (found >= minimum) return true;
1806 }
17891807 }
17901808 }
17911809
1810 for (list[i..n]) |item| {
1811 found += @intFromBool(item == element);
1812 if (found >= minimum) return true;
1813 }
1814
17921815 return false;
17931816}
17941817
1795test containsAtLeastScalar {
1796 try testing.expect(containsAtLeastScalar(u8, "aa", 0, 'a'));
1797 try testing.expect(containsAtLeastScalar(u8, "aa", 1, 'a'));
1798 try testing.expect(containsAtLeastScalar(u8, "aa", 2, 'a'));
1799 try testing.expect(!containsAtLeastScalar(u8, "aa", 3, 'a'));
1818test containsAtLeastScalar2 {
1819 try testing.expect(containsAtLeastScalar2(u8, "aa", 'a', 0));
1820 try testing.expect(containsAtLeastScalar2(u8, "aa", 'a', 1));
1821 try testing.expect(containsAtLeastScalar2(u8, "aa", 'a', 2));
1822 try testing.expect(!containsAtLeastScalar2(u8, "aa", 'a', 3));
18001823
1801 try testing.expect(containsAtLeastScalar(u8, "adadda", 3, 'd'));
1802 try testing.expect(!containsAtLeastScalar(u8, "adadda", 4, 'd'));
1824 try testing.expect(containsAtLeastScalar2(u8, "adadda", 'd', 3));
1825 try testing.expect(!containsAtLeastScalar2(u8, "adadda", 'd', 4));
18031826}
18041827
18051828/// Reads an integer from memory with size equal to bytes.len.
lib/std/net.zig deleted-2430
......@@ -1,2430 +0,0 @@
1//! Cross-platform networking abstractions.
2
3const std = @import("std.zig");
4const builtin = @import("builtin");
5const assert = std.debug.assert;
6const net = @This();
7const mem = std.mem;
8const posix = std.posix;
9const fs = std.fs;
10const Io = std.Io;
11const native_endian = builtin.target.cpu.arch.endian();
12const native_os = builtin.os.tag;
13const windows = std.os.windows;
14const Allocator = std.mem.Allocator;
15const ArrayList = std.ArrayListUnmanaged;
16const File = std.fs.File;
17
18// Windows 10 added support for unix sockets in build 17063, redstone 4 is the
19// first release to support them.
20pub const has_unix_sockets = switch (native_os) {
21 .windows => builtin.os.version_range.windows.isAtLeast(.win10_rs4) orelse false,
22 .wasi => false,
23 else => true,
24};
25
26pub const IPParseError = error{
27 Overflow,
28 InvalidEnd,
29 InvalidCharacter,
30 Incomplete,
31};
32
33pub const IPv4ParseError = IPParseError || error{NonCanonical};
34
35pub const IPv6ParseError = IPParseError || error{InvalidIpv4Mapping};
36pub const IPv6InterfaceError = posix.SocketError || posix.IoCtl_SIOCGIFINDEX_Error || error{NameTooLong};
37pub const IPv6ResolveError = IPv6ParseError || IPv6InterfaceError;
38
39pub const Address = extern union {
40 any: posix.sockaddr,
41 in: Ip4Address,
42 in6: Ip6Address,
43 un: if (has_unix_sockets) posix.sockaddr.un else void,
44
45 /// Parse an IP address which may include a port. For IPv4, this is just written `address:port`.
46 /// For IPv6, RFC 3986 defines this as an "IP literal", and the port is differentiated from the
47 /// address by surrounding the address part in brackets '[addr]:port'. Even if the port is not
48 /// given, the brackets are mandatory.
49 pub fn parseIpAndPort(str: []const u8) error{ InvalidAddress, InvalidPort }!Address {
50 if (str.len == 0) return error.InvalidAddress;
51 if (str[0] == '[') {
52 const addr_end = std.mem.indexOfScalar(u8, str, ']') orelse
53 return error.InvalidAddress;
54 const addr_str = str[1..addr_end];
55 const port: u16 = p: {
56 if (addr_end == str.len - 1) break :p 0;
57 if (str[addr_end + 1] != ':') return error.InvalidAddress;
58 break :p parsePort(str[addr_end + 2 ..]) orelse return error.InvalidPort;
59 };
60 return parseIp6(addr_str, port) catch error.InvalidAddress;
61 } else {
62 if (std.mem.indexOfScalar(u8, str, ':')) |idx| {
63 // hold off on `error.InvalidPort` since `error.InvalidAddress` might make more sense
64 const port: ?u16 = parsePort(str[idx + 1 ..]);
65 const addr = parseIp4(str[0..idx], port orelse 0) catch return error.InvalidAddress;
66 if (port == null) return error.InvalidPort;
67 return addr;
68 } else {
69 return parseIp4(str, 0) catch error.InvalidAddress;
70 }
71 }
72 }
73 fn parsePort(str: []const u8) ?u16 {
74 var p: u16 = 0;
75 for (str) |c| switch (c) {
76 '0'...'9' => {
77 const shifted = std.math.mul(u16, p, 10) catch return null;
78 p = std.math.add(u16, shifted, c - '0') catch return null;
79 },
80 else => return null,
81 };
82 if (p == 0) return null;
83 return p;
84 }
85
86 /// Parse the given IP address string into an Address value.
87 /// It is recommended to use `resolveIp` instead, to handle
88 /// IPv6 link-local unix addresses.
89 pub fn parseIp(name: []const u8, port: u16) !Address {
90 if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {
91 error.Overflow,
92 error.InvalidEnd,
93 error.InvalidCharacter,
94 error.Incomplete,
95 error.NonCanonical,
96 => {},
97 }
98
99 if (parseIp6(name, port)) |ip6| return ip6 else |err| switch (err) {
100 error.Overflow,
101 error.InvalidEnd,
102 error.InvalidCharacter,
103 error.Incomplete,
104 error.InvalidIpv4Mapping,
105 => {},
106 }
107
108 return error.InvalidIPAddressFormat;
109 }
110
111 pub fn resolveIp(name: []const u8, port: u16) !Address {
112 if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {
113 error.Overflow,
114 error.InvalidEnd,
115 error.InvalidCharacter,
116 error.Incomplete,
117 error.NonCanonical,
118 => {},
119 }
120
121 if (resolveIp6(name, port)) |ip6| return ip6 else |err| switch (err) {
122 error.Overflow,
123 error.InvalidEnd,
124 error.InvalidCharacter,
125 error.Incomplete,
126 error.InvalidIpv4Mapping,
127 => {},
128 else => return err,
129 }
130
131 return error.InvalidIPAddressFormat;
132 }
133
134 pub fn parseExpectingFamily(name: []const u8, family: posix.sa_family_t, port: u16) !Address {
135 switch (family) {
136 posix.AF.INET => return parseIp4(name, port),
137 posix.AF.INET6 => return parseIp6(name, port),
138 posix.AF.UNSPEC => return parseIp(name, port),
139 else => unreachable,
140 }
141 }
142
143 pub fn parseIp6(buf: []const u8, port: u16) IPv6ParseError!Address {
144 return .{ .in6 = try Ip6Address.parse(buf, port) };
145 }
146
147 pub fn resolveIp6(buf: []const u8, port: u16) IPv6ResolveError!Address {
148 return .{ .in6 = try Ip6Address.resolve(buf, port) };
149 }
150
151 pub fn parseIp4(buf: []const u8, port: u16) IPv4ParseError!Address {
152 return .{ .in = try Ip4Address.parse(buf, port) };
153 }
154
155 pub fn initIp4(addr: [4]u8, port: u16) Address {
156 return .{ .in = Ip4Address.init(addr, port) };
157 }
158
159 pub fn initIp6(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Address {
160 return .{ .in6 = Ip6Address.init(addr, port, flowinfo, scope_id) };
161 }
162
163 pub fn initUnix(path: []const u8) !Address {
164 var sock_addr = posix.sockaddr.un{
165 .family = posix.AF.UNIX,
166 .path = undefined,
167 };
168
169 // Add 1 to ensure a terminating 0 is present in the path array for maximum portability.
170 if (path.len + 1 > sock_addr.path.len) return error.NameTooLong;
171
172 @memset(&sock_addr.path, 0);
173 @memcpy(sock_addr.path[0..path.len], path);
174
175 return .{ .un = sock_addr };
176 }
177
178 /// Returns the port in native endian.
179 /// Asserts that the address is ip4 or ip6.
180 pub fn getPort(self: Address) u16 {
181 return switch (self.any.family) {
182 posix.AF.INET => self.in.getPort(),
183 posix.AF.INET6 => self.in6.getPort(),
184 else => unreachable,
185 };
186 }
187
188 /// `port` is native-endian.
189 /// Asserts that the address is ip4 or ip6.
190 pub fn setPort(self: *Address, port: u16) void {
191 switch (self.any.family) {
192 posix.AF.INET => self.in.setPort(port),
193 posix.AF.INET6 => self.in6.setPort(port),
194 else => unreachable,
195 }
196 }
197
198 /// Asserts that `addr` is an IP address.
199 /// This function will read past the end of the pointer, with a size depending
200 /// on the address family.
201 pub fn initPosix(addr: *align(4) const posix.sockaddr) Address {
202 switch (addr.family) {
203 posix.AF.INET => return Address{ .in = Ip4Address{ .sa = @as(*const posix.sockaddr.in, @ptrCast(addr)).* } },
204 posix.AF.INET6 => return Address{ .in6 = Ip6Address{ .sa = @as(*const posix.sockaddr.in6, @ptrCast(addr)).* } },
205 else => unreachable,
206 }
207 }
208
209 pub fn format(self: Address, w: *Io.Writer) Io.Writer.Error!void {
210 switch (self.any.family) {
211 posix.AF.INET => try self.in.format(w),
212 posix.AF.INET6 => try self.in6.format(w),
213 posix.AF.UNIX => {
214 if (!has_unix_sockets) unreachable;
215 try w.writeAll(std.mem.sliceTo(&self.un.path, 0));
216 },
217 else => unreachable,
218 }
219 }
220
221 pub fn eql(a: Address, b: Address) bool {
222 const a_bytes = @as([*]const u8, @ptrCast(&a.any))[0..a.getOsSockLen()];
223 const b_bytes = @as([*]const u8, @ptrCast(&b.any))[0..b.getOsSockLen()];
224 return mem.eql(u8, a_bytes, b_bytes);
225 }
226
227 pub fn getOsSockLen(self: Address) posix.socklen_t {
228 switch (self.any.family) {
229 posix.AF.INET => return self.in.getOsSockLen(),
230 posix.AF.INET6 => return self.in6.getOsSockLen(),
231 posix.AF.UNIX => {
232 if (!has_unix_sockets) {
233 unreachable;
234 }
235
236 // Using the full length of the structure here is more portable than returning
237 // the number of bytes actually used by the currently stored path.
238 // This also is correct regardless if we are passing a socket address to the kernel
239 // (e.g. in bind, connect, sendto) since we ensure the path is 0 terminated in
240 // initUnix() or if we are receiving a socket address from the kernel and must
241 // provide the full buffer size (e.g. getsockname, getpeername, recvfrom, accept).
242 //
243 // To access the path, std.mem.sliceTo(&address.un.path, 0) should be used.
244 return @as(posix.socklen_t, @intCast(@sizeOf(posix.sockaddr.un)));
245 },
246
247 else => unreachable,
248 }
249 }
250
251 pub const ListenError = posix.SocketError || posix.BindError || posix.ListenError ||
252 posix.SetSockOptError || posix.GetSockNameError;
253
254 pub const ListenOptions = struct {
255 /// How many connections the kernel will accept on the application's behalf.
256 /// If more than this many connections pool in the kernel, clients will start
257 /// seeing "Connection refused".
258 kernel_backlog: u31 = 128,
259 /// Sets SO_REUSEADDR and SO_REUSEPORT on POSIX.
260 /// Sets SO_REUSEADDR on Windows, which is roughly equivalent.
261 reuse_address: bool = false,
262 /// Sets O_NONBLOCK.
263 force_nonblocking: bool = false,
264 };
265
266 /// The returned `Server` has an open `stream`.
267 pub fn listen(address: Address, options: ListenOptions) ListenError!Server {
268 const nonblock: u32 = if (options.force_nonblocking) posix.SOCK.NONBLOCK else 0;
269 const sock_flags = posix.SOCK.STREAM | posix.SOCK.CLOEXEC | nonblock;
270 const proto: u32 = if (address.any.family == posix.AF.UNIX) 0 else posix.IPPROTO.TCP;
271
272 const sockfd = try posix.socket(address.any.family, sock_flags, proto);
273 var s: Server = .{
274 .listen_address = undefined,
275 .stream = .{ .handle = sockfd },
276 };
277 errdefer s.stream.close();
278
279 if (options.reuse_address) {
280 try posix.setsockopt(
281 sockfd,
282 posix.SOL.SOCKET,
283 posix.SO.REUSEADDR,
284 &mem.toBytes(@as(c_int, 1)),
285 );
286 if (@hasDecl(posix.SO, "REUSEPORT") and address.any.family != posix.AF.UNIX) {
287 try posix.setsockopt(
288 sockfd,
289 posix.SOL.SOCKET,
290 posix.SO.REUSEPORT,
291 &mem.toBytes(@as(c_int, 1)),
292 );
293 }
294 }
295
296 var socklen = address.getOsSockLen();
297 try posix.bind(sockfd, &address.any, socklen);
298 try posix.listen(sockfd, options.kernel_backlog);
299 try posix.getsockname(sockfd, &s.listen_address.any, &socklen);
300 return s;
301 }
302};
303
304pub const Ip4Address = extern struct {
305 sa: posix.sockaddr.in,
306
307 pub fn parse(buf: []const u8, port: u16) IPv4ParseError!Ip4Address {
308 var result: Ip4Address = .{
309 .sa = .{
310 .port = mem.nativeToBig(u16, port),
311 .addr = undefined,
312 },
313 };
314 const out_ptr = mem.asBytes(&result.sa.addr);
315
316 var x: u8 = 0;
317 var index: u8 = 0;
318 var saw_any_digits = false;
319 var has_zero_prefix = false;
320 for (buf) |c| {
321 if (c == '.') {
322 if (!saw_any_digits) {
323 return error.InvalidCharacter;
324 }
325 if (index == 3) {
326 return error.InvalidEnd;
327 }
328 out_ptr[index] = x;
329 index += 1;
330 x = 0;
331 saw_any_digits = false;
332 has_zero_prefix = false;
333 } else if (c >= '0' and c <= '9') {
334 if (c == '0' and !saw_any_digits) {
335 has_zero_prefix = true;
336 } else if (has_zero_prefix) {
337 return error.NonCanonical;
338 }
339 saw_any_digits = true;
340 x = try std.math.mul(u8, x, 10);
341 x = try std.math.add(u8, x, c - '0');
342 } else {
343 return error.InvalidCharacter;
344 }
345 }
346 if (index == 3 and saw_any_digits) {
347 out_ptr[index] = x;
348 return result;
349 }
350
351 return error.Incomplete;
352 }
353
354 pub fn resolveIp(name: []const u8, port: u16) !Ip4Address {
355 if (parse(name, port)) |ip4| return ip4 else |err| switch (err) {
356 error.Overflow,
357 error.InvalidEnd,
358 error.InvalidCharacter,
359 error.Incomplete,
360 error.NonCanonical,
361 => {},
362 }
363 return error.InvalidIPAddressFormat;
364 }
365
366 pub fn init(addr: [4]u8, port: u16) Ip4Address {
367 return Ip4Address{
368 .sa = posix.sockaddr.in{
369 .port = mem.nativeToBig(u16, port),
370 .addr = @as(*align(1) const u32, @ptrCast(&addr)).*,
371 },
372 };
373 }
374
375 /// Returns the port in native endian.
376 /// Asserts that the address is ip4 or ip6.
377 pub fn getPort(self: Ip4Address) u16 {
378 return mem.bigToNative(u16, self.sa.port);
379 }
380
381 /// `port` is native-endian.
382 /// Asserts that the address is ip4 or ip6.
383 pub fn setPort(self: *Ip4Address, port: u16) void {
384 self.sa.port = mem.nativeToBig(u16, port);
385 }
386
387 pub fn format(self: Ip4Address, w: *Io.Writer) Io.Writer.Error!void {
388 const bytes: *const [4]u8 = @ptrCast(&self.sa.addr);
389 try w.print("{d}.{d}.{d}.{d}:{d}", .{ bytes[0], bytes[1], bytes[2], bytes[3], self.getPort() });
390 }
391
392 pub fn getOsSockLen(self: Ip4Address) posix.socklen_t {
393 _ = self;
394 return @sizeOf(posix.sockaddr.in);
395 }
396};
397
398pub const Ip6Address = extern struct {
399 sa: posix.sockaddr.in6,
400
401 /// Parse a given IPv6 address string into an Address.
402 /// Assumes the Scope ID of the address is fully numeric.
403 /// For non-numeric addresses, see `resolveIp6`.
404 pub fn parse(buf: []const u8, port: u16) IPv6ParseError!Ip6Address {
405 var result = Ip6Address{
406 .sa = posix.sockaddr.in6{
407 .scope_id = 0,
408 .port = mem.nativeToBig(u16, port),
409 .flowinfo = 0,
410 .addr = undefined,
411 },
412 };
413 var ip_slice: *[16]u8 = result.sa.addr[0..];
414
415 var tail: [16]u8 = undefined;
416
417 var x: u16 = 0;
418 var saw_any_digits = false;
419 var index: u8 = 0;
420 var scope_id = false;
421 var abbrv = false;
422 for (buf, 0..) |c, i| {
423 if (scope_id) {
424 if (c >= '0' and c <= '9') {
425 const digit = c - '0';
426 {
427 const ov = @mulWithOverflow(result.sa.scope_id, 10);
428 if (ov[1] != 0) return error.Overflow;
429 result.sa.scope_id = ov[0];
430 }
431 {
432 const ov = @addWithOverflow(result.sa.scope_id, digit);
433 if (ov[1] != 0) return error.Overflow;
434 result.sa.scope_id = ov[0];
435 }
436 } else {
437 return error.InvalidCharacter;
438 }
439 } else if (c == ':') {
440 if (!saw_any_digits) {
441 if (abbrv) return error.InvalidCharacter; // ':::'
442 if (i != 0) abbrv = true;
443 @memset(ip_slice[index..], 0);
444 ip_slice = tail[0..];
445 index = 0;
446 continue;
447 }
448 if (index == 14) {
449 return error.InvalidEnd;
450 }
451 ip_slice[index] = @as(u8, @truncate(x >> 8));
452 index += 1;
453 ip_slice[index] = @as(u8, @truncate(x));
454 index += 1;
455
456 x = 0;
457 saw_any_digits = false;
458 } else if (c == '%') {
459 if (!saw_any_digits) {
460 return error.InvalidCharacter;
461 }
462 scope_id = true;
463 saw_any_digits = false;
464 } else if (c == '.') {
465 if (!abbrv or ip_slice[0] != 0xff or ip_slice[1] != 0xff) {
466 // must start with '::ffff:'
467 return error.InvalidIpv4Mapping;
468 }
469 const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1;
470 const addr = (Ip4Address.parse(buf[start_index..], 0) catch {
471 return error.InvalidIpv4Mapping;
472 }).sa.addr;
473 ip_slice = result.sa.addr[0..];
474 ip_slice[10] = 0xff;
475 ip_slice[11] = 0xff;
476
477 const ptr = mem.sliceAsBytes(@as(*const [1]u32, &addr)[0..]);
478
479 ip_slice[12] = ptr[0];
480 ip_slice[13] = ptr[1];
481 ip_slice[14] = ptr[2];
482 ip_slice[15] = ptr[3];
483 return result;
484 } else {
485 const digit = try std.fmt.charToDigit(c, 16);
486 {
487 const ov = @mulWithOverflow(x, 16);
488 if (ov[1] != 0) return error.Overflow;
489 x = ov[0];
490 }
491 {
492 const ov = @addWithOverflow(x, digit);
493 if (ov[1] != 0) return error.Overflow;
494 x = ov[0];
495 }
496 saw_any_digits = true;
497 }
498 }
499
500 if (!saw_any_digits and !abbrv) {
501 return error.Incomplete;
502 }
503 if (!abbrv and index < 14) {
504 return error.Incomplete;
505 }
506
507 if (index == 14) {
508 ip_slice[14] = @as(u8, @truncate(x >> 8));
509 ip_slice[15] = @as(u8, @truncate(x));
510 return result;
511 } else {
512 ip_slice[index] = @as(u8, @truncate(x >> 8));
513 index += 1;
514 ip_slice[index] = @as(u8, @truncate(x));
515 index += 1;
516 @memcpy(result.sa.addr[16 - index ..][0..index], ip_slice[0..index]);
517 return result;
518 }
519 }
520
521 pub fn resolve(buf: []const u8, port: u16) IPv6ResolveError!Ip6Address {
522 // TODO: Unify the implementations of resolveIp6 and parseIp6.
523 var result = Ip6Address{
524 .sa = posix.sockaddr.in6{
525 .scope_id = 0,
526 .port = mem.nativeToBig(u16, port),
527 .flowinfo = 0,
528 .addr = undefined,
529 },
530 };
531 var ip_slice: *[16]u8 = result.sa.addr[0..];
532
533 var tail: [16]u8 = undefined;
534
535 var x: u16 = 0;
536 var saw_any_digits = false;
537 var index: u8 = 0;
538 var abbrv = false;
539
540 var scope_id = false;
541 var scope_id_value: [posix.IFNAMESIZE - 1]u8 = undefined;
542 var scope_id_index: usize = 0;
543
544 for (buf, 0..) |c, i| {
545 if (scope_id) {
546 // Handling of percent-encoding should be for an URI library.
547 if ((c >= '0' and c <= '9') or
548 (c >= 'A' and c <= 'Z') or
549 (c >= 'a' and c <= 'z') or
550 (c == '-') or (c == '.') or (c == '_') or (c == '~'))
551 {
552 if (scope_id_index >= scope_id_value.len) {
553 return error.Overflow;
554 }
555
556 scope_id_value[scope_id_index] = c;
557 scope_id_index += 1;
558 } else {
559 return error.InvalidCharacter;
560 }
561 } else if (c == ':') {
562 if (!saw_any_digits) {
563 if (abbrv) return error.InvalidCharacter; // ':::'
564 if (i != 0) abbrv = true;
565 @memset(ip_slice[index..], 0);
566 ip_slice = tail[0..];
567 index = 0;
568 continue;
569 }
570 if (index == 14) {
571 return error.InvalidEnd;
572 }
573 ip_slice[index] = @as(u8, @truncate(x >> 8));
574 index += 1;
575 ip_slice[index] = @as(u8, @truncate(x));
576 index += 1;
577
578 x = 0;
579 saw_any_digits = false;
580 } else if (c == '%') {
581 if (!saw_any_digits) {
582 return error.InvalidCharacter;
583 }
584 scope_id = true;
585 saw_any_digits = false;
586 } else if (c == '.') {
587 if (!abbrv or ip_slice[0] != 0xff or ip_slice[1] != 0xff) {
588 // must start with '::ffff:'
589 return error.InvalidIpv4Mapping;
590 }
591 const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1;
592 const addr = (Ip4Address.parse(buf[start_index..], 0) catch {
593 return error.InvalidIpv4Mapping;
594 }).sa.addr;
595 ip_slice = result.sa.addr[0..];
596 ip_slice[10] = 0xff;
597 ip_slice[11] = 0xff;
598
599 const ptr = mem.sliceAsBytes(@as(*const [1]u32, &addr)[0..]);
600
601 ip_slice[12] = ptr[0];
602 ip_slice[13] = ptr[1];
603 ip_slice[14] = ptr[2];
604 ip_slice[15] = ptr[3];
605 return result;
606 } else {
607 const digit = try std.fmt.charToDigit(c, 16);
608 {
609 const ov = @mulWithOverflow(x, 16);
610 if (ov[1] != 0) return error.Overflow;
611 x = ov[0];
612 }
613 {
614 const ov = @addWithOverflow(x, digit);
615 if (ov[1] != 0) return error.Overflow;
616 x = ov[0];
617 }
618 saw_any_digits = true;
619 }
620 }
621
622 if (!saw_any_digits and !abbrv) {
623 return error.Incomplete;
624 }
625
626 if (scope_id and scope_id_index == 0) {
627 return error.Incomplete;
628 }
629
630 var resolved_scope_id: u32 = 0;
631 if (scope_id_index > 0) {
632 const scope_id_str = scope_id_value[0..scope_id_index];
633 resolved_scope_id = std.fmt.parseInt(u32, scope_id_str, 10) catch |err| blk: {
634 if (err != error.InvalidCharacter) return err;
635 break :blk try if_nametoindex(scope_id_str);
636 };
637 }
638
639 result.sa.scope_id = resolved_scope_id;
640
641 if (index == 14) {
642 ip_slice[14] = @as(u8, @truncate(x >> 8));
643 ip_slice[15] = @as(u8, @truncate(x));
644 return result;
645 } else {
646 ip_slice[index] = @as(u8, @truncate(x >> 8));
647 index += 1;
648 ip_slice[index] = @as(u8, @truncate(x));
649 index += 1;
650 @memcpy(result.sa.addr[16 - index ..][0..index], ip_slice[0..index]);
651 return result;
652 }
653 }
654
655 pub fn init(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Ip6Address {
656 return Ip6Address{
657 .sa = posix.sockaddr.in6{
658 .addr = addr,
659 .port = mem.nativeToBig(u16, port),
660 .flowinfo = flowinfo,
661 .scope_id = scope_id,
662 },
663 };
664 }
665
666 /// Returns the port in native endian.
667 /// Asserts that the address is ip4 or ip6.
668 pub fn getPort(self: Ip6Address) u16 {
669 return mem.bigToNative(u16, self.sa.port);
670 }
671
672 /// `port` is native-endian.
673 /// Asserts that the address is ip4 or ip6.
674 pub fn setPort(self: *Ip6Address, port: u16) void {
675 self.sa.port = mem.nativeToBig(u16, port);
676 }
677
678 pub fn format(self: Ip6Address, w: *Io.Writer) Io.Writer.Error!void {
679 const port = mem.bigToNative(u16, self.sa.port);
680 if (mem.eql(u8, self.sa.addr[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) {
681 try w.print("[::ffff:{d}.{d}.{d}.{d}]:{d}", .{
682 self.sa.addr[12],
683 self.sa.addr[13],
684 self.sa.addr[14],
685 self.sa.addr[15],
686 port,
687 });
688 return;
689 }
690 const big_endian_parts = @as(*align(1) const [8]u16, @ptrCast(&self.sa.addr));
691 const native_endian_parts = switch (native_endian) {
692 .big => big_endian_parts.*,
693 .little => blk: {
694 var buf: [8]u16 = undefined;
695 for (big_endian_parts, 0..) |part, i| {
696 buf[i] = mem.bigToNative(u16, part);
697 }
698 break :blk buf;
699 },
700 };
701
702 // Find the longest zero run
703 var longest_start: usize = 8;
704 var longest_len: usize = 0;
705 var current_start: usize = 0;
706 var current_len: usize = 0;
707
708 for (native_endian_parts, 0..) |part, i| {
709 if (part == 0) {
710 if (current_len == 0) {
711 current_start = i;
712 }
713 current_len += 1;
714 if (current_len > longest_len) {
715 longest_start = current_start;
716 longest_len = current_len;
717 }
718 } else {
719 current_len = 0;
720 }
721 }
722
723 // Only compress if the longest zero run is 2 or more
724 if (longest_len < 2) {
725 longest_start = 8;
726 longest_len = 0;
727 }
728
729 try w.writeAll("[");
730 var i: usize = 0;
731 var abbrv = false;
732 while (i < native_endian_parts.len) : (i += 1) {
733 if (i == longest_start) {
734 // Emit "::" for the longest zero run
735 if (!abbrv) {
736 try w.writeAll(if (i == 0) "::" else ":");
737 abbrv = true;
738 }
739 i += longest_len - 1; // Skip the compressed range
740 continue;
741 }
742 if (abbrv) {
743 abbrv = false;
744 }
745 try w.print("{x}", .{native_endian_parts[i]});
746 if (i != native_endian_parts.len - 1) {
747 try w.writeAll(":");
748 }
749 }
750 if (self.sa.scope_id != 0) {
751 try w.print("%{}", .{self.sa.scope_id});
752 }
753 try w.print("]:{}", .{port});
754 }
755
756 pub fn getOsSockLen(self: Ip6Address) posix.socklen_t {
757 _ = self;
758 return @sizeOf(posix.sockaddr.in6);
759 }
760};
761
762pub fn connectUnixSocket(path: []const u8) !Stream {
763 const opt_non_block = 0;
764 const sockfd = try posix.socket(
765 posix.AF.UNIX,
766 posix.SOCK.STREAM | posix.SOCK.CLOEXEC | opt_non_block,
767 0,
768 );
769 errdefer Stream.close(.{ .handle = sockfd });
770
771 var addr = try Address.initUnix(path);
772 try posix.connect(sockfd, &addr.any, addr.getOsSockLen());
773
774 return .{ .handle = sockfd };
775}
776
777fn if_nametoindex(name: []const u8) IPv6InterfaceError!u32 {
778 if (native_os == .linux) {
779 var ifr: posix.ifreq = undefined;
780 const sockfd = try posix.socket(posix.AF.UNIX, posix.SOCK.DGRAM | posix.SOCK.CLOEXEC, 0);
781 defer Stream.close(.{ .handle = sockfd });
782
783 @memcpy(ifr.ifrn.name[0..name.len], name);
784 ifr.ifrn.name[name.len] = 0;
785
786 // TODO investigate if this needs to be integrated with evented I/O.
787 try posix.ioctl_SIOCGIFINDEX(sockfd, &ifr);
788
789 return @bitCast(ifr.ifru.ivalue);
790 }
791
792 if (native_os.isDarwin()) {
793 if (name.len >= posix.IFNAMESIZE)
794 return error.NameTooLong;
795
796 var if_name: [posix.IFNAMESIZE:0]u8 = undefined;
797 @memcpy(if_name[0..name.len], name);
798 if_name[name.len] = 0;
799 const if_slice = if_name[0..name.len :0];
800 const index = std.c.if_nametoindex(if_slice);
801 if (index == 0)
802 return error.InterfaceNotFound;
803 return @as(u32, @bitCast(index));
804 }
805
806 if (native_os == .windows) {
807 if (name.len >= posix.IFNAMESIZE)
808 return error.NameTooLong;
809
810 var interface_name: [posix.IFNAMESIZE:0]u8 = undefined;
811 @memcpy(interface_name[0..name.len], name);
812 interface_name[name.len] = 0;
813 const index = std.os.windows.ws2_32.if_nametoindex(@as([*:0]const u8, &interface_name));
814 if (index == 0)
815 return error.InterfaceNotFound;
816 return index;
817 }
818
819 @compileError("std.net.if_nametoindex unimplemented for this OS");
820}
821
822pub const AddressList = struct {
823 arena: std.heap.ArenaAllocator,
824 addrs: []Address,
825 canon_name: ?[]u8,
826
827 pub fn deinit(self: *AddressList) void {
828 // Here we copy the arena allocator into stack memory, because
829 // otherwise it would destroy itself while it was still working.
830 var arena = self.arena;
831 arena.deinit();
832 // self is destroyed
833 }
834};
835
836pub const TcpConnectToHostError = GetAddressListError || TcpConnectToAddressError;
837
838/// All memory allocated with `allocator` will be freed before this function returns.
839pub fn tcpConnectToHost(allocator: Allocator, name: []const u8, port: u16) TcpConnectToHostError!Stream {
840 const list = try getAddressList(allocator, name, port);
841 defer list.deinit();
842
843 if (list.addrs.len == 0) return error.UnknownHostName;
844
845 for (list.addrs) |addr| {
846 return tcpConnectToAddress(addr) catch |err| switch (err) {
847 error.ConnectionRefused => {
848 continue;
849 },
850 else => return err,
851 };
852 }
853 return posix.ConnectError.ConnectionRefused;
854}
855
856pub const TcpConnectToAddressError = posix.SocketError || posix.ConnectError;
857
858pub fn tcpConnectToAddress(address: Address) TcpConnectToAddressError!Stream {
859 const nonblock = 0;
860 const sock_flags = posix.SOCK.STREAM | nonblock |
861 (if (native_os == .windows) 0 else posix.SOCK.CLOEXEC);
862 const sockfd = try posix.socket(address.any.family, sock_flags, posix.IPPROTO.TCP);
863 errdefer Stream.close(.{ .handle = sockfd });
864
865 try posix.connect(sockfd, &address.any, address.getOsSockLen());
866
867 return Stream{ .handle = sockfd };
868}
869
870// TODO: Instead of having a massive error set, make the error set have categories, and then
871// store the sub-error as a diagnostic value.
872const GetAddressListError = Allocator.Error || File.OpenError || File.ReadError || posix.SocketError || posix.BindError || posix.SetSockOptError || error{
873 TemporaryNameServerFailure,
874 NameServerFailure,
875 AddressFamilyNotSupported,
876 UnknownHostName,
877 ServiceUnavailable,
878 Unexpected,
879
880 HostLacksNetworkAddresses,
881
882 InvalidCharacter,
883 InvalidEnd,
884 NonCanonical,
885 Overflow,
886 Incomplete,
887 InvalidIpv4Mapping,
888 InvalidIPAddressFormat,
889
890 InterfaceNotFound,
891 FileSystem,
892 ResolveConfParseFailed,
893};
894
895/// Call `AddressList.deinit` on the result.
896pub fn getAddressList(gpa: Allocator, name: []const u8, port: u16) GetAddressListError!*AddressList {
897 const result = blk: {
898 var arena = std.heap.ArenaAllocator.init(gpa);
899 errdefer arena.deinit();
900
901 const result = try arena.allocator().create(AddressList);
902 result.* = AddressList{
903 .arena = arena,
904 .addrs = undefined,
905 .canon_name = null,
906 };
907 break :blk result;
908 };
909 const arena = result.arena.allocator();
910 errdefer result.deinit();
911
912 if (native_os == .windows) {
913 const name_c = try gpa.dupeZ(u8, name);
914 defer gpa.free(name_c);
915
916 const port_c = try std.fmt.allocPrintSentinel(gpa, "{d}", .{port}, 0);
917 defer gpa.free(port_c);
918
919 const ws2_32 = windows.ws2_32;
920 const hints: posix.addrinfo = .{
921 .flags = .{ .NUMERICSERV = true },
922 .family = posix.AF.UNSPEC,
923 .socktype = posix.SOCK.STREAM,
924 .protocol = posix.IPPROTO.TCP,
925 .canonname = null,
926 .addr = null,
927 .addrlen = 0,
928 .next = null,
929 };
930 var res: ?*posix.addrinfo = null;
931 var first = true;
932 while (true) {
933 const rc = ws2_32.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res);
934 switch (@as(windows.ws2_32.WinsockError, @enumFromInt(@as(u16, @intCast(rc))))) {
935 @as(windows.ws2_32.WinsockError, @enumFromInt(0)) => break,
936 .WSATRY_AGAIN => return error.TemporaryNameServerFailure,
937 .WSANO_RECOVERY => return error.NameServerFailure,
938 .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported,
939 .WSA_NOT_ENOUGH_MEMORY => return error.OutOfMemory,
940 .WSAHOST_NOT_FOUND => return error.UnknownHostName,
941 .WSATYPE_NOT_FOUND => return error.ServiceUnavailable,
942 .WSAEINVAL => unreachable,
943 .WSAESOCKTNOSUPPORT => unreachable,
944 .WSANOTINITIALISED => {
945 if (!first) return error.Unexpected;
946 first = false;
947 try windows.callWSAStartup();
948 continue;
949 },
950 else => |err| return windows.unexpectedWSAError(err),
951 }
952 }
953 defer ws2_32.freeaddrinfo(res);
954
955 const addr_count = blk: {
956 var count: usize = 0;
957 var it = res;
958 while (it) |info| : (it = info.next) {
959 if (info.addr != null) {
960 count += 1;
961 }
962 }
963 break :blk count;
964 };
965 result.addrs = try arena.alloc(Address, addr_count);
966
967 var it = res;
968 var i: usize = 0;
969 while (it) |info| : (it = info.next) {
970 const addr = info.addr orelse continue;
971 result.addrs[i] = Address.initPosix(@alignCast(addr));
972
973 if (info.canonname) |n| {
974 if (result.canon_name == null) {
975 result.canon_name = try arena.dupe(u8, mem.sliceTo(n, 0));
976 }
977 }
978 i += 1;
979 }
980
981 return result;
982 }
983
984 if (builtin.link_libc) {
985 const name_c = try gpa.dupeZ(u8, name);
986 defer gpa.free(name_c);
987
988 const port_c = try std.fmt.allocPrintSentinel(gpa, "{d}", .{port}, 0);
989 defer gpa.free(port_c);
990
991 const hints: posix.addrinfo = .{
992 .flags = .{ .NUMERICSERV = true },
993 .family = posix.AF.UNSPEC,
994 .socktype = posix.SOCK.STREAM,
995 .protocol = posix.IPPROTO.TCP,
996 .canonname = null,
997 .addr = null,
998 .addrlen = 0,
999 .next = null,
1000 };
1001 var res: ?*posix.addrinfo = null;
1002 switch (posix.system.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res)) {
1003 @as(posix.system.EAI, @enumFromInt(0)) => {},
1004 .ADDRFAMILY => return error.HostLacksNetworkAddresses,
1005 .AGAIN => return error.TemporaryNameServerFailure,
1006 .BADFLAGS => unreachable, // Invalid hints
1007 .FAIL => return error.NameServerFailure,
1008 .FAMILY => return error.AddressFamilyNotSupported,
1009 .MEMORY => return error.OutOfMemory,
1010 .NODATA => return error.HostLacksNetworkAddresses,
1011 .NONAME => return error.UnknownHostName,
1012 .SERVICE => return error.ServiceUnavailable,
1013 .SOCKTYPE => unreachable, // Invalid socket type requested in hints
1014 .SYSTEM => switch (posix.errno(-1)) {
1015 else => |e| return posix.unexpectedErrno(e),
1016 },
1017 else => unreachable,
1018 }
1019 defer if (res) |some| posix.system.freeaddrinfo(some);
1020
1021 const addr_count = blk: {
1022 var count: usize = 0;
1023 var it = res;
1024 while (it) |info| : (it = info.next) {
1025 if (info.addr != null) {
1026 count += 1;
1027 }
1028 }
1029 break :blk count;
1030 };
1031 result.addrs = try arena.alloc(Address, addr_count);
1032
1033 var it = res;
1034 var i: usize = 0;
1035 while (it) |info| : (it = info.next) {
1036 const addr = info.addr orelse continue;
1037 result.addrs[i] = Address.initPosix(@alignCast(addr));
1038
1039 if (info.canonname) |n| {
1040 if (result.canon_name == null) {
1041 result.canon_name = try arena.dupe(u8, mem.sliceTo(n, 0));
1042 }
1043 }
1044 i += 1;
1045 }
1046
1047 return result;
1048 }
1049
1050 if (native_os == .linux) {
1051 const family = posix.AF.UNSPEC;
1052 var lookup_addrs: ArrayList(LookupAddr) = .empty;
1053 defer lookup_addrs.deinit(gpa);
1054
1055 var canon: ArrayList(u8) = .empty;
1056 defer canon.deinit(gpa);
1057
1058 try linuxLookupName(gpa, &lookup_addrs, &canon, name, family, .{ .NUMERICSERV = true }, port);
1059
1060 result.addrs = try arena.alloc(Address, lookup_addrs.items.len);
1061 if (canon.items.len != 0) {
1062 result.canon_name = try arena.dupe(u8, canon.items);
1063 }
1064
1065 for (lookup_addrs.items, 0..) |lookup_addr, i| {
1066 result.addrs[i] = lookup_addr.addr;
1067 assert(result.addrs[i].getPort() == port);
1068 }
1069
1070 return result;
1071 }
1072 @compileError("std.net.getAddressList unimplemented for this OS");
1073}
1074
1075const LookupAddr = struct {
1076 addr: Address,
1077 sortkey: i32 = 0,
1078};
1079
1080const DAS_USABLE = 0x40000000;
1081const DAS_MATCHINGSCOPE = 0x20000000;
1082const DAS_MATCHINGLABEL = 0x10000000;
1083const DAS_PREC_SHIFT = 20;
1084const DAS_SCOPE_SHIFT = 16;
1085const DAS_PREFIX_SHIFT = 8;
1086const DAS_ORDER_SHIFT = 0;
1087
1088fn linuxLookupName(
1089 gpa: Allocator,
1090 addrs: *ArrayList(LookupAddr),
1091 canon: *ArrayList(u8),
1092 opt_name: ?[]const u8,
1093 family: posix.sa_family_t,
1094 flags: posix.AI,
1095 port: u16,
1096) !void {
1097 if (opt_name) |name| {
1098 // reject empty name and check len so it fits into temp bufs
1099 canon.items.len = 0;
1100 try canon.appendSlice(gpa, name);
1101 if (Address.parseExpectingFamily(name, family, port)) |addr| {
1102 try addrs.append(gpa, .{ .addr = addr });
1103 } else |name_err| if (flags.NUMERICHOST) {
1104 return name_err;
1105 } else {
1106 try linuxLookupNameFromHosts(gpa, addrs, canon, name, family, port);
1107 if (addrs.items.len == 0) {
1108 // RFC 6761 Section 6.3.3
1109 // Name resolution APIs and libraries SHOULD recognize localhost
1110 // names as special and SHOULD always return the IP loopback address
1111 // for address queries and negative responses for all other query
1112 // types.
1113
1114 // Check for equal to "localhost(.)" or ends in ".localhost(.)"
1115 const localhost = if (name[name.len - 1] == '.') "localhost." else "localhost";
1116 if (mem.endsWith(u8, name, localhost) and (name.len == localhost.len or name[name.len - localhost.len] == '.')) {
1117 try addrs.append(gpa, .{ .addr = .{ .in = Ip4Address.parse("127.0.0.1", port) catch unreachable } });
1118 try addrs.append(gpa, .{ .addr = .{ .in6 = Ip6Address.parse("::1", port) catch unreachable } });
1119 return;
1120 }
1121
1122 try linuxLookupNameFromDnsSearch(gpa, addrs, canon, name, family, port);
1123 }
1124 }
1125 } else {
1126 try canon.resize(gpa, 0);
1127 try addrs.ensureUnusedCapacity(gpa, 2);
1128 linuxLookupNameFromNull(addrs, family, flags, port);
1129 }
1130 if (addrs.items.len == 0) return error.UnknownHostName;
1131
1132 // No further processing is needed if there are fewer than 2
1133 // results or if there are only IPv4 results.
1134 if (addrs.items.len == 1 or family == posix.AF.INET) return;
1135 const all_ip4 = for (addrs.items) |addr| {
1136 if (addr.addr.any.family != posix.AF.INET) break false;
1137 } else true;
1138 if (all_ip4) return;
1139
1140 // The following implements a subset of RFC 3484/6724 destination
1141 // address selection by generating a single 31-bit sort key for
1142 // each address. Rules 3, 4, and 7 are omitted for having
1143 // excessive runtime and code size cost and dubious benefit.
1144 // So far the label/precedence table cannot be customized.
1145 // This implementation is ported from musl libc.
1146 // A more idiomatic "ziggy" implementation would be welcome.
1147 for (addrs.items, 0..) |*addr, i| {
1148 var key: i32 = 0;
1149 var sa6: posix.sockaddr.in6 = undefined;
1150 @memset(@as([*]u8, @ptrCast(&sa6))[0..@sizeOf(posix.sockaddr.in6)], 0);
1151 var da6 = posix.sockaddr.in6{
1152 .family = posix.AF.INET6,
1153 .scope_id = addr.addr.in6.sa.scope_id,
1154 .port = 65535,
1155 .flowinfo = 0,
1156 .addr = [1]u8{0} ** 16,
1157 };
1158 var sa4: posix.sockaddr.in = undefined;
1159 @memset(@as([*]u8, @ptrCast(&sa4))[0..@sizeOf(posix.sockaddr.in)], 0);
1160 var da4 = posix.sockaddr.in{
1161 .family = posix.AF.INET,
1162 .port = 65535,
1163 .addr = 0,
1164 .zero = [1]u8{0} ** 8,
1165 };
1166 var sa: *align(4) posix.sockaddr = undefined;
1167 var da: *align(4) posix.sockaddr = undefined;
1168 var salen: posix.socklen_t = undefined;
1169 var dalen: posix.socklen_t = undefined;
1170 if (addr.addr.any.family == posix.AF.INET6) {
1171 da6.addr = addr.addr.in6.sa.addr;
1172 da = @ptrCast(&da6);
1173 dalen = @sizeOf(posix.sockaddr.in6);
1174 sa = @ptrCast(&sa6);
1175 salen = @sizeOf(posix.sockaddr.in6);
1176 } else {
1177 sa6.addr[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*;
1178 da6.addr[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*;
1179 mem.writeInt(u32, da6.addr[12..], addr.addr.in.sa.addr, native_endian);
1180 da4.addr = addr.addr.in.sa.addr;
1181 da = @ptrCast(&da4);
1182 dalen = @sizeOf(posix.sockaddr.in);
1183 sa = @ptrCast(&sa4);
1184 salen = @sizeOf(posix.sockaddr.in);
1185 }
1186 const dpolicy = policyOf(da6.addr);
1187 const dscope: i32 = scopeOf(da6.addr);
1188 const dlabel = dpolicy.label;
1189 const dprec: i32 = dpolicy.prec;
1190 const MAXADDRS = 3;
1191 var prefixlen: i32 = 0;
1192 const sock_flags = posix.SOCK.DGRAM | posix.SOCK.CLOEXEC;
1193 if (posix.socket(addr.addr.any.family, sock_flags, posix.IPPROTO.UDP)) |fd| syscalls: {
1194 defer Stream.close(.{ .handle = fd });
1195 posix.connect(fd, da, dalen) catch break :syscalls;
1196 key |= DAS_USABLE;
1197 posix.getsockname(fd, sa, &salen) catch break :syscalls;
1198 if (addr.addr.any.family == posix.AF.INET) {
1199 mem.writeInt(u32, sa6.addr[12..16], sa4.addr, native_endian);
1200 }
1201 if (dscope == @as(i32, scopeOf(sa6.addr))) key |= DAS_MATCHINGSCOPE;
1202 if (dlabel == labelOf(sa6.addr)) key |= DAS_MATCHINGLABEL;
1203 prefixlen = prefixMatch(sa6.addr, da6.addr);
1204 } else |_| {}
1205 key |= dprec << DAS_PREC_SHIFT;
1206 key |= (15 - dscope) << DAS_SCOPE_SHIFT;
1207 key |= prefixlen << DAS_PREFIX_SHIFT;
1208 key |= (MAXADDRS - @as(i32, @intCast(i))) << DAS_ORDER_SHIFT;
1209 addr.sortkey = key;
1210 }
1211 mem.sort(LookupAddr, addrs.items, {}, addrCmpLessThan);
1212}
1213
1214const Policy = struct {
1215 addr: [16]u8,
1216 len: u8,
1217 mask: u8,
1218 prec: u8,
1219 label: u8,
1220};
1221
1222const defined_policies = [_]Policy{
1223 Policy{
1224 .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01".*,
1225 .len = 15,
1226 .mask = 0xff,
1227 .prec = 50,
1228 .label = 0,
1229 },
1230 Policy{
1231 .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00".*,
1232 .len = 11,
1233 .mask = 0xff,
1234 .prec = 35,
1235 .label = 4,
1236 },
1237 Policy{
1238 .addr = "\x20\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
1239 .len = 1,
1240 .mask = 0xff,
1241 .prec = 30,
1242 .label = 2,
1243 },
1244 Policy{
1245 .addr = "\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
1246 .len = 3,
1247 .mask = 0xff,
1248 .prec = 5,
1249 .label = 5,
1250 },
1251 Policy{
1252 .addr = "\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
1253 .len = 0,
1254 .mask = 0xfe,
1255 .prec = 3,
1256 .label = 13,
1257 },
1258 // These are deprecated and/or returned to the address
1259 // pool, so despite the RFC, treating them as special
1260 // is probably wrong.
1261 // { "", 11, 0xff, 1, 3 },
1262 // { "\xfe\xc0", 1, 0xc0, 1, 11 },
1263 // { "\x3f\xfe", 1, 0xff, 1, 12 },
1264 // Last rule must match all addresses to stop loop.
1265 Policy{
1266 .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
1267 .len = 0,
1268 .mask = 0,
1269 .prec = 40,
1270 .label = 1,
1271 },
1272};
1273
1274fn policyOf(a: [16]u8) *const Policy {
1275 for (&defined_policies) |*policy| {
1276 if (!mem.eql(u8, a[0..policy.len], policy.addr[0..policy.len])) continue;
1277 if ((a[policy.len] & policy.mask) != policy.addr[policy.len]) continue;
1278 return policy;
1279 }
1280 unreachable;
1281}
1282
1283fn scopeOf(a: [16]u8) u8 {
1284 if (IN6_IS_ADDR_MULTICAST(a)) return a[1] & 15;
1285 if (IN6_IS_ADDR_LINKLOCAL(a)) return 2;
1286 if (IN6_IS_ADDR_LOOPBACK(a)) return 2;
1287 if (IN6_IS_ADDR_SITELOCAL(a)) return 5;
1288 return 14;
1289}
1290
1291fn prefixMatch(s: [16]u8, d: [16]u8) u8 {
1292 // TODO: This FIXME inherited from porting from musl libc.
1293 // I don't want this to go into zig std lib 1.0.0.
1294
1295 // FIXME: The common prefix length should be limited to no greater
1296 // than the nominal length of the prefix portion of the source
1297 // address. However the definition of the source prefix length is
1298 // not clear and thus this limiting is not yet implemented.
1299 var i: u8 = 0;
1300 while (i < 128 and ((s[i / 8] ^ d[i / 8]) & (@as(u8, 128) >> @as(u3, @intCast(i % 8)))) == 0) : (i += 1) {}
1301 return i;
1302}
1303
1304fn labelOf(a: [16]u8) u8 {
1305 return policyOf(a).label;
1306}
1307
1308fn IN6_IS_ADDR_MULTICAST(a: [16]u8) bool {
1309 return a[0] == 0xff;
1310}
1311
1312fn IN6_IS_ADDR_LINKLOCAL(a: [16]u8) bool {
1313 return a[0] == 0xfe and (a[1] & 0xc0) == 0x80;
1314}
1315
1316fn IN6_IS_ADDR_LOOPBACK(a: [16]u8) bool {
1317 return a[0] == 0 and a[1] == 0 and
1318 a[2] == 0 and
1319 a[12] == 0 and a[13] == 0 and
1320 a[14] == 0 and a[15] == 1;
1321}
1322
1323fn IN6_IS_ADDR_SITELOCAL(a: [16]u8) bool {
1324 return a[0] == 0xfe and (a[1] & 0xc0) == 0xc0;
1325}
1326
1327// Parameters `b` and `a` swapped to make this descending.
1328fn addrCmpLessThan(context: void, b: LookupAddr, a: LookupAddr) bool {
1329 _ = context;
1330 return a.sortkey < b.sortkey;
1331}
1332
1333fn linuxLookupNameFromNull(
1334 addrs: *ArrayList(LookupAddr),
1335 family: posix.sa_family_t,
1336 flags: posix.AI,
1337 port: u16,
1338) void {
1339 if (flags.PASSIVE) {
1340 if (family != posix.AF.INET6) {
1341 addrs.appendAssumeCapacity(.{
1342 .addr = Address.initIp4([1]u8{0} ** 4, port),
1343 });
1344 }
1345 if (family != posix.AF.INET) {
1346 addrs.appendAssumeCapacity(.{
1347 .addr = Address.initIp6([1]u8{0} ** 16, port, 0, 0),
1348 });
1349 }
1350 } else {
1351 if (family != posix.AF.INET6) {
1352 addrs.appendAssumeCapacity(.{
1353 .addr = Address.initIp4([4]u8{ 127, 0, 0, 1 }, port),
1354 });
1355 }
1356 if (family != posix.AF.INET) {
1357 addrs.appendAssumeCapacity(.{
1358 .addr = Address.initIp6(([1]u8{0} ** 15) ++ [1]u8{1}, port, 0, 0),
1359 });
1360 }
1361 }
1362}
1363
1364fn linuxLookupNameFromHosts(
1365 gpa: Allocator,
1366 addrs: *ArrayList(LookupAddr),
1367 canon: *ArrayList(u8),
1368 name: []const u8,
1369 family: posix.sa_family_t,
1370 port: u16,
1371) !void {
1372 const file = fs.openFileAbsoluteZ("/etc/hosts", .{}) catch |err| switch (err) {
1373 error.FileNotFound,
1374 error.NotDir,
1375 error.AccessDenied,
1376 => return,
1377 else => |e| return e,
1378 };
1379 defer file.close();
1380
1381 var line_buf: [512]u8 = undefined;
1382 var file_reader = file.reader(&line_buf);
1383 return parseHosts(gpa, addrs, canon, name, family, port, &file_reader.interface) catch |err| switch (err) {
1384 error.OutOfMemory => return error.OutOfMemory,
1385 error.ReadFailed => return file_reader.err.?,
1386 };
1387}
1388
1389fn parseHosts(
1390 gpa: Allocator,
1391 addrs: *ArrayList(LookupAddr),
1392 canon: *ArrayList(u8),
1393 name: []const u8,
1394 family: posix.sa_family_t,
1395 port: u16,
1396 br: *Io.Reader,
1397) error{ OutOfMemory, ReadFailed }!void {
1398 while (true) {
1399 const line = br.takeDelimiter('\n') catch |err| switch (err) {
1400 error.StreamTooLong => {
1401 // Skip lines that are too long.
1402 _ = br.discardDelimiterInclusive('\n') catch |e| switch (e) {
1403 error.EndOfStream => break,
1404 error.ReadFailed => return error.ReadFailed,
1405 };
1406 continue;
1407 },
1408 error.ReadFailed => return error.ReadFailed,
1409 } orelse {
1410 break; // end of stream
1411 };
1412 var split_it = mem.splitScalar(u8, line, '#');
1413 const no_comment_line = split_it.first();
1414
1415 var line_it = mem.tokenizeAny(u8, no_comment_line, " \t");
1416 const ip_text = line_it.next() orelse continue;
1417 var first_name_text: ?[]const u8 = null;
1418 while (line_it.next()) |name_text| {
1419 if (first_name_text == null) first_name_text = name_text;
1420 if (mem.eql(u8, name_text, name)) {
1421 break;
1422 }
1423 } else continue;
1424
1425 const addr = Address.parseExpectingFamily(ip_text, family, port) catch |err| switch (err) {
1426 error.Overflow,
1427 error.InvalidEnd,
1428 error.InvalidCharacter,
1429 error.Incomplete,
1430 error.InvalidIPAddressFormat,
1431 error.InvalidIpv4Mapping,
1432 error.NonCanonical,
1433 => continue,
1434 };
1435 try addrs.append(gpa, .{ .addr = addr });
1436
1437 // first name is canonical name
1438 const name_text = first_name_text.?;
1439 if (isValidHostName(name_text)) {
1440 canon.items.len = 0;
1441 try canon.appendSlice(gpa, name_text);
1442 }
1443 }
1444}
1445
1446test parseHosts {
1447 if (builtin.os.tag == .wasi) {
1448 // TODO parsing addresses should not have OS dependencies
1449 return error.SkipZigTest;
1450 }
1451 var reader: Io.Reader = .fixed(
1452 \\127.0.0.1 localhost
1453 \\::1 localhost
1454 \\127.0.0.2 abcd
1455 );
1456 var addrs: ArrayList(LookupAddr) = .empty;
1457 defer addrs.deinit(std.testing.allocator);
1458 var canon: ArrayList(u8) = .empty;
1459 defer canon.deinit(std.testing.allocator);
1460 try parseHosts(std.testing.allocator, &addrs, &canon, "abcd", posix.AF.UNSPEC, 1234, &reader);
1461 try std.testing.expectEqual(1, addrs.items.len);
1462 try std.testing.expectFmt("127.0.0.2:1234", "{f}", .{addrs.items[0].addr});
1463}
1464
1465pub fn isValidHostName(hostname: []const u8) bool {
1466 if (hostname.len >= 254) return false;
1467 if (!std.unicode.utf8ValidateSlice(hostname)) return false;
1468 for (hostname) |byte| {
1469 if (!std.ascii.isAscii(byte) or byte == '.' or byte == '-' or std.ascii.isAlphanumeric(byte)) {
1470 continue;
1471 }
1472 return false;
1473 }
1474 return true;
1475}
1476
1477fn linuxLookupNameFromDnsSearch(
1478 gpa: Allocator,
1479 addrs: *ArrayList(LookupAddr),
1480 canon: *ArrayList(u8),
1481 name: []const u8,
1482 family: posix.sa_family_t,
1483 port: u16,
1484) !void {
1485 var rc: ResolvConf = undefined;
1486 rc.init(gpa) catch return error.ResolveConfParseFailed;
1487 defer rc.deinit();
1488
1489 // Count dots, suppress search when >=ndots or name ends in
1490 // a dot, which is an explicit request for global scope.
1491 var dots: usize = 0;
1492 for (name) |byte| {
1493 if (byte == '.') dots += 1;
1494 }
1495
1496 const search = if (dots >= rc.ndots or mem.endsWith(u8, name, "."))
1497 ""
1498 else
1499 rc.search.items;
1500
1501 var canon_name = name;
1502
1503 // Strip final dot for canon, fail if multiple trailing dots.
1504 if (mem.endsWith(u8, canon_name, ".")) canon_name.len -= 1;
1505 if (mem.endsWith(u8, canon_name, ".")) return error.UnknownHostName;
1506
1507 // Name with search domain appended is setup in canon[]. This both
1508 // provides the desired default canonical name (if the requested
1509 // name is not a CNAME record) and serves as a buffer for passing
1510 // the full requested name to name_from_dns.
1511 try canon.resize(gpa, canon_name.len);
1512 @memcpy(canon.items, canon_name);
1513 try canon.append(gpa, '.');
1514
1515 var tok_it = mem.tokenizeAny(u8, search, " \t");
1516 while (tok_it.next()) |tok| {
1517 canon.shrinkRetainingCapacity(canon_name.len + 1);
1518 try canon.appendSlice(gpa, tok);
1519 try linuxLookupNameFromDns(gpa, addrs, canon, canon.items, family, rc, port);
1520 if (addrs.items.len != 0) return;
1521 }
1522
1523 canon.shrinkRetainingCapacity(canon_name.len);
1524 return linuxLookupNameFromDns(gpa, addrs, canon, name, family, rc, port);
1525}
1526
1527const dpc_ctx = struct {
1528 gpa: Allocator,
1529 addrs: *ArrayList(LookupAddr),
1530 canon: *ArrayList(u8),
1531 port: u16,
1532};
1533
1534fn linuxLookupNameFromDns(
1535 gpa: Allocator,
1536 addrs: *ArrayList(LookupAddr),
1537 canon: *ArrayList(u8),
1538 name: []const u8,
1539 family: posix.sa_family_t,
1540 rc: ResolvConf,
1541 port: u16,
1542) !void {
1543 const ctx: dpc_ctx = .{
1544 .gpa = gpa,
1545 .addrs = addrs,
1546 .canon = canon,
1547 .port = port,
1548 };
1549 const AfRr = struct {
1550 af: posix.sa_family_t,
1551 rr: u8,
1552 };
1553 const afrrs = [_]AfRr{
1554 .{ .af = posix.AF.INET6, .rr = posix.RR.A },
1555 .{ .af = posix.AF.INET, .rr = posix.RR.AAAA },
1556 };
1557 var qbuf: [2][280]u8 = undefined;
1558 var abuf: [2][512]u8 = undefined;
1559 var qp: [2][]const u8 = undefined;
1560 const apbuf = [2][]u8{ &abuf[0], &abuf[1] };
1561 var nq: usize = 0;
1562
1563 for (afrrs) |afrr| {
1564 if (family != afrr.af) {
1565 const len = posix.res_mkquery(0, name, 1, afrr.rr, &[_]u8{}, null, &qbuf[nq]);
1566 qp[nq] = qbuf[nq][0..len];
1567 nq += 1;
1568 }
1569 }
1570
1571 var ap = [2][]u8{ apbuf[0], apbuf[1] };
1572 ap[0].len = 0;
1573 ap[1].len = 0;
1574
1575 try rc.resMSendRc(qp[0..nq], ap[0..nq], apbuf[0..nq]);
1576
1577 var i: usize = 0;
1578 while (i < nq) : (i += 1) {
1579 dnsParse(ap[i], ctx, dnsParseCallback) catch {};
1580 }
1581
1582 if (addrs.items.len != 0) return;
1583 if (ap[0].len < 4 or (ap[0][3] & 15) == 2) return error.TemporaryNameServerFailure;
1584 if ((ap[0][3] & 15) == 0) return error.UnknownHostName;
1585 if ((ap[0][3] & 15) == 3) return;
1586 return error.NameServerFailure;
1587}
1588
1589const ResolvConf = struct {
1590 gpa: Allocator,
1591 attempts: u32,
1592 ndots: u32,
1593 timeout: u32,
1594 search: ArrayList(u8),
1595 /// TODO there are actually only allowed to be maximum 3 nameservers, no need
1596 /// for an array list.
1597 ns: ArrayList(LookupAddr),
1598
1599 /// Returns `error.StreamTooLong` if a line is longer than 512 bytes.
1600 /// TODO: https://github.com/ziglang/zig/issues/2765 and https://github.com/ziglang/zig/issues/2761
1601 fn init(rc: *ResolvConf, gpa: Allocator) !void {
1602 rc.* = .{
1603 .gpa = gpa,
1604 .ns = .empty,
1605 .search = .empty,
1606 .ndots = 1,
1607 .timeout = 5,
1608 .attempts = 2,
1609 };
1610 errdefer rc.deinit();
1611
1612 const file = fs.openFileAbsoluteZ("/etc/resolv.conf", .{}) catch |err| switch (err) {
1613 error.FileNotFound,
1614 error.NotDir,
1615 error.AccessDenied,
1616 => return linuxLookupNameFromNumericUnspec(gpa, &rc.ns, "127.0.0.1", 53),
1617 else => |e| return e,
1618 };
1619 defer file.close();
1620
1621 var line_buf: [512]u8 = undefined;
1622 var file_reader = file.reader(&line_buf);
1623 return parse(rc, &file_reader.interface) catch |err| switch (err) {
1624 error.ReadFailed => return file_reader.err.?,
1625 else => |e| return e,
1626 };
1627 }
1628
1629 const Directive = enum { options, nameserver, domain, search };
1630 const Option = enum { ndots, attempts, timeout };
1631
1632 fn parse(rc: *ResolvConf, reader: *Io.Reader) !void {
1633 const gpa = rc.gpa;
1634 while (reader.takeSentinel('\n')) |line_with_comment| {
1635 const line = line: {
1636 var split = mem.splitScalar(u8, line_with_comment, '#');
1637 break :line split.first();
1638 };
1639 var line_it = mem.tokenizeAny(u8, line, " \t");
1640
1641 const token = line_it.next() orelse continue;
1642 switch (std.meta.stringToEnum(Directive, token) orelse continue) {
1643 .options => while (line_it.next()) |sub_tok| {
1644 var colon_it = mem.splitScalar(u8, sub_tok, ':');
1645 const name = colon_it.first();
1646 const value_txt = colon_it.next() orelse continue;
1647 const value = std.fmt.parseInt(u8, value_txt, 10) catch |err| switch (err) {
1648 error.Overflow => 255,
1649 error.InvalidCharacter => continue,
1650 };
1651 switch (std.meta.stringToEnum(Option, name) orelse continue) {
1652 .ndots => rc.ndots = @min(value, 15),
1653 .attempts => rc.attempts = @min(value, 10),
1654 .timeout => rc.timeout = @min(value, 60),
1655 }
1656 },
1657 .nameserver => {
1658 const ip_txt = line_it.next() orelse continue;
1659 try linuxLookupNameFromNumericUnspec(gpa, &rc.ns, ip_txt, 53);
1660 },
1661 .domain, .search => {
1662 rc.search.items.len = 0;
1663 try rc.search.appendSlice(gpa, line_it.rest());
1664 },
1665 }
1666 } else |err| switch (err) {
1667 error.EndOfStream => if (reader.bufferedLen() != 0) return error.EndOfStream,
1668 else => |e| return e,
1669 }
1670
1671 if (rc.ns.items.len == 0) {
1672 return linuxLookupNameFromNumericUnspec(gpa, &rc.ns, "127.0.0.1", 53);
1673 }
1674 }
1675
1676 fn resMSendRc(
1677 rc: ResolvConf,
1678 queries: []const []const u8,
1679 answers: [][]u8,
1680 answer_bufs: []const []u8,
1681 ) !void {
1682 const gpa = rc.gpa;
1683 const timeout = 1000 * rc.timeout;
1684 const attempts = rc.attempts;
1685
1686 var sl: posix.socklen_t = @sizeOf(posix.sockaddr.in);
1687 var family: posix.sa_family_t = posix.AF.INET;
1688
1689 var ns_list: ArrayList(Address) = .empty;
1690 defer ns_list.deinit(gpa);
1691
1692 try ns_list.resize(gpa, rc.ns.items.len);
1693
1694 for (ns_list.items, rc.ns.items) |*ns, iplit| {
1695 ns.* = iplit.addr;
1696 assert(ns.getPort() == 53);
1697 if (iplit.addr.any.family != posix.AF.INET) {
1698 family = posix.AF.INET6;
1699 }
1700 }
1701
1702 const flags = posix.SOCK.DGRAM | posix.SOCK.CLOEXEC | posix.SOCK.NONBLOCK;
1703 const fd = posix.socket(family, flags, 0) catch |err| switch (err) {
1704 error.AddressFamilyNotSupported => blk: {
1705 // Handle case where system lacks IPv6 support
1706 if (family == posix.AF.INET6) {
1707 family = posix.AF.INET;
1708 break :blk try posix.socket(posix.AF.INET, flags, 0);
1709 }
1710 return err;
1711 },
1712 else => |e| return e,
1713 };
1714 defer Stream.close(.{ .handle = fd });
1715
1716 // Past this point, there are no errors. Each individual query will
1717 // yield either no reply (indicated by zero length) or an answer
1718 // packet which is up to the caller to interpret.
1719
1720 // Convert any IPv4 addresses in a mixed environment to v4-mapped
1721 if (family == posix.AF.INET6) {
1722 try posix.setsockopt(
1723 fd,
1724 posix.SOL.IPV6,
1725 std.os.linux.IPV6.V6ONLY,
1726 &mem.toBytes(@as(c_int, 0)),
1727 );
1728 for (ns_list.items) |*ns| {
1729 if (ns.any.family != posix.AF.INET) continue;
1730 mem.writeInt(u32, ns.in6.sa.addr[12..], ns.in.sa.addr, native_endian);
1731 ns.in6.sa.addr[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*;
1732 ns.any.family = posix.AF.INET6;
1733 ns.in6.sa.flowinfo = 0;
1734 ns.in6.sa.scope_id = 0;
1735 }
1736 sl = @sizeOf(posix.sockaddr.in6);
1737 }
1738
1739 // Get local address and open/bind a socket
1740 var sa: Address = undefined;
1741 @memset(@as([*]u8, @ptrCast(&sa))[0..@sizeOf(Address)], 0);
1742 sa.any.family = family;
1743 try posix.bind(fd, &sa.any, sl);
1744
1745 var pfd = [1]posix.pollfd{posix.pollfd{
1746 .fd = fd,
1747 .events = posix.POLL.IN,
1748 .revents = undefined,
1749 }};
1750 const retry_interval = timeout / attempts;
1751 var next: u32 = 0;
1752 var t2: u64 = @bitCast(std.time.milliTimestamp());
1753 const t0 = t2;
1754 var t1 = t2 - retry_interval;
1755
1756 var servfail_retry: usize = undefined;
1757
1758 outer: while (t2 - t0 < timeout) : (t2 = @as(u64, @bitCast(std.time.milliTimestamp()))) {
1759 if (t2 - t1 >= retry_interval) {
1760 // Query all configured nameservers in parallel
1761 var i: usize = 0;
1762 while (i < queries.len) : (i += 1) {
1763 if (answers[i].len == 0) {
1764 for (ns_list.items) |*ns| {
1765 _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns.any, sl) catch undefined;
1766 }
1767 }
1768 }
1769 t1 = t2;
1770 servfail_retry = 2 * queries.len;
1771 }
1772
1773 // Wait for a response, or until time to retry
1774 const clamped_timeout = @min(@as(u31, std.math.maxInt(u31)), t1 + retry_interval - t2);
1775 const nevents = posix.poll(&pfd, clamped_timeout) catch 0;
1776 if (nevents == 0) continue;
1777
1778 while (true) {
1779 var sl_copy = sl;
1780 const rlen = posix.recvfrom(fd, answer_bufs[next], 0, &sa.any, &sl_copy) catch break;
1781
1782 // Ignore non-identifiable packets
1783 if (rlen < 4) continue;
1784
1785 // Ignore replies from addresses we didn't send to
1786 const ns = for (ns_list.items) |*ns| {
1787 if (ns.eql(sa)) break ns;
1788 } else continue;
1789
1790 // Find which query this answer goes with, if any
1791 var i: usize = next;
1792 while (i < queries.len and (answer_bufs[next][0] != queries[i][0] or
1793 answer_bufs[next][1] != queries[i][1])) : (i += 1)
1794 {}
1795
1796 if (i == queries.len) continue;
1797 if (answers[i].len != 0) continue;
1798
1799 // Only accept positive or negative responses;
1800 // retry immediately on server failure, and ignore
1801 // all other codes such as refusal.
1802 switch (answer_bufs[next][3] & 15) {
1803 0, 3 => {},
1804 2 => if (servfail_retry != 0) {
1805 servfail_retry -= 1;
1806 _ = posix.sendto(fd, queries[i], posix.MSG.NOSIGNAL, &ns.any, sl) catch undefined;
1807 },
1808 else => continue,
1809 }
1810
1811 // Store answer in the right slot, or update next
1812 // available temp slot if it's already in place.
1813 answers[i].len = rlen;
1814 if (i == next) {
1815 while (next < queries.len and answers[next].len != 0) : (next += 1) {}
1816 } else {
1817 @memcpy(answer_bufs[i][0..rlen], answer_bufs[next][0..rlen]);
1818 }
1819
1820 if (next == queries.len) break :outer;
1821 }
1822 }
1823 }
1824
1825 fn deinit(rc: *ResolvConf) void {
1826 const gpa = rc.gpa;
1827 rc.ns.deinit(gpa);
1828 rc.search.deinit(gpa);
1829 rc.* = undefined;
1830 }
1831};
1832
1833fn linuxLookupNameFromNumericUnspec(
1834 gpa: Allocator,
1835 addrs: *ArrayList(LookupAddr),
1836 name: []const u8,
1837 port: u16,
1838) !void {
1839 const addr = try Address.resolveIp(name, port);
1840 try addrs.append(gpa, .{ .addr = addr });
1841}
1842
1843fn dnsParse(
1844 r: []const u8,
1845 ctx: anytype,
1846 comptime callback: anytype,
1847) !void {
1848 // This implementation is ported from musl libc.
1849 // A more idiomatic "ziggy" implementation would be welcome.
1850 if (r.len < 12) return error.InvalidDnsPacket;
1851 if ((r[3] & 15) != 0) return;
1852 var p = r.ptr + 12;
1853 var qdcount = r[4] * @as(usize, 256) + r[5];
1854 var ancount = r[6] * @as(usize, 256) + r[7];
1855 if (qdcount + ancount > 64) return error.InvalidDnsPacket;
1856 while (qdcount != 0) {
1857 qdcount -= 1;
1858 while (@intFromPtr(p) - @intFromPtr(r.ptr) < r.len and p[0] -% 1 < 127) p += 1;
1859 if (p[0] > 193 or (p[0] == 193 and p[1] > 254) or @intFromPtr(p) > @intFromPtr(r.ptr) + r.len - 6)
1860 return error.InvalidDnsPacket;
1861 p += @as(usize, 5) + @intFromBool(p[0] != 0);
1862 }
1863 while (ancount != 0) {
1864 ancount -= 1;
1865 while (@intFromPtr(p) - @intFromPtr(r.ptr) < r.len and p[0] -% 1 < 127) p += 1;
1866 if (p[0] > 193 or (p[0] == 193 and p[1] > 254) or @intFromPtr(p) > @intFromPtr(r.ptr) + r.len - 6)
1867 return error.InvalidDnsPacket;
1868 p += @as(usize, 1) + @intFromBool(p[0] != 0);
1869 const len = p[8] * @as(usize, 256) + p[9];
1870 if (@intFromPtr(p) + len > @intFromPtr(r.ptr) + r.len) return error.InvalidDnsPacket;
1871 try callback(ctx, p[1], p[10..][0..len], r);
1872 p += 10 + len;
1873 }
1874}
1875
1876fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8) !void {
1877 const gpa = ctx.gpa;
1878 switch (rr) {
1879 posix.RR.A => {
1880 if (data.len != 4) return error.InvalidDnsARecord;
1881 try ctx.addrs.append(gpa, .{
1882 .addr = Address.initIp4(data[0..4].*, ctx.port),
1883 });
1884 },
1885 posix.RR.AAAA => {
1886 if (data.len != 16) return error.InvalidDnsAAAARecord;
1887 try ctx.addrs.append(gpa, .{
1888 .addr = Address.initIp6(data[0..16].*, ctx.port, 0, 0),
1889 });
1890 },
1891 posix.RR.CNAME => {
1892 var tmp: [256]u8 = undefined;
1893 // Returns len of compressed name. strlen to get canon name.
1894 _ = try posix.dn_expand(packet, data, &tmp);
1895 const canon_name = mem.sliceTo(&tmp, 0);
1896 if (isValidHostName(canon_name)) {
1897 ctx.canon.items.len = 0;
1898 try ctx.canon.appendSlice(gpa, canon_name);
1899 }
1900 },
1901 else => return,
1902 }
1903}
1904
1905pub const Stream = struct {
1906 /// Underlying platform-defined type which may or may not be
1907 /// interchangeable with a file system file descriptor.
1908 handle: Handle,
1909
1910 pub const Handle = switch (native_os) {
1911 .windows => windows.ws2_32.SOCKET,
1912 else => posix.fd_t,
1913 };
1914
1915 pub fn close(s: Stream) void {
1916 switch (native_os) {
1917 .windows => windows.closesocket(s.handle) catch unreachable,
1918 else => posix.close(s.handle),
1919 }
1920 }
1921
1922 pub const ReadError = posix.ReadError || error{
1923 SocketNotBound,
1924 MessageTooBig,
1925 NetworkSubsystemFailed,
1926 ConnectionResetByPeer,
1927 SocketNotConnected,
1928 };
1929
1930 pub const WriteError = posix.SendMsgError || error{
1931 ConnectionResetByPeer,
1932 SocketNotBound,
1933 MessageTooBig,
1934 NetworkSubsystemFailed,
1935 SystemResources,
1936 SocketNotConnected,
1937 Unexpected,
1938 };
1939
1940 pub const Reader = switch (native_os) {
1941 .windows => struct {
1942 /// Use `interface` for portable code.
1943 interface_state: Io.Reader,
1944 /// Use `getStream` for portable code.
1945 net_stream: Stream,
1946 /// Use `getError` for portable code.
1947 error_state: ?Error,
1948
1949 pub const Error = ReadError;
1950
1951 pub fn getStream(r: *const Reader) Stream {
1952 return r.net_stream;
1953 }
1954
1955 pub fn getError(r: *const Reader) ?Error {
1956 return r.error_state;
1957 }
1958
1959 pub fn interface(r: *Reader) *Io.Reader {
1960 return &r.interface_state;
1961 }
1962
1963 pub fn init(net_stream: Stream, buffer: []u8) Reader {
1964 return .{
1965 .interface_state = .{
1966 .vtable = &.{
1967 .stream = stream,
1968 .readVec = readVec,
1969 },
1970 .buffer = buffer,
1971 .seek = 0,
1972 .end = 0,
1973 },
1974 .net_stream = net_stream,
1975 .error_state = null,
1976 };
1977 }
1978
1979 fn stream(io_r: *Io.Reader, io_w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
1980 const dest = limit.slice(try io_w.writableSliceGreedy(1));
1981 var bufs: [1][]u8 = .{dest};
1982 const n = try readVec(io_r, &bufs);
1983 io_w.advance(n);
1984 return n;
1985 }
1986
1987 fn readVec(io_r: *std.Io.Reader, data: [][]u8) Io.Reader.Error!usize {
1988 const r: *Reader = @alignCast(@fieldParentPtr("interface_state", io_r));
1989 var iovecs: [max_buffers_len]windows.ws2_32.WSABUF = undefined;
1990 const bufs_n, const data_size = try io_r.writableVectorWsa(&iovecs, data);
1991 const bufs = iovecs[0..bufs_n];
1992 assert(bufs[0].len != 0);
1993 const n = streamBufs(r, bufs) catch |err| {
1994 r.error_state = err;
1995 return error.ReadFailed;
1996 };
1997 if (n == 0) return error.EndOfStream;
1998 if (n > data_size) {
1999 io_r.end += n - data_size;
2000 return data_size;
2001 }
2002 return n;
2003 }
2004
2005 fn handleRecvError(winsock_error: windows.ws2_32.WinsockError) Error!void {
2006 switch (winsock_error) {
2007 .WSAECONNRESET => return error.ConnectionResetByPeer,
2008 .WSAEFAULT => unreachable, // a pointer is not completely contained in user address space.
2009 .WSAEINPROGRESS, .WSAEINTR => unreachable, // deprecated and removed in WSA 2.2
2010 .WSAEINVAL => return error.SocketNotBound,
2011 .WSAEMSGSIZE => return error.MessageTooBig,
2012 .WSAENETDOWN => return error.NetworkSubsystemFailed,
2013 .WSAENETRESET => return error.ConnectionResetByPeer,
2014 .WSAENOTCONN => return error.SocketNotConnected,
2015 .WSAEWOULDBLOCK => return error.WouldBlock,
2016 .WSANOTINITIALISED => unreachable, // WSAStartup must be called before this function
2017 .WSA_IO_PENDING => unreachable,
2018 .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O
2019 else => |err| return windows.unexpectedWSAError(err),
2020 }
2021 }
2022
2023 fn streamBufs(r: *Reader, bufs: []windows.ws2_32.WSABUF) Error!u32 {
2024 var flags: u32 = 0;
2025 var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED);
2026
2027 var n: u32 = undefined;
2028 if (windows.ws2_32.WSARecv(
2029 r.net_stream.handle,
2030 bufs.ptr,
2031 @intCast(bufs.len),
2032 &n,
2033 &flags,
2034 &overlapped,
2035 null,
2036 ) == windows.ws2_32.SOCKET_ERROR) switch (windows.ws2_32.WSAGetLastError()) {
2037 .WSA_IO_PENDING => {
2038 var result_flags: u32 = undefined;
2039 if (windows.ws2_32.WSAGetOverlappedResult(
2040 r.net_stream.handle,
2041 &overlapped,
2042 &n,
2043 windows.TRUE,
2044 &result_flags,
2045 ) == windows.FALSE) try handleRecvError(windows.ws2_32.WSAGetLastError());
2046 },
2047 else => |winsock_error| try handleRecvError(winsock_error),
2048 };
2049
2050 return n;
2051 }
2052 },
2053 else => struct {
2054 /// Use `getStream`, `interface`, and `getError` for portable code.
2055 file_reader: File.Reader,
2056
2057 pub const Error = ReadError;
2058
2059 pub fn interface(r: *Reader) *Io.Reader {
2060 return &r.file_reader.interface;
2061 }
2062
2063 pub fn init(net_stream: Stream, buffer: []u8) Reader {
2064 return .{
2065 .file_reader = .{
2066 .interface = File.Reader.initInterface(buffer),
2067 .file = .{ .handle = net_stream.handle },
2068 .mode = .streaming,
2069 .seek_err = error.Unseekable,
2070 .size_err = error.Streaming,
2071 },
2072 };
2073 }
2074
2075 pub fn getStream(r: *const Reader) Stream {
2076 return .{ .handle = r.file_reader.file.handle };
2077 }
2078
2079 pub fn getError(r: *const Reader) ?Error {
2080 return r.file_reader.err;
2081 }
2082 },
2083 };
2084
2085 pub const Writer = switch (native_os) {
2086 .windows => struct {
2087 /// This field is present on all systems.
2088 interface: Io.Writer,
2089 /// Use `getStream` for cross-platform support.
2090 stream: Stream,
2091 /// This field is present on all systems.
2092 err: ?Error = null,
2093
2094 pub const Error = WriteError;
2095
2096 pub fn init(stream: Stream, buffer: []u8) Writer {
2097 return .{
2098 .stream = stream,
2099 .interface = .{
2100 .vtable = &.{ .drain = drain },
2101 .buffer = buffer,
2102 },
2103 };
2104 }
2105
2106 pub fn getStream(w: *const Writer) Stream {
2107 return w.stream;
2108 }
2109
2110 fn addWsaBuf(v: []windows.ws2_32.WSABUF, i: *u32, bytes: []const u8) void {
2111 const cap = std.math.maxInt(u32);
2112 var remaining = bytes;
2113 while (remaining.len > cap) {
2114 if (v.len - i.* == 0) return;
2115 v[i.*] = .{ .buf = @constCast(remaining.ptr), .len = cap };
2116 i.* += 1;
2117 remaining = remaining[cap..];
2118 } else {
2119 @branchHint(.likely);
2120 if (v.len - i.* == 0) return;
2121 v[i.*] = .{ .buf = @constCast(remaining.ptr), .len = @intCast(remaining.len) };
2122 i.* += 1;
2123 }
2124 }
2125
2126 fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
2127 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
2128 const buffered = io_w.buffered();
2129 comptime assert(native_os == .windows);
2130 var iovecs: [max_buffers_len]windows.ws2_32.WSABUF = undefined;
2131 var len: u32 = 0;
2132 addWsaBuf(&iovecs, &len, buffered);
2133 for (data[0 .. data.len - 1]) |bytes| addWsaBuf(&iovecs, &len, bytes);
2134 const pattern = data[data.len - 1];
2135 if (iovecs.len - len != 0) switch (splat) {
2136 0 => {},
2137 1 => addWsaBuf(&iovecs, &len, pattern),
2138 else => switch (pattern.len) {
2139 0 => {},
2140 1 => {
2141 const splat_buffer_candidate = io_w.buffer[io_w.end..];
2142 var backup_buffer: [64]u8 = undefined;
2143 const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len)
2144 splat_buffer_candidate
2145 else
2146 &backup_buffer;
2147 const memset_len = @min(splat_buffer.len, splat);
2148 const buf = splat_buffer[0..memset_len];
2149 @memset(buf, pattern[0]);
2150 addWsaBuf(&iovecs, &len, buf);
2151 var remaining_splat = splat - buf.len;
2152 while (remaining_splat > splat_buffer.len and len < iovecs.len) {
2153 addWsaBuf(&iovecs, &len, splat_buffer);
2154 remaining_splat -= splat_buffer.len;
2155 }
2156 addWsaBuf(&iovecs, &len, splat_buffer[0..remaining_splat]);
2157 },
2158 else => for (0..@min(splat, iovecs.len - len)) |_| {
2159 addWsaBuf(&iovecs, &len, pattern);
2160 },
2161 },
2162 };
2163 const n = sendBufs(w.stream.handle, iovecs[0..len]) catch |err| {
2164 w.err = err;
2165 return error.WriteFailed;
2166 };
2167 return io_w.consume(n);
2168 }
2169
2170 fn handleSendError(winsock_error: windows.ws2_32.WinsockError) Error!void {
2171 switch (winsock_error) {
2172 .WSAECONNABORTED => return error.ConnectionResetByPeer,
2173 .WSAECONNRESET => return error.ConnectionResetByPeer,
2174 .WSAEFAULT => unreachable, // a pointer is not completely contained in user address space.
2175 .WSAEINPROGRESS, .WSAEINTR => unreachable, // deprecated and removed in WSA 2.2
2176 .WSAEINVAL => return error.SocketNotBound,
2177 .WSAEMSGSIZE => return error.MessageTooBig,
2178 .WSAENETDOWN => return error.NetworkSubsystemFailed,
2179 .WSAENETRESET => return error.ConnectionResetByPeer,
2180 .WSAENOBUFS => return error.SystemResources,
2181 .WSAENOTCONN => return error.SocketNotConnected,
2182 .WSAENOTSOCK => unreachable, // not a socket
2183 .WSAEOPNOTSUPP => unreachable, // only for message-oriented sockets
2184 .WSAESHUTDOWN => unreachable, // cannot send on a socket after write shutdown
2185 .WSAEWOULDBLOCK => return error.WouldBlock,
2186 .WSANOTINITIALISED => unreachable, // WSAStartup must be called before this function
2187 .WSA_IO_PENDING => unreachable,
2188 .WSA_OPERATION_ABORTED => unreachable, // not using overlapped I/O
2189 else => |err| return windows.unexpectedWSAError(err),
2190 }
2191 }
2192
2193 fn sendBufs(handle: Stream.Handle, bufs: []windows.ws2_32.WSABUF) Error!u32 {
2194 var n: u32 = undefined;
2195 var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED);
2196 if (windows.ws2_32.WSASend(
2197 handle,
2198 bufs.ptr,
2199 @intCast(bufs.len),
2200 &n,
2201 0,
2202 &overlapped,
2203 null,
2204 ) == windows.ws2_32.SOCKET_ERROR) switch (windows.ws2_32.WSAGetLastError()) {
2205 .WSA_IO_PENDING => {
2206 var result_flags: u32 = undefined;
2207 if (windows.ws2_32.WSAGetOverlappedResult(
2208 handle,
2209 &overlapped,
2210 &n,
2211 windows.TRUE,
2212 &result_flags,
2213 ) == windows.FALSE) try handleSendError(windows.ws2_32.WSAGetLastError());
2214 },
2215 else => |winsock_error| try handleSendError(winsock_error),
2216 };
2217
2218 return n;
2219 }
2220 },
2221 else => struct {
2222 /// This field is present on all systems.
2223 interface: Io.Writer,
2224
2225 err: ?Error = null,
2226 file_writer: File.Writer,
2227
2228 pub const Error = WriteError;
2229
2230 pub fn init(stream: Stream, buffer: []u8) Writer {
2231 return .{
2232 .interface = .{
2233 .vtable = &.{
2234 .drain = drain,
2235 .sendFile = sendFile,
2236 },
2237 .buffer = buffer,
2238 },
2239 .file_writer = .initStreaming(.{ .handle = stream.handle }, &.{}),
2240 };
2241 }
2242
2243 pub fn getStream(w: *const Writer) Stream {
2244 return .{ .handle = w.file_writer.file.handle };
2245 }
2246
2247 fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"), bytes: []const u8) void {
2248 // OS checks ptr addr before length so zero length vectors must be omitted.
2249 if (bytes.len == 0) return;
2250 if (v.len - i.* == 0) return;
2251 v[i.*] = .{ .base = bytes.ptr, .len = bytes.len };
2252 i.* += 1;
2253 }
2254
2255 fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
2256 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
2257 const buffered = io_w.buffered();
2258 var iovecs: [max_buffers_len]posix.iovec_const = undefined;
2259 var msg: posix.msghdr_const = .{
2260 .name = null,
2261 .namelen = 0,
2262 .iov = &iovecs,
2263 .iovlen = 0,
2264 .control = null,
2265 .controllen = 0,
2266 .flags = 0,
2267 };
2268 addBuf(&iovecs, &msg.iovlen, buffered);
2269 for (data[0 .. data.len - 1]) |bytes| addBuf(&iovecs, &msg.iovlen, bytes);
2270 const pattern = data[data.len - 1];
2271 if (iovecs.len - msg.iovlen != 0) switch (splat) {
2272 0 => {},
2273 1 => addBuf(&iovecs, &msg.iovlen, pattern),
2274 else => switch (pattern.len) {
2275 0 => {},
2276 1 => {
2277 const splat_buffer_candidate = io_w.buffer[io_w.end..];
2278 var backup_buffer: [64]u8 = undefined;
2279 const splat_buffer = if (splat_buffer_candidate.len >= backup_buffer.len)
2280 splat_buffer_candidate
2281 else
2282 &backup_buffer;
2283 const memset_len = @min(splat_buffer.len, splat);
2284 const buf = splat_buffer[0..memset_len];
2285 @memset(buf, pattern[0]);
2286 addBuf(&iovecs, &msg.iovlen, buf);
2287 var remaining_splat = splat - buf.len;
2288 while (remaining_splat > splat_buffer.len and iovecs.len - msg.iovlen != 0) {
2289 assert(buf.len == splat_buffer.len);
2290 addBuf(&iovecs, &msg.iovlen, splat_buffer);
2291 remaining_splat -= splat_buffer.len;
2292 }
2293 addBuf(&iovecs, &msg.iovlen, splat_buffer[0..remaining_splat]);
2294 },
2295 else => for (0..@min(splat, iovecs.len - msg.iovlen)) |_| {
2296 addBuf(&iovecs, &msg.iovlen, pattern);
2297 },
2298 },
2299 };
2300 const flags = posix.MSG.NOSIGNAL;
2301 return io_w.consume(posix.sendmsg(w.file_writer.file.handle, &msg, flags) catch |err| {
2302 w.err = err;
2303 return error.WriteFailed;
2304 });
2305 }
2306
2307 fn sendFile(io_w: *Io.Writer, file_reader: *File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
2308 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
2309 const n = try w.file_writer.interface.sendFileHeader(io_w.buffered(), file_reader, limit);
2310 return io_w.consume(n);
2311 }
2312 },
2313 };
2314
2315 pub fn reader(stream: Stream, buffer: []u8) Reader {
2316 return .init(stream, buffer);
2317 }
2318
2319 pub fn writer(stream: Stream, buffer: []u8) Writer {
2320 return .init(stream, buffer);
2321 }
2322
2323 const max_buffers_len = 8;
2324
2325 /// Deprecated in favor of `Reader`.
2326 pub fn read(self: Stream, buffer: []u8) ReadError!usize {
2327 if (native_os == .windows) {
2328 return windows.ReadFile(self.handle, buffer, null);
2329 }
2330
2331 return posix.read(self.handle, buffer);
2332 }
2333
2334 /// Deprecated in favor of `Reader`.
2335 pub fn readv(s: Stream, iovecs: []const posix.iovec) ReadError!usize {
2336 if (native_os == .windows) {
2337 if (iovecs.len == 0) return 0;
2338 const first = iovecs[0];
2339 return windows.ReadFile(s.handle, first.base[0..first.len], null);
2340 }
2341
2342 return posix.readv(s.handle, iovecs);
2343 }
2344
2345 /// Deprecated in favor of `Reader`.
2346 pub fn readAtLeast(s: Stream, buffer: []u8, len: usize) ReadError!usize {
2347 assert(len <= buffer.len);
2348 var index: usize = 0;
2349 while (index < len) {
2350 const amt = try s.read(buffer[index..]);
2351 if (amt == 0) break;
2352 index += amt;
2353 }
2354 return index;
2355 }
2356
2357 /// Deprecated in favor of `Writer`.
2358 pub fn write(self: Stream, buffer: []const u8) WriteError!usize {
2359 var stream_writer = self.writer(&.{});
2360 return stream_writer.interface.writeVec(&.{buffer}) catch return stream_writer.err.?;
2361 }
2362
2363 /// Deprecated in favor of `Writer`.
2364 pub fn writeAll(self: Stream, bytes: []const u8) WriteError!void {
2365 var index: usize = 0;
2366 while (index < bytes.len) {
2367 index += try self.write(bytes[index..]);
2368 }
2369 }
2370
2371 /// Deprecated in favor of `Writer`.
2372 pub fn writev(self: Stream, iovecs: []const posix.iovec_const) WriteError!usize {
2373 return @errorCast(posix.writev(self.handle, iovecs));
2374 }
2375
2376 /// Deprecated in favor of `Writer`.
2377 pub fn writevAll(self: Stream, iovecs: []posix.iovec_const) WriteError!void {
2378 if (iovecs.len == 0) return;
2379
2380 var i: usize = 0;
2381 while (true) {
2382 var amt = try self.writev(iovecs[i..]);
2383 while (amt >= iovecs[i].len) {
2384 amt -= iovecs[i].len;
2385 i += 1;
2386 if (i >= iovecs.len) return;
2387 }
2388 iovecs[i].base += amt;
2389 iovecs[i].len -= amt;
2390 }
2391 }
2392};
2393
2394pub const Server = struct {
2395 listen_address: Address,
2396 stream: Stream,
2397
2398 pub const Connection = struct {
2399 stream: Stream,
2400 address: Address,
2401 };
2402
2403 pub fn deinit(s: *Server) void {
2404 s.stream.close();
2405 s.* = undefined;
2406 }
2407
2408 pub const AcceptError = posix.AcceptError;
2409
2410 /// Blocks until a client connects to the server. The returned `Connection` has
2411 /// an open stream.
2412 pub fn accept(s: *Server) AcceptError!Connection {
2413 var accepted_addr: Address = undefined;
2414 var addr_len: posix.socklen_t = @sizeOf(Address);
2415 const fd = try posix.accept(s.stream.handle, &accepted_addr.any, &addr_len, posix.SOCK.CLOEXEC);
2416 return .{
2417 .stream = .{ .handle = fd },
2418 .address = accepted_addr,
2419 };
2420 }
2421};
2422
2423test {
2424 if (builtin.os.tag != .wasi) {
2425 _ = Server;
2426 _ = Stream;
2427 _ = Address;
2428 _ = @import("net/test.zig");
2429 }
2430}
lib/std/net/test.zig deleted-373
......@@ -1,373 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const net = std.net;
4const mem = std.mem;
5const testing = std.testing;
6
7test "parse and render IP addresses at comptime" {
8 comptime {
9 const ipv6addr = net.Address.parseIp("::1", 0) catch unreachable;
10 try std.testing.expectFmt("[::1]:0", "{f}", .{ipv6addr});
11
12 const ipv4addr = net.Address.parseIp("127.0.0.1", 0) catch unreachable;
13 try std.testing.expectFmt("127.0.0.1:0", "{f}", .{ipv4addr});
14
15 try testing.expectError(error.InvalidIPAddressFormat, net.Address.parseIp("::123.123.123.123", 0));
16 try testing.expectError(error.InvalidIPAddressFormat, net.Address.parseIp("127.01.0.1", 0));
17 try testing.expectError(error.InvalidIPAddressFormat, net.Address.resolveIp("::123.123.123.123", 0));
18 try testing.expectError(error.InvalidIPAddressFormat, net.Address.resolveIp("127.01.0.1", 0));
19 }
20}
21
22test "format IPv6 address with no zero runs" {
23 const addr = try std.net.Address.parseIp6("2001:db8:1:2:3:4:5:6", 0);
24 try std.testing.expectFmt("[2001:db8:1:2:3:4:5:6]:0", "{f}", .{addr});
25}
26
27test "parse IPv6 addresses and check compressed form" {
28 try std.testing.expectFmt("[2001:db8::1:0:0:2]:0", "{f}", .{
29 try std.net.Address.parseIp6("2001:0db8:0000:0000:0001:0000:0000:0002", 0),
30 });
31 try std.testing.expectFmt("[2001:db8::1:2]:0", "{f}", .{
32 try std.net.Address.parseIp6("2001:0db8:0000:0000:0000:0000:0001:0002", 0),
33 });
34 try std.testing.expectFmt("[2001:db8:1:0:1::2]:0", "{f}", .{
35 try std.net.Address.parseIp6("2001:0db8:0001:0000:0001:0000:0000:0002", 0),
36 });
37}
38
39test "parse IPv6 address, check raw bytes" {
40 const expected_raw: [16]u8 = .{
41 0x20, 0x01, 0x0d, 0xb8, // 2001:db8
42 0x00, 0x00, 0x00, 0x00, // :0000:0000
43 0x00, 0x01, 0x00, 0x00, // :0001:0000
44 0x00, 0x00, 0x00, 0x02, // :0000:0002
45 };
46
47 const addr = try std.net.Address.parseIp6("2001:db8:0000:0000:0001:0000:0000:0002", 0);
48
49 const actual_raw = addr.in6.sa.addr[0..];
50 try std.testing.expectEqualSlices(u8, expected_raw[0..], actual_raw);
51}
52
53test "parse and render IPv6 addresses" {
54 var buffer: [100]u8 = undefined;
55 const ips = [_][]const u8{
56 "FF01:0:0:0:0:0:0:FB",
57 "FF01::Fb",
58 "::1",
59 "::",
60 "1::",
61 "2001:db8::",
62 "::1234:5678",
63 "2001:db8::1234:5678",
64 "FF01::FB%1234",
65 "::ffff:123.5.123.5",
66 };
67 const printed = [_][]const u8{
68 "ff01::fb",
69 "ff01::fb",
70 "::1",
71 "::",
72 "1::",
73 "2001:db8::",
74 "::1234:5678",
75 "2001:db8::1234:5678",
76 "ff01::fb%1234",
77 "::ffff:123.5.123.5",
78 };
79 for (ips, 0..) |ip, i| {
80 const addr = net.Address.parseIp6(ip, 0) catch unreachable;
81 var newIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr}) catch unreachable;
82 try std.testing.expect(std.mem.eql(u8, printed[i], newIp[1 .. newIp.len - 3]));
83
84 if (builtin.os.tag == .linux) {
85 const addr_via_resolve = net.Address.resolveIp6(ip, 0) catch unreachable;
86 var newResolvedIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr_via_resolve}) catch unreachable;
87 try std.testing.expect(std.mem.eql(u8, printed[i], newResolvedIp[1 .. newResolvedIp.len - 3]));
88 }
89 }
90
91 try testing.expectError(error.InvalidCharacter, net.Address.parseIp6(":::", 0));
92 try testing.expectError(error.Overflow, net.Address.parseIp6("FF001::FB", 0));
93 try testing.expectError(error.InvalidCharacter, net.Address.parseIp6("FF01::Fb:zig", 0));
94 try testing.expectError(error.InvalidEnd, net.Address.parseIp6("FF01:0:0:0:0:0:0:FB:", 0));
95 try testing.expectError(error.Incomplete, net.Address.parseIp6("FF01:", 0));
96 try testing.expectError(error.InvalidIpv4Mapping, net.Address.parseIp6("::123.123.123.123", 0));
97 try testing.expectError(error.Incomplete, net.Address.parseIp6("1", 0));
98 // TODO Make this test pass on other operating systems.
99 if (builtin.os.tag == .linux or comptime builtin.os.tag.isDarwin() or builtin.os.tag == .windows) {
100 try testing.expectError(error.Incomplete, net.Address.resolveIp6("ff01::fb%", 0));
101 // Assumes IFNAMESIZE will always be a multiple of 2
102 try testing.expectError(error.Overflow, net.Address.resolveIp6("ff01::fb%wlp3" ++ "s0" ** @divExact(std.posix.IFNAMESIZE - 4, 2), 0));
103 try testing.expectError(error.Overflow, net.Address.resolveIp6("ff01::fb%12345678901234", 0));
104 }
105}
106
107test "invalid but parseable IPv6 scope ids" {
108 if (builtin.os.tag != .linux and comptime !builtin.os.tag.isDarwin() and builtin.os.tag != .windows) {
109 // Currently, resolveIp6 with alphanumerical scope IDs only works on Linux.
110 // TODO Make this test pass on other operating systems.
111 return error.SkipZigTest;
112 }
113
114 try testing.expectError(error.InterfaceNotFound, net.Address.resolveIp6("ff01::fb%123s45678901234", 0));
115}
116
117test "parse and render IPv4 addresses" {
118 var buffer: [18]u8 = undefined;
119 for ([_][]const u8{
120 "0.0.0.0",
121 "255.255.255.255",
122 "1.2.3.4",
123 "123.255.0.91",
124 "127.0.0.1",
125 }) |ip| {
126 const addr = net.Address.parseIp4(ip, 0) catch unreachable;
127 var newIp = std.fmt.bufPrint(buffer[0..], "{f}", .{addr}) catch unreachable;
128 try std.testing.expect(std.mem.eql(u8, ip, newIp[0 .. newIp.len - 2]));
129 }
130
131 try testing.expectError(error.Overflow, net.Address.parseIp4("256.0.0.1", 0));
132 try testing.expectError(error.InvalidCharacter, net.Address.parseIp4("x.0.0.1", 0));
133 try testing.expectError(error.InvalidEnd, net.Address.parseIp4("127.0.0.1.1", 0));
134 try testing.expectError(error.Incomplete, net.Address.parseIp4("127.0.0.", 0));
135 try testing.expectError(error.InvalidCharacter, net.Address.parseIp4("100..0.1", 0));
136 try testing.expectError(error.NonCanonical, net.Address.parseIp4("127.01.0.1", 0));
137}
138
139test "parse and render UNIX addresses" {
140 if (builtin.os.tag == .wasi) return error.SkipZigTest;
141 if (!net.has_unix_sockets) return error.SkipZigTest;
142
143 const addr = net.Address.initUnix("/tmp/testpath") catch unreachable;
144 try std.testing.expectFmt("/tmp/testpath", "{f}", .{addr});
145
146 const too_long = [_]u8{'a'} ** 200;
147 try testing.expectError(error.NameTooLong, net.Address.initUnix(too_long[0..]));
148}
149
150test "resolve DNS" {
151 if (builtin.os.tag == .wasi) return error.SkipZigTest;
152
153 if (builtin.os.tag == .windows) {
154 _ = try std.os.windows.WSAStartup(2, 2);
155 }
156 defer {
157 if (builtin.os.tag == .windows) {
158 std.os.windows.WSACleanup() catch unreachable;
159 }
160 }
161
162 // Resolve localhost, this should not fail.
163 {
164 const localhost_v4 = try net.Address.parseIp("127.0.0.1", 80);
165 const localhost_v6 = try net.Address.parseIp("::2", 80);
166
167 const result = try net.getAddressList(testing.allocator, "localhost", 80);
168 defer result.deinit();
169 for (result.addrs) |addr| {
170 if (addr.eql(localhost_v4) or addr.eql(localhost_v6)) break;
171 } else @panic("unexpected address for localhost");
172 }
173
174 {
175 // The tests are required to work even when there is no Internet connection,
176 // so some of these errors we must accept and skip the test.
177 const result = net.getAddressList(testing.allocator, "example.com", 80) catch |err| switch (err) {
178 error.UnknownHostName => return error.SkipZigTest,
179 error.TemporaryNameServerFailure => return error.SkipZigTest,
180 else => return err,
181 };
182 result.deinit();
183 }
184}
185
186test "listen on a port, send bytes, receive bytes" {
187 if (builtin.single_threaded) return error.SkipZigTest;
188 if (builtin.os.tag == .wasi) return error.SkipZigTest;
189
190 if (builtin.os.tag == .windows) {
191 _ = try std.os.windows.WSAStartup(2, 2);
192 }
193 defer {
194 if (builtin.os.tag == .windows) {
195 std.os.windows.WSACleanup() catch unreachable;
196 }
197 }
198
199 // Try only the IPv4 variant as some CI builders have no IPv6 localhost
200 // configured.
201 const localhost = try net.Address.parseIp("127.0.0.1", 0);
202
203 var server = try localhost.listen(.{});
204 defer server.deinit();
205
206 const S = struct {
207 fn clientFn(server_address: net.Address) !void {
208 const socket = try net.tcpConnectToAddress(server_address);
209 defer socket.close();
210
211 var stream_writer = socket.writer(&.{});
212 try stream_writer.interface.writeAll("Hello world!");
213 }
214 };
215
216 const t = try std.Thread.spawn(.{}, S.clientFn, .{server.listen_address});
217 defer t.join();
218
219 var client = try server.accept();
220 defer client.stream.close();
221 var buf: [16]u8 = undefined;
222 var stream_reader = client.stream.reader(&.{});
223 const n = try stream_reader.interface().readSliceShort(&buf);
224
225 try testing.expectEqual(@as(usize, 12), n);
226 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
227}
228
229test "listen on an in use port" {
230 if (builtin.os.tag != .linux and comptime !builtin.os.tag.isDarwin() and builtin.os.tag != .windows) {
231 // TODO build abstractions for other operating systems
232 return error.SkipZigTest;
233 }
234
235 const localhost = try net.Address.parseIp("127.0.0.1", 0);
236
237 var server1 = try localhost.listen(.{ .reuse_address = true });
238 defer server1.deinit();
239
240 var server2 = try server1.listen_address.listen(.{ .reuse_address = true });
241 defer server2.deinit();
242}
243
244fn testClientToHost(allocator: mem.Allocator, name: []const u8, port: u16) anyerror!void {
245 if (builtin.os.tag == .wasi) return error.SkipZigTest;
246
247 const connection = try net.tcpConnectToHost(allocator, name, port);
248 defer connection.close();
249
250 var buf: [100]u8 = undefined;
251 const len = try connection.read(&buf);
252 const msg = buf[0..len];
253 try testing.expect(mem.eql(u8, msg, "hello from server\n"));
254}
255
256fn testClient(addr: net.Address) anyerror!void {
257 if (builtin.os.tag == .wasi) return error.SkipZigTest;
258
259 const socket_file = try net.tcpConnectToAddress(addr);
260 defer socket_file.close();
261
262 var buf: [100]u8 = undefined;
263 const len = try socket_file.read(&buf);
264 const msg = buf[0..len];
265 try testing.expect(mem.eql(u8, msg, "hello from server\n"));
266}
267
268fn testServer(server: *net.Server) anyerror!void {
269 if (builtin.os.tag == .wasi) return error.SkipZigTest;
270
271 var client = try server.accept();
272
273 const stream = client.stream.writer();
274 try stream.print("hello from server\n", .{});
275}
276
277test "listen on a unix socket, send bytes, receive bytes" {
278 if (builtin.single_threaded) return error.SkipZigTest;
279 if (!net.has_unix_sockets) return error.SkipZigTest;
280
281 if (builtin.os.tag == .windows) {
282 _ = try std.os.windows.WSAStartup(2, 2);
283 }
284 defer {
285 if (builtin.os.tag == .windows) {
286 std.os.windows.WSACleanup() catch unreachable;
287 }
288 }
289
290 const socket_path = try generateFileName("socket.unix");
291 defer testing.allocator.free(socket_path);
292
293 const socket_addr = try net.Address.initUnix(socket_path);
294 defer std.fs.cwd().deleteFile(socket_path) catch {};
295
296 var server = try socket_addr.listen(.{});
297 defer server.deinit();
298
299 const S = struct {
300 fn clientFn(path: []const u8) !void {
301 const socket = try net.connectUnixSocket(path);
302 defer socket.close();
303
304 var stream_writer = socket.writer(&.{});
305 try stream_writer.interface.writeAll("Hello world!");
306 }
307 };
308
309 const t = try std.Thread.spawn(.{}, S.clientFn, .{socket_path});
310 defer t.join();
311
312 var client = try server.accept();
313 defer client.stream.close();
314 var buf: [16]u8 = undefined;
315 var stream_reader = client.stream.reader(&.{});
316 const n = try stream_reader.interface().readSliceShort(&buf);
317
318 try testing.expectEqual(@as(usize, 12), n);
319 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
320}
321
322test "listen on a unix socket with reuse_address option" {
323 if (!net.has_unix_sockets) return error.SkipZigTest;
324 // Windows doesn't implement reuse port option.
325 if (builtin.os.tag == .windows) return error.SkipZigTest;
326
327 const socket_path = try generateFileName("socket.unix");
328 defer testing.allocator.free(socket_path);
329
330 const socket_addr = try net.Address.initUnix(socket_path);
331 defer std.fs.cwd().deleteFile(socket_path) catch {};
332
333 var server = try socket_addr.listen(.{ .reuse_address = true });
334 server.deinit();
335}
336
337fn generateFileName(base_name: []const u8) ![]const u8 {
338 const random_bytes_count = 12;
339 const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count);
340 var random_bytes: [12]u8 = undefined;
341 std.crypto.random.bytes(&random_bytes);
342 var sub_path: [sub_path_len]u8 = undefined;
343 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
344 return std.fmt.allocPrint(testing.allocator, "{s}-{s}", .{ sub_path[0..], base_name });
345}
346
347test "non-blocking tcp server" {
348 if (builtin.os.tag == .wasi) return error.SkipZigTest;
349 if (true) {
350 // https://github.com/ziglang/zig/issues/18315
351 return error.SkipZigTest;
352 }
353
354 const localhost = try net.Address.parseIp("127.0.0.1", 0);
355 var server = localhost.listen(.{ .force_nonblocking = true });
356 defer server.deinit();
357
358 const accept_err = server.accept();
359 try testing.expectError(error.WouldBlock, accept_err);
360
361 const socket_file = try net.tcpConnectToAddress(server.listen_address);
362 defer socket_file.close();
363
364 var client = try server.accept();
365 defer client.stream.close();
366 const stream = client.stream.writer();
367 try stream.print("hello from server\n", .{});
368
369 var buf: [100]u8 = undefined;
370 const len = try socket_file.read(&buf);
371 const msg = buf[0..len];
372 try testing.expect(mem.eql(u8, msg, "hello from server\n"));
373}
lib/std/os.zig+7-25
......@@ -57,7 +57,7 @@ pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (native_o
5757};
5858
5959/// Call from Windows-specific code if you already have a WTF-16LE encoded, null terminated string.
60/// Otherwise use `access` or `accessZ`.
60/// Otherwise use `access`.
6161pub fn accessW(path: [*:0]const u16) windows.GetFileAttributesError!void {
6262 const ret = try windows.GetFileAttributesW(path);
6363 if (ret != windows.INVALID_FILE_ATTRIBUTES) {
......@@ -137,8 +137,6 @@ pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix.
137137 switch (err) {
138138 error.NotLink => unreachable,
139139 error.BadPathName => unreachable,
140 error.InvalidUtf8 => unreachable, // WASI-only
141 error.InvalidWtf8 => unreachable, // Windows-only
142140 error.UnsupportedReparsePointType => unreachable, // Windows-only
143141 error.NetworkNotFound => unreachable, // Windows-only
144142 else => |e| return e,
......@@ -153,7 +151,6 @@ pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix.
153151 const target = posix.readlinkZ(proc_path, out_buffer) catch |err| switch (err) {
154152 error.UnsupportedReparsePointType => unreachable,
155153 error.NotLink => unreachable,
156 error.InvalidUtf8 => unreachable, // WASI-only
157154 else => |e| return e,
158155 };
159156 return target;
......@@ -201,28 +198,13 @@ pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix.
201198 }
202199}
203200
204/// WASI-only. Same as `fstatat` but targeting WASI.
205/// `pathname` should be encoded as valid UTF-8.
206/// See also `fstatat`.
207pub fn fstatat_wasi(dirfd: posix.fd_t, pathname: []const u8, flags: wasi.lookupflags_t) posix.FStatAtError!wasi.filestat_t {
208 var stat: wasi.filestat_t = undefined;
209 switch (wasi.path_filestat_get(dirfd, flags, pathname.ptr, pathname.len, &stat)) {
210 .SUCCESS => return stat,
211 .INVAL => unreachable,
212 .BADF => unreachable, // Always a race condition.
213 .NOMEM => return error.SystemResources,
214 .ACCES => return error.AccessDenied,
215 .FAULT => unreachable,
216 .NAMETOOLONG => return error.NameTooLong,
217 .NOENT => return error.FileNotFound,
218 .NOTDIR => return error.FileNotFound,
219 .NOTCAPABLE => return error.AccessDenied,
220 .ILSEQ => return error.InvalidUtf8,
221 else => |err| return posix.unexpectedErrno(err),
222 }
223}
201pub const FstatError = error{
202 SystemResources,
203 AccessDenied,
204 Unexpected,
205};
224206
225pub fn fstat_wasi(fd: posix.fd_t) posix.FStatError!wasi.filestat_t {
207pub fn fstat_wasi(fd: posix.fd_t) FstatError!wasi.filestat_t {
226208 var stat: wasi.filestat_t = undefined;
227209 switch (wasi.fd_filestat_get(fd, &stat)) {
228210 .SUCCESS => return stat,
lib/std/os/emscripten.zig+1-44
......@@ -479,50 +479,7 @@ pub const SHUT = struct {
479479 pub const RDWR = 2;
480480};
481481
482pub const SIG = struct {
483 pub const BLOCK = 0;
484 pub const UNBLOCK = 1;
485 pub const SETMASK = 2;
486
487 pub const HUP = 1;
488 pub const INT = 2;
489 pub const QUIT = 3;
490 pub const ILL = 4;
491 pub const TRAP = 5;
492 pub const ABRT = 6;
493 pub const IOT = ABRT;
494 pub const BUS = 7;
495 pub const FPE = 8;
496 pub const KILL = 9;
497 pub const USR1 = 10;
498 pub const SEGV = 11;
499 pub const USR2 = 12;
500 pub const PIPE = 13;
501 pub const ALRM = 14;
502 pub const TERM = 15;
503 pub const STKFLT = 16;
504 pub const CHLD = 17;
505 pub const CONT = 18;
506 pub const STOP = 19;
507 pub const TSTP = 20;
508 pub const TTIN = 21;
509 pub const TTOU = 22;
510 pub const URG = 23;
511 pub const XCPU = 24;
512 pub const XFSZ = 25;
513 pub const VTALRM = 26;
514 pub const PROF = 27;
515 pub const WINCH = 28;
516 pub const IO = 29;
517 pub const POLL = 29;
518 pub const PWR = 30;
519 pub const SYS = 31;
520 pub const UNUSED = SIG.SYS;
521
522 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(std.math.maxInt(usize));
523 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
524 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);
525};
482pub const SIG = linux.SIG;
526483
527484pub const Sigaction = extern struct {
528485 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;
lib/std/os/linux.zig+146-191
......@@ -1,10 +1,8 @@
11//! This file provides the system interface functions for Linux matching those
22//! that are provided by libc, whether or not libc is linked. The following
33//! abstractions are made:
4//! * Work around kernel bugs and limitations. For example, see sendmmsg.
54//! * Implement all the syscalls in the same way that libc functions will
65//! provide `rename` when only the `renameat` syscall exists.
7//! * Does not support POSIX thread cancellation.
86const std = @import("../std.zig");
97const builtin = @import("builtin");
108const assert = std.debug.assert;
......@@ -624,7 +622,7 @@ pub fn fork() usize {
624622 } else if (@hasField(SYS, "fork")) {
625623 return syscall0(.fork);
626624 } else {
627 return syscall2(.clone, SIG.CHLD, 0);
625 return syscall2(.clone, @intFromEnum(SIG.CHLD), 0);
628626 }
629627}
630628
......@@ -1534,16 +1532,16 @@ pub fn getrandom(buf: [*]u8, count: usize, flags: u32) usize {
15341532 return syscall3(.getrandom, @intFromPtr(buf), count, flags);
15351533}
15361534
1537pub fn kill(pid: pid_t, sig: i32) usize {
1538 return syscall2(.kill, @as(usize, @bitCast(@as(isize, pid))), @as(usize, @bitCast(@as(isize, sig))));
1535pub fn kill(pid: pid_t, sig: SIG) usize {
1536 return syscall2(.kill, @as(usize, @bitCast(@as(isize, pid))), @intFromEnum(sig));
15391537}
15401538
1541pub fn tkill(tid: pid_t, sig: i32) usize {
1542 return syscall2(.tkill, @as(usize, @bitCast(@as(isize, tid))), @as(usize, @bitCast(@as(isize, sig))));
1539pub fn tkill(tid: pid_t, sig: SIG) usize {
1540 return syscall2(.tkill, @as(usize, @bitCast(@as(isize, tid))), @intFromEnum(sig));
15431541}
15441542
1545pub fn tgkill(tgid: pid_t, tid: pid_t, sig: i32) usize {
1546 return syscall3(.tgkill, @as(usize, @bitCast(@as(isize, tgid))), @as(usize, @bitCast(@as(isize, tid))), @as(usize, @bitCast(@as(isize, sig))));
1543pub fn tgkill(tgid: pid_t, tid: pid_t, sig: SIG) usize {
1544 return syscall3(.tgkill, @as(usize, @bitCast(@as(isize, tgid))), @as(usize, @bitCast(@as(isize, tid))), @intFromEnum(sig));
15471545}
15481546
15491547pub fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8) usize {
......@@ -1836,7 +1834,7 @@ pub fn seteuid(euid: uid_t) usize {
18361834 // id will not be changed. Since uid_t is unsigned, this wraps around to the
18371835 // max value in C.
18381836 comptime assert(@typeInfo(uid_t) == .int and @typeInfo(uid_t).int.signedness == .unsigned);
1839 return setresuid(std.math.maxInt(uid_t), euid, std.math.maxInt(uid_t));
1837 return setresuid(maxInt(uid_t), euid, maxInt(uid_t));
18401838}
18411839
18421840pub fn setegid(egid: gid_t) usize {
......@@ -1847,7 +1845,7 @@ pub fn setegid(egid: gid_t) usize {
18471845 // id will not be changed. Since gid_t is unsigned, this wraps around to the
18481846 // max value in C.
18491847 comptime assert(@typeInfo(uid_t) == .int and @typeInfo(uid_t).int.signedness == .unsigned);
1850 return setresgid(std.math.maxInt(gid_t), egid, std.math.maxInt(gid_t));
1848 return setresgid(maxInt(gid_t), egid, maxInt(gid_t));
18511849}
18521850
18531851pub fn getresuid(ruid: *uid_t, euid: *uid_t, suid: *uid_t) usize {
......@@ -1925,11 +1923,11 @@ pub fn sigprocmask(flags: u32, noalias set: ?*const sigset_t, noalias oldset: ?*
19251923 return syscall4(.rt_sigprocmask, flags, @intFromPtr(set), @intFromPtr(oldset), NSIG / 8);
19261924}
19271925
1928pub fn sigaction(sig: u8, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) usize {
1929 assert(sig > 0);
1930 assert(sig < NSIG);
1931 assert(sig != SIG.KILL);
1932 assert(sig != SIG.STOP);
1926pub fn sigaction(sig: SIG, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) usize {
1927 assert(@intFromEnum(sig) > 0);
1928 assert(@intFromEnum(sig) < NSIG);
1929 assert(sig != .KILL);
1930 assert(sig != .STOP);
19331931
19341932 var ksa: k_sigaction = undefined;
19351933 var oldksa: k_sigaction = undefined;
......@@ -1960,8 +1958,8 @@ pub fn sigaction(sig: u8, noalias act: ?*const Sigaction, noalias oact: ?*Sigact
19601958
19611959 const result = switch (native_arch) {
19621960 // The sparc version of rt_sigaction needs the restorer function to be passed as an argument too.
1963 .sparc, .sparc64 => syscall5(.rt_sigaction, sig, ksa_arg, oldksa_arg, @intFromPtr(ksa.restorer), mask_size),
1964 else => syscall4(.rt_sigaction, sig, ksa_arg, oldksa_arg, mask_size),
1961 .sparc, .sparc64 => syscall5(.rt_sigaction, @intFromEnum(sig), ksa_arg, oldksa_arg, @intFromPtr(ksa.restorer), mask_size),
1962 else => syscall4(.rt_sigaction, @intFromEnum(sig), ksa_arg, oldksa_arg, mask_size),
19651963 };
19661964 if (E.init(result) != .SUCCESS) return result;
19671965
......@@ -2011,27 +2009,27 @@ pub fn sigfillset() sigset_t {
20112009 return [_]SigsetElement{~@as(SigsetElement, 0)} ** sigset_len;
20122010}
20132011
2014fn sigset_bit_index(sig: usize) struct { word: usize, mask: SigsetElement } {
2015 assert(sig > 0);
2016 assert(sig < NSIG);
2017 const bit = sig - 1;
2012fn sigset_bit_index(sig: SIG) struct { word: usize, mask: SigsetElement } {
2013 assert(@intFromEnum(sig) > 0);
2014 assert(@intFromEnum(sig) < NSIG);
2015 const bit = @intFromEnum(sig) - 1;
20182016 return .{
20192017 .word = bit / @bitSizeOf(SigsetElement),
20202018 .mask = @as(SigsetElement, 1) << @truncate(bit % @bitSizeOf(SigsetElement)),
20212019 };
20222020}
20232021
2024pub fn sigaddset(set: *sigset_t, sig: usize) void {
2022pub fn sigaddset(set: *sigset_t, sig: SIG) void {
20252023 const index = sigset_bit_index(sig);
20262024 (set.*)[index.word] |= index.mask;
20272025}
20282026
2029pub fn sigdelset(set: *sigset_t, sig: usize) void {
2027pub fn sigdelset(set: *sigset_t, sig: SIG) void {
20302028 const index = sigset_bit_index(sig);
20312029 (set.*)[index.word] ^= index.mask;
20322030}
20332031
2034pub fn sigismember(set: *const sigset_t, sig: usize) bool {
2032pub fn sigismember(set: *const sigset_t, sig: SIG) bool {
20352033 const index = sigset_bit_index(sig);
20362034 return ((set.*)[index.word] & index.mask) != 0;
20372035}
......@@ -2081,44 +2079,7 @@ pub fn sendmsg(fd: i32, msg: *const msghdr_const, flags: u32) usize {
20812079 }
20822080}
20832081
2084pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize {
2085 if (@typeInfo(usize).int.bits > @typeInfo(@typeInfo(mmsghdr).@"struct".fields[1].type).int.bits) {
2086 // workaround kernel brokenness:
2087 // if adding up all iov_len overflows a i32 then split into multiple calls
2088 // see https://www.openwall.com/lists/musl/2014/06/07/5
2089 const kvlen = if (vlen > IOV_MAX) IOV_MAX else vlen; // matches kernel
2090 var next_unsent: usize = 0;
2091 for (msgvec[0..kvlen], 0..) |*msg, i| {
2092 var size: i32 = 0;
2093 const msg_iovlen = @as(usize, @intCast(msg.hdr.iovlen)); // kernel side this is treated as unsigned
2094 for (msg.hdr.iov[0..msg_iovlen]) |iov| {
2095 if (iov.len > std.math.maxInt(i32) or @addWithOverflow(size, @as(i32, @intCast(iov.len)))[1] != 0) {
2096 // batch-send all messages up to the current message
2097 if (next_unsent < i) {
2098 const batch_size = i - next_unsent;
2099 const r = syscall4(.sendmmsg, @as(usize, @bitCast(@as(isize, fd))), @intFromPtr(&msgvec[next_unsent]), batch_size, flags);
2100 if (E.init(r) != .SUCCESS) return next_unsent;
2101 if (r < batch_size) return next_unsent + r;
2102 }
2103 // send current message as own packet
2104 const r = sendmsg(fd, &msg.hdr, flags);
2105 if (E.init(r) != .SUCCESS) return r;
2106 // Linux limits the total bytes sent by sendmsg to INT_MAX, so this cast is safe.
2107 msg.len = @as(u32, @intCast(r));
2108 next_unsent = i + 1;
2109 break;
2110 }
2111 size += @intCast(iov.len);
2112 }
2113 }
2114 if (next_unsent < kvlen or next_unsent == 0) { // want to make sure at least one syscall occurs (e.g. to trigger MSG.EOR)
2115 const batch_size = kvlen - next_unsent;
2116 const r = syscall4(.sendmmsg, @as(usize, @bitCast(@as(isize, fd))), @intFromPtr(&msgvec[next_unsent]), batch_size, flags);
2117 if (E.init(r) != .SUCCESS) return r;
2118 return next_unsent + r;
2119 }
2120 return kvlen;
2121 }
2082pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr, vlen: u32, flags: u32) usize {
21222083 return syscall4(.sendmmsg, @as(usize, @bitCast(@as(isize, fd))), @intFromPtr(msgvec), vlen, flags);
21232084}
21242085
......@@ -2674,11 +2635,11 @@ pub fn pidfd_getfd(pidfd: fd_t, targetfd: fd_t, flags: u32) usize {
26742635 );
26752636}
26762637
2677pub fn pidfd_send_signal(pidfd: fd_t, sig: i32, info: ?*siginfo_t, flags: u32) usize {
2638pub fn pidfd_send_signal(pidfd: fd_t, sig: SIG, info: ?*siginfo_t, flags: u32) usize {
26782639 return syscall4(
26792640 .pidfd_send_signal,
26802641 @as(usize, @bitCast(@as(isize, pidfd))),
2681 @as(usize, @bitCast(@as(isize, sig))),
2642 @intFromEnum(sig),
26822643 @intFromPtr(info),
26832644 flags,
26842645 );
......@@ -3775,136 +3736,138 @@ pub const SA = if (is_mips) struct {
37753736 pub const RESTORER = 0x04000000;
37763737};
37773738
3778pub const SIG = if (is_mips) struct {
3739pub const SIG = if (is_mips) enum(u32) {
37793740 pub const BLOCK = 1;
37803741 pub const UNBLOCK = 2;
37813742 pub const SETMASK = 3;
37823743
3783 // https://github.com/torvalds/linux/blob/ca91b9500108d4cf083a635c2e11c884d5dd20ea/arch/mips/include/uapi/asm/signal.h#L25
3784 pub const HUP = 1;
3785 pub const INT = 2;
3786 pub const QUIT = 3;
3787 pub const ILL = 4;
3788 pub const TRAP = 5;
3789 pub const ABRT = 6;
3790 pub const IOT = ABRT;
3791 pub const EMT = 7;
3792 pub const FPE = 8;
3793 pub const KILL = 9;
3794 pub const BUS = 10;
3795 pub const SEGV = 11;
3796 pub const SYS = 12;
3797 pub const PIPE = 13;
3798 pub const ALRM = 14;
3799 pub const TERM = 15;
3800 pub const USR1 = 16;
3801 pub const USR2 = 17;
3802 pub const CHLD = 18;
3803 pub const PWR = 19;
3804 pub const WINCH = 20;
3805 pub const URG = 21;
3806 pub const IO = 22;
3807 pub const POLL = IO;
3808 pub const STOP = 23;
3809 pub const TSTP = 24;
3810 pub const CONT = 25;
3811 pub const TTIN = 26;
3812 pub const TTOU = 27;
3813 pub const VTALRM = 28;
3814 pub const PROF = 29;
3815 pub const XCPU = 30;
3816 pub const XFZ = 31;
3817
38183744 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
38193745 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
38203746 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);
3821} else if (is_sparc) struct {
3747
3748 pub const IOT: SIG = .ABRT;
3749 pub const POLL: SIG = .IO;
3750
3751 // /arch/mips/include/uapi/asm/signal.h#L25
3752 HUP = 1,
3753 INT = 2,
3754 QUIT = 3,
3755 ILL = 4,
3756 TRAP = 5,
3757 ABRT = 6,
3758 EMT = 7,
3759 FPE = 8,
3760 KILL = 9,
3761 BUS = 10,
3762 SEGV = 11,
3763 SYS = 12,
3764 PIPE = 13,
3765 ALRM = 14,
3766 TERM = 15,
3767 USR1 = 16,
3768 USR2 = 17,
3769 CHLD = 18,
3770 PWR = 19,
3771 WINCH = 20,
3772 URG = 21,
3773 IO = 22,
3774 STOP = 23,
3775 TSTP = 24,
3776 CONT = 25,
3777 TTIN = 26,
3778 TTOU = 27,
3779 VTALRM = 28,
3780 PROF = 29,
3781 XCPU = 30,
3782 XFZ = 31,
3783} else if (is_sparc) enum(u32) {
38223784 pub const BLOCK = 1;
38233785 pub const UNBLOCK = 2;
38243786 pub const SETMASK = 4;
38253787
3826 pub const HUP = 1;
3827 pub const INT = 2;
3828 pub const QUIT = 3;
3829 pub const ILL = 4;
3830 pub const TRAP = 5;
3831 pub const ABRT = 6;
3832 pub const EMT = 7;
3833 pub const FPE = 8;
3834 pub const KILL = 9;
3835 pub const BUS = 10;
3836 pub const SEGV = 11;
3837 pub const SYS = 12;
3838 pub const PIPE = 13;
3839 pub const ALRM = 14;
3840 pub const TERM = 15;
3841 pub const URG = 16;
3842 pub const STOP = 17;
3843 pub const TSTP = 18;
3844 pub const CONT = 19;
3845 pub const CHLD = 20;
3846 pub const TTIN = 21;
3847 pub const TTOU = 22;
3848 pub const POLL = 23;
3849 pub const XCPU = 24;
3850 pub const XFSZ = 25;
3851 pub const VTALRM = 26;
3852 pub const PROF = 27;
3853 pub const WINCH = 28;
3854 pub const LOST = 29;
3855 pub const USR1 = 30;
3856 pub const USR2 = 31;
3857 pub const IOT = ABRT;
3858 pub const CLD = CHLD;
3859 pub const PWR = LOST;
3860 pub const IO = SIG.POLL;
3861
38623788 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
38633789 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
38643790 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);
3865} else struct {
3791
3792 pub const IOT: SIG = .ABRT;
3793 pub const CLD: SIG = .CHLD;
3794 pub const PWR: SIG = .LOST;
3795 pub const POLL: SIG = .IO;
3796
3797 HUP = 1,
3798 INT = 2,
3799 QUIT = 3,
3800 ILL = 4,
3801 TRAP = 5,
3802 ABRT = 6,
3803 EMT = 7,
3804 FPE = 8,
3805 KILL = 9,
3806 BUS = 10,
3807 SEGV = 11,
3808 SYS = 12,
3809 PIPE = 13,
3810 ALRM = 14,
3811 TERM = 15,
3812 URG = 16,
3813 STOP = 17,
3814 TSTP = 18,
3815 CONT = 19,
3816 CHLD = 20,
3817 TTIN = 21,
3818 TTOU = 22,
3819 IO = 23,
3820 XCPU = 24,
3821 XFSZ = 25,
3822 VTALRM = 26,
3823 PROF = 27,
3824 WINCH = 28,
3825 LOST = 29,
3826 USR1 = 30,
3827 USR2 = 31,
3828} else enum(u32) {
38663829 pub const BLOCK = 0;
38673830 pub const UNBLOCK = 1;
38683831 pub const SETMASK = 2;
38693832
3870 pub const HUP = 1;
3871 pub const INT = 2;
3872 pub const QUIT = 3;
3873 pub const ILL = 4;
3874 pub const TRAP = 5;
3875 pub const ABRT = 6;
3876 pub const IOT = ABRT;
3877 pub const BUS = 7;
3878 pub const FPE = 8;
3879 pub const KILL = 9;
3880 pub const USR1 = 10;
3881 pub const SEGV = 11;
3882 pub const USR2 = 12;
3883 pub const PIPE = 13;
3884 pub const ALRM = 14;
3885 pub const TERM = 15;
3886 pub const STKFLT = 16;
3887 pub const CHLD = 17;
3888 pub const CONT = 18;
3889 pub const STOP = 19;
3890 pub const TSTP = 20;
3891 pub const TTIN = 21;
3892 pub const TTOU = 22;
3893 pub const URG = 23;
3894 pub const XCPU = 24;
3895 pub const XFSZ = 25;
3896 pub const VTALRM = 26;
3897 pub const PROF = 27;
3898 pub const WINCH = 28;
3899 pub const IO = 29;
3900 pub const POLL = 29;
3901 pub const PWR = 30;
3902 pub const SYS = 31;
3903 pub const UNUSED = SIG.SYS;
3904
39053833 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
39063834 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
39073835 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);
3836
3837 pub const POLL: SIG = .IO;
3838 pub const IOT: SIG = .ABRT;
3839
3840 HUP = 1,
3841 INT = 2,
3842 QUIT = 3,
3843 ILL = 4,
3844 TRAP = 5,
3845 ABRT = 6,
3846 BUS = 7,
3847 FPE = 8,
3848 KILL = 9,
3849 USR1 = 10,
3850 SEGV = 11,
3851 USR2 = 12,
3852 PIPE = 13,
3853 ALRM = 14,
3854 TERM = 15,
3855 STKFLT = 16,
3856 CHLD = 17,
3857 CONT = 18,
3858 STOP = 19,
3859 TSTP = 20,
3860 TTIN = 21,
3861 TTOU = 22,
3862 URG = 23,
3863 XCPU = 24,
3864 XFSZ = 25,
3865 VTALRM = 26,
3866 PROF = 27,
3867 WINCH = 28,
3868 IO = 29,
3869 PWR = 30,
3870 SYS = 31,
39083871};
39093872
39103873pub const kernel_rwf = u32;
......@@ -5825,7 +5788,7 @@ pub const TFD = switch (native_arch) {
58255788};
58265789
58275790const k_sigaction_funcs = struct {
5828 const handler = ?*align(1) const fn (i32) callconv(.c) void;
5791 const handler = ?*align(1) const fn (SIG) callconv(.c) void;
58295792 const restorer = *const fn () callconv(.c) void;
58305793};
58315794
......@@ -5856,8 +5819,8 @@ pub const k_sigaction = switch (native_arch) {
58565819///
58575820/// Renamed from `sigaction` to `Sigaction` to avoid conflict with the syscall.
58585821pub const Sigaction = struct {
5859 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;
5860 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;
5822 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
5823 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
58615824
58625825 handler: extern union {
58635826 handler: ?handler_fn,
......@@ -5994,11 +5957,6 @@ pub const mmsghdr = extern struct {
59945957 len: u32,
59955958};
59965959
5997pub const mmsghdr_const = extern struct {
5998 hdr: msghdr_const,
5999 len: u32,
6000};
6001
60025960pub const epoll_data = extern union {
60035961 ptr: usize,
60045962 fd: i32,
......@@ -6304,14 +6262,14 @@ const siginfo_fields_union = extern union {
63046262
63056263pub const siginfo_t = if (is_mips)
63066264 extern struct {
6307 signo: i32,
6265 signo: SIG,
63086266 code: i32,
63096267 errno: i32,
63106268 fields: siginfo_fields_union,
63116269 }
63126270else
63136271 extern struct {
6314 signo: i32,
6272 signo: SIG,
63156273 errno: i32,
63166274 code: i32,
63176275 fields: siginfo_fields_union,
......@@ -7140,12 +7098,6 @@ pub const IPPROTO = struct {
71407098 pub const MAX = 256;
71417099};
71427100
7143pub const RR = struct {
7144 pub const A = 1;
7145 pub const CNAME = 5;
7146 pub const AAAA = 28;
7147};
7148
71497101pub const tcp_repair_opt = extern struct {
71507102 opt_code: u32,
71517103 opt_val: u32,
......@@ -8700,7 +8652,7 @@ pub const PR = enum(i32) {
87008652 pub const SET_MM_MAP = 14;
87018653 pub const SET_MM_MAP_SIZE = 15;
87028654
8703 pub const SET_PTRACER_ANY = std.math.maxInt(c_ulong);
8655 pub const SET_PTRACER_ANY = maxInt(c_ulong);
87048656
87058657 pub const FP_MODE_FR = 1 << 0;
87068658 pub const FP_MODE_FRE = 1 << 1;
......@@ -9884,8 +9836,10 @@ pub const msghdr = extern struct {
98849836 name: ?*sockaddr,
98859837 namelen: socklen_t,
98869838 iov: [*]iovec,
9839 /// The kernel and glibc use `usize` for this field; POSIX and musl use `c_int`.
98879840 iovlen: usize,
98889841 control: ?*anyopaque,
9842 /// The kernel and glibc use `usize` for this field; POSIX and musl use `socklen_t`.
98899843 controllen: usize,
98909844 flags: u32,
98919845};
......@@ -9902,6 +9856,7 @@ pub const msghdr_const = extern struct {
99029856
99039857// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/socket.h?id=b320789d6883cc00ac78ce83bccbfe7ed58afcf0#n105
99049858pub const cmsghdr = extern struct {
9859 /// The kernel and glibc use `usize` for this field; musl uses `socklen_t`.
99059860 len: usize,
99069861 level: i32,
99079862 type: i32,
lib/std/os/linux/IoUring.zig+161-138
......@@ -3,14 +3,14 @@ const std = @import("std");
33const builtin = @import("builtin");
44const assert = std.debug.assert;
55const mem = std.mem;
6const net = std.net;
6const net = std.Io.net;
77const posix = std.posix;
88const linux = std.os.linux;
99const testing = std.testing;
1010const is_linux = builtin.os.tag == .linux;
1111const page_size_min = std.heap.page_size_min;
1212
13fd: posix.fd_t = -1,
13fd: linux.fd_t = -1,
1414sq: SubmissionQueue,
1515cq: CompletionQueue,
1616flags: u32,
......@@ -62,7 +62,7 @@ pub fn init_params(entries: u16, p: *linux.io_uring_params) !IoUring {
6262 .NOSYS => return error.SystemOutdated,
6363 else => |errno| return posix.unexpectedErrno(errno),
6464 }
65 const fd = @as(posix.fd_t, @intCast(res));
65 const fd = @as(linux.fd_t, @intCast(res));
6666 assert(fd >= 0);
6767 errdefer posix.close(fd);
6868
......@@ -341,7 +341,7 @@ pub fn cq_advance(self: *IoUring, count: u32) void {
341341/// apply to the write, since the fsync may complete before the write is issued to the disk.
342342/// You should preferably use `link_with_next_sqe()` on a write's SQE to link it with an fsync,
343343/// or else insert a full write barrier using `drain_previous_sqes()` when queueing an fsync.
344pub fn fsync(self: *IoUring, user_data: u64, fd: posix.fd_t, flags: u32) !*linux.io_uring_sqe {
344pub fn fsync(self: *IoUring, user_data: u64, fd: linux.fd_t, flags: u32) !*linux.io_uring_sqe {
345345 const sqe = try self.get_sqe();
346346 sqe.prep_fsync(fd, flags);
347347 sqe.user_data = user_data;
......@@ -386,7 +386,7 @@ pub const ReadBuffer = union(enum) {
386386pub fn read(
387387 self: *IoUring,
388388 user_data: u64,
389 fd: posix.fd_t,
389 fd: linux.fd_t,
390390 buffer: ReadBuffer,
391391 offset: u64,
392392) !*linux.io_uring_sqe {
......@@ -409,7 +409,7 @@ pub fn read(
409409pub fn write(
410410 self: *IoUring,
411411 user_data: u64,
412 fd: posix.fd_t,
412 fd: linux.fd_t,
413413 buffer: []const u8,
414414 offset: u64,
415415) !*linux.io_uring_sqe {
......@@ -433,7 +433,7 @@ pub fn write(
433433/// See https://github.com/axboe/liburing/issues/291
434434///
435435/// Returns a pointer to the SQE so that you can further modify the SQE for advanced use cases.
436pub fn splice(self: *IoUring, user_data: u64, fd_in: posix.fd_t, off_in: u64, fd_out: posix.fd_t, off_out: u64, len: usize) !*linux.io_uring_sqe {
436pub fn splice(self: *IoUring, user_data: u64, fd_in: linux.fd_t, off_in: u64, fd_out: linux.fd_t, off_out: u64, len: usize) !*linux.io_uring_sqe {
437437 const sqe = try self.get_sqe();
438438 sqe.prep_splice(fd_in, off_in, fd_out, off_out, len);
439439 sqe.user_data = user_data;
......@@ -448,7 +448,7 @@ pub fn splice(self: *IoUring, user_data: u64, fd_in: posix.fd_t, off_in: u64, fd
448448pub fn read_fixed(
449449 self: *IoUring,
450450 user_data: u64,
451 fd: posix.fd_t,
451 fd: linux.fd_t,
452452 buffer: *posix.iovec,
453453 offset: u64,
454454 buffer_index: u16,
......@@ -466,7 +466,7 @@ pub fn read_fixed(
466466pub fn writev(
467467 self: *IoUring,
468468 user_data: u64,
469 fd: posix.fd_t,
469 fd: linux.fd_t,
470470 iovecs: []const posix.iovec_const,
471471 offset: u64,
472472) !*linux.io_uring_sqe {
......@@ -484,7 +484,7 @@ pub fn writev(
484484pub fn write_fixed(
485485 self: *IoUring,
486486 user_data: u64,
487 fd: posix.fd_t,
487 fd: linux.fd_t,
488488 buffer: *posix.iovec,
489489 offset: u64,
490490 buffer_index: u16,
......@@ -501,7 +501,7 @@ pub fn write_fixed(
501501pub fn accept(
502502 self: *IoUring,
503503 user_data: u64,
504 fd: posix.fd_t,
504 fd: linux.fd_t,
505505 addr: ?*posix.sockaddr,
506506 addrlen: ?*posix.socklen_t,
507507 flags: u32,
......@@ -523,7 +523,7 @@ pub fn accept(
523523pub fn accept_multishot(
524524 self: *IoUring,
525525 user_data: u64,
526 fd: posix.fd_t,
526 fd: linux.fd_t,
527527 addr: ?*posix.sockaddr,
528528 addrlen: ?*posix.socklen_t,
529529 flags: u32,
......@@ -548,7 +548,7 @@ pub fn accept_multishot(
548548pub fn accept_direct(
549549 self: *IoUring,
550550 user_data: u64,
551 fd: posix.fd_t,
551 fd: linux.fd_t,
552552 addr: ?*posix.sockaddr,
553553 addrlen: ?*posix.socklen_t,
554554 flags: u32,
......@@ -564,7 +564,7 @@ pub fn accept_direct(
564564pub fn accept_multishot_direct(
565565 self: *IoUring,
566566 user_data: u64,
567 fd: posix.fd_t,
567 fd: linux.fd_t,
568568 addr: ?*posix.sockaddr,
569569 addrlen: ?*posix.socklen_t,
570570 flags: u32,
......@@ -580,7 +580,7 @@ pub fn accept_multishot_direct(
580580pub fn connect(
581581 self: *IoUring,
582582 user_data: u64,
583 fd: posix.fd_t,
583 fd: linux.fd_t,
584584 addr: *const posix.sockaddr,
585585 addrlen: posix.socklen_t,
586586) !*linux.io_uring_sqe {
......@@ -595,8 +595,8 @@ pub fn connect(
595595pub fn epoll_ctl(
596596 self: *IoUring,
597597 user_data: u64,
598 epfd: posix.fd_t,
599 fd: posix.fd_t,
598 epfd: linux.fd_t,
599 fd: linux.fd_t,
600600 op: u32,
601601 ev: ?*linux.epoll_event,
602602) !*linux.io_uring_sqe {
......@@ -626,7 +626,7 @@ pub const RecvBuffer = union(enum) {
626626pub fn recv(
627627 self: *IoUring,
628628 user_data: u64,
629 fd: posix.fd_t,
629 fd: linux.fd_t,
630630 buffer: RecvBuffer,
631631 flags: u32,
632632) !*linux.io_uring_sqe {
......@@ -650,7 +650,7 @@ pub fn recv(
650650pub fn send(
651651 self: *IoUring,
652652 user_data: u64,
653 fd: posix.fd_t,
653 fd: linux.fd_t,
654654 buffer: []const u8,
655655 flags: u32,
656656) !*linux.io_uring_sqe {
......@@ -678,7 +678,7 @@ pub fn send(
678678pub fn send_zc(
679679 self: *IoUring,
680680 user_data: u64,
681 fd: posix.fd_t,
681 fd: linux.fd_t,
682682 buffer: []const u8,
683683 send_flags: u32,
684684 zc_flags: u16,
......@@ -695,7 +695,7 @@ pub fn send_zc(
695695pub fn send_zc_fixed(
696696 self: *IoUring,
697697 user_data: u64,
698 fd: posix.fd_t,
698 fd: linux.fd_t,
699699 buffer: []const u8,
700700 send_flags: u32,
701701 zc_flags: u16,
......@@ -713,8 +713,8 @@ pub fn send_zc_fixed(
713713pub fn recvmsg(
714714 self: *IoUring,
715715 user_data: u64,
716 fd: posix.fd_t,
717 msg: *posix.msghdr,
716 fd: linux.fd_t,
717 msg: *linux.msghdr,
718718 flags: u32,
719719) !*linux.io_uring_sqe {
720720 const sqe = try self.get_sqe();
......@@ -729,8 +729,8 @@ pub fn recvmsg(
729729pub fn sendmsg(
730730 self: *IoUring,
731731 user_data: u64,
732 fd: posix.fd_t,
733 msg: *const posix.msghdr_const,
732 fd: linux.fd_t,
733 msg: *const linux.msghdr_const,
734734 flags: u32,
735735) !*linux.io_uring_sqe {
736736 const sqe = try self.get_sqe();
......@@ -745,8 +745,8 @@ pub fn sendmsg(
745745pub fn sendmsg_zc(
746746 self: *IoUring,
747747 user_data: u64,
748 fd: posix.fd_t,
749 msg: *const posix.msghdr_const,
748 fd: linux.fd_t,
749 msg: *const linux.msghdr_const,
750750 flags: u32,
751751) !*linux.io_uring_sqe {
752752 const sqe = try self.get_sqe();
......@@ -761,7 +761,7 @@ pub fn sendmsg_zc(
761761pub fn openat(
762762 self: *IoUring,
763763 user_data: u64,
764 fd: posix.fd_t,
764 fd: linux.fd_t,
765765 path: [*:0]const u8,
766766 flags: linux.O,
767767 mode: posix.mode_t,
......@@ -786,7 +786,7 @@ pub fn openat(
786786pub fn openat_direct(
787787 self: *IoUring,
788788 user_data: u64,
789 fd: posix.fd_t,
789 fd: linux.fd_t,
790790 path: [*:0]const u8,
791791 flags: linux.O,
792792 mode: posix.mode_t,
......@@ -801,7 +801,7 @@ pub fn openat_direct(
801801/// Queues (but does not submit) an SQE to perform a `close(2)`.
802802/// Returns a pointer to the SQE.
803803/// Available since 5.6.
804pub fn close(self: *IoUring, user_data: u64, fd: posix.fd_t) !*linux.io_uring_sqe {
804pub fn close(self: *IoUring, user_data: u64, fd: linux.fd_t) !*linux.io_uring_sqe {
805805 const sqe = try self.get_sqe();
806806 sqe.prep_close(fd);
807807 sqe.user_data = user_data;
......@@ -896,7 +896,7 @@ pub fn link_timeout(
896896pub fn poll_add(
897897 self: *IoUring,
898898 user_data: u64,
899 fd: posix.fd_t,
899 fd: linux.fd_t,
900900 poll_mask: u32,
901901) !*linux.io_uring_sqe {
902902 const sqe = try self.get_sqe();
......@@ -939,7 +939,7 @@ pub fn poll_update(
939939pub fn fallocate(
940940 self: *IoUring,
941941 user_data: u64,
942 fd: posix.fd_t,
942 fd: linux.fd_t,
943943 mode: i32,
944944 offset: u64,
945945 len: u64,
......@@ -955,7 +955,7 @@ pub fn fallocate(
955955pub fn statx(
956956 self: *IoUring,
957957 user_data: u64,
958 fd: posix.fd_t,
958 fd: linux.fd_t,
959959 path: [:0]const u8,
960960 flags: u32,
961961 mask: u32,
......@@ -1008,9 +1008,9 @@ pub fn shutdown(
10081008pub fn renameat(
10091009 self: *IoUring,
10101010 user_data: u64,
1011 old_dir_fd: posix.fd_t,
1011 old_dir_fd: linux.fd_t,
10121012 old_path: [*:0]const u8,
1013 new_dir_fd: posix.fd_t,
1013 new_dir_fd: linux.fd_t,
10141014 new_path: [*:0]const u8,
10151015 flags: u32,
10161016) !*linux.io_uring_sqe {
......@@ -1025,7 +1025,7 @@ pub fn renameat(
10251025pub fn unlinkat(
10261026 self: *IoUring,
10271027 user_data: u64,
1028 dir_fd: posix.fd_t,
1028 dir_fd: linux.fd_t,
10291029 path: [*:0]const u8,
10301030 flags: u32,
10311031) !*linux.io_uring_sqe {
......@@ -1040,7 +1040,7 @@ pub fn unlinkat(
10401040pub fn mkdirat(
10411041 self: *IoUring,
10421042 user_data: u64,
1043 dir_fd: posix.fd_t,
1043 dir_fd: linux.fd_t,
10441044 path: [*:0]const u8,
10451045 mode: posix.mode_t,
10461046) !*linux.io_uring_sqe {
......@@ -1056,7 +1056,7 @@ pub fn symlinkat(
10561056 self: *IoUring,
10571057 user_data: u64,
10581058 target: [*:0]const u8,
1059 new_dir_fd: posix.fd_t,
1059 new_dir_fd: linux.fd_t,
10601060 link_path: [*:0]const u8,
10611061) !*linux.io_uring_sqe {
10621062 const sqe = try self.get_sqe();
......@@ -1070,9 +1070,9 @@ pub fn symlinkat(
10701070pub fn linkat(
10711071 self: *IoUring,
10721072 user_data: u64,
1073 old_dir_fd: posix.fd_t,
1073 old_dir_fd: linux.fd_t,
10741074 old_path: [*:0]const u8,
1075 new_dir_fd: posix.fd_t,
1075 new_dir_fd: linux.fd_t,
10761076 new_path: [*:0]const u8,
10771077 flags: u32,
10781078) !*linux.io_uring_sqe {
......@@ -1144,7 +1144,7 @@ pub fn waitid(
11441144/// Registering file descriptors will wait for the ring to idle.
11451145/// Files are automatically unregistered by the kernel when the ring is torn down.
11461146/// An application need unregister only if it wants to register a new array of file descriptors.
1147pub fn register_files(self: *IoUring, fds: []const posix.fd_t) !void {
1147pub fn register_files(self: *IoUring, fds: []const linux.fd_t) !void {
11481148 assert(self.fd >= 0);
11491149 const res = linux.io_uring_register(
11501150 self.fd,
......@@ -1163,7 +1163,7 @@ pub fn register_files(self: *IoUring, fds: []const posix.fd_t) !void {
11631163/// * removing an existing entry (set the fd to -1)
11641164/// * replacing an existing entry with a new fd
11651165/// Adding new file descriptors must be done with `register_files`.
1166pub fn register_files_update(self: *IoUring, offset: u32, fds: []const posix.fd_t) !void {
1166pub fn register_files_update(self: *IoUring, offset: u32, fds: []const linux.fd_t) !void {
11671167 assert(self.fd >= 0);
11681168
11691169 const FilesUpdate = extern struct {
......@@ -1232,7 +1232,7 @@ pub fn register_file_alloc_range(self: *IoUring, offset: u32, len: u32) !void {
12321232/// Registers the file descriptor for an eventfd that will be notified of completion events on
12331233/// an io_uring instance.
12341234/// Only a single a eventfd can be registered at any given point in time.
1235pub fn register_eventfd(self: *IoUring, fd: posix.fd_t) !void {
1235pub fn register_eventfd(self: *IoUring, fd: linux.fd_t) !void {
12361236 assert(self.fd >= 0);
12371237 const res = linux.io_uring_register(
12381238 self.fd,
......@@ -1247,7 +1247,7 @@ pub fn register_eventfd(self: *IoUring, fd: posix.fd_t) !void {
12471247/// an io_uring instance. Notifications are only posted for events that complete in an async manner.
12481248/// This means that events that complete inline while being submitted do not trigger a notification event.
12491249/// Only a single eventfd can be registered at any given point in time.
1250pub fn register_eventfd_async(self: *IoUring, fd: posix.fd_t) !void {
1250pub fn register_eventfd_async(self: *IoUring, fd: linux.fd_t) !void {
12511251 assert(self.fd >= 0);
12521252 const res = linux.io_uring_register(
12531253 self.fd,
......@@ -1405,7 +1405,7 @@ pub fn socket_direct_alloc(
14051405pub fn bind(
14061406 self: *IoUring,
14071407 user_data: u64,
1408 fd: posix.fd_t,
1408 fd: linux.fd_t,
14091409 addr: *const posix.sockaddr,
14101410 addrlen: posix.socklen_t,
14111411 flags: u32,
......@@ -1422,7 +1422,7 @@ pub fn bind(
14221422pub fn listen(
14231423 self: *IoUring,
14241424 user_data: u64,
1425 fd: posix.fd_t,
1425 fd: linux.fd_t,
14261426 backlog: usize,
14271427 flags: u32,
14281428) !*linux.io_uring_sqe {
......@@ -1513,7 +1513,7 @@ pub const SubmissionQueue = struct {
15131513 sqe_head: u32 = 0,
15141514 sqe_tail: u32 = 0,
15151515
1516 pub fn init(fd: posix.fd_t, p: linux.io_uring_params) !SubmissionQueue {
1516 pub fn init(fd: linux.fd_t, p: linux.io_uring_params) !SubmissionQueue {
15171517 assert(fd >= 0);
15181518 assert((p.features & linux.IORING_FEAT_SINGLE_MMAP) != 0);
15191519 const size = @max(
......@@ -1576,7 +1576,7 @@ pub const CompletionQueue = struct {
15761576 overflow: *u32,
15771577 cqes: []linux.io_uring_cqe,
15781578
1579 pub fn init(fd: posix.fd_t, p: linux.io_uring_params, sq: SubmissionQueue) !CompletionQueue {
1579 pub fn init(fd: linux.fd_t, p: linux.io_uring_params, sq: SubmissionQueue) !CompletionQueue {
15801580 assert(fd >= 0);
15811581 assert((p.features & linux.IORING_FEAT_SINGLE_MMAP) != 0);
15821582 const mmap = sq.mmap;
......@@ -1677,7 +1677,7 @@ pub const BufferGroup = struct {
16771677 }
16781678
16791679 // Prepare recv operation which will select buffer from this group.
1680 pub fn recv(self: *BufferGroup, user_data: u64, fd: posix.fd_t, flags: u32) !*linux.io_uring_sqe {
1680 pub fn recv(self: *BufferGroup, user_data: u64, fd: linux.fd_t, flags: u32) !*linux.io_uring_sqe {
16811681 var sqe = try self.ring.get_sqe();
16821682 sqe.prep_rw(.RECV, fd, 0, 0, 0);
16831683 sqe.rw_flags = flags;
......@@ -1688,7 +1688,7 @@ pub const BufferGroup = struct {
16881688 }
16891689
16901690 // Prepare multishot recv operation which will select buffer from this group.
1691 pub fn recv_multishot(self: *BufferGroup, user_data: u64, fd: posix.fd_t, flags: u32) !*linux.io_uring_sqe {
1691 pub fn recv_multishot(self: *BufferGroup, user_data: u64, fd: linux.fd_t, flags: u32) !*linux.io_uring_sqe {
16921692 var sqe = try self.recv(user_data, fd, flags);
16931693 sqe.ioprio |= linux.IORING_RECV_MULTISHOT;
16941694 return sqe;
......@@ -1732,7 +1732,7 @@ pub const BufferGroup = struct {
17321732/// `entries` is the number of entries requested in the buffer ring, must be power of 2.
17331733/// `group_id` is the chosen buffer group ID, unique in IO_Uring.
17341734pub fn setup_buf_ring(
1735 fd: posix.fd_t,
1735 fd: linux.fd_t,
17361736 entries: u16,
17371737 group_id: u16,
17381738 flags: linux.io_uring_buf_reg.Flags,
......@@ -1758,7 +1758,7 @@ pub fn setup_buf_ring(
17581758}
17591759
17601760fn register_buf_ring(
1761 fd: posix.fd_t,
1761 fd: linux.fd_t,
17621762 addr: u64,
17631763 entries: u32,
17641764 group_id: u16,
......@@ -1780,7 +1780,7 @@ fn register_buf_ring(
17801780 try handle_register_buf_ring_result(res);
17811781}
17821782
1783fn unregister_buf_ring(fd: posix.fd_t, group_id: u16) !void {
1783fn unregister_buf_ring(fd: linux.fd_t, group_id: u16) !void {
17841784 var reg = mem.zeroInit(linux.io_uring_buf_reg, .{
17851785 .bgid = group_id,
17861786 });
......@@ -1802,7 +1802,7 @@ fn handle_register_buf_ring_result(res: usize) !void {
18021802}
18031803
18041804// Unregisters a previously registered shared buffer ring, returned from io_uring_setup_buf_ring.
1805pub fn free_buf_ring(fd: posix.fd_t, br: *align(page_size_min) linux.io_uring_buf_ring, entries: u32, group_id: u16) void {
1805pub fn free_buf_ring(fd: linux.fd_t, br: *align(page_size_min) linux.io_uring_buf_ring, entries: u32, group_id: u16) void {
18061806 unregister_buf_ring(fd, group_id) catch {};
18071807 var mmap: []align(page_size_min) u8 = undefined;
18081808 mmap.ptr = @ptrCast(br);
......@@ -1873,7 +1873,7 @@ test "nop" {
18731873 };
18741874 defer {
18751875 ring.deinit();
1876 testing.expectEqual(@as(posix.fd_t, -1), ring.fd) catch @panic("test failed");
1876 testing.expectEqual(@as(linux.fd_t, -1), ring.fd) catch @panic("test failed");
18771877 }
18781878
18791879 const sqe = try ring.nop(0xaaaaaaaa);
......@@ -1949,7 +1949,7 @@ test "readv" {
19491949 // https://github.com/torvalds/linux/blob/v5.4/fs/io_uring.c#L3119-L3124 vs
19501950 // https://github.com/torvalds/linux/blob/v5.8/fs/io_uring.c#L6687-L6691
19511951 // We therefore avoid stressing sparse fd sets here:
1952 var registered_fds = [_]posix.fd_t{0} ** 1;
1952 var registered_fds = [_]linux.fd_t{0} ** 1;
19531953 const fd_index = 0;
19541954 registered_fds[fd_index] = fd;
19551955 try ring.register_files(registered_fds[0..]);
......@@ -2361,28 +2361,31 @@ test "sendmsg/recvmsg" {
23612361 };
23622362 defer ring.deinit();
23632363
2364 var address_server = try net.Address.parseIp4("127.0.0.1", 0);
2364 var address_server: linux.sockaddr.in = .{
2365 .port = 0,
2366 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
2367 };
23652368
2366 const server = try posix.socket(address_server.any.family, posix.SOCK.DGRAM, 0);
2369 const server = try posix.socket(address_server.family, posix.SOCK.DGRAM, 0);
23672370 defer posix.close(server);
23682371 try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEPORT, &mem.toBytes(@as(c_int, 1)));
23692372 try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));
2370 try posix.bind(server, &address_server.any, address_server.getOsSockLen());
2373 try posix.bind(server, addrAny(&address_server), @sizeOf(linux.sockaddr.in));
23712374
23722375 // set address_server to the OS-chosen IP/port.
2373 var slen: posix.socklen_t = address_server.getOsSockLen();
2374 try posix.getsockname(server, &address_server.any, &slen);
2376 var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in);
2377 try posix.getsockname(server, addrAny(&address_server), &slen);
23752378
2376 const client = try posix.socket(address_server.any.family, posix.SOCK.DGRAM, 0);
2379 const client = try posix.socket(address_server.family, posix.SOCK.DGRAM, 0);
23772380 defer posix.close(client);
23782381
23792382 const buffer_send = [_]u8{42} ** 128;
23802383 const iovecs_send = [_]posix.iovec_const{
23812384 posix.iovec_const{ .base = &buffer_send, .len = buffer_send.len },
23822385 };
2383 const msg_send: posix.msghdr_const = .{
2384 .name = &address_server.any,
2385 .namelen = address_server.getOsSockLen(),
2386 const msg_send: linux.msghdr_const = .{
2387 .name = addrAny(&address_server),
2388 .namelen = @sizeOf(linux.sockaddr.in),
23862389 .iov = &iovecs_send,
23872390 .iovlen = 1,
23882391 .control = null,
......@@ -2398,11 +2401,13 @@ test "sendmsg/recvmsg" {
23982401 var iovecs_recv = [_]posix.iovec{
23992402 posix.iovec{ .base = &buffer_recv, .len = buffer_recv.len },
24002403 };
2401 const addr = [_]u8{0} ** 4;
2402 var address_recv = net.Address.initIp4(addr, 0);
2403 var msg_recv: posix.msghdr = .{
2404 .name = &address_recv.any,
2405 .namelen = address_recv.getOsSockLen(),
2404 var address_recv: linux.sockaddr.in = .{
2405 .port = 0,
2406 .addr = 0,
2407 };
2408 var msg_recv: linux.msghdr = .{
2409 .name = addrAny(&address_recv),
2410 .namelen = @sizeOf(linux.sockaddr.in),
24062411 .iov = &iovecs_recv,
24072412 .iovlen = 1,
24082413 .control = null,
......@@ -2441,6 +2446,8 @@ test "sendmsg/recvmsg" {
24412446test "timeout (after a relative time)" {
24422447 if (!is_linux) return error.SkipZigTest;
24432448
2449 const io = testing.io;
2450
24442451 var ring = IoUring.init(1, 0) catch |err| switch (err) {
24452452 error.SystemOutdated => return error.SkipZigTest,
24462453 error.PermissionDenied => return error.SkipZigTest,
......@@ -2452,12 +2459,12 @@ test "timeout (after a relative time)" {
24522459 const margin = 5;
24532460 const ts: linux.kernel_timespec = .{ .sec = 0, .nsec = ms * 1000000 };
24542461
2455 const started = std.time.milliTimestamp();
2462 const started = try std.Io.Clock.awake.now(io);
24562463 const sqe = try ring.timeout(0x55555555, &ts, 0, 0);
24572464 try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe.opcode);
24582465 try testing.expectEqual(@as(u32, 1), try ring.submit());
24592466 const cqe = try ring.copy_cqe();
2460 const stopped = std.time.milliTimestamp();
2467 const stopped = try std.Io.Clock.awake.now(io);
24612468
24622469 try testing.expectEqual(linux.io_uring_cqe{
24632470 .user_data = 0x55555555,
......@@ -2466,7 +2473,8 @@ test "timeout (after a relative time)" {
24662473 }, cqe);
24672474
24682475 // Tests should not depend on timings: skip test if outside margin.
2469 if (!std.math.approxEqAbs(f64, ms, @as(f64, @floatFromInt(stopped - started)), margin)) return error.SkipZigTest;
2476 const ms_elapsed = started.durationTo(stopped).toMilliseconds();
2477 if (ms_elapsed > margin) return error.SkipZigTest;
24702478}
24712479
24722480test "timeout (after a number of completions)" {
......@@ -2777,7 +2785,7 @@ test "register_files_update" {
27772785 const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
27782786 defer posix.close(fd);
27792787
2780 var registered_fds = [_]posix.fd_t{0} ** 2;
2788 var registered_fds = [_]linux.fd_t{0} ** 2;
27812789 const fd_index = 0;
27822790 const fd_index2 = 1;
27832791 registered_fds[fd_index] = fd;
......@@ -2861,19 +2869,22 @@ test "shutdown" {
28612869 };
28622870 defer ring.deinit();
28632871
2864 var address = try net.Address.parseIp4("127.0.0.1", 0);
2872 var address: linux.sockaddr.in = .{
2873 .port = 0,
2874 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
2875 };
28652876
28662877 // Socket bound, expect shutdown to work
28672878 {
2868 const server = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
2879 const server = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
28692880 defer posix.close(server);
28702881 try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));
2871 try posix.bind(server, &address.any, address.getOsSockLen());
2882 try posix.bind(server, addrAny(&address), @sizeOf(linux.sockaddr.in));
28722883 try posix.listen(server, 1);
28732884
28742885 // set address to the OS-chosen IP/port.
2875 var slen: posix.socklen_t = address.getOsSockLen();
2876 try posix.getsockname(server, &address.any, &slen);
2886 var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in);
2887 try posix.getsockname(server, addrAny(&address), &slen);
28772888
28782889 const shutdown_sqe = try ring.shutdown(0x445445445, server, linux.SHUT.RD);
28792890 try testing.expectEqual(linux.IORING_OP.SHUTDOWN, shutdown_sqe.opcode);
......@@ -2898,7 +2909,7 @@ test "shutdown" {
28982909
28992910 // Socket not bound, expect to fail with ENOTCONN
29002911 {
2901 const server = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
2912 const server = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
29022913 defer posix.close(server);
29032914
29042915 const shutdown_sqe = ring.shutdown(0x445445445, server, linux.SHUT.RD) catch |err| switch (err) {
......@@ -2966,22 +2977,11 @@ test "renameat" {
29662977 }, cqe);
29672978
29682979 // Validate that the old file doesn't exist anymore
2969 {
2970 _ = tmp.dir.openFile(old_path, .{}) catch |err| switch (err) {
2971 error.FileNotFound => {},
2972 else => std.debug.panic("unexpected error: {}", .{err}),
2973 };
2974 }
2980 try testing.expectError(error.FileNotFound, tmp.dir.openFile(old_path, .{}));
29752981
29762982 // Validate that the new file exists with the proper content
2977 {
2978 const new_file = try tmp.dir.openFile(new_path, .{});
2979 defer new_file.close();
2980
2981 var new_file_data: [16]u8 = undefined;
2982 const bytes_read = try new_file.readAll(&new_file_data);
2983 try testing.expectEqualStrings("hello", new_file_data[0..bytes_read]);
2984 }
2983 var new_file_data: [16]u8 = undefined;
2984 try testing.expectEqualStrings("hello", try tmp.dir.readFile(new_path, &new_file_data));
29852985}
29862986
29872987test "unlinkat" {
......@@ -3179,12 +3179,8 @@ test "linkat" {
31793179 }, cqe);
31803180
31813181 // Validate the second file
3182 const second_file = try tmp.dir.openFile(second_path, .{});
3183 defer second_file.close();
3184
31853182 var second_file_data: [16]u8 = undefined;
3186 const bytes_read = try second_file.readAll(&second_file_data);
3187 try testing.expectEqualStrings("hello", second_file_data[0..bytes_read]);
3183 try testing.expectEqualStrings("hello", try tmp.dir.readFile(second_path, &second_file_data));
31883184}
31893185
31903186test "provide_buffers: read" {
......@@ -3588,7 +3584,10 @@ const SocketTestHarness = struct {
35883584
35893585fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness {
35903586 // Create a TCP server socket
3591 var address = try net.Address.parseIp4("127.0.0.1", 0);
3587 var address: linux.sockaddr.in = .{
3588 .port = 0,
3589 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
3590 };
35923591 const listener_socket = try createListenerSocket(&address);
35933592 errdefer posix.close(listener_socket);
35943593
......@@ -3598,9 +3597,9 @@ fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness {
35983597 _ = try ring.accept(0xaaaaaaaa, listener_socket, &accept_addr, &accept_addr_len, 0);
35993598
36003599 // Create a TCP client socket
3601 const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3600 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
36023601 errdefer posix.close(client);
3603 _ = try ring.connect(0xcccccccc, client, &address.any, address.getOsSockLen());
3602 _ = try ring.connect(0xcccccccc, client, addrAny(&address), @sizeOf(linux.sockaddr.in));
36043603
36053604 try testing.expectEqual(@as(u32, 2), try ring.submit());
36063605
......@@ -3636,18 +3635,18 @@ fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness {
36363635 };
36373636}
36383637
3639fn createListenerSocket(address: *net.Address) !posix.socket_t {
3638fn createListenerSocket(address: *linux.sockaddr.in) !posix.socket_t {
36403639 const kernel_backlog = 1;
3641 const listener_socket = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3640 const listener_socket = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
36423641 errdefer posix.close(listener_socket);
36433642
36443643 try posix.setsockopt(listener_socket, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));
3645 try posix.bind(listener_socket, &address.any, address.getOsSockLen());
3644 try posix.bind(listener_socket, addrAny(address), @sizeOf(linux.sockaddr.in));
36463645 try posix.listen(listener_socket, kernel_backlog);
36473646
36483647 // set address to the OS-chosen IP/port.
3649 var slen: posix.socklen_t = address.getOsSockLen();
3650 try posix.getsockname(listener_socket, &address.any, &slen);
3648 var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in);
3649 try posix.getsockname(listener_socket, addrAny(address), &slen);
36513650
36523651 return listener_socket;
36533652}
......@@ -3662,7 +3661,10 @@ test "accept multishot" {
36623661 };
36633662 defer ring.deinit();
36643663
3665 var address = try net.Address.parseIp4("127.0.0.1", 0);
3664 var address: linux.sockaddr.in = .{
3665 .port = 0,
3666 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
3667 };
36663668 const listener_socket = try createListenerSocket(&address);
36673669 defer posix.close(listener_socket);
36683670
......@@ -3676,9 +3678,9 @@ test "accept multishot" {
36763678 var nr: usize = 4; // number of clients to connect
36773679 while (nr > 0) : (nr -= 1) {
36783680 // connect client
3679 const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3681 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
36803682 errdefer posix.close(client);
3681 try posix.connect(client, &address.any, address.getOsSockLen());
3683 try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
36823684
36833685 // test accept completion
36843686 var cqe = try ring.copy_cqe();
......@@ -3756,10 +3758,13 @@ test "accept_direct" {
37563758 else => return err,
37573759 };
37583760 defer ring.deinit();
3759 var address = try net.Address.parseIp4("127.0.0.1", 0);
3761 var address: linux.sockaddr.in = .{
3762 .port = 0,
3763 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
3764 };
37603765
37613766 // register direct file descriptors
3762 var registered_fds = [_]posix.fd_t{-1} ** 2;
3767 var registered_fds = [_]linux.fd_t{-1} ** 2;
37633768 try ring.register_files(registered_fds[0..]);
37643769
37653770 const listener_socket = try createListenerSocket(&address);
......@@ -3779,8 +3784,8 @@ test "accept_direct" {
37793784 try testing.expectEqual(@as(u32, 1), try ring.submit());
37803785
37813786 // connect
3782 const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3783 try posix.connect(client, &address.any, address.getOsSockLen());
3787 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3788 try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
37843789 defer posix.close(client);
37853790
37863791 // accept completion
......@@ -3813,8 +3818,8 @@ test "accept_direct" {
38133818 _ = try ring.accept_direct(accept_userdata, listener_socket, null, null, 0);
38143819 try testing.expectEqual(@as(u32, 1), try ring.submit());
38153820 // connect
3816 const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3817 try posix.connect(client, &address.any, address.getOsSockLen());
3821 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3822 try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
38183823 defer posix.close(client);
38193824 // completion with error
38203825 const cqe_accept = try ring.copy_cqe();
......@@ -3830,6 +3835,11 @@ test "accept_direct" {
38303835test "accept_multishot_direct" {
38313836 try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 });
38323837
3838 if (builtin.cpu.arch == .riscv64) {
3839 // https://github.com/ziglang/zig/issues/25734
3840 return error.SkipZigTest;
3841 }
3842
38333843 var ring = IoUring.init(1, 0) catch |err| switch (err) {
38343844 error.SystemOutdated => return error.SkipZigTest,
38353845 error.PermissionDenied => return error.SkipZigTest,
......@@ -3837,9 +3847,12 @@ test "accept_multishot_direct" {
38373847 };
38383848 defer ring.deinit();
38393849
3840 var address = try net.Address.parseIp4("127.0.0.1", 0);
3850 var address: linux.sockaddr.in = .{
3851 .port = 0,
3852 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
3853 };
38413854
3842 var registered_fds = [_]posix.fd_t{-1} ** 2;
3855 var registered_fds = [_]linux.fd_t{-1} ** 2;
38433856 try ring.register_files(registered_fds[0..]);
38443857
38453858 const listener_socket = try createListenerSocket(&address);
......@@ -3855,8 +3868,8 @@ test "accept_multishot_direct" {
38553868
38563869 for (registered_fds) |_| {
38573870 // connect
3858 const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3859 try posix.connect(client, &address.any, address.getOsSockLen());
3871 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3872 try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
38603873 defer posix.close(client);
38613874
38623875 // accept completion
......@@ -3870,8 +3883,8 @@ test "accept_multishot_direct" {
38703883 // Multishot is terminated (more flag is not set).
38713884 {
38723885 // connect
3873 const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3874 try posix.connect(client, &address.any, address.getOsSockLen());
3886 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3887 try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
38753888 defer posix.close(client);
38763889 // completion with error
38773890 const cqe_accept = try ring.copy_cqe();
......@@ -3902,7 +3915,7 @@ test "socket" {
39023915 // test completion
39033916 var cqe = try ring.copy_cqe();
39043917 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
3905 const fd: posix.fd_t = @intCast(cqe.res);
3918 const fd: linux.fd_t = @intCast(cqe.res);
39063919 try testing.expect(fd > 2);
39073920
39083921 posix.close(fd);
......@@ -3918,7 +3931,7 @@ test "socket_direct/socket_direct_alloc/close_direct" {
39183931 };
39193932 defer ring.deinit();
39203933
3921 var registered_fds = [_]posix.fd_t{-1} ** 3;
3934 var registered_fds = [_]linux.fd_t{-1} ** 3;
39223935 try ring.register_files(registered_fds[0..]);
39233936
39243937 // create socket in registered file descriptor at index 0 (last param)
......@@ -3944,7 +3957,10 @@ test "socket_direct/socket_direct_alloc/close_direct" {
39443957 try testing.expect(cqe_socket.res == 2); // returns registered file index
39453958
39463959 // use sockets from registered_fds in connect operation
3947 var address = try net.Address.parseIp4("127.0.0.1", 0);
3960 var address: linux.sockaddr.in = .{
3961 .port = 0,
3962 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
3963 };
39483964 const listener_socket = try createListenerSocket(&address);
39493965 defer posix.close(listener_socket);
39503966 const accept_userdata: u64 = 0xaaaaaaaa;
......@@ -3954,7 +3970,7 @@ test "socket_direct/socket_direct_alloc/close_direct" {
39543970 // prepare accept
39553971 _ = try ring.accept(accept_userdata, listener_socket, null, null, 0);
39563972 // prepare connect with fixed socket
3957 const connect_sqe = try ring.connect(connect_userdata, @intCast(fd_index), &address.any, address.getOsSockLen());
3973 const connect_sqe = try ring.connect(connect_userdata, @intCast(fd_index), addrAny(&address), @sizeOf(linux.sockaddr.in));
39583974 connect_sqe.flags |= linux.IOSQE_FIXED_FILE; // fd is fixed file index
39593975 // submit both
39603976 try testing.expectEqual(@as(u32, 2), try ring.submit());
......@@ -3996,7 +4012,7 @@ test "openat_direct/close_direct" {
39964012 };
39974013 defer ring.deinit();
39984014
3999 var registered_fds = [_]posix.fd_t{-1} ** 3;
4015 var registered_fds = [_]linux.fd_t{-1} ** 3;
40004016 try ring.register_files(registered_fds[0..]);
40014017
40024018 var tmp = std.testing.tmpDir(.{});
......@@ -4383,7 +4399,7 @@ test "ring mapped buffers multishot recv" {
43834399fn buf_grp_recv_submit_get_cqe(
43844400 ring: *IoUring,
43854401 buf_grp: *BufferGroup,
4386 fd: posix.fd_t,
4402 fd: linux.fd_t,
43874403 user_data: u64,
43884404) !linux.io_uring_cqe {
43894405 // prepare and submit recv
......@@ -4483,24 +4499,27 @@ test "bind/listen/connect" {
44834499 // LISTEN is higher required operation
44844500 if (!probe.is_supported(.LISTEN)) return error.SkipZigTest;
44854501
4486 var addr = net.Address.initIp4([4]u8{ 127, 0, 0, 1 }, 0);
4487 const proto: u32 = if (addr.any.family == linux.AF.UNIX) 0 else linux.IPPROTO.TCP;
4502 var addr: linux.sockaddr.in = .{
4503 .port = 0,
4504 .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
4505 };
4506 const proto: u32 = if (addr.family == linux.AF.UNIX) 0 else linux.IPPROTO.TCP;
44884507
44894508 const listen_fd = brk: {
44904509 // Create socket
4491 _ = try ring.socket(1, addr.any.family, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, proto, 0);
4510 _ = try ring.socket(1, addr.family, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, proto, 0);
44924511 try testing.expectEqual(1, try ring.submit());
44934512 var cqe = try ring.copy_cqe();
44944513 try testing.expectEqual(1, cqe.user_data);
44954514 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
4496 const listen_fd: posix.fd_t = @intCast(cqe.res);
4515 const listen_fd: linux.fd_t = @intCast(cqe.res);
44974516 try testing.expect(listen_fd > 2);
44984517
44994518 // Prepare: set socket option * 2, bind, listen
45004519 var optval: u32 = 1;
45014520 (try ring.setsockopt(2, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEADDR, mem.asBytes(&optval))).link_next();
45024521 (try ring.setsockopt(3, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEPORT, mem.asBytes(&optval))).link_next();
4503 (try ring.bind(4, listen_fd, &addr.any, addr.getOsSockLen(), 0)).link_next();
4522 (try ring.bind(4, listen_fd, addrAny(&addr), @sizeOf(linux.sockaddr.in), 0)).link_next();
45044523 _ = try ring.listen(5, listen_fd, 1, 0);
45054524 // Submit 4 operations
45064525 try testing.expectEqual(4, try ring.submit());
......@@ -4521,28 +4540,28 @@ test "bind/listen/connect" {
45214540 try testing.expectEqual(1, optval);
45224541
45234542 // Read system assigned port into addr
4524 var addr_len: posix.socklen_t = addr.getOsSockLen();
4525 try posix.getsockname(listen_fd, &addr.any, &addr_len);
4543 var addr_len: posix.socklen_t = @sizeOf(linux.sockaddr.in);
4544 try posix.getsockname(listen_fd, addrAny(&addr), &addr_len);
45264545
45274546 break :brk listen_fd;
45284547 };
45294548
45304549 const connect_fd = brk: {
45314550 // Create connect socket
4532 _ = try ring.socket(6, addr.any.family, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, proto, 0);
4551 _ = try ring.socket(6, addr.family, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, proto, 0);
45334552 try testing.expectEqual(1, try ring.submit());
45344553 const cqe = try ring.copy_cqe();
45354554 try testing.expectEqual(6, cqe.user_data);
45364555 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
45374556 // Get connect socket fd
4538 const connect_fd: posix.fd_t = @intCast(cqe.res);
4557 const connect_fd: linux.fd_t = @intCast(cqe.res);
45394558 try testing.expect(connect_fd > 2 and connect_fd != listen_fd);
45404559 break :brk connect_fd;
45414560 };
45424561
45434562 // Prepare accept/connect operations
45444563 _ = try ring.accept(7, listen_fd, null, null, 0);
4545 _ = try ring.connect(8, connect_fd, &addr.any, addr.getOsSockLen());
4564 _ = try ring.connect(8, connect_fd, addrAny(&addr), @sizeOf(linux.sockaddr.in));
45464565 try testing.expectEqual(2, try ring.submit());
45474566 // Get listener accepted socket
45484567 var accept_fd: posix.socket_t = 0;
......@@ -4604,3 +4623,7 @@ fn testSendRecv(ring: *IoUring, send_fd: posix.socket_t, recv_fd: posix.socket_t
46044623 try testing.expectEqualSlices(u8, buffer_send, buffer_recv[0..buffer_send.len]);
46054624 try testing.expectEqualSlices(u8, buffer_send, buffer_recv[buffer_send.len..]);
46064625}
4626
4627fn addrAny(addr: *linux.sockaddr.in) *linux.sockaddr {
4628 return @ptrCast(addr);
4629}
lib/std/os/linux/s390x.zig+7-1
......@@ -136,7 +136,13 @@ pub fn clone() callconv(.naked) u64 {
136136 );
137137}
138138
139pub const restore = restore_rt;
139pub fn restore() callconv(.naked) noreturn {
140 asm volatile (
141 \\svc 0
142 :
143 : [number] "{r1}" (@intFromEnum(SYS.sigreturn)),
144 );
145}
140146
141147pub fn restore_rt() callconv(.naked) noreturn {
142148 asm volatile (
lib/std/os/linux/test.zig+24-46
......@@ -1,5 +1,7 @@
1const std = @import("../../std.zig");
21const builtin = @import("builtin");
2
3const std = @import("../../std.zig");
4const assert = std.debug.assert;
35const linux = std.os.linux;
46const mem = std.mem;
57const elf = std.elf;
......@@ -128,58 +130,32 @@ test "fadvise" {
128130}
129131
130132test "sigset_t" {
131 std.debug.assert(@sizeOf(linux.sigset_t) == (linux.NSIG / 8));
133 const SIG = linux.SIG;
134 assert(@sizeOf(linux.sigset_t) == (linux.NSIG / 8));
132135
133136 var sigset = linux.sigemptyset();
134137
135138 // See that none are set, then set each one, see that they're all set, then
136139 // remove them all, and then see that none are set.
137140 for (1..linux.NSIG) |i| {
138 try expectEqual(linux.sigismember(&sigset, @truncate(i)), false);
141 const sig = std.meta.intToEnum(SIG, i) catch continue;
142 try expectEqual(false, linux.sigismember(&sigset, sig));
139143 }
140144 for (1..linux.NSIG) |i| {
141 linux.sigaddset(&sigset, @truncate(i));
145 const sig = std.meta.intToEnum(SIG, i) catch continue;
146 linux.sigaddset(&sigset, sig);
142147 }
143148 for (1..linux.NSIG) |i| {
144 try expectEqual(linux.sigismember(&sigset, @truncate(i)), true);
149 const sig = std.meta.intToEnum(SIG, i) catch continue;
150 try expectEqual(true, linux.sigismember(&sigset, sig));
145151 }
146152 for (1..linux.NSIG) |i| {
147 linux.sigdelset(&sigset, @truncate(i));
153 const sig = std.meta.intToEnum(SIG, i) catch continue;
154 linux.sigdelset(&sigset, sig);
148155 }
149156 for (1..linux.NSIG) |i| {
150 try expectEqual(linux.sigismember(&sigset, @truncate(i)), false);
151 }
152
153 // Kernel sigset_t is either 2+ 32-bit values or 1+ 64-bit value(s).
154 const sigset_len = @typeInfo(linux.sigset_t).array.len;
155 const sigset_elemis64 = 64 == @bitSizeOf(@typeInfo(linux.sigset_t).array.child);
156
157 linux.sigaddset(&sigset, 1);
158 try expectEqual(sigset[0], 1);
159 if (sigset_len > 1) {
160 try expectEqual(sigset[1], 0);
161 }
162
163 linux.sigaddset(&sigset, 31);
164 try expectEqual(sigset[0], 0x4000_0001);
165 if (sigset_len > 1) {
166 try expectEqual(sigset[1], 0);
167 }
168
169 linux.sigaddset(&sigset, 36);
170 if (sigset_elemis64) {
171 try expectEqual(sigset[0], 0x8_4000_0001);
172 } else {
173 try expectEqual(sigset[0], 0x4000_0001);
174 try expectEqual(sigset[1], 0x8);
175 }
176
177 linux.sigaddset(&sigset, 64);
178 if (sigset_elemis64) {
179 try expectEqual(sigset[0], 0x8000_0008_4000_0001);
180 } else {
181 try expectEqual(sigset[0], 0x4000_0001);
182 try expectEqual(sigset[1], 0x8000_0008);
157 const sig = std.meta.intToEnum(SIG, i) catch continue;
158 try expectEqual(false, linux.sigismember(&sigset, sig));
183159 }
184160}
185161
......@@ -187,14 +163,16 @@ test "sigfillset" {
187163 // unlike the C library, all the signals are set in the kernel-level fillset
188164 const sigset = linux.sigfillset();
189165 for (1..linux.NSIG) |i| {
190 try expectEqual(linux.sigismember(&sigset, @truncate(i)), true);
166 const sig = std.meta.intToEnum(linux.SIG, i) catch continue;
167 try expectEqual(true, linux.sigismember(&sigset, sig));
191168 }
192169}
193170
194171test "sigemptyset" {
195172 const sigset = linux.sigemptyset();
196173 for (1..linux.NSIG) |i| {
197 try expectEqual(linux.sigismember(&sigset, @truncate(i)), false);
174 const sig = std.meta.intToEnum(linux.SIG, i) catch continue;
175 try expectEqual(false, linux.sigismember(&sigset, sig));
198176 }
199177}
200178
......@@ -208,14 +186,14 @@ test "sysinfo" {
208186}
209187
210188comptime {
211 std.debug.assert(128 == @as(u32, @bitCast(linux.FUTEX_OP{ .cmd = @enumFromInt(0), .private = true, .realtime = false })));
212 std.debug.assert(256 == @as(u32, @bitCast(linux.FUTEX_OP{ .cmd = @enumFromInt(0), .private = false, .realtime = true })));
189 assert(128 == @as(u32, @bitCast(linux.FUTEX_OP{ .cmd = @enumFromInt(0), .private = true, .realtime = false })));
190 assert(256 == @as(u32, @bitCast(linux.FUTEX_OP{ .cmd = @enumFromInt(0), .private = false, .realtime = true })));
213191
214192 // Check futex_param4 union is packed correctly
215193 const param_union = linux.futex_param4{
216194 .val2 = 0xaabbcc,
217195 };
218 std.debug.assert(@intFromPtr(param_union.timeout) == 0xaabbcc);
196 assert(@intFromPtr(param_union.timeout) == 0xaabbcc);
219197}
220198
221199test "futex v1" {
......@@ -298,8 +276,8 @@ test "futex v1" {
298276}
299277
300278comptime {
301 std.debug.assert(2 == @as(u32, @bitCast(linux.FUTEX2_FLAGS{ .size = .U32, .private = false })));
302 std.debug.assert(128 == @as(u32, @bitCast(linux.FUTEX2_FLAGS{ .size = @enumFromInt(0), .private = true })));
279 assert(2 == @as(u32, @bitCast(linux.FUTEX2_FLAGS{ .size = .U32, .private = false })));
280 assert(128 == @as(u32, @bitCast(linux.FUTEX2_FLAGS{ .size = @enumFromInt(0), .private = true })));
303281}
304282
305283test "futex2_waitv" {
lib/std/os/linux/x86.zig+2
......@@ -159,12 +159,14 @@ pub fn clone() callconv(.naked) u32 {
159159pub fn restore() callconv(.naked) noreturn {
160160 switch (builtin.zig_backend) {
161161 .stage2_c => asm volatile (
162 \\ addl $4, %%esp
162163 \\ movl %[number], %%eax
163164 \\ int $0x80
164165 :
165166 : [number] "i" (@intFromEnum(SYS.sigreturn)),
166167 ),
167168 else => asm volatile (
169 \\ addl $4, %%esp
168170 \\ int $0x80
169171 :
170172 : [number] "{eax}" (@intFromEnum(SYS.sigreturn)),
lib/std/os/windows.zig+51-177
......@@ -5,12 +5,14 @@
55//! slices as well as APIs which accept null-terminated WTF16LE byte buffers.
66
77const builtin = @import("builtin");
8const native_arch = builtin.cpu.arch;
9
810const std = @import("../std.zig");
11const Io = std.Io;
912const mem = std.mem;
1013const assert = std.debug.assert;
1114const math = std.math;
1215const maxInt = std.math.maxInt;
13const native_arch = builtin.cpu.arch;
1416const UnexpectedError = std.posix.UnexpectedError;
1517
1618test {
......@@ -87,7 +89,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
8789 };
8890 var attr = OBJECT_ATTRIBUTES{
8991 .Length = @sizeOf(OBJECT_ATTRIBUTES),
90 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else options.dir,
92 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir,
9193 .Attributes = if (options.sa) |ptr| blk: { // Note we do not use OBJ_CASE_INSENSITIVE here.
9294 const inherit: ULONG = if (ptr.bInheritHandle == TRUE) OBJ_INHERIT else 0;
9395 break :blk inherit;
......@@ -146,7 +148,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
146148 // call has failed. There is not really a sane way to handle
147149 // this other than retrying the creation after the OS finishes
148150 // the deletion.
149 std.Thread.sleep(std.time.ns_per_ms);
151 _ = kernel32.SleepEx(1, TRUE);
150152 continue;
151153 },
152154 .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
......@@ -604,7 +606,7 @@ pub const ReadFileError = error{
604606 BrokenPipe,
605607 /// The specified network name is no longer available.
606608 ConnectionResetByPeer,
607 OperationAborted,
609 Canceled,
608610 /// Unable to read file due to lock.
609611 LockViolation,
610612 /// Known to be possible when:
......@@ -654,7 +656,7 @@ pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usiz
654656
655657pub const WriteFileError = error{
656658 SystemResources,
657 OperationAborted,
659 Canceled,
658660 BrokenPipe,
659661 NotOpenForWriting,
660662 /// The process cannot access the file because another process has locked
......@@ -694,7 +696,7 @@ pub fn WriteFile(
694696 switch (GetLastError()) {
695697 .INVALID_USER_BUFFER => return error.SystemResources,
696698 .NOT_ENOUGH_MEMORY => return error.SystemResources,
697 .OPERATION_ABORTED => return error.OperationAborted,
699 .OPERATION_ABORTED => return error.Canceled,
698700 .NOT_ENOUGH_QUOTA => return error.SystemResources,
699701 .IO_PENDING => unreachable,
700702 .NO_DATA => return error.BrokenPipe,
......@@ -845,7 +847,7 @@ pub fn CreateSymbolicLink(
845847 // the C:\ drive.
846848 .rooted => break :target_path target_path,
847849 // Keep relative paths relative, but anything else needs to get NT-prefixed.
848 else => if (!std.fs.path.isAbsoluteWindowsWTF16(target_path))
850 else => if (!std.fs.path.isAbsoluteWindowsWtf16(target_path))
849851 break :target_path target_path,
850852 },
851853 // Already an NT path, no need to do anything to it
......@@ -854,7 +856,7 @@ pub fn CreateSymbolicLink(
854856 }
855857 var prefixed_target_path = try wToPrefixedFileW(dir, target_path);
856858 // We do this after prefixing to ensure that drive-relative paths are treated as absolute
857 is_target_absolute = std.fs.path.isAbsoluteWindowsWTF16(prefixed_target_path.span());
859 is_target_absolute = std.fs.path.isAbsoluteWindowsWtf16(prefixed_target_path.span());
858860 break :target_path prefixed_target_path.span();
859861 };
860862
......@@ -862,7 +864,7 @@ pub fn CreateSymbolicLink(
862864 var buffer: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
863865 const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4;
864866 const header_len = @sizeOf(ULONG) + @sizeOf(USHORT) * 2;
865 const target_is_absolute = std.fs.path.isAbsoluteWindowsWTF16(final_target_path);
867 const target_is_absolute = std.fs.path.isAbsoluteWindowsWtf16(final_target_path);
866868 const symlink_data = SYMLINK_DATA{
867869 .ReparseTag = IO_REPARSE_TAG_SYMLINK,
868870 .ReparseDataLength = @intCast(buf_len - header_len),
......@@ -903,7 +905,7 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin
903905 };
904906 var attr = OBJECT_ATTRIBUTES{
905907 .Length = @sizeOf(OBJECT_ATTRIBUTES),
906 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else dir,
908 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir,
907909 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
908910 .ObjectName = &nt_name,
909911 .SecurityDescriptor = null,
......@@ -1033,7 +1035,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
10331035
10341036 var attr = OBJECT_ATTRIBUTES{
10351037 .Length = @sizeOf(OBJECT_ATTRIBUTES),
1036 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else options.dir,
1038 .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir,
10371039 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
10381040 .ObjectName = &nt_name,
10391041 .SecurityDescriptor = null,
......@@ -1572,131 +1574,6 @@ pub fn GetFileAttributesW(lpFileName: [*:0]const u16) GetFileAttributesError!DWO
15721574 return rc;
15731575}
15741576
1575pub fn WSAStartup(majorVersion: u8, minorVersion: u8) !ws2_32.WSADATA {
1576 var wsadata: ws2_32.WSADATA = undefined;
1577 return switch (ws2_32.WSAStartup((@as(WORD, minorVersion) << 8) | majorVersion, &wsadata)) {
1578 0 => wsadata,
1579 else => |err_int| switch (@as(ws2_32.WinsockError, @enumFromInt(@as(u16, @intCast(err_int))))) {
1580 .WSASYSNOTREADY => return error.SystemNotAvailable,
1581 .WSAVERNOTSUPPORTED => return error.VersionNotSupported,
1582 .WSAEINPROGRESS => return error.BlockingOperationInProgress,
1583 .WSAEPROCLIM => return error.ProcessFdQuotaExceeded,
1584 else => |err| return unexpectedWSAError(err),
1585 },
1586 };
1587}
1588
1589pub fn WSACleanup() !void {
1590 return switch (ws2_32.WSACleanup()) {
1591 0 => {},
1592 ws2_32.SOCKET_ERROR => switch (ws2_32.WSAGetLastError()) {
1593 .WSANOTINITIALISED => return error.NotInitialized,
1594 .WSAENETDOWN => return error.NetworkNotAvailable,
1595 .WSAEINPROGRESS => return error.BlockingOperationInProgress,
1596 else => |err| return unexpectedWSAError(err),
1597 },
1598 else => unreachable,
1599 };
1600}
1601
1602var wsa_startup_mutex: std.Thread.Mutex = .{};
1603
1604pub fn callWSAStartup() !void {
1605 wsa_startup_mutex.lock();
1606 defer wsa_startup_mutex.unlock();
1607
1608 // Here we could use a flag to prevent multiple threads to prevent
1609 // multiple calls to WSAStartup, but it doesn't matter. We're globally
1610 // leaking the resource intentionally, and the mutex already prevents
1611 // data races within the WSAStartup function.
1612 _ = WSAStartup(2, 2) catch |err| switch (err) {
1613 error.SystemNotAvailable => return error.SystemResources,
1614 error.VersionNotSupported => return error.Unexpected,
1615 error.BlockingOperationInProgress => return error.Unexpected,
1616 error.ProcessFdQuotaExceeded => return error.ProcessFdQuotaExceeded,
1617 error.Unexpected => return error.Unexpected,
1618 };
1619}
1620
1621/// Microsoft requires WSAStartup to be called to initialize, or else
1622/// WSASocketW will return WSANOTINITIALISED.
1623/// Since this is a standard library, we do not have the luxury of
1624/// putting initialization code anywhere, because we would not want
1625/// to pay the cost of calling WSAStartup if there ended up being no
1626/// networking. Also, if Zig code is used as a library, Zig is not in
1627/// charge of the start code, and we couldn't put in any initialization
1628/// code even if we wanted to.
1629/// The documentation for WSAStartup mentions that there must be a
1630/// matching WSACleanup call. It is not possible for the Zig Standard
1631/// Library to honor this for the same reason - there is nowhere to put
1632/// deinitialization code.
1633/// So, API users of the zig std lib have two options:
1634/// * (recommended) The simple, cross-platform way: just call `WSASocketW`
1635/// and don't worry about it. Zig will call WSAStartup() in a thread-safe
1636/// manner and never deinitialize networking. This is ideal for an
1637/// application which has the capability to do networking.
1638/// * The getting-your-hands-dirty way: call `WSAStartup()` before doing
1639/// networking, so that the error handling code for WSANOTINITIALISED never
1640/// gets run, which then allows the application or library to call `WSACleanup()`.
1641/// This could make sense for a library, which has init and deinit
1642/// functions for the whole library's lifetime.
1643pub fn WSASocketW(
1644 af: i32,
1645 socket_type: i32,
1646 protocol: i32,
1647 protocolInfo: ?*ws2_32.WSAPROTOCOL_INFOW,
1648 g: ws2_32.GROUP,
1649 dwFlags: DWORD,
1650) !ws2_32.SOCKET {
1651 var first = true;
1652 while (true) {
1653 const rc = ws2_32.WSASocketW(af, socket_type, protocol, protocolInfo, g, dwFlags);
1654 if (rc == ws2_32.INVALID_SOCKET) {
1655 switch (ws2_32.WSAGetLastError()) {
1656 .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported,
1657 .WSAEMFILE => return error.ProcessFdQuotaExceeded,
1658 .WSAENOBUFS => return error.SystemResources,
1659 .WSAEPROTONOSUPPORT => return error.ProtocolNotSupported,
1660 .WSANOTINITIALISED => {
1661 if (!first) return error.Unexpected;
1662 first = false;
1663 try callWSAStartup();
1664 continue;
1665 },
1666 else => |err| return unexpectedWSAError(err),
1667 }
1668 }
1669 return rc;
1670 }
1671}
1672
1673pub fn bind(s: ws2_32.SOCKET, name: *const ws2_32.sockaddr, namelen: ws2_32.socklen_t) i32 {
1674 return ws2_32.bind(s, name, @as(i32, @intCast(namelen)));
1675}
1676
1677pub fn listen(s: ws2_32.SOCKET, backlog: u31) i32 {
1678 return ws2_32.listen(s, backlog);
1679}
1680
1681pub fn closesocket(s: ws2_32.SOCKET) !void {
1682 switch (ws2_32.closesocket(s)) {
1683 0 => {},
1684 ws2_32.SOCKET_ERROR => switch (ws2_32.WSAGetLastError()) {
1685 else => |err| return unexpectedWSAError(err),
1686 },
1687 else => unreachable,
1688 }
1689}
1690
1691pub fn accept(s: ws2_32.SOCKET, name: ?*ws2_32.sockaddr, namelen: ?*ws2_32.socklen_t) ws2_32.SOCKET {
1692 assert((name == null) == (namelen == null));
1693 return ws2_32.accept(s, name, @as(?*i32, @ptrCast(namelen)));
1694}
1695
1696pub fn getsockname(s: ws2_32.SOCKET, name: *ws2_32.sockaddr, namelen: *ws2_32.socklen_t) i32 {
1697 return ws2_32.getsockname(s, name, @as(*i32, @ptrCast(namelen)));
1698}
1699
17001577pub fn getpeername(s: ws2_32.SOCKET, name: *ws2_32.sockaddr, namelen: *ws2_32.socklen_t) i32 {
17011578 return ws2_32.getpeername(s, name, @as(*i32, @ptrCast(namelen)));
17021579}
......@@ -2219,25 +2096,25 @@ pub fn peb() *PEB {
22192096/// Universal Time (UTC).
22202097/// This function returns the number of nanoseconds since the canonical epoch,
22212098/// which is the POSIX one (Jan 01, 1970 AD).
2222pub fn fromSysTime(hns: i64) i128 {
2099pub fn fromSysTime(hns: i64) Io.Timestamp {
22232100 const adjusted_epoch: i128 = hns + std.time.epoch.windows * (std.time.ns_per_s / 100);
2224 return adjusted_epoch * 100;
2101 return .fromNanoseconds(@intCast(adjusted_epoch * 100));
22252102}
22262103
2227pub fn toSysTime(ns: i128) i64 {
2228 const hns = @divFloor(ns, 100);
2104pub fn toSysTime(ns: Io.Timestamp) i64 {
2105 const hns = @divFloor(ns.nanoseconds, 100);
22292106 return @as(i64, @intCast(hns)) - std.time.epoch.windows * (std.time.ns_per_s / 100);
22302107}
22312108
2232pub fn fileTimeToNanoSeconds(ft: FILETIME) i128 {
2109pub fn fileTimeToNanoSeconds(ft: FILETIME) Io.Timestamp {
22332110 const hns = (@as(i64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
22342111 return fromSysTime(hns);
22352112}
22362113
22372114/// Converts a number of nanoseconds since the POSIX epoch to a Windows FILETIME.
2238pub fn nanoSecondsToFileTime(ns: i128) FILETIME {
2115pub fn nanoSecondsToFileTime(ns: Io.Timestamp) FILETIME {
22392116 const adjusted: u64 = @bitCast(toSysTime(ns));
2240 return FILETIME{
2117 return .{
22412118 .dwHighDateTime = @as(u32, @truncate(adjusted >> 32)),
22422119 .dwLowDateTime = @as(u32, @truncate(adjusted)),
22432120 };
......@@ -2425,7 +2302,7 @@ pub fn normalizePath(comptime T: type, path: []T) RemoveDotDirsError!usize {
24252302 return prefix_len + try removeDotDirsSanitized(T, path[prefix_len..new_len]);
24262303}
24272304
2428pub const Wtf8ToPrefixedFileWError = error{InvalidWtf8} || Wtf16ToPrefixedFileWError;
2305pub const Wtf8ToPrefixedFileWError = Wtf16ToPrefixedFileWError;
24292306
24302307/// Same as `sliceToPrefixedFileW` but accepts a pointer
24312308/// to a null-terminated WTF-8 encoded path.
......@@ -2438,7 +2315,9 @@ pub fn cStrToPrefixedFileW(dir: ?HANDLE, s: [*:0]const u8) Wtf8ToPrefixedFileWEr
24382315/// https://wtf-8.codeberg.page/
24392316pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) Wtf8ToPrefixedFileWError!PathSpace {
24402317 var temp_path: PathSpace = undefined;
2441 temp_path.len = try std.unicode.wtf8ToWtf16Le(&temp_path.data, path);
2318 temp_path.len = std.unicode.wtf8ToWtf16Le(&temp_path.data, path) catch |err| switch (err) {
2319 error.InvalidWtf8 => return error.BadPathName,
2320 };
24422321 temp_path.data[temp_path.len] = 0;
24432322 return wToPrefixedFileW(dir, temp_path.span());
24442323}
......@@ -2812,38 +2691,6 @@ inline fn MAKELANGID(p: c_ushort, s: c_ushort) LANGID {
28122691 return (s << 10) | p;
28132692}
28142693
2815/// Loads a Winsock extension function in runtime specified by a GUID.
2816pub fn loadWinsockExtensionFunction(comptime T: type, sock: ws2_32.SOCKET, guid: GUID) !T {
2817 var function: T = undefined;
2818 var num_bytes: DWORD = undefined;
2819
2820 const rc = ws2_32.WSAIoctl(
2821 sock,
2822 ws2_32.SIO_GET_EXTENSION_FUNCTION_POINTER,
2823 &guid,
2824 @sizeOf(GUID),
2825 @as(?*anyopaque, @ptrFromInt(@intFromPtr(&function))),
2826 @sizeOf(T),
2827 &num_bytes,
2828 null,
2829 null,
2830 );
2831
2832 if (rc == ws2_32.SOCKET_ERROR) {
2833 return switch (ws2_32.WSAGetLastError()) {
2834 .WSAEOPNOTSUPP => error.OperationNotSupported,
2835 .WSAENOTSOCK => error.FileDescriptorNotASocket,
2836 else => |err| unexpectedWSAError(err),
2837 };
2838 }
2839
2840 if (num_bytes != @sizeOf(T)) {
2841 return error.ShortRead;
2842 }
2843
2844 return function;
2845}
2846
28472694/// Call this when you made a windows DLL call or something that does SetLastError
28482695/// and you get an unexpected error.
28492696pub fn unexpectedError(err: Win32Error) UnexpectedError {
......@@ -2881,6 +2728,20 @@ pub fn unexpectedStatus(status: NTSTATUS) UnexpectedError {
28812728 return error.Unexpected;
28822729}
28832730
2731pub fn statusBug(status: NTSTATUS) UnexpectedError {
2732 switch (builtin.mode) {
2733 .Debug => std.debug.panic("programmer bug caused syscall status: {t}", .{status}),
2734 else => return error.Unexpected,
2735 }
2736}
2737
2738pub fn errorBug(err: Win32Error) UnexpectedError {
2739 switch (builtin.mode) {
2740 .Debug => std.debug.panic("programmer bug caused syscall status: {t}", .{err}),
2741 else => return error.Unexpected,
2742 }
2743}
2744
28842745pub const Win32Error = @import("windows/win32error.zig").Win32Error;
28852746pub const NTSTATUS = @import("windows/ntstatus.zig").NTSTATUS;
28862747pub const LANG = @import("windows/lang.zig");
......@@ -5737,3 +5598,16 @@ pub fn ProcessBaseAddress(handle: HANDLE) ProcessBaseAddressError!HMODULE {
57375598 const ppeb: *const PEB = @ptrCast(@alignCast(peb_out.ptr));
57385599 return ppeb.ImageBaseAddress;
57395600}
5601
5602pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) error{ BadPathName, NameTooLong }!usize {
5603 // Each u8 in UTF-8/WTF-8 correlates to at most one u16 in UTF-16LE/WTF-16LE.
5604 if (wtf16le.len < wtf8.len) {
5605 const utf16_len = std.unicode.calcUtf16LeLenImpl(wtf8, .can_encode_surrogate_half) catch
5606 return error.BadPathName;
5607 if (utf16_len > wtf16le.len)
5608 return error.NameTooLong;
5609 }
5610 return std.unicode.wtf8ToWtf16Le(wtf16le, wtf8) catch |err| switch (err) {
5611 error.InvalidWtf8 => return error.BadPathName,
5612 };
5613}
lib/std/os/windows/kernel32.zig+4-3
......@@ -326,10 +326,11 @@ pub extern "kernel32" fn ExitProcess(
326326 exit_code: UINT,
327327) callconv(.winapi) noreturn;
328328
329// TODO: SleepEx with bAlertable=false.
330pub extern "kernel32" fn Sleep(
329// TODO: implement via ntdll instead
330pub extern "kernel32" fn SleepEx(
331331 dwMilliseconds: DWORD,
332) callconv(.winapi) void;
332 bAlertable: BOOL,
333) callconv(.winapi) DWORD;
333334
334335// TODO: Wrapper around NtQueryInformationProcess with `PROCESS_BASIC_INFORMATION`.
335336pub extern "kernel32" fn GetExitCodeProcess(
lib/std/os/windows/test.zig-25
......@@ -237,28 +237,3 @@ test "removeDotDirs" {
237237 try testRemoveDotDirs("a\\b\\..\\", "a\\");
238238 try testRemoveDotDirs("a\\b\\..\\c", "a\\c");
239239}
240
241test "loadWinsockExtensionFunction" {
242 _ = try windows.WSAStartup(2, 2);
243 defer windows.WSACleanup() catch unreachable;
244
245 const LPFN_CONNECTEX = *const fn (
246 Socket: windows.ws2_32.SOCKET,
247 SockAddr: *const windows.ws2_32.sockaddr,
248 SockLen: std.posix.socklen_t,
249 SendBuf: ?*const anyopaque,
250 SendBufLen: windows.DWORD,
251 BytesSent: *windows.DWORD,
252 Overlapped: *windows.OVERLAPPED,
253 ) callconv(.winapi) windows.BOOL;
254
255 _ = windows.loadWinsockExtensionFunction(
256 LPFN_CONNECTEX,
257 try std.posix.socket(std.posix.AF.INET, std.posix.SOCK.DGRAM, 0),
258 windows.ws2_32.WSAID_CONNECTEX,
259 ) catch |err| switch (err) {
260 error.OperationNotSupported => unreachable,
261 error.ShortRead => unreachable,
262 else => |e| return e,
263 };
264}
lib/std/os/windows/ws2_32.zig+137-333
......@@ -702,28 +702,32 @@ pub const FIONBIO = -2147195266;
702702pub const ADDRINFOEX_VERSION_2 = 2;
703703pub const ADDRINFOEX_VERSION_3 = 3;
704704pub const ADDRINFOEX_VERSION_4 = 4;
705pub const NS_ALL = 0;
706pub const NS_SAP = 1;
707pub const NS_NDS = 2;
708pub const NS_PEER_BROWSE = 3;
709pub const NS_SLP = 5;
710pub const NS_DHCP = 6;
711pub const NS_TCPIP_LOCAL = 10;
712pub const NS_TCPIP_HOSTS = 11;
713pub const NS_DNS = 12;
714pub const NS_NETBT = 13;
715pub const NS_WINS = 14;
716pub const NS_NLA = 15;
717pub const NS_NBP = 20;
718pub const NS_MS = 30;
719pub const NS_STDA = 31;
720pub const NS_NTDS = 32;
721pub const NS_EMAIL = 37;
722pub const NS_X500 = 40;
723pub const NS_NIS = 41;
724pub const NS_NISPLUS = 42;
725pub const NS_WRQ = 50;
726pub const NS_NETDES = 60;
705
706pub const NS = enum(u32) {
707 ALL = 0,
708 SAP = 1,
709 NDS = 2,
710 PEER_BROWSE = 3,
711 SLP = 5,
712 DHCP = 6,
713 TCPIP_LOCAL = 10,
714 TCPIP_HOSTS = 11,
715 DNS = 12,
716 NETBT = 13,
717 WINS = 14,
718 NLA = 15,
719 NBP = 20,
720 MS = 30,
721 STDA = 31,
722 NTDS = 32,
723 EMAIL = 37,
724 X500 = 40,
725 NIS = 41,
726 NISPLUS = 42,
727 WRQ = 50,
728 NETDES = 60,
729};
730
727731pub const NI_NOFQDN = 1;
728732pub const NI_NUMERICHOST = 2;
729733pub const NI_NAMEREQD = 4;
......@@ -1080,31 +1084,18 @@ pub const WSANETWORKEVENTS = extern struct {
10801084 iErrorCode: [10]i32,
10811085};
10821086
1083pub const addrinfo = addrinfoa;
1084
1085pub const addrinfoa = extern struct {
1087pub const ADDRINFOEXW = extern struct {
10861088 flags: AI,
10871089 family: i32,
10881090 socktype: i32,
10891091 protocol: i32,
10901092 addrlen: usize,
1091 canonname: ?[*:0]u8,
1093 canonname: ?[*:0]u16,
10921094 addr: ?*sockaddr,
1093 next: ?*addrinfo,
1094};
1095
1096pub const addrinfoexA = extern struct {
1097 flags: AI,
1098 family: i32,
1099 socktype: i32,
1100 protocol: i32,
1101 addrlen: usize,
1102 canonname: [*:0]u8,
1103 addr: *sockaddr,
1104 blob: *anyopaque,
1095 blob: ?*anyopaque,
11051096 bloblen: usize,
1106 provider: *GUID,
1107 next: *addrinfoexA,
1097 provider: ?*GUID,
1098 next: ?*ADDRINFOEXW,
11081099};
11091100
11101101pub const sockaddr = extern struct {
......@@ -1271,130 +1262,105 @@ pub const timeval = extern struct {
12711262 usec: LONG,
12721263};
12731264
1274// https://docs.microsoft.com/en-au/windows/win32/winsock/windows-sockets-error-codes-2
1265/// https://docs.microsoft.com/en-au/windows/win32/winsock/windows-sockets-error-codes-2
12751266pub const WinsockError = enum(u16) {
12761267 /// Specified event object handle is invalid.
12771268 /// An application attempts to use an event object, but the specified handle is not valid.
1278 WSA_INVALID_HANDLE = 6,
1279
1269 INVALID_HANDLE = 6,
12801270 /// Insufficient memory available.
12811271 /// An application used a Windows Sockets function that directly maps to a Windows function.
12821272 /// The Windows function is indicating a lack of required memory resources.
1283 WSA_NOT_ENOUGH_MEMORY = 8,
1284
1273 NOT_ENOUGH_MEMORY = 8,
12851274 /// One or more parameters are invalid.
12861275 /// An application used a Windows Sockets function which directly maps to a Windows function.
12871276 /// The Windows function is indicating a problem with one or more parameters.
1288 WSA_INVALID_PARAMETER = 87,
1289
1277 INVALID_PARAMETER = 87,
12901278 /// Overlapped operation aborted.
12911279 /// An overlapped operation was canceled due to the closure of the socket, or the execution of the SIO_FLUSH command in WSAIoctl.
1292 WSA_OPERATION_ABORTED = 995,
1293
1280 OPERATION_ABORTED = 995,
12941281 /// Overlapped I/O event object not in signaled state.
12951282 /// The application has tried to determine the status of an overlapped operation which is not yet completed.
12961283 /// Applications that use WSAGetOverlappedResult (with the fWait flag set to FALSE) in a polling mode to determine when an overlapped operation has completed, get this error code until the operation is complete.
1297 WSA_IO_INCOMPLETE = 996,
1298
1284 IO_INCOMPLETE = 996,
12991285 /// The application has initiated an overlapped operation that cannot be completed immediately.
13001286 /// A completion indication will be given later when the operation has been completed.
1301 WSA_IO_PENDING = 997,
1302
1287 IO_PENDING = 997,
13031288 /// Interrupted function call.
13041289 /// A blocking operation was interrupted by a call to WSACancelBlockingCall.
1305 WSAEINTR = 10004,
1306
1290 EINTR = 10004,
13071291 /// File handle is not valid.
13081292 /// The file handle supplied is not valid.
1309 WSAEBADF = 10009,
1310
1293 EBADF = 10009,
13111294 /// Permission denied.
13121295 /// An attempt was made to access a socket in a way forbidden by its access permissions.
13131296 /// An example is using a broadcast address for sendto without broadcast permission being set using setsockopt(SO.BROADCAST).
13141297 /// Another possible reason for the WSAEACCES error is that when the bind function is called (on Windows NT 4.0 with SP4 and later), another application, service, or kernel mode driver is bound to the same address with exclusive access.
13151298 /// Such exclusive access is a new feature of Windows NT 4.0 with SP4 and later, and is implemented by using the SO.EXCLUSIVEADDRUSE option.
1316 WSAEACCES = 10013,
1317
1299 EACCES = 10013,
13181300 /// Bad address.
13191301 /// The system detected an invalid pointer address in attempting to use a pointer argument of a call.
13201302 /// This error occurs if an application passes an invalid pointer value, or if the length of the buffer is too small.
13211303 /// For instance, if the length of an argument, which is a sockaddr structure, is smaller than the sizeof(sockaddr).
1322 WSAEFAULT = 10014,
1323
1304 EFAULT = 10014,
13241305 /// Invalid argument.
13251306 /// Some invalid argument was supplied (for example, specifying an invalid level to the setsockopt function).
13261307 /// In some instances, it also refers to the current state of the socket—for instance, calling accept on a socket that is not listening.
1327 WSAEINVAL = 10022,
1328
1308 EINVAL = 10022,
13291309 /// Too many open files.
13301310 /// Too many open sockets. Each implementation may have a maximum number of socket handles available, either globally, per process, or per thread.
1331 WSAEMFILE = 10024,
1332
1311 EMFILE = 10024,
13331312 /// Resource temporarily unavailable.
13341313 /// This error is returned from operations on nonblocking sockets that cannot be completed immediately, for example recv when no data is queued to be read from the socket.
13351314 /// It is a nonfatal error, and the operation should be retried later.
13361315 /// It is normal for WSAEWOULDBLOCK to be reported as the result from calling connect on a nonblocking SOCK.STREAM socket, since some time must elapse for the connection to be established.
1337 WSAEWOULDBLOCK = 10035,
1338
1316 EWOULDBLOCK = 10035,
13391317 /// Operation now in progress.
13401318 /// A blocking operation is currently executing.
13411319 /// Windows Sockets only allows a single blocking operation—per- task or thread—to be outstanding, and if any other function call is made (whether or not it references that or any other socket) the function fails with the WSAEINPROGRESS error.
1342 WSAEINPROGRESS = 10036,
1343
1320 EINPROGRESS = 10036,
13441321 /// Operation already in progress.
13451322 /// An operation was attempted on a nonblocking socket with an operation already in progress—that is, calling connect a second time on a nonblocking socket that is already connecting, or canceling an asynchronous request (WSAAsyncGetXbyY) that has already been canceled or completed.
1346 WSAEALREADY = 10037,
1347
1323 EALREADY = 10037,
13481324 /// Socket operation on nonsocket.
13491325 /// An operation was attempted on something that is not a socket.
13501326 /// Either the socket handle parameter did not reference a valid socket, or for select, a member of an fd_set was not valid.
1351 WSAENOTSOCK = 10038,
1352
1327 ENOTSOCK = 10038,
13531328 /// Destination address required.
13541329 /// A required address was omitted from an operation on a socket.
13551330 /// For example, this error is returned if sendto is called with the remote address of ADDR_ANY.
1356 WSAEDESTADDRREQ = 10039,
1357
1331 EDESTADDRREQ = 10039,
13581332 /// Message too long.
13591333 /// A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram was smaller than the datagram itself.
1360 WSAEMSGSIZE = 10040,
1361
1334 EMSGSIZE = 10040,
13621335 /// Protocol wrong type for socket.
13631336 /// A protocol was specified in the socket function call that does not support the semantics of the socket type requested.
13641337 /// For example, the ARPA Internet UDP protocol cannot be specified with a socket type of SOCK.STREAM.
1365 WSAEPROTOTYPE = 10041,
1366
1338 EPROTOTYPE = 10041,
13671339 /// Bad protocol option.
13681340 /// An unknown, invalid or unsupported option or level was specified in a getsockopt or setsockopt call.
1369 WSAENOPROTOOPT = 10042,
1370
1341 ENOPROTOOPT = 10042,
13711342 /// Protocol not supported.
13721343 /// The requested protocol has not been configured into the system, or no implementation for it exists.
13731344 /// For example, a socket call requests a SOCK.DGRAM socket, but specifies a stream protocol.
1374 WSAEPROTONOSUPPORT = 10043,
1375
1345 EPROTONOSUPPORT = 10043,
13761346 /// Socket type not supported.
13771347 /// The support for the specified socket type does not exist in this address family.
13781348 /// For example, the optional type SOCK.RAW might be selected in a socket call, and the implementation does not support SOCK.RAW sockets at all.
1379 WSAESOCKTNOSUPPORT = 10044,
1380
1349 ESOCKTNOSUPPORT = 10044,
13811350 /// Operation not supported.
13821351 /// The attempted operation is not supported for the type of object referenced.
13831352 /// Usually this occurs when a socket descriptor to a socket that cannot support this operation is trying to accept a connection on a datagram socket.
1384 WSAEOPNOTSUPP = 10045,
1385
1353 EOPNOTSUPP = 10045,
13861354 /// Protocol family not supported.
13871355 /// The protocol family has not been configured into the system or no implementation for it exists.
13881356 /// This message has a slightly different meaning from WSAEAFNOSUPPORT.
13891357 /// However, it is interchangeable in most cases, and all Windows Sockets functions that return one of these messages also specify WSAEAFNOSUPPORT.
1390 WSAEPFNOSUPPORT = 10046,
1391
1358 EPFNOSUPPORT = 10046,
13921359 /// Address family not supported by protocol family.
13931360 /// An address incompatible with the requested protocol was used.
13941361 /// All sockets are created with an associated address family (that is, AF.INET for Internet Protocols) and a generic protocol type (that is, SOCK.STREAM).
13951362 /// This error is returned if an incorrect protocol is explicitly requested in the socket call, or if an address of the wrong family is used for a socket, for example, in sendto.
1396 WSAEAFNOSUPPORT = 10047,
1397
1363 EAFNOSUPPORT = 10047,
13981364 /// Address already in use.
13991365 /// Typically, only one usage of each socket address (protocol/IP address/port) is permitted.
14001366 /// This error occurs if an application attempts to bind a socket to an IP address/port that has already been used for an existing socket, or a socket that was not closed properly, or one that is still in the process of closing.
......@@ -1402,115 +1368,91 @@ pub const WinsockError = enum(u16) {
14021368 /// Client applications usually need not call bind at all—connect chooses an unused port automatically.
14031369 /// When bind is called with a wildcard address (involving ADDR_ANY), a WSAEADDRINUSE error could be delayed until the specific address is committed.
14041370 /// This could happen with a call to another function later, including connect, listen, WSAConnect, or WSAJoinLeaf.
1405 WSAEADDRINUSE = 10048,
1406
1371 EADDRINUSE = 10048,
14071372 /// Cannot assign requested address.
14081373 /// The requested address is not valid in its context.
14091374 /// This normally results from an attempt to bind to an address that is not valid for the local computer.
14101375 /// This can also result from connect, sendto, WSAConnect, WSAJoinLeaf, or WSASendTo when the remote address or port is not valid for a remote computer (for example, address or port 0).
1411 WSAEADDRNOTAVAIL = 10049,
1412
1376 EADDRNOTAVAIL = 10049,
14131377 /// Network is down.
14141378 /// A socket operation encountered a dead network.
14151379 /// This could indicate a serious failure of the network system (that is, the protocol stack that the Windows Sockets DLL runs over), the network interface, or the local network itself.
1416 WSAENETDOWN = 10050,
1417
1380 ENETDOWN = 10050,
14181381 /// Network is unreachable.
14191382 /// A socket operation was attempted to an unreachable network.
14201383 /// This usually means the local software knows no route to reach the remote host.
1421 WSAENETUNREACH = 10051,
1422
1384 ENETUNREACH = 10051,
14231385 /// Network dropped connection on reset.
14241386 /// The connection has been broken due to keep-alive activity detecting a failure while the operation was in progress.
14251387 /// It can also be returned by setsockopt if an attempt is made to set SO.KEEPALIVE on a connection that has already failed.
1426 WSAENETRESET = 10052,
1427
1388 ENETRESET = 10052,
14281389 /// Software caused connection abort.
14291390 /// An established connection was aborted by the software in your host computer, possibly due to a data transmission time-out or protocol error.
1430 WSAECONNABORTED = 10053,
1431
1391 ECONNABORTED = 10053,
14321392 /// Connection reset by peer.
14331393 /// An existing connection was forcibly closed by the remote host.
14341394 /// This normally results if the peer application on the remote host is suddenly stopped, the host is rebooted, the host or remote network interface is disabled, or the remote host uses a hard close (see setsockopt for more information on the SO.LINGER option on the remote socket).
14351395 /// This error may also result if a connection was broken due to keep-alive activity detecting a failure while one or more operations are in progress.
14361396 /// Operations that were in progress fail with WSAENETRESET. Subsequent operations fail with WSAECONNRESET.
1437 WSAECONNRESET = 10054,
1438
1397 ECONNRESET = 10054,
14391398 /// No buffer space available.
14401399 /// An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full.
1441 WSAENOBUFS = 10055,
1442
1400 ENOBUFS = 10055,
14431401 /// Socket is already connected.
14441402 /// A connect request was made on an already-connected socket.
14451403 /// Some implementations also return this error if sendto is called on a connected SOCK.DGRAM socket (for SOCK.STREAM sockets, the to parameter in sendto is ignored) although other implementations treat this as a legal occurrence.
1446 WSAEISCONN = 10056,
1447
1404 EISCONN = 10056,
14481405 /// Socket is not connected.
14491406 /// A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram socket using sendto) no address was supplied.
14501407 /// Any other type of operation might also return this error—for example, setsockopt setting SO.KEEPALIVE if the connection has been reset.
1451 WSAENOTCONN = 10057,
1452
1408 ENOTCONN = 10057,
14531409 /// Cannot send after socket shutdown.
14541410 /// A request to send or receive data was disallowed because the socket had already been shut down in that direction with a previous shutdown call.
14551411 /// By calling shutdown a partial close of a socket is requested, which is a signal that sending or receiving, or both have been discontinued.
1456 WSAESHUTDOWN = 10058,
1457
1412 ESHUTDOWN = 10058,
14581413 /// Too many references.
14591414 /// Too many references to some kernel object.
1460 WSAETOOMANYREFS = 10059,
1461
1415 ETOOMANYREFS = 10059,
14621416 /// Connection timed out.
14631417 /// A connection attempt failed because the connected party did not properly respond after a period of time, or the established connection failed because the connected host has failed to respond.
1464 WSAETIMEDOUT = 10060,
1465
1418 ETIMEDOUT = 10060,
14661419 /// Connection refused.
14671420 /// No connection could be made because the target computer actively refused it.
14681421 /// This usually results from trying to connect to a service that is inactive on the foreign host—that is, one with no server application running.
1469 WSAECONNREFUSED = 10061,
1470
1422 ECONNREFUSED = 10061,
14711423 /// Cannot translate name.
14721424 /// Cannot translate a name.
1473 WSAELOOP = 10062,
1474
1425 ELOOP = 10062,
14751426 /// Name too long.
14761427 /// A name component or a name was too long.
1477 WSAENAMETOOLONG = 10063,
1478
1428 ENAMETOOLONG = 10063,
14791429 /// Host is down.
14801430 /// A socket operation failed because the destination host is down. A socket operation encountered a dead host.
14811431 /// Networking activity on the local host has not been initiated.
14821432 /// These conditions are more likely to be indicated by the error WSAETIMEDOUT.
1483 WSAEHOSTDOWN = 10064,
1484
1433 EHOSTDOWN = 10064,
14851434 /// No route to host.
14861435 /// A socket operation was attempted to an unreachable host. See WSAENETUNREACH.
1487 WSAEHOSTUNREACH = 10065,
1488
1436 EHOSTUNREACH = 10065,
14891437 /// Directory not empty.
14901438 /// Cannot remove a directory that is not empty.
1491 WSAENOTEMPTY = 10066,
1492
1439 ENOTEMPTY = 10066,
14931440 /// Too many processes.
14941441 /// A Windows Sockets implementation may have a limit on the number of applications that can use it simultaneously.
14951442 /// WSAStartup may fail with this error if the limit has been reached.
1496 WSAEPROCLIM = 10067,
1497
1443 EPROCLIM = 10067,
14981444 /// User quota exceeded.
14991445 /// Ran out of user quota.
1500 WSAEUSERS = 10068,
1501
1446 EUSERS = 10068,
15021447 /// Disk quota exceeded.
15031448 /// Ran out of disk quota.
1504 WSAEDQUOT = 10069,
1505
1449 EDQUOT = 10069,
15061450 /// Stale file handle reference.
15071451 /// The file handle reference is no longer available.
1508 WSAESTALE = 10070,
1509
1452 ESTALE = 10070,
15101453 /// Item is remote.
15111454 /// The item is not available locally.
1512 WSAEREMOTE = 10071,
1513
1455 EREMOTE = 10071,
15141456 /// Network subsystem is unavailable.
15151457 /// This error is returned by WSAStartup if the Windows Sockets implementation cannot function at this time because the underlying system it uses to provide network services is currently unavailable.
15161458 /// Users should check:
......@@ -1518,47 +1460,38 @@ pub const WinsockError = enum(u16) {
15181460 /// - That they are not trying to use more than one Windows Sockets implementation simultaneously.
15191461 /// - If there is more than one Winsock DLL on your system, be sure the first one in the path is appropriate for the network subsystem currently loaded.
15201462 /// - The Windows Sockets implementation documentation to be sure all necessary components are currently installed and configured correctly.
1521 WSASYSNOTREADY = 10091,
1522
1463 SYSNOTREADY = 10091,
15231464 /// Winsock.dll version out of range.
15241465 /// The current Windows Sockets implementation does not support the Windows Sockets specification version requested by the application.
15251466 /// Check that no old Windows Sockets DLL files are being accessed.
1526 WSAVERNOTSUPPORTED = 10092,
1527
1467 VERNOTSUPPORTED = 10092,
15281468 /// Successful WSAStartup not yet performed.
15291469 /// Either the application has not called WSAStartup or WSAStartup failed.
15301470 /// The application may be accessing a socket that the current active task does not own (that is, trying to share a socket between tasks), or WSACleanup has been called too many times.
1531 WSANOTINITIALISED = 10093,
1532
1471 NOTINITIALISED = 10093,
15331472 /// Graceful shutdown in progress.
15341473 /// Returned by WSARecv and WSARecvFrom to indicate that the remote party has initiated a graceful shutdown sequence.
1535 WSAEDISCON = 10101,
1536
1474 EDISCON = 10101,
15371475 /// No more results.
15381476 /// No more results can be returned by the WSALookupServiceNext function.
1539 WSAENOMORE = 10102,
1540
1477 ENOMORE = 10102,
15411478 /// Call has been canceled.
15421479 /// A call to the WSALookupServiceEnd function was made while this call was still processing. The call has been canceled.
1543 WSAECANCELLED = 10103,
1544
1480 ECANCELLED = 10103,
15451481 /// Procedure call table is invalid.
15461482 /// The service provider procedure call table is invalid.
15471483 /// A service provider returned a bogus procedure table to Ws2_32.dll.
15481484 /// This is usually caused by one or more of the function pointers being NULL.
1549 WSAEINVALIDPROCTABLE = 10104,
1550
1485 EINVALIDPROCTABLE = 10104,
15511486 /// Service provider is invalid.
15521487 /// The requested service provider is invalid.
15531488 /// This error is returned by the WSCGetProviderInfo and WSCGetProviderInfo32 functions if the protocol entry specified could not be found.
15541489 /// This error is also returned if the service provider returned a version number other than 2.0.
1555 WSAEINVALIDPROVIDER = 10105,
1556
1490 EINVALIDPROVIDER = 10105,
15571491 /// Service provider failed to initialize.
15581492 /// The requested service provider could not be loaded or initialized.
15591493 /// This error is returned if either a service provider's DLL could not be loaded (LoadLibrary failed) or the provider's WSPStartup or NSPStartup function failed.
1560 WSAEPROVIDERFAILEDINIT = 10106,
1561
1494 EPROVIDERFAILEDINIT = 10106,
15621495 /// System call failure.
15631496 /// A system call that should never fail has failed.
15641497 /// This is a generic error code, returned under various conditions.
......@@ -1566,157 +1499,120 @@ pub const WinsockError = enum(u16) {
15661499 /// For example, if a call to WaitForMultipleEvents fails or one of the registry functions fails trying to manipulate the protocol/namespace catalogs.
15671500 /// Returned when a provider does not return SUCCESS and does not provide an extended error code.
15681501 /// Can indicate a service provider implementation error.
1569 WSASYSCALLFAILURE = 10107,
1570
1502 SYSCALLFAILURE = 10107,
15711503 /// Service not found.
15721504 /// No such service is known. The service cannot be found in the specified name space.
1573 WSASERVICE_NOT_FOUND = 10108,
1574
1505 SERVICE_NOT_FOUND = 10108,
15751506 /// Class type not found.
15761507 /// The specified class was not found.
1577 WSATYPE_NOT_FOUND = 10109,
1578
1508 TYPE_NOT_FOUND = 10109,
15791509 /// No more results.
15801510 /// No more results can be returned by the WSALookupServiceNext function.
1581 WSA_E_NO_MORE = 10110,
1582
1511 E_NO_MORE = 10110,
15831512 /// Call was canceled.
15841513 /// A call to the WSALookupServiceEnd function was made while this call was still processing. The call has been canceled.
1585 WSA_E_CANCELLED = 10111,
1586
1514 E_CANCELLED = 10111,
15871515 /// Database query was refused.
15881516 /// A database query failed because it was actively refused.
1589 WSAEREFUSED = 10112,
1590
1517 EREFUSED = 10112,
15911518 /// Host not found.
15921519 /// No such host is known. The name is not an official host name or alias, or it cannot be found in the database(s) being queried.
15931520 /// This error may also be returned for protocol and service queries, and means that the specified name could not be found in the relevant database.
1594 WSAHOST_NOT_FOUND = 11001,
1595
1521 HOST_NOT_FOUND = 11001,
15961522 /// Nonauthoritative host not found.
15971523 /// This is usually a temporary error during host name resolution and means that the local server did not receive a response from an authoritative server. A retry at some time later may be successful.
1598 WSATRY_AGAIN = 11002,
1599
1524 TRY_AGAIN = 11002,
16001525 /// This is a nonrecoverable error.
16011526 /// This indicates that some sort of nonrecoverable error occurred during a database lookup.
16021527 /// This may be because the database files (for example, BSD-compatible HOSTS, SERVICES, or PROTOCOLS files) could not be found, or a DNS request was returned by the server with a severe error.
1603 WSANO_RECOVERY = 11003,
1604
1528 NO_RECOVERY = 11003,
16051529 /// Valid name, no data record of requested type.
16061530 /// The requested name is valid and was found in the database, but it does not have the correct associated data being resolved for.
16071531 /// The usual example for this is a host name-to-address translation attempt (using gethostbyname or WSAAsyncGetHostByName) which uses the DNS (Domain Name Server).
16081532 /// An MX record is returned but no A record—indicating the host itself exists, but is not directly reachable.
1609 WSANO_DATA = 11004,
1610
1533 NO_DATA = 11004,
16111534 /// QoS receivers.
16121535 /// At least one QoS reserve has arrived.
1613 WSA_QOS_RECEIVERS = 11005,
1614
1536 QOS_RECEIVERS = 11005,
16151537 /// QoS senders.
16161538 /// At least one QoS send path has arrived.
1617 WSA_QOS_SENDERS = 11006,
1618
1539 QOS_SENDERS = 11006,
16191540 /// No QoS senders.
16201541 /// There are no QoS senders.
1621 WSA_QOS_NO_SENDERS = 11007,
1622
1542 QOS_NO_SENDERS = 11007,
16231543 /// QoS no receivers.
16241544 /// There are no QoS receivers.
1625 WSA_QOS_NO_RECEIVERS = 11008,
1626
1545 QOS_NO_RECEIVERS = 11008,
16271546 /// QoS request confirmed.
16281547 /// The QoS reserve request has been confirmed.
1629 WSA_QOS_REQUEST_CONFIRMED = 11009,
1630
1548 QOS_REQUEST_CONFIRMED = 11009,
16311549 /// QoS admission error.
16321550 /// A QoS error occurred due to lack of resources.
1633 WSA_QOS_ADMISSION_FAILURE = 11010,
1634
1551 QOS_ADMISSION_FAILURE = 11010,
16351552 /// QoS policy failure.
16361553 /// The QoS request was rejected because the policy system couldn't allocate the requested resource within the existing policy.
1637 WSA_QOS_POLICY_FAILURE = 11011,
1638
1554 QOS_POLICY_FAILURE = 11011,
16391555 /// QoS bad style.
16401556 /// An unknown or conflicting QoS style was encountered.
1641 WSA_QOS_BAD_STYLE = 11012,
1642
1557 QOS_BAD_STYLE = 11012,
16431558 /// QoS bad object.
16441559 /// A problem was encountered with some part of the filterspec or the provider-specific buffer in general.
1645 WSA_QOS_BAD_OBJECT = 11013,
1646
1560 QOS_BAD_OBJECT = 11013,
16471561 /// QoS traffic control error.
16481562 /// An error with the underlying traffic control (TC) API as the generic QoS request was converted for local enforcement by the TC API.
16491563 /// This could be due to an out of memory error or to an internal QoS provider error.
1650 WSA_QOS_TRAFFIC_CTRL_ERROR = 11014,
1651
1564 QOS_TRAFFIC_CTRL_ERROR = 11014,
16521565 /// QoS generic error.
16531566 /// A general QoS error.
1654 WSA_QOS_GENERIC_ERROR = 11015,
1655
1567 QOS_GENERIC_ERROR = 11015,
16561568 /// QoS service type error.
16571569 /// An invalid or unrecognized service type was found in the QoS flowspec.
1658 WSA_QOS_ESERVICETYPE = 11016,
1659
1570 QOS_ESERVICETYPE = 11016,
16601571 /// QoS flowspec error.
16611572 /// An invalid or inconsistent flowspec was found in the QOS structure.
1662 WSA_QOS_EFLOWSPEC = 11017,
1663
1573 QOS_EFLOWSPEC = 11017,
16641574 /// Invalid QoS provider buffer.
16651575 /// An invalid QoS provider-specific buffer.
1666 WSA_QOS_EPROVSPECBUF = 11018,
1667
1576 QOS_EPROVSPECBUF = 11018,
16681577 /// Invalid QoS filter style.
16691578 /// An invalid QoS filter style was used.
1670 WSA_QOS_EFILTERSTYLE = 11019,
1671
1579 QOS_EFILTERSTYLE = 11019,
16721580 /// Invalid QoS filter type.
16731581 /// An invalid QoS filter type was used.
1674 WSA_QOS_EFILTERTYPE = 11020,
1675
1582 QOS_EFILTERTYPE = 11020,
16761583 /// Incorrect QoS filter count.
16771584 /// An incorrect number of QoS FILTERSPECs were specified in the FLOWDESCRIPTOR.
1678 WSA_QOS_EFILTERCOUNT = 11021,
1679
1585 QOS_EFILTERCOUNT = 11021,
16801586 /// Invalid QoS object length.
16811587 /// An object with an invalid ObjectLength field was specified in the QoS provider-specific buffer.
1682 WSA_QOS_EOBJLENGTH = 11022,
1683
1588 QOS_EOBJLENGTH = 11022,
16841589 /// Incorrect QoS flow count.
16851590 /// An incorrect number of flow descriptors was specified in the QoS structure.
1686 WSA_QOS_EFLOWCOUNT = 11023,
1687
1591 QOS_EFLOWCOUNT = 11023,
16881592 /// Unrecognized QoS object.
16891593 /// An unrecognized object was found in the QoS provider-specific buffer.
1690 WSA_QOS_EUNKOWNPSOBJ = 11024,
1691
1594 QOS_EUNKOWNPSOBJ = 11024,
16921595 /// Invalid QoS policy object.
16931596 /// An invalid policy object was found in the QoS provider-specific buffer.
1694 WSA_QOS_EPOLICYOBJ = 11025,
1695
1597 QOS_EPOLICYOBJ = 11025,
16961598 /// Invalid QoS flow descriptor.
16971599 /// An invalid QoS flow descriptor was found in the flow descriptor list.
1698 WSA_QOS_EFLOWDESC = 11026,
1699
1600 QOS_EFLOWDESC = 11026,
17001601 /// Invalid QoS provider-specific flowspec.
17011602 /// An invalid or inconsistent flowspec was found in the QoS provider-specific buffer.
1702 WSA_QOS_EPSFLOWSPEC = 11027,
1703
1603 QOS_EPSFLOWSPEC = 11027,
17041604 /// Invalid QoS provider-specific filterspec.
17051605 /// An invalid FILTERSPEC was found in the QoS provider-specific buffer.
1706 WSA_QOS_EPSFILTERSPEC = 11028,
1707
1606 QOS_EPSFILTERSPEC = 11028,
17081607 /// Invalid QoS shape discard mode object.
17091608 /// An invalid shape discard mode object was found in the QoS provider-specific buffer.
1710 WSA_QOS_ESDMODEOBJ = 11029,
1711
1609 QOS_ESDMODEOBJ = 11029,
17121610 /// Invalid QoS shaping rate object.
17131611 /// An invalid shaping rate object was found in the QoS provider-specific buffer.
1714 WSA_QOS_ESHAPERATEOBJ = 11030,
1715
1612 QOS_ESHAPERATEOBJ = 11030,
17161613 /// Reserved policy QoS element type.
17171614 /// A reserved policy element was found in the QoS provider-specific buffer.
1718 WSA_QOS_RESERVED_PETYPE = 11031,
1719
1615 QOS_RESERVED_PETYPE = 11031,
17201616 _,
17211617};
17221618
......@@ -1946,18 +1842,6 @@ pub extern "ws2_32" fn WSAConnectByNameW(
19461842 Reserved: *OVERLAPPED,
19471843) callconv(.winapi) BOOL;
19481844
1949pub extern "ws2_32" fn WSAConnectByNameA(
1950 s: SOCKET,
1951 nodename: [*:0]const u8,
1952 servicename: [*:0]const u8,
1953 LocalAddressLength: ?*u32,
1954 LocalAddress: ?*sockaddr,
1955 RemoteAddressLength: ?*u32,
1956 RemoteAddress: ?*sockaddr,
1957 timeout: ?*const timeval,
1958 Reserved: *OVERLAPPED,
1959) callconv(.winapi) BOOL;
1960
19611845pub extern "ws2_32" fn WSAConnectByList(
19621846 s: SOCKET,
19631847 SocketAddress: *SOCKET_ADDRESS_LIST,
......@@ -1971,12 +1855,6 @@ pub extern "ws2_32" fn WSAConnectByList(
19711855
19721856pub extern "ws2_32" fn WSACreateEvent() callconv(.winapi) HANDLE;
19731857
1974pub extern "ws2_32" fn WSADuplicateSocketA(
1975 s: SOCKET,
1976 dwProcessId: u32,
1977 lpProtocolInfo: *WSAPROTOCOL_INFOA,
1978) callconv(.winapi) i32;
1979
19801858pub extern "ws2_32" fn WSADuplicateSocketW(
19811859 s: SOCKET,
19821860 dwProcessId: u32,
......@@ -1989,12 +1867,6 @@ pub extern "ws2_32" fn WSAEnumNetworkEvents(
19891867 lpNetworkEvents: *WSANETWORKEVENTS,
19901868) callconv(.winapi) i32;
19911869
1992pub extern "ws2_32" fn WSAEnumProtocolsA(
1993 lpiProtocols: ?*i32,
1994 lpProtocolBuffer: ?*WSAPROTOCOL_INFOA,
1995 lpdwBufferLength: *u32,
1996) callconv(.winapi) i32;
1997
19981870pub extern "ws2_32" fn WSAEnumProtocolsW(
19991871 lpiProtocols: ?*i32,
20001872 lpProtocolBuffer: ?*WSAPROTOCOL_INFOW,
......@@ -2137,15 +2009,6 @@ pub extern "ws2_32" fn WSASetEvent(
21372009 hEvent: HANDLE,
21382010) callconv(.winapi) BOOL;
21392011
2140pub extern "ws2_32" fn WSASocketA(
2141 af: i32,
2142 @"type": i32,
2143 protocol: i32,
2144 lpProtocolInfo: ?*WSAPROTOCOL_INFOA,
2145 g: u32,
2146 dwFlags: u32,
2147) callconv(.winapi) SOCKET;
2148
21492012pub extern "ws2_32" fn WSASocketW(
21502013 af: i32,
21512014 @"type": i32,
......@@ -2163,14 +2026,6 @@ pub extern "ws2_32" fn WSAWaitForMultipleEvents(
21632026 fAlertable: BOOL,
21642027) callconv(.winapi) u32;
21652028
2166pub extern "ws2_32" fn WSAAddressToStringA(
2167 lpsaAddress: *sockaddr,
2168 dwAddressLength: u32,
2169 lpProtocolInfo: ?*WSAPROTOCOL_INFOA,
2170 lpszAddressString: [*]u8,
2171 lpdwAddressStringLength: *u32,
2172) callconv(.winapi) i32;
2173
21742029pub extern "ws2_32" fn WSAAddressToStringW(
21752030 lpsaAddress: *sockaddr,
21762031 dwAddressLength: u32,
......@@ -2179,14 +2034,6 @@ pub extern "ws2_32" fn WSAAddressToStringW(
21792034 lpdwAddressStringLength: *u32,
21802035) callconv(.winapi) i32;
21812036
2182pub extern "ws2_32" fn WSAStringToAddressA(
2183 AddressString: [*:0]const u8,
2184 AddressFamily: i32,
2185 lpProtocolInfo: ?*WSAPROTOCOL_INFOA,
2186 lpAddress: *sockaddr,
2187 lpAddressLength: *i32,
2188) callconv(.winapi) i32;
2189
21902037pub extern "ws2_32" fn WSAStringToAddressW(
21912038 AddressString: [*:0]const u16,
21922039 AddressFamily: i32,
......@@ -2251,32 +2098,14 @@ pub extern "ws2_32" fn WSAProviderCompleteAsyncCall(
22512098 iRetCode: i32,
22522099) callconv(.winapi) i32;
22532100
2254pub extern "mswsock" fn EnumProtocolsA(
2255 lpiProtocols: ?*i32,
2256 lpProtocolBuffer: *anyopaque,
2257 lpdwBufferLength: *u32,
2258) callconv(.winapi) i32;
2259
22602101pub extern "mswsock" fn EnumProtocolsW(
22612102 lpiProtocols: ?*i32,
22622103 lpProtocolBuffer: *anyopaque,
22632104 lpdwBufferLength: *u32,
22642105) callconv(.winapi) i32;
22652106
2266pub extern "mswsock" fn GetAddressByNameA(
2267 dwNameSpace: u32,
2268 lpServiceType: *GUID,
2269 lpServiceName: ?[*:0]u8,
2270 lpiProtocols: ?*i32,
2271 dwResolution: u32,
2272 lpServiceAsyncInfo: ?*SERVICE_ASYNC_INFO,
2273 lpCsaddrBuffer: *anyopaque,
2274 lpAliasBuffer: ?[*:0]const u8,
2275 lpdwAliasBufferLength: *u32,
2276) callconv(.winapi) i32;
2277
22782107pub extern "mswsock" fn GetAddressByNameW(
2279 dwNameSpace: u32,
2108 dwNameSpace: NS,
22802109 lpServiceType: *GUID,
22812110 lpServiceName: ?[*:0]u16,
22822111 lpiProtocols: ?*i32,
......@@ -2288,45 +2117,28 @@ pub extern "mswsock" fn GetAddressByNameW(
22882117 lpdwAliasBufferLength: *u32,
22892118) callconv(.winapi) i32;
22902119
2291pub extern "mswsock" fn GetTypeByNameA(
2292 lpServiceName: [*:0]u8,
2293 lpServiceType: *GUID,
2294) callconv(.winapi) i32;
2295
22962120pub extern "mswsock" fn GetTypeByNameW(
22972121 lpServiceName: [*:0]u16,
22982122 lpServiceType: *GUID,
22992123) callconv(.winapi) i32;
23002124
2301pub extern "mswsock" fn GetNameByTypeA(
2302 lpServiceType: *GUID,
2303 lpServiceName: [*:0]u8,
2304 dwNameLength: u32,
2305) callconv(.winapi) i32;
2306
23072125pub extern "mswsock" fn GetNameByTypeW(
23082126 lpServiceType: *GUID,
23092127 lpServiceName: [*:0]u16,
23102128 dwNameLength: u32,
23112129) callconv(.winapi) i32;
23122130
2313pub extern "ws2_32" fn getaddrinfo(
2314 pNodeName: ?[*:0]const u8,
2315 pServiceName: ?[*:0]const u8,
2316 pHints: ?*const addrinfoa,
2317 ppResult: *?*addrinfoa,
2318) callconv(.winapi) i32;
2319
2320pub extern "ws2_32" fn GetAddrInfoExA(
2321 pName: ?[*:0]const u8,
2322 pServiceName: ?[*:0]const u8,
2323 dwNameSapce: u32,
2131pub extern "ws2_32" fn GetAddrInfoExW(
2132 pName: ?[*:0]const u16,
2133 pServiceName: ?[*:0]const u16,
2134 dwNameSpace: NS,
23242135 lpNspId: ?*GUID,
2325 hints: ?*const addrinfoexA,
2326 ppResult: **addrinfoexA,
2136 hints: ?*const ADDRINFOEXW,
2137 ppResult: **ADDRINFOEXW,
23272138 timeout: ?*timeval,
23282139 lpOverlapped: ?*OVERLAPPED,
23292140 lpCompletionRoutine: ?LPLOOKUPSERVICE_COMPLETION_ROUTINE,
2141 lpNameHandle: ?*HANDLE,
23302142) callconv(.winapi) i32;
23312143
23322144pub extern "ws2_32" fn GetAddrInfoExCancel(
......@@ -2337,12 +2149,8 @@ pub extern "ws2_32" fn GetAddrInfoExOverlappedResult(
23372149 lpOverlapped: *OVERLAPPED,
23382150) callconv(.winapi) i32;
23392151
2340pub extern "ws2_32" fn freeaddrinfo(
2341 pAddrInfo: ?*addrinfoa,
2342) callconv(.winapi) void;
2343
2344pub extern "ws2_32" fn FreeAddrInfoEx(
2345 pAddrInfoEx: ?*addrinfoexA,
2152pub extern "ws2_32" fn FreeAddrInfoExW(
2153 pAddrInfoEx: ?*ADDRINFOEXW,
23462154) callconv(.winapi) void;
23472155
23482156pub extern "ws2_32" fn getnameinfo(
......@@ -2354,7 +2162,3 @@ pub extern "ws2_32" fn getnameinfo(
23542162 ServiceBufferName: u32,
23552163 Flags: i32,
23562164) callconv(.winapi) i32;
2357
2358pub extern "iphlpapi" fn if_nametoindex(
2359 InterfaceName: [*:0]const u8,
2360) callconv(.winapi) u32;
lib/std/posix.zig+246-1118
......@@ -52,6 +52,10 @@ else switch (native_os) {
5252 pub const fd_t = void;
5353 pub const uid_t = void;
5454 pub const gid_t = void;
55 pub const mode_t = u0;
56 pub const ino_t = void;
57 pub const IFNAMESIZE = {};
58 pub const SIG = void;
5559 },
5660};
5761
......@@ -98,7 +102,6 @@ pub const POSIX_FADV = system.POSIX_FADV;
98102pub const PR = system.PR;
99103pub const PROT = system.PROT;
100104pub const RLIM = system.RLIM;
101pub const RR = system.RR;
102105pub const S = system.S;
103106pub const SA = system.SA;
104107pub const SC = system.SC;
......@@ -357,6 +360,7 @@ pub const FChmodAtError = FChmodError || error{
357360 ProcessFdQuotaExceeded,
358361 /// The procfs fallback was used but the system exceeded it open file limit.
359362 SystemFdQuotaExceeded,
363 Canceled,
360364};
361365
362366/// Changes the `mode` of `path` relative to the directory referred to by
......@@ -486,7 +490,9 @@ fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtEr
486490 const stat = fstatatZ(pathfd, "", AT.EMPTY_PATH) catch |err| switch (err) {
487491 error.NameTooLong => unreachable,
488492 error.FileNotFound => unreachable,
489 error.InvalidUtf8 => unreachable,
493 error.Streaming => unreachable,
494 error.BadPathName => return error.Unexpected,
495 error.Canceled => return error.Canceled,
490496 else => |e| return e,
491497 };
492498 if ((stat.mode & S.IFMT) == S.IFLNK)
......@@ -664,18 +670,22 @@ pub fn getrandom(buffer: []u8) GetRandomError!void {
664670 return getRandomBytesDevURandom(buffer);
665671}
666672
667fn getRandomBytesDevURandom(buf: []u8) !void {
673fn getRandomBytesDevURandom(buf: []u8) GetRandomError!void {
668674 const fd = try openZ("/dev/urandom", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
669675 defer close(fd);
670676
671 const st = try fstat(fd);
677 const st = fstat(fd) catch |err| switch (err) {
678 error.Streaming => return error.NoDevice,
679 else => |e| return e,
680 };
672681 if (!S.ISCHR(st.mode)) {
673682 return error.NoDevice;
674683 }
675684
676 const file: fs.File = .{ .handle = fd };
677 var file_reader = file.readerStreaming(&.{});
678 file_reader.interface.readSliceAll(buf) catch return error.Unexpected;
685 var i: usize = 0;
686 while (i < buf.len) {
687 i += read(fd, buf[i..]) catch return error.Unexpected;
688 }
679689}
680690
681691/// Causes abnormal process termination.
......@@ -699,7 +709,7 @@ pub fn abort() noreturn {
699709 // for user-defined signal handlers that want to restore some state in
700710 // some program sections and crash in others.
701711 // So, the user-installed SIGABRT handler is run, if present.
702 raise(SIG.ABRT) catch {};
712 raise(.ABRT) catch {};
703713
704714 // Disable all signal handlers.
705715 const filledset = linux.sigfillset();
......@@ -719,17 +729,17 @@ pub fn abort() noreturn {
719729 .mask = sigemptyset(),
720730 .flags = 0,
721731 };
722 sigaction(SIG.ABRT, &sigact, null);
732 sigaction(.ABRT, &sigact, null);
723733
724 _ = linux.tkill(linux.gettid(), SIG.ABRT);
734 _ = linux.tkill(linux.gettid(), .ABRT);
725735
726736 var sigabrtmask = sigemptyset();
727 sigaddset(&sigabrtmask, SIG.ABRT);
737 sigaddset(&sigabrtmask, .ABRT);
728738 sigprocmask(SIG.UNBLOCK, &sigabrtmask, null);
729739
730740 // Beyond this point should be unreachable.
731741 @as(*allowzero volatile u8, @ptrFromInt(0)).* = 0;
732 raise(SIG.KILL) catch {};
742 raise(.KILL) catch {};
733743 exit(127); // Pid 1 might not be signalled in some containers.
734744 }
735745 switch (native_os) {
......@@ -740,7 +750,7 @@ pub fn abort() noreturn {
740750
741751pub const RaiseError = UnexpectedError;
742752
743pub fn raise(sig: u8) RaiseError!void {
753pub fn raise(sig: SIG) RaiseError!void {
744754 if (builtin.link_libc) {
745755 switch (errno(system.raise(sig))) {
746756 .SUCCESS => return,
......@@ -768,7 +778,7 @@ pub fn raise(sig: u8) RaiseError!void {
768778
769779pub const KillError = error{ ProcessNotFound, PermissionDenied } || UnexpectedError;
770780
771pub fn kill(pid: pid_t, sig: u8) KillError!void {
781pub fn kill(pid: pid_t, sig: SIG) KillError!void {
772782 switch (errno(system.kill(pid, sig))) {
773783 .SUCCESS => return,
774784 .INVAL => unreachable, // invalid signal
......@@ -805,36 +815,7 @@ pub fn exit(status: u8) noreturn {
805815 system.exit(status);
806816}
807817
808pub const ReadError = error{
809 InputOutput,
810 SystemResources,
811 IsDir,
812 OperationAborted,
813 BrokenPipe,
814 ConnectionResetByPeer,
815 ConnectionTimedOut,
816 NotOpenForReading,
817 SocketNotConnected,
818
819 /// This error occurs when no global event loop is configured,
820 /// and reading from the file descriptor would block.
821 WouldBlock,
822
823 /// reading a timerfd with CANCEL_ON_SET will lead to this error
824 /// when the clock goes through a discontinuous change
825 Canceled,
826
827 /// In WASI, this error occurs when the file descriptor does
828 /// not hold the required rights to read from it.
829 AccessDenied,
830
831 /// This error occurs in Linux if the process to be read from
832 /// no longer exists.
833 ProcessNotFound,
834
835 /// Unable to read file due to lock.
836 LockViolation,
837} || UnexpectedError;
818pub const ReadError = std.Io.File.Reader.Error;
838819
839820/// Returns the number of bytes that were read, which can be less than
840821/// buf.len. If 0 bytes were read, that means EOF.
......@@ -869,9 +850,9 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
869850 .ISDIR => return error.IsDir,
870851 .NOBUFS => return error.SystemResources,
871852 .NOMEM => return error.SystemResources,
872 .NOTCONN => return error.SocketNotConnected,
853 .NOTCONN => return error.SocketUnconnected,
873854 .CONNRESET => return error.ConnectionResetByPeer,
874 .TIMEDOUT => return error.ConnectionTimedOut,
855 .TIMEDOUT => return error.Timeout,
875856 .NOTCAPABLE => return error.AccessDenied,
876857 else => |err| return unexpectedErrno(err),
877858 }
......@@ -898,9 +879,9 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
898879 .ISDIR => return error.IsDir,
899880 .NOBUFS => return error.SystemResources,
900881 .NOMEM => return error.SystemResources,
901 .NOTCONN => return error.SocketNotConnected,
882 .NOTCONN => return error.SocketUnconnected,
902883 .CONNRESET => return error.ConnectionResetByPeer,
903 .TIMEDOUT => return error.ConnectionTimedOut,
884 .TIMEDOUT => return error.Timeout,
904885 else => |err| return unexpectedErrno(err),
905886 }
906887 }
......@@ -921,7 +902,6 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
921902/// a pointer within the address space of the application.
922903pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
923904 if (native_os == .windows) {
924 // TODO improve this to use ReadFileScatter
925905 if (iov.len == 0) return 0;
926906 const first = iov[0];
927907 return read(fd, first.base[0..first.len]);
......@@ -939,9 +919,9 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
939919 .ISDIR => return error.IsDir,
940920 .NOBUFS => return error.SystemResources,
941921 .NOMEM => return error.SystemResources,
942 .NOTCONN => return error.SocketNotConnected,
922 .NOTCONN => return error.SocketUnconnected,
943923 .CONNRESET => return error.ConnectionResetByPeer,
944 .TIMEDOUT => return error.ConnectionTimedOut,
924 .TIMEDOUT => return error.Timeout,
945925 .NOTCAPABLE => return error.AccessDenied,
946926 else => |err| return unexpectedErrno(err),
947927 }
......@@ -961,15 +941,15 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
961941 .ISDIR => return error.IsDir,
962942 .NOBUFS => return error.SystemResources,
963943 .NOMEM => return error.SystemResources,
964 .NOTCONN => return error.SocketNotConnected,
944 .NOTCONN => return error.SocketUnconnected,
965945 .CONNRESET => return error.ConnectionResetByPeer,
966 .TIMEDOUT => return error.ConnectionTimedOut,
946 .TIMEDOUT => return error.Timeout,
967947 else => |err| return unexpectedErrno(err),
968948 }
969949 }
970950}
971951
972pub const PReadError = ReadError || error{Unseekable};
952pub const PReadError = std.Io.File.ReadPositionalError;
973953
974954/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
975955///
......@@ -1008,9 +988,9 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
1008988 .ISDIR => return error.IsDir,
1009989 .NOBUFS => return error.SystemResources,
1010990 .NOMEM => return error.SystemResources,
1011 .NOTCONN => return error.SocketNotConnected,
991 .NOTCONN => return error.SocketUnconnected,
1012992 .CONNRESET => return error.ConnectionResetByPeer,
1013 .TIMEDOUT => return error.ConnectionTimedOut,
993 .TIMEDOUT => return error.Timeout,
1014994 .NXIO => return error.Unseekable,
1015995 .SPIPE => return error.Unseekable,
1016996 .OVERFLOW => return error.Unseekable,
......@@ -1041,9 +1021,9 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
10411021 .ISDIR => return error.IsDir,
10421022 .NOBUFS => return error.SystemResources,
10431023 .NOMEM => return error.SystemResources,
1044 .NOTCONN => return error.SocketNotConnected,
1024 .NOTCONN => return error.SocketUnconnected,
10451025 .CONNRESET => return error.ConnectionResetByPeer,
1046 .TIMEDOUT => return error.ConnectionTimedOut,
1026 .TIMEDOUT => return error.Timeout,
10471027 .NXIO => return error.Unseekable,
10481028 .SPIPE => return error.Unseekable,
10491029 .OVERFLOW => return error.Unseekable,
......@@ -1159,9 +1139,9 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {
11591139 .ISDIR => return error.IsDir,
11601140 .NOBUFS => return error.SystemResources,
11611141 .NOMEM => return error.SystemResources,
1162 .NOTCONN => return error.SocketNotConnected,
1142 .NOTCONN => return error.SocketUnconnected,
11631143 .CONNRESET => return error.ConnectionResetByPeer,
1164 .TIMEDOUT => return error.ConnectionTimedOut,
1144 .TIMEDOUT => return error.Timeout,
11651145 .NXIO => return error.Unseekable,
11661146 .SPIPE => return error.Unseekable,
11671147 .OVERFLOW => return error.Unseekable,
......@@ -1185,9 +1165,9 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {
11851165 .ISDIR => return error.IsDir,
11861166 .NOBUFS => return error.SystemResources,
11871167 .NOMEM => return error.SystemResources,
1188 .NOTCONN => return error.SocketNotConnected,
1168 .NOTCONN => return error.SocketUnconnected,
11891169 .CONNRESET => return error.ConnectionResetByPeer,
1190 .TIMEDOUT => return error.ConnectionTimedOut,
1170 .TIMEDOUT => return error.Timeout,
11911171 .NXIO => return error.Unseekable,
11921172 .SPIPE => return error.Unseekable,
11931173 .OVERFLOW => return error.Unseekable,
......@@ -1209,7 +1189,7 @@ pub const WriteError = error{
12091189 PermissionDenied,
12101190 BrokenPipe,
12111191 SystemResources,
1212 OperationAborted,
1192 Canceled,
12131193 NotOpenForWriting,
12141194
12151195 /// The process cannot access the file because another process has locked
......@@ -1232,7 +1212,7 @@ pub const WriteError = error{
12321212
12331213 /// The socket type requires that message be sent atomically, and the size of the message
12341214 /// to be sent made this impossible. The message is not transmitted.
1235 MessageTooBig,
1215 MessageOversize,
12361216} || UnexpectedError;
12371217
12381218/// Write to a file descriptor.
......@@ -1314,7 +1294,7 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
13141294 .CONNRESET => return error.ConnectionResetByPeer,
13151295 .BUSY => return error.DeviceBusy,
13161296 .NXIO => return error.NoDevice,
1317 .MSGSIZE => return error.MessageTooBig,
1297 .MSGSIZE => return error.MessageOversize,
13181298 else => |err| return unexpectedErrno(err),
13191299 }
13201300 }
......@@ -1570,81 +1550,7 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz
15701550 }
15711551}
15721552
1573pub const OpenError = error{
1574 /// In WASI, this error may occur when the file descriptor does
1575 /// not hold the required rights to open a new resource relative to it.
1576 AccessDenied,
1577 PermissionDenied,
1578 SymLinkLoop,
1579 ProcessFdQuotaExceeded,
1580 SystemFdQuotaExceeded,
1581 NoDevice,
1582 /// Either:
1583 /// * One of the path components does not exist.
1584 /// * Cwd was used, but cwd has been deleted.
1585 /// * The path associated with the open directory handle has been deleted.
1586 /// * On macOS, multiple processes or threads raced to create the same file
1587 /// with `O.EXCL` set to `false`.
1588 FileNotFound,
1589
1590 /// The path exceeded `max_path_bytes` bytes.
1591 NameTooLong,
1592
1593 /// Insufficient kernel memory was available, or
1594 /// the named file is a FIFO and per-user hard limit on
1595 /// memory allocation for pipes has been reached.
1596 SystemResources,
1597
1598 /// The file is too large to be opened. This error is unreachable
1599 /// for 64-bit targets, as well as when opening directories.
1600 FileTooBig,
1601
1602 /// The path refers to directory but the `DIRECTORY` flag was not provided.
1603 IsDir,
1604
1605 /// A new path cannot be created because the device has no room for the new file.
1606 /// This error is only reachable when the `CREAT` flag is provided.
1607 NoSpaceLeft,
1608
1609 /// A component used as a directory in the path was not, in fact, a directory, or
1610 /// `DIRECTORY` was specified and the path was not a directory.
1611 NotDir,
1612
1613 /// The path already exists and the `CREAT` and `EXCL` flags were provided.
1614 PathAlreadyExists,
1615 DeviceBusy,
1616
1617 /// The underlying filesystem does not support file locks
1618 FileLocksNotSupported,
1619
1620 /// Path contains characters that are disallowed by the underlying filesystem.
1621 BadPathName,
1622
1623 /// WASI-only; file paths must be valid UTF-8.
1624 InvalidUtf8,
1625
1626 /// Windows-only; file paths provided by the user must be valid WTF-8.
1627 /// https://wtf-8.codeberg.page/
1628 InvalidWtf8,
1629
1630 /// On Windows, `\\server` or `\\server\share` was not found.
1631 NetworkNotFound,
1632
1633 /// This error occurs in Linux if the process to be open was not found.
1634 ProcessNotFound,
1635
1636 /// One of these three things:
1637 /// * pathname refers to an executable image which is currently being
1638 /// executed and write access was requested.
1639 /// * pathname refers to a file that is currently in use as a swap
1640 /// file, and the O_TRUNC flag was specified.
1641 /// * pathname refers to a file that is currently being read by the
1642 /// kernel (e.g., for module/firmware loading), and write access was
1643 /// requested.
1644 FileBusy,
1645
1646 WouldBlock,
1647} || UnexpectedError;
1553pub const OpenError = std.Io.File.OpenError || error{WouldBlock};
16481554
16491555/// Open and possibly create a file. Keeps trying if it gets interrupted.
16501556/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
......@@ -1699,10 +1605,7 @@ pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t {
16991605 .PERM => return error.PermissionDenied,
17001606 .EXIST => return error.PathAlreadyExists,
17011607 .BUSY => return error.DeviceBusy,
1702 .ILSEQ => |err| if (native_os == .wasi)
1703 return error.InvalidUtf8
1704 else
1705 return unexpectedErrno(err),
1608 .ILSEQ => return error.BadPathName,
17061609 else => |err| return unexpectedErrno(err),
17071610 }
17081611 }
......@@ -1718,119 +1621,12 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenE
17181621 if (native_os == .windows) {
17191622 @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
17201623 } else if (native_os == .wasi and !builtin.link_libc) {
1721 // `mode` is ignored on WASI, which does not support unix-style file permissions
1722 const opts = try openOptionsFromFlagsWasi(flags);
1723 const fd = try openatWasi(
1724 dir_fd,
1725 file_path,
1726 opts.lookup_flags,
1727 opts.oflags,
1728 opts.fs_flags,
1729 opts.fs_rights_base,
1730 opts.fs_rights_inheriting,
1731 );
1732 errdefer close(fd);
1733
1734 if (flags.write) {
1735 const info = try std.os.fstat_wasi(fd);
1736 if (info.filetype == .DIRECTORY)
1737 return error.IsDir;
1738 }
1739
1740 return fd;
1624 @compileError("use std.Io instead");
17411625 }
17421626 const file_path_c = try toPosixPath(file_path);
17431627 return openatZ(dir_fd, &file_path_c, flags, mode);
17441628}
17451629
1746/// Open and possibly create a file in WASI.
1747pub fn openatWasi(
1748 dir_fd: fd_t,
1749 file_path: []const u8,
1750 lookup_flags: wasi.lookupflags_t,
1751 oflags: wasi.oflags_t,
1752 fdflags: wasi.fdflags_t,
1753 base: wasi.rights_t,
1754 inheriting: wasi.rights_t,
1755) OpenError!fd_t {
1756 while (true) {
1757 var fd: fd_t = undefined;
1758 switch (wasi.path_open(dir_fd, lookup_flags, file_path.ptr, file_path.len, oflags, base, inheriting, fdflags, &fd)) {
1759 .SUCCESS => return fd,
1760 .INTR => continue,
1761
1762 .FAULT => unreachable,
1763 // Provides INVAL with a linux host on a bad path name, but NOENT on Windows
1764 .INVAL => return error.BadPathName,
1765 .BADF => unreachable,
1766 .ACCES => return error.AccessDenied,
1767 .FBIG => return error.FileTooBig,
1768 .OVERFLOW => return error.FileTooBig,
1769 .ISDIR => return error.IsDir,
1770 .LOOP => return error.SymLinkLoop,
1771 .MFILE => return error.ProcessFdQuotaExceeded,
1772 .NAMETOOLONG => return error.NameTooLong,
1773 .NFILE => return error.SystemFdQuotaExceeded,
1774 .NODEV => return error.NoDevice,
1775 .NOENT => return error.FileNotFound,
1776 .NOMEM => return error.SystemResources,
1777 .NOSPC => return error.NoSpaceLeft,
1778 .NOTDIR => return error.NotDir,
1779 .PERM => return error.PermissionDenied,
1780 .EXIST => return error.PathAlreadyExists,
1781 .BUSY => return error.DeviceBusy,
1782 .NOTCAPABLE => return error.AccessDenied,
1783 .ILSEQ => return error.InvalidUtf8,
1784 else => |err| return unexpectedErrno(err),
1785 }
1786 }
1787}
1788
1789/// A struct to contain all lookup/rights flags accepted by `wasi.path_open`
1790const WasiOpenOptions = struct {
1791 oflags: wasi.oflags_t,
1792 lookup_flags: wasi.lookupflags_t,
1793 fs_rights_base: wasi.rights_t,
1794 fs_rights_inheriting: wasi.rights_t,
1795 fs_flags: wasi.fdflags_t,
1796};
1797
1798/// Compute rights + flags corresponding to the provided POSIX access mode.
1799fn openOptionsFromFlagsWasi(oflag: O) OpenError!WasiOpenOptions {
1800 const w = std.os.wasi;
1801
1802 // Next, calculate the read/write rights to request, depending on the
1803 // provided POSIX access mode
1804 var rights: w.rights_t = .{};
1805 if (oflag.read) {
1806 rights.FD_READ = true;
1807 rights.FD_READDIR = true;
1808 }
1809 if (oflag.write) {
1810 rights.FD_DATASYNC = true;
1811 rights.FD_WRITE = true;
1812 rights.FD_ALLOCATE = true;
1813 rights.FD_FILESTAT_SET_SIZE = true;
1814 }
1815
1816 // https://github.com/ziglang/zig/issues/18882
1817 const flag_bits: u32 = @bitCast(oflag);
1818 const oflags_int: u16 = @as(u12, @truncate(flag_bits >> 12));
1819 const fs_flags_int: u16 = @as(u12, @truncate(flag_bits));
1820
1821 return .{
1822 // https://github.com/ziglang/zig/issues/18882
1823 .oflags = @bitCast(oflags_int),
1824 .lookup_flags = .{
1825 .SYMLINK_FOLLOW = !oflag.NOFOLLOW,
1826 },
1827 .fs_rights_base = rights,
1828 .fs_rights_inheriting = rights,
1829 // https://github.com/ziglang/zig/issues/18882
1830 .fs_flags = @bitCast(fs_flags_int),
1831 };
1832}
1833
18341630/// Open and possibly create a file. Keeps trying if it gets interrupted.
18351631/// `file_path` is relative to the open directory handle `dir_fd`.
18361632/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
......@@ -1875,10 +1671,7 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) O
18751671 .AGAIN => return error.WouldBlock,
18761672 .TXTBSY => return error.FileBusy,
18771673 .NXIO => return error.NoDevice,
1878 .ILSEQ => |err| if (native_os == .wasi)
1879 return error.InvalidUtf8
1880 else
1881 return unexpectedErrno(err),
1674 .ILSEQ => return error.BadPathName,
18821675 else => |err| return unexpectedErrno(err),
18831676 }
18841677 }
......@@ -2132,14 +1925,9 @@ pub const SymLinkError = error{
21321925 ReadOnlyFileSystem,
21331926 NotDir,
21341927 NameTooLong,
2135
2136 /// WASI-only; file paths must be valid UTF-8.
2137 InvalidUtf8,
2138
2139 /// Windows-only; file paths provided by the user must be valid WTF-8.
1928 /// WASI: file paths must be valid UTF-8.
1929 /// Windows: file paths provided by the user must be valid WTF-8.
21401930 /// https://wtf-8.codeberg.page/
2141 InvalidWtf8,
2142
21431931 BadPathName,
21441932} || UnexpectedError;
21451933
......@@ -2186,10 +1974,7 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin
21861974 .NOMEM => return error.SystemResources,
21871975 .NOSPC => return error.NoSpaceLeft,
21881976 .ROFS => return error.ReadOnlyFileSystem,
2189 .ILSEQ => |err| if (native_os == .wasi)
2190 return error.InvalidUtf8
2191 else
2192 return unexpectedErrno(err),
1977 .ILSEQ => return error.BadPathName,
21931978 else => |err| return unexpectedErrno(err),
21941979 }
21951980}
......@@ -2235,7 +2020,7 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c
22352020 .NOSPC => return error.NoSpaceLeft,
22362021 .ROFS => return error.ReadOnlyFileSystem,
22372022 .NOTCAPABLE => return error.AccessDenied,
2238 .ILSEQ => return error.InvalidUtf8,
2023 .ILSEQ => return error.BadPathName,
22392024 else => |err| return unexpectedErrno(err),
22402025 }
22412026}
......@@ -2264,10 +2049,7 @@ pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:
22642049 .NOMEM => return error.SystemResources,
22652050 .NOSPC => return error.NoSpaceLeft,
22662051 .ROFS => return error.ReadOnlyFileSystem,
2267 .ILSEQ => |err| if (native_os == .wasi)
2268 return error.InvalidUtf8
2269 else
2270 return unexpectedErrno(err),
2052 .ILSEQ => return error.BadPathName,
22712053 else => |err| return unexpectedErrno(err),
22722054 }
22732055}
......@@ -2286,9 +2068,7 @@ pub const LinkError = UnexpectedError || error{
22862068 NoSpaceLeft,
22872069 ReadOnlyFileSystem,
22882070 NotSameFileSystem,
2289
2290 /// WASI-only; file paths must be valid UTF-8.
2291 InvalidUtf8,
2071 BadPathName,
22922072};
22932073
22942074/// On WASI, both paths should be encoded as valid UTF-8.
......@@ -2314,10 +2094,7 @@ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8) LinkError!void {
23142094 .ROFS => return error.ReadOnlyFileSystem,
23152095 .XDEV => return error.NotSameFileSystem,
23162096 .INVAL => unreachable,
2317 .ILSEQ => |err| if (native_os == .wasi)
2318 return error.InvalidUtf8
2319 else
2320 return unexpectedErrno(err),
2097 .ILSEQ => return error.BadPathName,
23212098 else => |err| return unexpectedErrno(err),
23222099 }
23232100}
......@@ -2368,10 +2145,7 @@ pub fn linkatZ(
23682145 .ROFS => return error.ReadOnlyFileSystem,
23692146 .XDEV => return error.NotSameFileSystem,
23702147 .INVAL => unreachable,
2371 .ILSEQ => |err| if (native_os == .wasi)
2372 return error.InvalidUtf8
2373 else
2374 return unexpectedErrno(err),
2148 .ILSEQ => return error.BadPathName,
23752149 else => |err| return unexpectedErrno(err),
23762150 }
23772151}
......@@ -2417,7 +2191,7 @@ pub fn linkat(
24172191 .ROFS => return error.ReadOnlyFileSystem,
24182192 .XDEV => return error.NotSameFileSystem,
24192193 .INVAL => unreachable,
2420 .ILSEQ => return error.InvalidUtf8,
2194 .ILSEQ => return error.BadPathName,
24212195 else => |err| return unexpectedErrno(err),
24222196 }
24232197 }
......@@ -2442,14 +2216,10 @@ pub const UnlinkError = error{
24422216 SystemResources,
24432217 ReadOnlyFileSystem,
24442218
2445 /// WASI-only; file paths must be valid UTF-8.
2446 InvalidUtf8,
2447
2448 /// Windows-only; file paths provided by the user must be valid WTF-8.
2219 /// WASI: file paths must be valid UTF-8.
2220 /// Windows: file paths provided by the user must be valid WTF-8.
24492221 /// https://wtf-8.codeberg.page/
2450 InvalidWtf8,
2451
2452 /// On Windows, file paths cannot contain these characters:
2222 /// Windows: file paths cannot contain these characters:
24532223 /// '/', '*', '?', '"', '<', '>', '|'
24542224 BadPathName,
24552225
......@@ -2500,10 +2270,7 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
25002270 .NOTDIR => return error.NotDir,
25012271 .NOMEM => return error.SystemResources,
25022272 .ROFS => return error.ReadOnlyFileSystem,
2503 .ILSEQ => |err| if (native_os == .wasi)
2504 return error.InvalidUtf8
2505 else
2506 return unexpectedErrno(err),
2273 .ILSEQ => return error.BadPathName,
25072274 else => |err| return unexpectedErrno(err),
25082275 }
25092276}
......@@ -2562,7 +2329,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro
25622329 .ROFS => return error.ReadOnlyFileSystem,
25632330 .NOTEMPTY => return error.DirNotEmpty,
25642331 .NOTCAPABLE => return error.AccessDenied,
2565 .ILSEQ => return error.InvalidUtf8,
2332 .ILSEQ => return error.BadPathName,
25662333
25672334 .INVAL => unreachable, // invalid flags, or pathname has . as last component
25682335 .BADF => unreachable, // always a race condition
......@@ -2595,10 +2362,7 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
25952362 .ROFS => return error.ReadOnlyFileSystem,
25962363 .EXIST => return error.DirNotEmpty,
25972364 .NOTEMPTY => return error.DirNotEmpty,
2598 .ILSEQ => |err| if (native_os == .wasi)
2599 return error.InvalidUtf8
2600 else
2601 return unexpectedErrno(err),
2365 .ILSEQ => return error.BadPathName,
26022366
26032367 .INVAL => unreachable, // invalid flags, or pathname has . as last component
26042368 .BADF => unreachable, // always a race condition
......@@ -2634,11 +2398,9 @@ pub const RenameError = error{
26342398 PathAlreadyExists,
26352399 ReadOnlyFileSystem,
26362400 RenameAcrossMountPoints,
2637 /// WASI-only; file paths must be valid UTF-8.
2638 InvalidUtf8,
2639 /// Windows-only; file paths provided by the user must be valid WTF-8.
2401 /// WASI: file paths must be valid UTF-8.
2402 /// Windows: file paths provided by the user must be valid WTF-8.
26402403 /// https://wtf-8.codeberg.page/
2641 InvalidWtf8,
26422404 BadPathName,
26432405 NoDevice,
26442406 SharingViolation,
......@@ -2700,10 +2462,7 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi
27002462 .NOTEMPTY => return error.PathAlreadyExists,
27012463 .ROFS => return error.ReadOnlyFileSystem,
27022464 .XDEV => return error.RenameAcrossMountPoints,
2703 .ILSEQ => |err| if (native_os == .wasi)
2704 return error.InvalidUtf8
2705 else
2706 return unexpectedErrno(err),
2465 .ILSEQ => return error.BadPathName,
27072466 else => |err| return unexpectedErrno(err),
27082467 }
27092468}
......@@ -2764,7 +2523,7 @@ fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!void {
27642523 .ROFS => return error.ReadOnlyFileSystem,
27652524 .XDEV => return error.RenameAcrossMountPoints,
27662525 .NOTCAPABLE => return error.AccessDenied,
2767 .ILSEQ => return error.InvalidUtf8,
2526 .ILSEQ => return error.BadPathName,
27682527 else => |err| return unexpectedErrno(err),
27692528 }
27702529}
......@@ -2815,10 +2574,7 @@ pub fn renameatZ(
28152574 .NOTEMPTY => return error.PathAlreadyExists,
28162575 .ROFS => return error.ReadOnlyFileSystem,
28172576 .XDEV => return error.RenameAcrossMountPoints,
2818 .ILSEQ => |err| if (native_os == .wasi)
2819 return error.InvalidUtf8
2820 else
2821 return unexpectedErrno(err),
2577 .ILSEQ => return error.BadPathName,
28222578 else => |err| return unexpectedErrno(err),
28232579 }
28242580}
......@@ -2869,7 +2625,7 @@ pub fn renameatW(
28692625 if (ReplaceIfExists == windows.TRUE) flags |= windows.FILE_RENAME_REPLACE_IF_EXISTS;
28702626 rename_info.* = .{
28712627 .Flags = flags,
2872 .RootDirectory = if (fs.path.isAbsoluteWindowsWTF16(new_path_w)) null else new_dir_fd,
2628 .RootDirectory = if (fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir_fd,
28732629 .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong
28742630 .FileName = undefined,
28752631 };
......@@ -2906,7 +2662,7 @@ pub fn renameatW(
29062662
29072663 rename_info.* = .{
29082664 .Flags = ReplaceIfExists,
2909 .RootDirectory = if (fs.path.isAbsoluteWindowsWTF16(new_path_w)) null else new_dir_fd,
2665 .RootDirectory = if (fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir_fd,
29102666 .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong
29112667 .FileName = undefined,
29122668 };
......@@ -2943,47 +2699,21 @@ pub fn renameatW(
29432699/// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding.
29442700pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: mode_t) MakeDirError!void {
29452701 if (native_os == .windows) {
2946 const sub_dir_path_w = try windows.sliceToPrefixedFileW(dir_fd, sub_dir_path);
2947 return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
2702 @compileError("use std.Io instead");
29482703 } else if (native_os == .wasi and !builtin.link_libc) {
2949 return mkdiratWasi(dir_fd, sub_dir_path, mode);
2704 @compileError("use std.Io instead");
29502705 } else {
29512706 const sub_dir_path_c = try toPosixPath(sub_dir_path);
29522707 return mkdiratZ(dir_fd, &sub_dir_path_c, mode);
29532708 }
29542709}
29552710
2956pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: mode_t) MakeDirError!void {
2957 _ = mode;
2958 switch (wasi.path_create_directory(dir_fd, sub_dir_path.ptr, sub_dir_path.len)) {
2959 .SUCCESS => return,
2960 .ACCES => return error.AccessDenied,
2961 .BADF => unreachable,
2962 .PERM => return error.PermissionDenied,
2963 .DQUOT => return error.DiskQuota,
2964 .EXIST => return error.PathAlreadyExists,
2965 .FAULT => unreachable,
2966 .LOOP => return error.SymLinkLoop,
2967 .MLINK => return error.LinkQuotaExceeded,
2968 .NAMETOOLONG => return error.NameTooLong,
2969 .NOENT => return error.FileNotFound,
2970 .NOMEM => return error.SystemResources,
2971 .NOSPC => return error.NoSpaceLeft,
2972 .NOTDIR => return error.NotDir,
2973 .ROFS => return error.ReadOnlyFileSystem,
2974 .NOTCAPABLE => return error.AccessDenied,
2975 .ILSEQ => return error.InvalidUtf8,
2976 else => |err| return unexpectedErrno(err),
2977 }
2978}
2979
29802711/// Same as `mkdirat` except the parameters are null-terminated.
29812712pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void {
29822713 if (native_os == .windows) {
2983 const sub_dir_path_w = try windows.cStrToPrefixedFileW(dir_fd, sub_dir_path);
2984 return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
2714 @compileError("use std.Io instead");
29852715 } else if (native_os == .wasi and !builtin.link_libc) {
2986 return mkdirat(dir_fd, mem.sliceTo(sub_dir_path, 0), mode);
2716 @compileError("use std.Io instead");
29872717 }
29882718 switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) {
29892719 .SUCCESS => return,
......@@ -3003,58 +2733,12 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: mode_t) MakeDir
30032733 .ROFS => return error.ReadOnlyFileSystem,
30042734 // dragonfly: when dir_fd is unlinked from filesystem
30052735 .NOTCONN => return error.FileNotFound,
3006 .ILSEQ => |err| if (native_os == .wasi)
3007 return error.InvalidUtf8
3008 else
3009 return unexpectedErrno(err),
2736 .ILSEQ => return error.BadPathName,
30102737 else => |err| return unexpectedErrno(err),
30112738 }
30122739}
30132740
3014/// Windows-only. Same as `mkdirat` except the parameter WTF16 LE encoded.
3015pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: mode_t) MakeDirError!void {
3016 _ = mode;
3017 const sub_dir_handle = windows.OpenFile(sub_path_w, .{
3018 .dir = dir_fd,
3019 .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
3020 .creation = windows.FILE_CREATE,
3021 .filter = .dir_only,
3022 }) catch |err| switch (err) {
3023 error.IsDir => return error.Unexpected,
3024 error.PipeBusy => return error.Unexpected,
3025 error.NoDevice => return error.Unexpected,
3026 error.WouldBlock => return error.Unexpected,
3027 error.AntivirusInterference => return error.Unexpected,
3028 else => |e| return e,
3029 };
3030 windows.CloseHandle(sub_dir_handle);
3031}
3032
3033pub const MakeDirError = error{
3034 /// In WASI, this error may occur when the file descriptor does
3035 /// not hold the required rights to create a new directory relative to it.
3036 AccessDenied,
3037 PermissionDenied,
3038 DiskQuota,
3039 PathAlreadyExists,
3040 SymLinkLoop,
3041 LinkQuotaExceeded,
3042 NameTooLong,
3043 FileNotFound,
3044 SystemResources,
3045 NoSpaceLeft,
3046 NotDir,
3047 ReadOnlyFileSystem,
3048 /// WASI-only; file paths must be valid UTF-8.
3049 InvalidUtf8,
3050 /// Windows-only; file paths provided by the user must be valid WTF-8.
3051 /// https://wtf-8.codeberg.page/
3052 InvalidWtf8,
3053 BadPathName,
3054 NoDevice,
3055 /// On Windows, `\\server` or `\\server\share` was not found.
3056 NetworkNotFound,
3057} || UnexpectedError;
2741pub const MakeDirError = std.Io.Dir.MakeError;
30582742
30592743/// Create a directory.
30602744/// `mode` is ignored on Windows and WASI.
......@@ -3099,10 +2783,7 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void {
30992783 .NOSPC => return error.NoSpaceLeft,
31002784 .NOTDIR => return error.NotDir,
31012785 .ROFS => return error.ReadOnlyFileSystem,
3102 .ILSEQ => |err| if (native_os == .wasi)
3103 return error.InvalidUtf8
3104 else
3105 return unexpectedErrno(err),
2786 .ILSEQ => return error.BadPathName,
31062787 else => |err| return unexpectedErrno(err),
31072788 }
31082789}
......@@ -3137,11 +2818,9 @@ pub const DeleteDirError = error{
31372818 NotDir,
31382819 DirNotEmpty,
31392820 ReadOnlyFileSystem,
3140 /// WASI-only; file paths must be valid UTF-8.
3141 InvalidUtf8,
3142 /// Windows-only; file paths provided by the user must be valid WTF-8.
2821 /// WASI: file paths must be valid UTF-8.
2822 /// Windows: file paths provided by the user must be valid WTF-8.
31432823 /// https://wtf-8.codeberg.page/
3144 InvalidWtf8,
31452824 BadPathName,
31462825 /// On Windows, `\\server` or `\\server\share` was not found.
31472826 NetworkNotFound,
......@@ -3193,10 +2872,7 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
31932872 .EXIST => return error.DirNotEmpty,
31942873 .NOTEMPTY => return error.DirNotEmpty,
31952874 .ROFS => return error.ReadOnlyFileSystem,
3196 .ILSEQ => |err| if (native_os == .wasi)
3197 return error.InvalidUtf8
3198 else
3199 return unexpectedErrno(err),
2875 .ILSEQ => return error.BadPathName,
32002876 else => |err| return unexpectedErrno(err),
32012877 }
32022878}
......@@ -3217,12 +2893,10 @@ pub const ChangeCurDirError = error{
32172893 FileNotFound,
32182894 SystemResources,
32192895 NotDir,
3220 BadPathName,
3221 /// WASI-only; file paths must be valid UTF-8.
3222 InvalidUtf8,
3223 /// Windows-only; file paths provided by the user must be valid WTF-8.
2896 /// WASI: file paths must be valid UTF-8.
2897 /// Windows: file paths provided by the user must be valid WTF-8.
32242898 /// https://wtf-8.codeberg.page/
3225 InvalidWtf8,
2899 BadPathName,
32262900} || UnexpectedError;
32272901
32282902/// Changes the current working directory of the calling process.
......@@ -3234,10 +2908,7 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
32342908 @compileError("WASI does not support os.chdir");
32352909 } else if (native_os == .windows) {
32362910 var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
3237 if (try std.unicode.checkWtf8ToWtf16LeOverflow(dir_path, &wtf16_dir_path)) {
3238 return error.NameTooLong;
3239 }
3240 const len = try std.unicode.wtf8ToWtf16Le(&wtf16_dir_path, dir_path);
2911 const len = try windows.wtf8ToWtf16Le(&wtf16_dir_path, dir_path);
32412912 return chdirW(wtf16_dir_path[0..len]);
32422913 } else {
32432914 const dir_path_c = try toPosixPath(dir_path);
......@@ -3253,10 +2924,7 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
32532924 if (native_os == .windows) {
32542925 const dir_path_span = mem.span(dir_path);
32552926 var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
3256 if (try std.unicode.checkWtf8ToWtf16LeOverflow(dir_path_span, &wtf16_dir_path)) {
3257 return error.NameTooLong;
3258 }
3259 const len = try std.unicode.wtf8ToWtf16Le(&wtf16_dir_path, dir_path_span);
2927 const len = try windows.wtf8ToWtf16Le(&wtf16_dir_path, dir_path_span);
32602928 return chdirW(wtf16_dir_path[0..len]);
32612929 } else if (native_os == .wasi and !builtin.link_libc) {
32622930 return chdir(mem.span(dir_path));
......@@ -3271,10 +2939,7 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
32712939 .NOENT => return error.FileNotFound,
32722940 .NOMEM => return error.SystemResources,
32732941 .NOTDIR => return error.NotDir,
3274 .ILSEQ => |err| if (native_os == .wasi)
3275 return error.InvalidUtf8
3276 else
3277 return unexpectedErrno(err),
2942 .ILSEQ => return error.BadPathName,
32782943 else => |err| return unexpectedErrno(err),
32792944 }
32802945}
......@@ -3320,11 +2985,9 @@ pub const ReadLinkError = error{
33202985 SystemResources,
33212986 NotLink,
33222987 NotDir,
3323 /// WASI-only; file paths must be valid UTF-8.
3324 InvalidUtf8,
3325 /// Windows-only; file paths provided by the user must be valid WTF-8.
2988 /// WASI: file paths must be valid UTF-8.
2989 /// Windows: file paths provided by the user must be valid WTF-8.
33262990 /// https://wtf-8.codeberg.page/
3327 InvalidWtf8,
33282991 BadPathName,
33292992 /// Windows-only. This error may occur if the opened reparse point is
33302993 /// of unsupported type.
......@@ -3380,10 +3043,7 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8
33803043 .NOENT => return error.FileNotFound,
33813044 .NOMEM => return error.SystemResources,
33823045 .NOTDIR => return error.NotDir,
3383 .ILSEQ => |err| if (native_os == .wasi)
3384 return error.InvalidUtf8
3385 else
3386 return unexpectedErrno(err),
3046 .ILSEQ => return error.BadPathName,
33873047 else => |err| return unexpectedErrno(err),
33883048 }
33893049}
......@@ -3425,7 +3085,7 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read
34253085 .NOMEM => return error.SystemResources,
34263086 .NOTDIR => return error.NotDir,
34273087 .NOTCAPABLE => return error.AccessDenied,
3428 .ILSEQ => return error.InvalidUtf8,
3088 .ILSEQ => return error.BadPathName,
34293089 else => |err| return unexpectedErrno(err),
34303090 }
34313091}
......@@ -3458,10 +3118,7 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read
34583118 .NOENT => return error.FileNotFound,
34593119 .NOMEM => return error.SystemResources,
34603120 .NOTDIR => return error.NotDir,
3461 .ILSEQ => |err| if (native_os == .wasi)
3462 return error.InvalidUtf8
3463 else
3464 return unexpectedErrno(err),
3121 .ILSEQ => return error.BadPathName,
34653122 else => |err| return unexpectedErrno(err),
34663123 }
34673124}
......@@ -3612,7 +3269,7 @@ pub const SocketError = error{
36123269 AccessDenied,
36133270
36143271 /// The implementation does not support the specified address family.
3615 AddressFamilyNotSupported,
3272 AddressFamilyUnsupported,
36163273
36173274 /// Unknown protocol, or protocol family not available.
36183275 ProtocolFamilyNotAvailable,
......@@ -3635,33 +3292,6 @@ pub const SocketError = error{
36353292} || UnexpectedError;
36363293
36373294pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t {
3638 if (native_os == .windows) {
3639 // These flags are not actually part of the Windows API, instead they are converted here for compatibility
3640 const filtered_sock_type = socket_type & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC);
3641 var flags: u32 = windows.ws2_32.WSA_FLAG_OVERLAPPED;
3642 if ((socket_type & SOCK.CLOEXEC) != 0) flags |= windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT;
3643
3644 const rc = try windows.WSASocketW(
3645 @bitCast(domain),
3646 @bitCast(filtered_sock_type),
3647 @bitCast(protocol),
3648 null,
3649 0,
3650 flags,
3651 );
3652 errdefer windows.closesocket(rc) catch unreachable;
3653 if ((socket_type & SOCK.NONBLOCK) != 0) {
3654 var mode: c_ulong = 1; // nonblocking
3655 if (windows.ws2_32.SOCKET_ERROR == windows.ws2_32.ioctlsocket(rc, windows.ws2_32.FIONBIO, &mode)) {
3656 switch (windows.ws2_32.WSAGetLastError()) {
3657 // have not identified any error codes that should be handled yet
3658 else => unreachable,
3659 }
3660 }
3661 }
3662 return rc;
3663 }
3664
36653295 const have_sock_flags = !builtin.target.os.tag.isDarwin() and native_os != .haiku;
36663296 const filtered_sock_type = if (!have_sock_flags)
36673297 socket_type & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC)
......@@ -3678,7 +3308,7 @@ pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t
36783308 return fd;
36793309 },
36803310 .ACCES => return error.AccessDenied,
3681 .AFNOSUPPORT => return error.AddressFamilyNotSupported,
3311 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
36823312 .INVAL => return error.ProtocolFamilyNotAvailable,
36833313 .MFILE => return error.ProcessFdQuotaExceeded,
36843314 .NFILE => return error.SystemFdQuotaExceeded,
......@@ -3718,7 +3348,7 @@ pub fn socketpair(domain: u32, socket_type: u32, protocol: u32) SocketError![2]s
37183348 return socks;
37193349 },
37203350 .ACCES => return error.AccessDenied,
3721 .AFNOSUPPORT => return error.AddressFamilyNotSupported,
3351 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
37223352 .INVAL => return error.ProtocolFamilyNotAvailable,
37233353 .MFILE => return error.ProcessFdQuotaExceeded,
37243354 .NFILE => return error.SystemFdQuotaExceeded,
......@@ -3738,10 +3368,10 @@ pub const ShutdownError = error{
37383368 BlockingOperationInProgress,
37393369
37403370 /// The network subsystem has failed.
3741 NetworkSubsystemFailed,
3371 NetworkDown,
37423372
37433373 /// The socket is not connected (connection-oriented sockets only).
3744 SocketNotConnected,
3374 SocketUnconnected,
37453375 SystemResources,
37463376} || UnexpectedError;
37473377
......@@ -3756,14 +3386,14 @@ pub fn shutdown(sock: socket_t, how: ShutdownHow) ShutdownError!void {
37563386 .both => windows.ws2_32.SD_BOTH,
37573387 });
37583388 if (0 != result) switch (windows.ws2_32.WSAGetLastError()) {
3759 .WSAECONNABORTED => return error.ConnectionAborted,
3760 .WSAECONNRESET => return error.ConnectionResetByPeer,
3761 .WSAEINPROGRESS => return error.BlockingOperationInProgress,
3762 .WSAEINVAL => unreachable,
3763 .WSAENETDOWN => return error.NetworkSubsystemFailed,
3764 .WSAENOTCONN => return error.SocketNotConnected,
3765 .WSAENOTSOCK => unreachable,
3766 .WSANOTINITIALISED => unreachable,
3389 .ECONNABORTED => return error.ConnectionAborted,
3390 .ECONNRESET => return error.ConnectionResetByPeer,
3391 .EINPROGRESS => return error.BlockingOperationInProgress,
3392 .EINVAL => unreachable,
3393 .ENETDOWN => return error.NetworkDown,
3394 .ENOTCONN => return error.SocketUnconnected,
3395 .ENOTSOCK => unreachable,
3396 .NOTINITIALISED => unreachable,
37673397 else => |err| return windows.unexpectedWSAError(err),
37683398 };
37693399 } else {
......@@ -3776,7 +3406,7 @@ pub fn shutdown(sock: socket_t, how: ShutdownHow) ShutdownError!void {
37763406 .SUCCESS => return,
37773407 .BADF => unreachable,
37783408 .INVAL => unreachable,
3779 .NOTCONN => return error.SocketNotConnected,
3409 .NOTCONN => return error.SocketUnconnected,
37803410 .NOTSOCK => unreachable,
37813411 .NOBUFS => return error.SystemResources,
37823412 else => |err| return unexpectedErrno(err),
......@@ -3785,70 +3415,17 @@ pub fn shutdown(sock: socket_t, how: ShutdownHow) ShutdownError!void {
37853415}
37863416
37873417pub const BindError = error{
3788 /// The address is protected, and the user is not the superuser.
3789 /// For UNIX domain sockets: Search permission is denied on a component
3790 /// of the path prefix.
3791 AccessDenied,
3792
3793 /// The given address is already in use, or in the case of Internet domain sockets,
3794 /// The port number was specified as zero in the socket
3795 /// address structure, but, upon attempting to bind to an ephemeral port, it was
3796 /// determined that all port numbers in the ephemeral port range are currently in
3797 /// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range ip(7).
3798 AddressInUse,
3799
3800 /// A nonexistent interface was requested or the requested address was not local.
3801 AddressNotAvailable,
3802
3803 /// The address is not valid for the address family of socket.
3804 AddressFamilyNotSupported,
3805
3806 /// Too many symbolic links were encountered in resolving addr.
38073418 SymLinkLoop,
3808
3809 /// addr is too long.
38103419 NameTooLong,
3811
3812 /// A component in the directory prefix of the socket pathname does not exist.
38133420 FileNotFound,
3814
3815 /// Insufficient kernel memory was available.
3816 SystemResources,
3817
3818 /// A component of the path prefix is not a directory.
38193421 NotDir,
3820
3821 /// The socket inode would reside on a read-only filesystem.
38223422 ReadOnlyFileSystem,
3423 AccessDenied,
3424} || std.Io.net.IpAddress.BindError;
38233425
3824 /// The network subsystem has failed.
3825 NetworkSubsystemFailed,
3826
3827 FileDescriptorNotASocket,
3828
3829 AlreadyBound,
3830} || UnexpectedError;
3831
3832/// addr is `*const T` where T is one of the sockaddr
38333426pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!void {
38343427 if (native_os == .windows) {
3835 const rc = windows.bind(sock, addr, len);
3836 if (rc == windows.ws2_32.SOCKET_ERROR) {
3837 switch (windows.ws2_32.WSAGetLastError()) {
3838 .WSANOTINITIALISED => unreachable, // not initialized WSA
3839 .WSAEACCES => return error.AccessDenied,
3840 .WSAEADDRINUSE => return error.AddressInUse,
3841 .WSAEADDRNOTAVAIL => return error.AddressNotAvailable,
3842 .WSAENOTSOCK => return error.FileDescriptorNotASocket,
3843 .WSAEFAULT => unreachable, // invalid pointers
3844 .WSAEINVAL => return error.AlreadyBound,
3845 .WSAENOBUFS => return error.SystemResources,
3846 .WSAENETDOWN => return error.NetworkSubsystemFailed,
3847 else => |err| return windows.unexpectedWSAError(err),
3848 }
3849 unreachable;
3850 }
3851 return;
3428 @compileError("use std.Io instead");
38523429 } else {
38533430 const rc = system.bind(sock, addr, len);
38543431 switch (errno(rc)) {
......@@ -3858,8 +3435,8 @@ pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!voi
38583435 .BADF => unreachable, // always a race condition if this error is returned
38593436 .INVAL => unreachable, // invalid parameters
38603437 .NOTSOCK => unreachable, // invalid `sockfd`
3861 .AFNOSUPPORT => return error.AddressFamilyNotSupported,
3862 .ADDRNOTAVAIL => return error.AddressNotAvailable,
3438 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
3439 .ADDRNOTAVAIL => return error.AddressUnavailable,
38633440 .FAULT => unreachable, // invalid `addr` pointer
38643441 .LOOP => return error.SymLinkLoop,
38653442 .NAMETOOLONG => return error.NameTooLong,
......@@ -3874,51 +3451,13 @@ pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!voi
38743451}
38753452
38763453pub const ListenError = error{
3877 /// Another socket is already listening on the same port.
3878 /// For Internet domain sockets, the socket referred to by sockfd had not previously
3879 /// been bound to an address and, upon attempting to bind it to an ephemeral port, it
3880 /// was determined that all port numbers in the ephemeral port range are currently in
3881 /// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range in ip(7).
3882 AddressInUse,
3883
3884 /// The file descriptor sockfd does not refer to a socket.
38853454 FileDescriptorNotASocket,
3886
3887 /// The socket is not of a type that supports the listen() operation.
38883455 OperationNotSupported,
3889
3890 /// The network subsystem has failed.
3891 NetworkSubsystemFailed,
3892
3893 /// Ran out of system resources
3894 /// On Windows it can either run out of socket descriptors or buffer space
3895 SystemResources,
3896
3897 /// Already connected
3898 AlreadyConnected,
3899
3900 /// Socket has not been bound yet
3901 SocketNotBound,
3902} || UnexpectedError;
3456} || std.Io.net.IpAddress.ListenError || std.Io.net.UnixAddress.ListenError;
39033457
39043458pub fn listen(sock: socket_t, backlog: u31) ListenError!void {
39053459 if (native_os == .windows) {
3906 const rc = windows.listen(sock, backlog);
3907 if (rc == windows.ws2_32.SOCKET_ERROR) {
3908 switch (windows.ws2_32.WSAGetLastError()) {
3909 .WSANOTINITIALISED => unreachable, // not initialized WSA
3910 .WSAENETDOWN => return error.NetworkSubsystemFailed,
3911 .WSAEADDRINUSE => return error.AddressInUse,
3912 .WSAEISCONN => return error.AlreadyConnected,
3913 .WSAEINVAL => return error.SocketNotBound,
3914 .WSAEMFILE, .WSAENOBUFS => return error.SystemResources,
3915 .WSAENOTSOCK => return error.FileDescriptorNotASocket,
3916 .WSAEOPNOTSUPP => return error.OperationNotSupported,
3917 .WSAEINPROGRESS => unreachable,
3918 else => |err| return windows.unexpectedWSAError(err),
3919 }
3920 }
3921 return;
3460 @compileError("use std.Io instead");
39223461 } else {
39233462 const rc = system.listen(sock, backlog);
39243463 switch (errno(rc)) {
......@@ -3932,70 +3471,12 @@ pub fn listen(sock: socket_t, backlog: u31) ListenError!void {
39323471 }
39333472}
39343473
3935pub const AcceptError = error{
3936 ConnectionAborted,
3937
3938 /// The file descriptor sockfd does not refer to a socket.
3939 FileDescriptorNotASocket,
3474pub const AcceptError = std.Io.net.Server.AcceptError;
39403475
3941 /// The per-process limit on the number of open file descriptors has been reached.
3942 ProcessFdQuotaExceeded,
3943
3944 /// The system-wide limit on the total number of open files has been reached.
3945 SystemFdQuotaExceeded,
3946
3947 /// Not enough free memory. This often means that the memory allocation is limited
3948 /// by the socket buffer limits, not by the system memory.
3949 SystemResources,
3950
3951 /// Socket is not listening for new connections.
3952 SocketNotListening,
3953
3954 ProtocolFailure,
3955
3956 /// Firewall rules forbid connection.
3957 BlockedByFirewall,
3958
3959 /// This error occurs when no global event loop is configured,
3960 /// and accepting from the socket would block.
3961 WouldBlock,
3962
3963 /// An incoming connection was indicated, but was subsequently terminated by the
3964 /// remote peer prior to accepting the call.
3965 ConnectionResetByPeer,
3966
3967 /// The network subsystem has failed.
3968 NetworkSubsystemFailed,
3969
3970 /// The referenced socket is not a type that supports connection-oriented service.
3971 OperationNotSupported,
3972} || UnexpectedError;
3973
3974/// Accept a connection on a socket.
3975/// If `sockfd` is opened in non blocking mode, the function will
3976/// return error.WouldBlock when EAGAIN is received.
39773476pub fn accept(
3978 /// This argument is a socket that has been created with `socket`, bound to a local address
3979 /// with `bind`, and is listening for connections after a `listen`.
39803477 sock: socket_t,
3981 /// This argument is a pointer to a sockaddr structure. This structure is filled in with the
3982 /// address of the peer socket, as known to the communications layer. The exact format of the
3983 /// address returned addr is determined by the socket's address family (see `socket` and the
3984 /// respective protocol man pages).
39853478 addr: ?*sockaddr,
3986 /// This argument is a value-result argument: the caller must initialize it to contain the
3987 /// size (in bytes) of the structure pointed to by addr; on return it will contain the actual size
3988 /// of the peer address.
3989 ///
3990 /// The returned address is truncated if the buffer provided is too small; in this case, `addr_size`
3991 /// will return a value greater than was supplied to the call.
39923479 addr_size: ?*socklen_t,
3993 /// The following values can be bitwise ORed in flags to obtain different behavior:
3994 /// * `SOCK.NONBLOCK` - Set the `NONBLOCK` file status flag on the open file description (see `open`)
3995 /// referred to by the new file descriptor. Using this flag saves extra calls to `fcntl` to achieve
3996 /// the same result.
3997 /// * `SOCK.CLOEXEC` - Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor. See the
3998 /// description of the `CLOEXEC` flag in `open` for reasons why this may be useful.
39993480 flags: u32,
40003481) AcceptError!socket_t {
40013482 const have_accept4 = !(builtin.target.os.tag.isDarwin() or native_os == .windows or native_os == .haiku);
......@@ -4004,29 +3485,11 @@ pub fn accept(
40043485 const accepted_sock: socket_t = while (true) {
40053486 const rc = if (have_accept4)
40063487 system.accept4(sock, addr, addr_size, flags)
4007 else if (native_os == .windows)
4008 windows.accept(sock, addr, addr_size)
40093488 else
40103489 system.accept(sock, addr, addr_size);
40113490
40123491 if (native_os == .windows) {
4013 if (rc == windows.ws2_32.INVALID_SOCKET) {
4014 switch (windows.ws2_32.WSAGetLastError()) {
4015 .WSANOTINITIALISED => unreachable, // not initialized WSA
4016 .WSAECONNRESET => return error.ConnectionResetByPeer,
4017 .WSAEFAULT => unreachable,
4018 .WSAENOTSOCK => return error.FileDescriptorNotASocket,
4019 .WSAEINVAL => return error.SocketNotListening,
4020 .WSAEMFILE => return error.ProcessFdQuotaExceeded,
4021 .WSAENETDOWN => return error.NetworkSubsystemFailed,
4022 .WSAENOBUFS => return error.FileDescriptorNotASocket,
4023 .WSAEOPNOTSUPP => return error.OperationNotSupported,
4024 .WSAEWOULDBLOCK => return error.WouldBlock,
4025 else => |err| return windows.unexpectedWSAError(err),
4026 }
4027 } else {
4028 break rc;
4029 }
3492 @compileError("use std.Io instead");
40303493 } else {
40313494 switch (errno(rc)) {
40323495 .SUCCESS => break @intCast(rc),
......@@ -4088,9 +3551,9 @@ fn setSockFlags(sock: socket_t, flags: u32) !void {
40883551 var mode: c_ulong = 1;
40893552 if (windows.ws2_32.ioctlsocket(sock, windows.ws2_32.FIONBIO, &mode) == windows.ws2_32.SOCKET_ERROR) {
40903553 switch (windows.ws2_32.WSAGetLastError()) {
4091 .WSANOTINITIALISED => unreachable,
4092 .WSAENETDOWN => return error.NetworkSubsystemFailed,
4093 .WSAENOTSOCK => return error.FileDescriptorNotASocket,
3554 .NOTINITIALISED => unreachable,
3555 .ENETDOWN => return error.NetworkDown,
3556 .ENOTSOCK => return error.FileDescriptorNotASocket,
40943557 // TODO: handle more errors
40953558 else => |err| return windows.unexpectedWSAError(err),
40963559 }
......@@ -4230,7 +3693,7 @@ pub const GetSockNameError = error{
42303693 SystemResources,
42313694
42323695 /// The network subsystem has failed.
4233 NetworkSubsystemFailed,
3696 NetworkDown,
42343697
42353698 /// Socket hasn't been bound yet
42363699 SocketNotBound,
......@@ -4243,11 +3706,11 @@ pub fn getsockname(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSock
42433706 const rc = windows.getsockname(sock, addr, addrlen);
42443707 if (rc == windows.ws2_32.SOCKET_ERROR) {
42453708 switch (windows.ws2_32.WSAGetLastError()) {
4246 .WSANOTINITIALISED => unreachable,
4247 .WSAENETDOWN => return error.NetworkSubsystemFailed,
4248 .WSAEFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value
4249 .WSAENOTSOCK => return error.FileDescriptorNotASocket,
4250 .WSAEINVAL => return error.SocketNotBound,
3709 .NOTINITIALISED => unreachable,
3710 .ENETDOWN => return error.NetworkDown,
3711 .EFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value
3712 .ENOTSOCK => return error.FileDescriptorNotASocket,
3713 .EINVAL => return error.SocketNotBound,
42513714 else => |err| return windows.unexpectedWSAError(err),
42523715 }
42533716 }
......@@ -4272,11 +3735,11 @@ pub fn getpeername(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSock
42723735 const rc = windows.getpeername(sock, addr, addrlen);
42733736 if (rc == windows.ws2_32.SOCKET_ERROR) {
42743737 switch (windows.ws2_32.WSAGetLastError()) {
4275 .WSANOTINITIALISED => unreachable,
4276 .WSAENETDOWN => return error.NetworkSubsystemFailed,
4277 .WSAEFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value
4278 .WSAENOTSOCK => return error.FileDescriptorNotASocket,
4279 .WSAEINVAL => return error.SocketNotBound,
3738 .NOTINITIALISED => unreachable,
3739 .ENETDOWN => return error.NetworkDown,
3740 .EFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value
3741 .ENOTSOCK => return error.FileDescriptorNotASocket,
3742 .EINVAL => return error.SocketNotBound,
42803743 else => |err| return windows.unexpectedWSAError(err),
42813744 }
42823745 }
......@@ -4296,86 +3759,11 @@ pub fn getpeername(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSock
42963759 }
42973760}
42983761
4299pub const ConnectError = error{
4300 /// For UNIX domain sockets, which are identified by pathname: Write permission is denied on the socket
4301 /// file, or search permission is denied for one of the directories in the path prefix.
4302 /// or
4303 /// The user tried to connect to a broadcast address without having the socket broadcast flag enabled or
4304 /// the connection request failed because of a local firewall rule.
4305 AccessDenied,
4306
4307 /// See AccessDenied
4308 PermissionDenied,
4309
4310 /// Local address is already in use.
4311 AddressInUse,
4312
4313 /// (Internet domain sockets) The socket referred to by sockfd had not previously been bound to an
4314 /// address and, upon attempting to bind it to an ephemeral port, it was determined that all port numbers
4315 /// in the ephemeral port range are currently in use. See the discussion of
4316 /// /proc/sys/net/ipv4/ip_local_port_range in ip(7).
4317 AddressNotAvailable,
4318
4319 /// The passed address didn't have the correct address family in its sa_family field.
4320 AddressFamilyNotSupported,
4321
4322 /// Insufficient entries in the routing cache.
4323 SystemResources,
4324
4325 /// A connect() on a stream socket found no one listening on the remote address.
4326 ConnectionRefused,
4327
4328 /// Network is unreachable.
4329 NetworkUnreachable,
4330
4331 /// Timeout while attempting connection. The server may be too busy to accept new connections. Note
4332 /// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
4333 ConnectionTimedOut,
4334
4335 /// This error occurs when no global event loop is configured,
4336 /// and connecting to the socket would block.
4337 WouldBlock,
4338
4339 /// The given path for the unix socket does not exist.
4340 FileNotFound,
4341
4342 /// Connection was reset by peer before connect could complete.
4343 ConnectionResetByPeer,
3762pub const ConnectError = std.Io.net.IpAddress.ConnectError || std.Io.net.UnixAddress.ConnectError;
43443763
4345 /// Socket is non-blocking and already has a pending connection in progress.
4346 ConnectionPending,
4347
4348 /// Socket was already connected
4349 AlreadyConnected,
4350} || UnexpectedError;
4351
4352/// Initiate a connection on a socket.
4353/// If `sockfd` is opened in non blocking mode, the function will
4354/// return error.WouldBlock when EAGAIN or EINPROGRESS is received.
43553764pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) ConnectError!void {
43563765 if (native_os == .windows) {
4357 const rc = windows.ws2_32.connect(sock, sock_addr, @intCast(len));
4358 if (rc == 0) return;
4359 switch (windows.ws2_32.WSAGetLastError()) {
4360 .WSAEADDRINUSE => return error.AddressInUse,
4361 .WSAEADDRNOTAVAIL => return error.AddressNotAvailable,
4362 .WSAECONNREFUSED => return error.ConnectionRefused,
4363 .WSAECONNRESET => return error.ConnectionResetByPeer,
4364 .WSAETIMEDOUT => return error.ConnectionTimedOut,
4365 .WSAEHOSTUNREACH, // TODO: should we return NetworkUnreachable in this case as well?
4366 .WSAENETUNREACH,
4367 => return error.NetworkUnreachable,
4368 .WSAEFAULT => unreachable,
4369 .WSAEINVAL => unreachable,
4370 .WSAEISCONN => return error.AlreadyConnected,
4371 .WSAENOTSOCK => unreachable,
4372 .WSAEWOULDBLOCK => return error.WouldBlock,
4373 .WSAEACCES => unreachable,
4374 .WSAENOBUFS => return error.SystemResources,
4375 .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported,
4376 else => |err| return windows.unexpectedWSAError(err),
4377 }
4378 return;
3766 @compileError("use std.Io instead");
43793767 }
43803768
43813769 while (true) {
......@@ -4383,9 +3771,8 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne
43833771 .SUCCESS => return,
43843772 .ACCES => return error.AccessDenied,
43853773 .PERM => return error.PermissionDenied,
4386 .ADDRINUSE => return error.AddressInUse,
4387 .ADDRNOTAVAIL => return error.AddressNotAvailable,
4388 .AFNOSUPPORT => return error.AddressFamilyNotSupported,
3774 .ADDRNOTAVAIL => return error.AddressUnavailable,
3775 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
43893776 .AGAIN, .INPROGRESS => return error.WouldBlock,
43903777 .ALREADY => return error.ConnectionPending,
43913778 .BADF => unreachable, // sockfd is not a valid open file descriptor.
......@@ -4393,12 +3780,12 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne
43933780 .CONNRESET => return error.ConnectionResetByPeer,
43943781 .FAULT => unreachable, // The socket structure address is outside the user's address space.
43953782 .INTR => continue,
4396 .ISCONN => return error.AlreadyConnected, // The socket is already connected.
3783 .ISCONN => @panic("AlreadyConnected"), // The socket is already connected.
43973784 .HOSTUNREACH => return error.NetworkUnreachable,
43983785 .NETUNREACH => return error.NetworkUnreachable,
43993786 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
44003787 .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
4401 .TIMEDOUT => return error.ConnectionTimedOut,
3788 .TIMEDOUT => return error.Timeout,
44023789 .NOENT => return error.FileNotFound, // Returned when socket is AF.UNIX and the given path does not exist.
44033790 .CONNABORTED => unreachable, // Tried to reuse socket that previously received error.ConnectionRefused.
44043791 else => |err| return unexpectedErrno(err),
......@@ -4446,8 +3833,8 @@ pub fn getsockoptError(sockfd: fd_t) ConnectError!void {
44463833 .ACCES => return error.AccessDenied,
44473834 .PERM => return error.PermissionDenied,
44483835 .ADDRINUSE => return error.AddressInUse,
4449 .ADDRNOTAVAIL => return error.AddressNotAvailable,
4450 .AFNOSUPPORT => return error.AddressFamilyNotSupported,
3836 .ADDRNOTAVAIL => return error.AddressUnavailable,
3837 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
44513838 .AGAIN => return error.SystemResources,
44523839 .ALREADY => return error.ConnectionPending,
44533840 .BADF => unreachable, // sockfd is not a valid open file descriptor.
......@@ -4458,7 +3845,7 @@ pub fn getsockoptError(sockfd: fd_t) ConnectError!void {
44583845 .NETUNREACH => return error.NetworkUnreachable,
44593846 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
44603847 .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
4461 .TIMEDOUT => return error.ConnectionTimedOut,
3848 .TIMEDOUT => return error.Timeout,
44623849 .CONNRESET => return error.ConnectionResetByPeer,
44633850 else => |err| return unexpectedErrno(err),
44643851 },
......@@ -4512,14 +3899,7 @@ pub fn wait4(pid: pid_t, flags: u32, ru: ?*rusage) WaitPidResult {
45123899 }
45133900}
45143901
4515pub const FStatError = error{
4516 SystemResources,
4517
4518 /// In WASI, this error may occur when the file descriptor does
4519 /// not hold the required rights to get its filestat information.
4520 AccessDenied,
4521 PermissionDenied,
4522} || UnexpectedError;
3902pub const FStatError = std.Io.File.StatError;
45233903
45243904/// Return information about a file descriptor.
45253905pub fn fstat(fd: fd_t) FStatError!Stat {
......@@ -4546,21 +3926,17 @@ pub const FStatAtError = FStatError || error{
45463926 NameTooLong,
45473927 FileNotFound,
45483928 SymLinkLoop,
4549 /// WASI-only; file paths must be valid UTF-8.
4550 InvalidUtf8,
3929 BadPathName,
45513930};
45523931
45533932/// Similar to `fstat`, but returns stat of a resource pointed to by `pathname`
45543933/// which is relative to `dirfd` handle.
45553934/// On WASI, `pathname` should be encoded as valid UTF-8.
45563935/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.
4557/// See also `fstatatZ` and `std.os.fstatat_wasi`.
3936/// See also `fstatatZ`.
45583937pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {
45593938 if (native_os == .wasi and !builtin.link_libc) {
4560 const filestat = try std.os.fstatat_wasi(dirfd, pathname, .{
4561 .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0,
4562 });
4563 return Stat.fromFilestat(filestat);
3939 @compileError("use std.Io instead");
45643940 } else if (native_os == .windows) {
45653941 @compileError("fstatat is not yet implemented on Windows");
45663942 } else {
......@@ -4573,10 +3949,7 @@ pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat
45733949/// See also `fstatat`.
45743950pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat {
45753951 if (native_os == .wasi and !builtin.link_libc) {
4576 const filestat = try std.os.fstatat_wasi(dirfd, mem.sliceTo(pathname, 0), .{
4577 .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0,
4578 });
4579 return Stat.fromFilestat(filestat);
3952 @compileError("use std.Io instead");
45803953 }
45813954
45823955 const fstatat_sym = if (lfs64_abi) system.fstatat64 else system.fstatat;
......@@ -4593,10 +3966,7 @@ pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!S
45933966 .LOOP => return error.SymLinkLoop,
45943967 .NOENT => return error.FileNotFound,
45953968 .NOTDIR => return error.FileNotFound,
4596 .ILSEQ => |err| if (native_os == .wasi)
4597 return error.InvalidUtf8
4598 else
4599 return unexpectedErrno(err),
3969 .ILSEQ => return error.BadPathName,
46003970 else => |err| return unexpectedErrno(err),
46013971 }
46023972}
......@@ -5069,32 +4439,29 @@ pub const AccessError = error{
50694439 NameTooLong,
50704440 InputOutput,
50714441 SystemResources,
5072 BadPathName,
50734442 FileBusy,
50744443 SymLinkLoop,
50754444 ReadOnlyFileSystem,
5076 /// WASI-only; file paths must be valid UTF-8.
5077 InvalidUtf8,
5078 /// Windows-only; file paths provided by the user must be valid WTF-8.
4445 /// WASI: file paths must be valid UTF-8.
4446 /// Windows: file paths provided by the user must be valid WTF-8.
50794447 /// https://wtf-8.codeberg.page/
5080 InvalidWtf8,
4448 BadPathName,
4449 Canceled,
50814450} || UnexpectedError;
50824451
50834452/// check user's permissions for a file
50844453///
50854454/// * On Windows, asserts `path` is valid [WTF-8](https://wtf-8.codeberg.page/).
5086/// * On WASI, invalid UTF-8 passed to `path` causes `error.InvalidUtf8`.
4455/// * On WASI, invalid UTF-8 passed to `path` causes `error.BadPathName`.
50874456/// * On other platforms, `path` is an opaque sequence of bytes with no particular encoding.
50884457///
50894458/// On Windows, `mode` is ignored. This is a POSIX API that is only partially supported by
50904459/// Windows. See `fs` for the cross-platform file system API.
50914460pub fn access(path: []const u8, mode: u32) AccessError!void {
50924461 if (native_os == .windows) {
5093 const path_w = try windows.sliceToPrefixedFileW(null, path);
5094 _ = try windows.GetFileAttributesW(path_w.span().ptr);
5095 return;
4462 @compileError("use std.Io instead");
50964463 } else if (native_os == .wasi and !builtin.link_libc) {
5097 return faccessat(AT.FDCWD, path, mode, 0);
4464 @compileError("wasi doesn't support absolute paths");
50984465 }
50994466 const path_c = try toPosixPath(path);
51004467 return accessZ(&path_c, mode);
......@@ -5103,9 +4470,7 @@ pub fn access(path: []const u8, mode: u32) AccessError!void {
51034470/// Same as `access` except `path` is null-terminated.
51044471pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
51054472 if (native_os == .windows) {
5106 const path_w = try windows.cStrToPrefixedFileW(null, path);
5107 _ = try windows.GetFileAttributesW(path_w.span().ptr);
5108 return;
4473 @compileError("use std.Io instead");
51094474 } else if (native_os == .wasi and !builtin.link_libc) {
51104475 return access(mem.sliceTo(path, 0), mode);
51114476 }
......@@ -5123,132 +4488,11 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
51234488 .FAULT => unreachable,
51244489 .IO => return error.InputOutput,
51254490 .NOMEM => return error.SystemResources,
5126 .ILSEQ => |err| if (native_os == .wasi)
5127 return error.InvalidUtf8
5128 else
5129 return unexpectedErrno(err),
4491 .ILSEQ => return error.BadPathName,
51304492 else => |err| return unexpectedErrno(err),
51314493 }
51324494}
51334495
5134/// Check user's permissions for a file, based on an open directory handle.
5135///
5136/// * On Windows, asserts `path` is valid [WTF-8](https://wtf-8.codeberg.page/).
5137/// * On WASI, invalid UTF-8 passed to `path` causes `error.InvalidUtf8`.
5138/// * On other platforms, `path` is an opaque sequence of bytes with no particular encoding.
5139///
5140/// On Windows, `mode` is ignored. This is a POSIX API that is only partially supported by
5141/// Windows. See `fs` for the cross-platform file system API.
5142pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessError!void {
5143 if (native_os == .windows) {
5144 const path_w = try windows.sliceToPrefixedFileW(dirfd, path);
5145 return faccessatW(dirfd, path_w.span().ptr);
5146 } else if (native_os == .wasi and !builtin.link_libc) {
5147 const resolved: RelativePathWasi = .{ .dir_fd = dirfd, .relative_path = path };
5148
5149 const st = try std.os.fstatat_wasi(dirfd, path, .{
5150 .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0,
5151 });
5152
5153 if (mode != F_OK) {
5154 var directory: wasi.fdstat_t = undefined;
5155 if (wasi.fd_fdstat_get(resolved.dir_fd, &directory) != .SUCCESS) {
5156 return error.AccessDenied;
5157 }
5158
5159 var rights: wasi.rights_t = .{};
5160 if (mode & R_OK != 0) {
5161 if (st.filetype == .DIRECTORY) {
5162 rights.FD_READDIR = true;
5163 } else {
5164 rights.FD_READ = true;
5165 }
5166 }
5167 if (mode & W_OK != 0) {
5168 rights.FD_WRITE = true;
5169 }
5170 // No validation for X_OK
5171
5172 // https://github.com/ziglang/zig/issues/18882
5173 const rights_int: u64 = @bitCast(rights);
5174 const inheriting_int: u64 = @bitCast(directory.fs_rights_inheriting);
5175 if ((rights_int & inheriting_int) != rights_int) {
5176 return error.AccessDenied;
5177 }
5178 }
5179 return;
5180 }
5181 const path_c = try toPosixPath(path);
5182 return faccessatZ(dirfd, &path_c, mode, flags);
5183}
5184
5185/// Same as `faccessat` except the path parameter is null-terminated.
5186pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) AccessError!void {
5187 if (native_os == .windows) {
5188 const path_w = try windows.cStrToPrefixedFileW(dirfd, path);
5189 return faccessatW(dirfd, path_w.span().ptr);
5190 } else if (native_os == .wasi and !builtin.link_libc) {
5191 return faccessat(dirfd, mem.sliceTo(path, 0), mode, flags);
5192 }
5193 switch (errno(system.faccessat(dirfd, path, mode, flags))) {
5194 .SUCCESS => return,
5195 .ACCES => return error.AccessDenied,
5196 .PERM => return error.PermissionDenied,
5197 .ROFS => return error.ReadOnlyFileSystem,
5198 .LOOP => return error.SymLinkLoop,
5199 .TXTBSY => return error.FileBusy,
5200 .NOTDIR => return error.FileNotFound,
5201 .NOENT => return error.FileNotFound,
5202 .NAMETOOLONG => return error.NameTooLong,
5203 .INVAL => unreachable,
5204 .FAULT => unreachable,
5205 .IO => return error.InputOutput,
5206 .NOMEM => return error.SystemResources,
5207 .ILSEQ => |err| if (native_os == .wasi)
5208 return error.InvalidUtf8
5209 else
5210 return unexpectedErrno(err),
5211 else => |err| return unexpectedErrno(err),
5212 }
5213}
5214
5215/// Same as `faccessat` except asserts the target is Windows and the path parameter
5216/// is NtDll-prefixed, null-terminated, WTF-16 encoded.
5217pub fn faccessatW(dirfd: fd_t, sub_path_w: [*:0]const u16) AccessError!void {
5218 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
5219 return;
5220 }
5221 if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
5222 return;
5223 }
5224
5225 const path_len_bytes = cast(u16, mem.sliceTo(sub_path_w, 0).len * 2) orelse return error.NameTooLong;
5226 var nt_name = windows.UNICODE_STRING{
5227 .Length = path_len_bytes,
5228 .MaximumLength = path_len_bytes,
5229 .Buffer = @constCast(sub_path_w),
5230 };
5231 var attr = windows.OBJECT_ATTRIBUTES{
5232 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
5233 .RootDirectory = if (fs.path.isAbsoluteWindowsW(sub_path_w)) null else dirfd,
5234 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
5235 .ObjectName = &nt_name,
5236 .SecurityDescriptor = null,
5237 .SecurityQualityOfService = null,
5238 };
5239 var basic_info: windows.FILE_BASIC_INFORMATION = undefined;
5240 switch (windows.ntdll.NtQueryAttributesFile(&attr, &basic_info)) {
5241 .SUCCESS => return,
5242 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
5243 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
5244 .OBJECT_NAME_INVALID => unreachable,
5245 .INVALID_PARAMETER => unreachable,
5246 .ACCESS_DENIED => return error.AccessDenied,
5247 .OBJECT_PATH_SYNTAX_BAD => unreachable,
5248 else => |rc| return windows.unexpectedStatus(rc),
5249 }
5250}
5251
52524496pub const PipeError = error{
52534497 SystemFdQuotaExceeded,
52544498 ProcessFdQuotaExceeded,
......@@ -5393,15 +4637,8 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {
53934637 }
53944638}
53954639
5396pub const SeekError = error{
5397 Unseekable,
5398
5399 /// In WASI, this error may occur when the file descriptor does
5400 /// not hold the required rights to seek on it.
5401 AccessDenied,
5402} || UnexpectedError;
4640pub const SeekError = std.Io.File.SeekError;
54034641
5404/// Repositions read/write file offset relative to the beginning.
54054642pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void {
54064643 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
54074644 var result: u64 = undefined;
......@@ -5645,16 +4882,15 @@ pub const RealPathError = error{
56454882 SystemResources,
56464883 NoSpaceLeft,
56474884 FileSystem,
5648 BadPathName,
56494885 DeviceBusy,
56504886 ProcessNotFound,
56514887
56524888 SharingViolation,
56534889 PipeBusy,
56544890
5655 /// Windows-only; file paths provided by the user must be valid WTF-8.
4891 /// Windows: file paths provided by the user must be valid WTF-8.
56564892 /// https://wtf-8.codeberg.page/
5657 InvalidWtf8,
4893 BadPathName,
56584894
56594895 /// On Windows, `\\server` or `\\server\share` was not found.
56604896 NetworkNotFound,
......@@ -5671,6 +4907,8 @@ pub const RealPathError = error{
56714907 /// On Windows, the volume does not contain a recognized file system. File
56724908 /// system drivers might not be loaded, or the volume may be corrupt.
56734909 UnrecognizedVolume,
4910
4911 Canceled,
56744912} || UnexpectedError;
56754913
56764914/// Return the canonicalized absolute pathname.
......@@ -5735,7 +4973,6 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[max_path_bytes]u8) RealP
57354973 error.FileLocksNotSupported => unreachable,
57364974 error.WouldBlock => unreachable,
57374975 error.FileBusy => unreachable, // not asking for write permissions
5738 error.InvalidUtf8 => unreachable, // WASI-only
57394976 else => |e| return e,
57404977 };
57414978 defer close(fd);
......@@ -5999,7 +5236,7 @@ pub fn sigemptyset() sigset_t {
59995236 return system.sigemptyset();
60005237}
60015238
6002pub fn sigaddset(set: *sigset_t, sig: u8) void {
5239pub fn sigaddset(set: *sigset_t, sig: SIG) void {
60035240 if (builtin.link_libc) {
60045241 switch (errno(system.sigaddset(set, sig))) {
60055242 .SUCCESS => return,
......@@ -6009,7 +5246,7 @@ pub fn sigaddset(set: *sigset_t, sig: u8) void {
60095246 system.sigaddset(set, sig);
60105247}
60115248
6012pub fn sigdelset(set: *sigset_t, sig: u8) void {
5249pub fn sigdelset(set: *sigset_t, sig: SIG) void {
60135250 if (builtin.link_libc) {
60145251 switch (errno(system.sigdelset(set, sig))) {
60155252 .SUCCESS => return,
......@@ -6019,7 +5256,7 @@ pub fn sigdelset(set: *sigset_t, sig: u8) void {
60195256 system.sigdelset(set, sig);
60205257}
60215258
6022pub fn sigismember(set: *const sigset_t, sig: u8) bool {
5259pub fn sigismember(set: *const sigset_t, sig: SIG) bool {
60235260 if (builtin.link_libc) {
60245261 const rc = system.sigismember(set, sig);
60255262 switch (errno(rc)) {
......@@ -6031,7 +5268,7 @@ pub fn sigismember(set: *const sigset_t, sig: u8) bool {
60315268}
60325269
60335270/// Examine and change a signal action.
6034pub fn sigaction(sig: u8, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) void {
5271pub fn sigaction(sig: SIG, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) void {
60355272 switch (errno(system.sigaction(sig, act, oact))) {
60365273 .SUCCESS => return,
60375274 // EINVAL means the signal is either invalid or some signal that cannot have its action
......@@ -6152,55 +5389,6 @@ pub fn uname() utsname {
61525389 }
61535390}
61545391
6155pub fn res_mkquery(
6156 op: u4,
6157 dname: []const u8,
6158 class: u8,
6159 ty: u8,
6160 data: []const u8,
6161 newrr: ?[*]const u8,
6162 buf: []u8,
6163) usize {
6164 _ = data;
6165 _ = newrr;
6166 // This implementation is ported from musl libc.
6167 // A more idiomatic "ziggy" implementation would be welcome.
6168 var name = dname;
6169 if (mem.endsWith(u8, name, ".")) name.len -= 1;
6170 assert(name.len <= 253);
6171 const n = 17 + name.len + @intFromBool(name.len != 0);
6172
6173 // Construct query template - ID will be filled later
6174 var q: [280]u8 = undefined;
6175 @memset(q[0..n], 0);
6176 q[2] = @as(u8, op) * 8 + 1;
6177 q[5] = 1;
6178 @memcpy(q[13..][0..name.len], name);
6179 var i: usize = 13;
6180 var j: usize = undefined;
6181 while (q[i] != 0) : (i = j + 1) {
6182 j = i;
6183 while (q[j] != 0 and q[j] != '.') : (j += 1) {}
6184 // TODO determine the circumstances for this and whether or
6185 // not this should be an error.
6186 if (j - i - 1 > 62) unreachable;
6187 q[i - 1] = @intCast(j - i);
6188 }
6189 q[i + 1] = ty;
6190 q[i + 3] = class;
6191
6192 // Make a reasonably unpredictable id
6193 const ts = clock_gettime(.REALTIME) catch unreachable;
6194 const UInt = std.meta.Int(.unsigned, @bitSizeOf(@TypeOf(ts.nsec)));
6195 const unsec: UInt = @bitCast(ts.nsec);
6196 const id: u32 = @truncate(unsec + unsec / 65536);
6197 q[0] = @truncate(id / 256);
6198 q[1] = @truncate(id);
6199
6200 @memcpy(buf[0..n], q[0..n]);
6201 return n;
6202}
6203
62045392pub const SendError = error{
62055393 /// (For UNIX domain sockets, which are identified by pathname) Write permission is denied
62065394 /// on the destination socket file, or search permission is denied for one of the
......@@ -6226,7 +5414,7 @@ pub const SendError = error{
62265414
62275415 /// The socket type requires that message be sent atomically, and the size of the message
62285416 /// to be sent made this impossible. The message is not transmitted.
6229 MessageTooBig,
5417 MessageOversize,
62305418
62315419 /// The output queue for a network interface was full. This generally indicates that the
62325420 /// interface has stopped sending, but may be caused by transient congestion. (Normally,
......@@ -6245,7 +5433,7 @@ pub const SendError = error{
62455433 NetworkUnreachable,
62465434
62475435 /// The local network interface used to reach the destination is down.
6248 NetworkSubsystemFailed,
5436 NetworkDown,
62495437
62505438 /// The destination address is not listening.
62515439 ConnectionRefused,
......@@ -6253,7 +5441,7 @@ pub const SendError = error{
62535441
62545442pub const SendMsgError = SendError || error{
62555443 /// The passed address didn't have the correct address family in its sa_family field.
6256 AddressFamilyNotSupported,
5444 AddressFamilyUnsupported,
62575445
62585446 /// Returned when socket is AF.UNIX and the given path has a symlink loop.
62595447 SymLinkLoop,
......@@ -6266,8 +5454,8 @@ pub const SendMsgError = SendError || error{
62665454 NotDir,
62675455
62685456 /// The socket is not connected (connection-oriented sockets only).
6269 SocketNotConnected,
6270 AddressNotAvailable,
5457 SocketUnconnected,
5458 AddressUnavailable,
62715459};
62725460
62735461pub fn sendmsg(
......@@ -6282,25 +5470,25 @@ pub fn sendmsg(
62825470 if (native_os == .windows) {
62835471 if (rc == windows.ws2_32.SOCKET_ERROR) {
62845472 switch (windows.ws2_32.WSAGetLastError()) {
6285 .WSAEACCES => return error.AccessDenied,
6286 .WSAEADDRNOTAVAIL => return error.AddressNotAvailable,
6287 .WSAECONNRESET => return error.ConnectionResetByPeer,
6288 .WSAEMSGSIZE => return error.MessageTooBig,
6289 .WSAENOBUFS => return error.SystemResources,
6290 .WSAENOTSOCK => return error.FileDescriptorNotASocket,
6291 .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported,
6292 .WSAEDESTADDRREQ => unreachable, // A destination address is required.
6293 .WSAEFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small.
6294 .WSAEHOSTUNREACH => return error.NetworkUnreachable,
6295 // TODO: WSAEINPROGRESS, WSAEINTR
6296 .WSAEINVAL => unreachable,
6297 .WSAENETDOWN => return error.NetworkSubsystemFailed,
6298 .WSAENETRESET => return error.ConnectionResetByPeer,
6299 .WSAENETUNREACH => return error.NetworkUnreachable,
6300 .WSAENOTCONN => return error.SocketNotConnected,
6301 .WSAESHUTDOWN => unreachable, // The socket has been shut down; it is not possible to WSASendTo on a socket after shutdown has been invoked with how set to SD_SEND or SD_BOTH.
6302 .WSAEWOULDBLOCK => return error.WouldBlock,
6303 .WSANOTINITIALISED => unreachable, // A successful WSAStartup call must occur before using this function.
5473 .EACCES => return error.AccessDenied,
5474 .EADDRNOTAVAIL => return error.AddressUnavailable,
5475 .ECONNRESET => return error.ConnectionResetByPeer,
5476 .EMSGSIZE => return error.MessageOversize,
5477 .ENOBUFS => return error.SystemResources,
5478 .ENOTSOCK => return error.FileDescriptorNotASocket,
5479 .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
5480 .EDESTADDRREQ => unreachable, // A destination address is required.
5481 .EFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small.
5482 .EHOSTUNREACH => return error.NetworkUnreachable,
5483 // TODO: EINPROGRESS, EINTR
5484 .EINVAL => unreachable,
5485 .ENETDOWN => return error.NetworkDown,
5486 .ENETRESET => return error.ConnectionResetByPeer,
5487 .ENETUNREACH => return error.NetworkUnreachable,
5488 .ENOTCONN => return error.SocketUnconnected,
5489 .ESHUTDOWN => unreachable, // The socket has been shut down; it is not possible to WSASendTo on a socket after shutdown has been invoked with how set to SD_SEND or SD_BOTH.
5490 .EWOULDBLOCK => return error.WouldBlock,
5491 .NOTINITIALISED => unreachable, // A successful WSAStartup call must occur before using this function.
63045492 else => |err| return windows.unexpectedWSAError(err),
63055493 }
63065494 } else {
......@@ -6320,21 +5508,21 @@ pub fn sendmsg(
63205508 .INTR => continue,
63215509 .INVAL => unreachable, // Invalid argument passed.
63225510 .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified
6323 .MSGSIZE => return error.MessageTooBig,
5511 .MSGSIZE => return error.MessageOversize,
63245512 .NOBUFS => return error.SystemResources,
63255513 .NOMEM => return error.SystemResources,
63265514 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
63275515 .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type.
63285516 .PIPE => return error.BrokenPipe,
6329 .AFNOSUPPORT => return error.AddressFamilyNotSupported,
5517 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
63305518 .LOOP => return error.SymLinkLoop,
63315519 .NAMETOOLONG => return error.NameTooLong,
63325520 .NOENT => return error.FileNotFound,
63335521 .NOTDIR => return error.NotDir,
63345522 .HOSTUNREACH => return error.NetworkUnreachable,
63355523 .NETUNREACH => return error.NetworkUnreachable,
6336 .NOTCONN => return error.SocketNotConnected,
6337 .NETDOWN => return error.NetworkSubsystemFailed,
5524 .NOTCONN => return error.SocketUnconnected,
5525 .NETDOWN => return error.NetworkDown,
63385526 else => |err| return unexpectedErrno(err),
63395527 }
63405528 }
......@@ -6365,7 +5553,7 @@ pub const SendToError = SendMsgError || error{
63655553/// Otherwise, the address of the target is given by `dest_addr` with `addrlen` specifying its size.
63665554///
63675555/// If the message is too long to pass atomically through the underlying protocol,
6368/// `SendError.MessageTooBig` is returned, and the message is not transmitted.
5556/// `SendError.MessageOversize` is returned, and the message is not transmitted.
63695557///
63705558/// There is no indication of failure to deliver.
63715559///
......@@ -6385,25 +5573,25 @@ pub fn sendto(
63855573 if (native_os == .windows) {
63865574 switch (windows.sendto(sockfd, buf.ptr, buf.len, flags, dest_addr, addrlen)) {
63875575 windows.ws2_32.SOCKET_ERROR => switch (windows.ws2_32.WSAGetLastError()) {
6388 .WSAEACCES => return error.AccessDenied,
6389 .WSAEADDRNOTAVAIL => return error.AddressNotAvailable,
6390 .WSAECONNRESET => return error.ConnectionResetByPeer,
6391 .WSAEMSGSIZE => return error.MessageTooBig,
6392 .WSAENOBUFS => return error.SystemResources,
6393 .WSAENOTSOCK => return error.FileDescriptorNotASocket,
6394 .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported,
6395 .WSAEDESTADDRREQ => unreachable, // A destination address is required.
6396 .WSAEFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small.
6397 .WSAEHOSTUNREACH => return error.NetworkUnreachable,
6398 // TODO: WSAEINPROGRESS, WSAEINTR
6399 .WSAEINVAL => unreachable,
6400 .WSAENETDOWN => return error.NetworkSubsystemFailed,
6401 .WSAENETRESET => return error.ConnectionResetByPeer,
6402 .WSAENETUNREACH => return error.NetworkUnreachable,
6403 .WSAENOTCONN => return error.SocketNotConnected,
6404 .WSAESHUTDOWN => unreachable, // The socket has been shut down; it is not possible to WSASendTo on a socket after shutdown has been invoked with how set to SD_SEND or SD_BOTH.
6405 .WSAEWOULDBLOCK => return error.WouldBlock,
6406 .WSANOTINITIALISED => unreachable, // A successful WSAStartup call must occur before using this function.
5576 .EACCES => return error.AccessDenied,
5577 .EADDRNOTAVAIL => return error.AddressUnavailable,
5578 .ECONNRESET => return error.ConnectionResetByPeer,
5579 .EMSGSIZE => return error.MessageOversize,
5580 .ENOBUFS => return error.SystemResources,
5581 .ENOTSOCK => return error.FileDescriptorNotASocket,
5582 .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
5583 .EDESTADDRREQ => unreachable, // A destination address is required.
5584 .EFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small.
5585 .EHOSTUNREACH => return error.NetworkUnreachable,
5586 // TODO: EINPROGRESS, EINTR
5587 .EINVAL => unreachable,
5588 .ENETDOWN => return error.NetworkDown,
5589 .ENETRESET => return error.ConnectionResetByPeer,
5590 .ENETUNREACH => return error.NetworkUnreachable,
5591 .ENOTCONN => return error.SocketUnconnected,
5592 .ESHUTDOWN => unreachable, // The socket has been shut down; it is not possible to WSASendTo on a socket after shutdown has been invoked with how set to SD_SEND or SD_BOTH.
5593 .EWOULDBLOCK => return error.WouldBlock,
5594 .NOTINITIALISED => unreachable, // A successful WSAStartup call must occur before using this function.
64075595 else => |err| return windows.unexpectedWSAError(err),
64085596 },
64095597 else => |rc| return @intCast(rc),
......@@ -6425,21 +5613,21 @@ pub fn sendto(
64255613 .INTR => continue,
64265614 .INVAL => return error.UnreachableAddress,
64275615 .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified
6428 .MSGSIZE => return error.MessageTooBig,
5616 .MSGSIZE => return error.MessageOversize,
64295617 .NOBUFS => return error.SystemResources,
64305618 .NOMEM => return error.SystemResources,
64315619 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
64325620 .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type.
64335621 .PIPE => return error.BrokenPipe,
6434 .AFNOSUPPORT => return error.AddressFamilyNotSupported,
5622 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
64355623 .LOOP => return error.SymLinkLoop,
64365624 .NAMETOOLONG => return error.NameTooLong,
64375625 .NOENT => return error.FileNotFound,
64385626 .NOTDIR => return error.NotDir,
64395627 .HOSTUNREACH => return error.NetworkUnreachable,
64405628 .NETUNREACH => return error.NetworkUnreachable,
6441 .NOTCONN => return error.SocketNotConnected,
6442 .NETDOWN => return error.NetworkSubsystemFailed,
5629 .NOTCONN => return error.SocketUnconnected,
5630 .NETDOWN => return error.NetworkDown,
64435631 else => |err| return unexpectedErrno(err),
64445632 }
64455633 }
......@@ -6471,14 +5659,14 @@ pub fn send(
64715659 flags: u32,
64725660) SendError!usize {
64735661 return sendto(sockfd, buf, flags, null, 0) catch |err| switch (err) {
6474 error.AddressFamilyNotSupported => unreachable,
5662 error.AddressFamilyUnsupported => unreachable,
64755663 error.SymLinkLoop => unreachable,
64765664 error.NameTooLong => unreachable,
64775665 error.FileNotFound => unreachable,
64785666 error.NotDir => unreachable,
64795667 error.NetworkUnreachable => unreachable,
6480 error.AddressNotAvailable => unreachable,
6481 error.SocketNotConnected => unreachable,
5668 error.AddressUnavailable => unreachable,
5669 error.SocketUnconnected => unreachable,
64825670 error.UnreachableAddress => unreachable,
64835671 else => |e| return e,
64845672 };
......@@ -6578,7 +5766,7 @@ pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len
65785766
65795767pub const PollError = error{
65805768 /// The network subsystem has failed.
6581 NetworkSubsystemFailed,
5769 NetworkDown,
65825770
65835771 /// The kernel had no space to allocate file descriptor tables.
65845772 SystemResources,
......@@ -6588,9 +5776,9 @@ pub fn poll(fds: []pollfd, timeout: i32) PollError!usize {
65885776 if (native_os == .windows) {
65895777 switch (windows.poll(fds.ptr, @intCast(fds.len), timeout)) {
65905778 windows.ws2_32.SOCKET_ERROR => switch (windows.ws2_32.WSAGetLastError()) {
6591 .WSANOTINITIALISED => unreachable,
6592 .WSAENETDOWN => return error.NetworkSubsystemFailed,
6593 .WSAENOBUFS => return error.SystemResources,
5779 .NOTINITIALISED => unreachable,
5780 .ENETDOWN => return error.NetworkDown,
5781 .ENOBUFS => return error.SystemResources,
65945782 // TODO: handle more errors
65955783 else => |err| return windows.unexpectedWSAError(err),
65965784 },
......@@ -6652,19 +5840,19 @@ pub const RecvFromError = error{
66525840 SystemResources,
66535841
66545842 ConnectionResetByPeer,
6655 ConnectionTimedOut,
5843 Timeout,
66565844
66575845 /// The socket has not been bound.
66585846 SocketNotBound,
66595847
66605848 /// The UDP message was too big for the buffer and part of it has been discarded
6661 MessageTooBig,
5849 MessageOversize,
66625850
66635851 /// The network subsystem has failed.
6664 NetworkSubsystemFailed,
5852 NetworkDown,
66655853
66665854 /// The socket is not connected (connection-oriented sockets only).
6667 SocketNotConnected,
5855 SocketUnconnected,
66685856
66695857 /// The other end closed the socket unexpectedly or a read is executed on a shut down socket
66705858 BrokenPipe,
......@@ -6688,14 +5876,14 @@ pub fn recvfrom(
66885876 if (native_os == .windows) {
66895877 if (rc == windows.ws2_32.SOCKET_ERROR) {
66905878 switch (windows.ws2_32.WSAGetLastError()) {
6691 .WSANOTINITIALISED => unreachable,
6692 .WSAECONNRESET => return error.ConnectionResetByPeer,
6693 .WSAEINVAL => return error.SocketNotBound,
6694 .WSAEMSGSIZE => return error.MessageTooBig,
6695 .WSAENETDOWN => return error.NetworkSubsystemFailed,
6696 .WSAENOTCONN => return error.SocketNotConnected,
6697 .WSAEWOULDBLOCK => return error.WouldBlock,
6698 .WSAETIMEDOUT => return error.ConnectionTimedOut,
5879 .NOTINITIALISED => unreachable,
5880 .ECONNRESET => return error.ConnectionResetByPeer,
5881 .EINVAL => return error.SocketNotBound,
5882 .EMSGSIZE => return error.MessageOversize,
5883 .ENETDOWN => return error.NetworkDown,
5884 .ENOTCONN => return error.SocketUnconnected,
5885 .EWOULDBLOCK => return error.WouldBlock,
5886 .ETIMEDOUT => return error.Timeout,
66995887 // TODO: handle more errors
67005888 else => |err| return windows.unexpectedWSAError(err),
67015889 }
......@@ -6708,14 +5896,14 @@ pub fn recvfrom(
67085896 .BADF => unreachable, // always a race condition
67095897 .FAULT => unreachable,
67105898 .INVAL => unreachable,
6711 .NOTCONN => return error.SocketNotConnected,
5899 .NOTCONN => return error.SocketUnconnected,
67125900 .NOTSOCK => unreachable,
67135901 .INTR => continue,
67145902 .AGAIN => return error.WouldBlock,
67155903 .NOMEM => return error.SystemResources,
67165904 .CONNREFUSED => return error.ConnectionRefused,
67175905 .CONNRESET => return error.ConnectionResetByPeer,
6718 .TIMEDOUT => return error.ConnectionTimedOut,
5906 .TIMEDOUT => return error.Timeout,
67195907 .PIPE => return error.BrokenPipe,
67205908 else => |err| return unexpectedErrno(err),
67215909 }
......@@ -6760,68 +5948,18 @@ pub fn recvmsg(
67605948 .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified
67615949 .NOBUFS => return error.SystemResources,
67625950 .NOMEM => return error.SystemResources,
6763 .NOTCONN => return error.SocketNotConnected,
5951 .NOTCONN => return error.SocketUnconnected,
67645952 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
6765 .MSGSIZE => return error.MessageTooBig,
5953 .MSGSIZE => return error.MessageOversize,
67665954 .PIPE => return error.BrokenPipe,
67675955 .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type.
67685956 .CONNRESET => return error.ConnectionResetByPeer,
6769 .NETDOWN => return error.NetworkSubsystemFailed,
5957 .NETDOWN => return error.NetworkDown,
67705958 else => |err| return unexpectedErrno(err),
67715959 }
67725960 }
67735961}
67745962
6775pub const DnExpandError = error{InvalidDnsPacket};
6776
6777pub fn dn_expand(
6778 msg: []const u8,
6779 comp_dn: []const u8,
6780 exp_dn: []u8,
6781) DnExpandError!usize {
6782 // This implementation is ported from musl libc.
6783 // A more idiomatic "ziggy" implementation would be welcome.
6784 var p = comp_dn.ptr;
6785 var len: usize = maxInt(usize);
6786 const end = msg.ptr + msg.len;
6787 if (p == end or exp_dn.len == 0) return error.InvalidDnsPacket;
6788 var dest = exp_dn.ptr;
6789 const dend = dest + @min(exp_dn.len, 254);
6790 // detect reference loop using an iteration counter
6791 var i: usize = 0;
6792 while (i < msg.len) : (i += 2) {
6793 // loop invariants: p<end, dest<dend
6794 if ((p[0] & 0xc0) != 0) {
6795 if (p + 1 == end) return error.InvalidDnsPacket;
6796 const j = @as(usize, p[0] & 0x3f) << 8 | p[1];
6797 if (len == maxInt(usize)) len = @intFromPtr(p) + 2 - @intFromPtr(comp_dn.ptr);
6798 if (j >= msg.len) return error.InvalidDnsPacket;
6799 p = msg.ptr + j;
6800 } else if (p[0] != 0) {
6801 if (dest != exp_dn.ptr) {
6802 dest[0] = '.';
6803 dest += 1;
6804 }
6805 var j = p[0];
6806 p += 1;
6807 if (j >= @intFromPtr(end) - @intFromPtr(p) or j >= @intFromPtr(dend) - @intFromPtr(dest)) {
6808 return error.InvalidDnsPacket;
6809 }
6810 while (j != 0) {
6811 j -= 1;
6812 dest[0] = p[0];
6813 dest += 1;
6814 p += 1;
6815 }
6816 } else {
6817 dest[0] = 0;
6818 if (len == maxInt(usize)) len = @intFromPtr(p) + 1 - @intFromPtr(comp_dn.ptr);
6819 return len;
6820 }
6821 }
6822 return error.InvalidDnsPacket;
6823}
6824
68255963pub const SetSockOptError = error{
68265964 /// The socket is already connected, and a specified option cannot be set while the socket is connected.
68275965 AlreadyConnected,
......@@ -6839,7 +5977,7 @@ pub const SetSockOptError = error{
68395977 PermissionDenied,
68405978
68415979 OperationNotSupported,
6842 NetworkSubsystemFailed,
5980 NetworkDown,
68435981 FileDescriptorNotASocket,
68445982 SocketNotBound,
68455983 NoDevice,
......@@ -6851,11 +5989,11 @@ pub fn setsockopt(fd: socket_t, level: i32, optname: u32, opt: []const u8) SetSo
68515989 const rc = windows.ws2_32.setsockopt(fd, level, @intCast(optname), opt.ptr, @intCast(opt.len));
68525990 if (rc == windows.ws2_32.SOCKET_ERROR) {
68535991 switch (windows.ws2_32.WSAGetLastError()) {
6854 .WSANOTINITIALISED => unreachable,
6855 .WSAENETDOWN => return error.NetworkSubsystemFailed,
6856 .WSAEFAULT => unreachable,
6857 .WSAENOTSOCK => return error.FileDescriptorNotASocket,
6858 .WSAEINVAL => return error.SocketNotBound,
5992 .NOTINITIALISED => unreachable,
5993 .ENETDOWN => return error.NetworkDown,
5994 .EFAULT => unreachable,
5995 .ENOTSOCK => return error.FileDescriptorNotASocket,
5996 .EINVAL => return error.SocketNotBound,
68595997 else => |err| return windows.unexpectedWSAError(err),
68605998 }
68615999 }
......@@ -7572,7 +6710,7 @@ pub fn ioctl_SIOCGIFINDEX(fd: fd_t, ifr: *ifreq) IoCtl_SIOCGIFINDEX_Error!void {
75726710 }
75736711}
75746712
7575const lfs64_abi = native_os == .linux and builtin.link_libc and (builtin.abi.isGnu() or builtin.abi.isAndroid());
6713pub const lfs64_abi = native_os == .linux and builtin.link_libc and (builtin.abi.isGnu() or builtin.abi.isAndroid());
75766714
75776715/// Whether or not `error.Unexpected` will print its value and a stack trace.
75786716///
......@@ -7584,17 +6722,7 @@ pub const unexpected_error_tracing = builtin.mode == .Debug and switch (builtin.
75846722 else => false,
75856723};
75866724
7587pub const UnexpectedError = error{
7588 /// The Operating System returned an undocumented error code.
7589 ///
7590 /// This error is in theory not possible, but it would be better
7591 /// to handle this error than to invoke undefined behavior.
7592 ///
7593 /// When this error code is observed, it usually means the Zig Standard
7594 /// Library needs a small patch to add the error code to the error set for
7595 /// the respective function.
7596 Unexpected,
7597};
6725pub const UnexpectedError = std.Io.UnexpectedError;
75986726
75996727/// Call this when you made a syscall or something that sets errno
76006728/// and you get an unexpected error.
lib/std/posix/test.zig+16-147
......@@ -109,64 +109,6 @@ test "open smoke test" {
109109 }
110110}
111111
112test "openat smoke test" {
113 if (native_os == .windows) return error.SkipZigTest;
114
115 // TODO verify file attributes using `fstatat`
116
117 var tmp = tmpDir(.{});
118 defer tmp.cleanup();
119
120 var fd: posix.fd_t = undefined;
121 const mode: posix.mode_t = if (native_os == .windows) 0 else 0o666;
122
123 // Create some file using `openat`.
124 fd = try posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
125 .ACCMODE = .RDWR,
126 .CREAT = true,
127 .EXCL = true,
128 }), mode);
129 posix.close(fd);
130
131 // Try this again with the same flags. This op should fail with error.PathAlreadyExists.
132 try expectError(error.PathAlreadyExists, posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
133 .ACCMODE = .RDWR,
134 .CREAT = true,
135 .EXCL = true,
136 }), mode));
137
138 // Try opening without `EXCL` flag.
139 fd = try posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
140 .ACCMODE = .RDWR,
141 .CREAT = true,
142 }), mode);
143 posix.close(fd);
144
145 // Try opening as a directory which should fail.
146 try expectError(error.NotDir, posix.openat(tmp.dir.fd, "some_file", CommonOpenFlags.lower(.{
147 .ACCMODE = .RDWR,
148 .DIRECTORY = true,
149 }), mode));
150
151 // Create some directory
152 try posix.mkdirat(tmp.dir.fd, "some_dir", mode);
153
154 // Open dir using `open`
155 fd = try posix.openat(tmp.dir.fd, "some_dir", CommonOpenFlags.lower(.{
156 .ACCMODE = .RDONLY,
157 .DIRECTORY = true,
158 }), mode);
159 posix.close(fd);
160
161 // Try opening as file which should fail (skip on wasi+libc due to
162 // https://github.com/bytecodealliance/wasmtime/issues/9054)
163 if (native_os != .wasi or !builtin.link_libc) {
164 try expectError(error.IsDir, posix.openat(tmp.dir.fd, "some_dir", CommonOpenFlags.lower(.{
165 .ACCMODE = .RDWR,
166 }), mode));
167 }
168}
169
170112test "readlink on Windows" {
171113 if (native_os != .windows) return error.SkipZigTest;
172114
......@@ -226,49 +168,6 @@ test "linkat with different directories" {
226168 }
227169}
228170
229test "fstatat" {
230 if ((builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) and builtin.os.tag == .linux and !builtin.link_libc) return error.SkipZigTest; // No `fstatat()`.
231 // enable when `fstat` and `fstatat` are implemented on Windows
232 if (native_os == .windows) return error.SkipZigTest;
233
234 var tmp = tmpDir(.{});
235 defer tmp.cleanup();
236
237 // create dummy file
238 const contents = "nonsense";
239 try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = contents });
240
241 // fetch file's info on the opened fd directly
242 const file = try tmp.dir.openFile("file.txt", .{});
243 const stat = try posix.fstat(file.handle);
244 defer file.close();
245
246 // now repeat but using `fstatat` instead
247 const statat = try posix.fstatat(tmp.dir.fd, "file.txt", posix.AT.SYMLINK_NOFOLLOW);
248
249 try expectEqual(stat.dev, statat.dev);
250 try expectEqual(stat.ino, statat.ino);
251 try expectEqual(stat.nlink, statat.nlink);
252 try expectEqual(stat.mode, statat.mode);
253 try expectEqual(stat.uid, statat.uid);
254 try expectEqual(stat.gid, statat.gid);
255 try expectEqual(stat.rdev, statat.rdev);
256 try expectEqual(stat.size, statat.size);
257 try expectEqual(stat.blksize, statat.blksize);
258
259 // The stat.blocks/statat.blocks count is managed by the filesystem and may
260 // change if the file is stored in a journal or "inline".
261 // try expectEqual(stat.blocks, statat.blocks);
262
263 // s390x-linux does not have nanosecond precision for fstat(), but it does for
264 // fstatat(). As a result, comparing the timestamps isn't worth the effort
265 if (!(builtin.cpu.arch == .s390x and builtin.os.tag == .linux)) {
266 try expectEqual(stat.atime(), statat.atime());
267 try expectEqual(stat.mtime(), statat.mtime());
268 try expectEqual(stat.ctime(), statat.ctime());
269 }
270}
271
272171test "readlinkat" {
273172 var tmp = tmpDir(.{});
274173 defer tmp.cleanup();
......@@ -621,25 +520,6 @@ test "getrlimit and setrlimit" {
621520 }
622521}
623522
624test "shutdown socket" {
625 if (native_os == .wasi)
626 return error.SkipZigTest;
627 if (native_os == .windows) {
628 _ = try std.os.windows.WSAStartup(2, 2);
629 }
630 defer {
631 if (native_os == .windows) {
632 std.os.windows.WSACleanup() catch unreachable;
633 }
634 }
635 const sock = try posix.socket(posix.AF.INET, posix.SOCK.STREAM, 0);
636 posix.shutdown(sock, .both) catch |err| switch (err) {
637 error.SocketNotConnected => {},
638 else => |e| return e,
639 };
640 std.net.Stream.close(.{ .handle = sock });
641}
642
643523test "sigrtmin/max" {
644524 if (native_os == .wasi or native_os == .windows or native_os == .macos) {
645525 return error.SkipZigTest;
......@@ -656,14 +536,15 @@ test "sigset empty/full" {
656536
657537 var set: posix.sigset_t = posix.sigemptyset();
658538 for (1..posix.NSIG) |i| {
659 try expectEqual(false, posix.sigismember(&set, @truncate(i)));
539 const sig = std.meta.intToEnum(posix.SIG, i) catch continue;
540 try expectEqual(false, posix.sigismember(&set, sig));
660541 }
661542
662543 // The C library can reserve some (unnamed) signals, so can't check the full
663544 // NSIG set is defined, but just test a couple:
664545 set = posix.sigfillset();
665 try expectEqual(true, posix.sigismember(&set, @truncate(posix.SIG.CHLD)));
666 try expectEqual(true, posix.sigismember(&set, @truncate(posix.SIG.INT)));
546 try expectEqual(true, posix.sigismember(&set, .CHLD));
547 try expectEqual(true, posix.sigismember(&set, .INT));
667548}
668549
669550// Some signals (i.e., 32 - 34 on glibc/musl) are not allowed to be added to a
......@@ -684,25 +565,30 @@ test "sigset add/del" {
684565 // See that none are set, then set each one, see that they're all set, then
685566 // remove them all, and then see that none are set.
686567 for (1..posix.NSIG) |i| {
687 try expectEqual(false, posix.sigismember(&sigset, @truncate(i)));
568 const sig = std.meta.intToEnum(posix.SIG, i) catch continue;
569 try expectEqual(false, posix.sigismember(&sigset, sig));
688570 }
689571 for (1..posix.NSIG) |i| {
690572 if (!reserved_signo(i)) {
691 posix.sigaddset(&sigset, @truncate(i));
573 const sig = std.meta.intToEnum(posix.SIG, i) catch continue;
574 posix.sigaddset(&sigset, sig);
692575 }
693576 }
694577 for (1..posix.NSIG) |i| {
695578 if (!reserved_signo(i)) {
696 try expectEqual(true, posix.sigismember(&sigset, @truncate(i)));
579 const sig = std.meta.intToEnum(posix.SIG, i) catch continue;
580 try expectEqual(true, posix.sigismember(&sigset, sig));
697581 }
698582 }
699583 for (1..posix.NSIG) |i| {
700584 if (!reserved_signo(i)) {
701 posix.sigdelset(&sigset, @truncate(i));
585 const sig = std.meta.intToEnum(posix.SIG, i) catch continue;
586 posix.sigdelset(&sigset, sig);
702587 }
703588 }
704589 for (1..posix.NSIG) |i| {
705 try expectEqual(false, posix.sigismember(&sigset, @truncate(i)));
590 const sig = std.meta.intToEnum(posix.SIG, i) catch continue;
591 try expectEqual(false, posix.sigismember(&sigset, sig));
706592 }
707593}
708594
......@@ -731,11 +617,8 @@ test "dup & dup2" {
731617 try dup2ed.writeAll("dup2");
732618 }
733619
734 var file = try tmp.dir.openFile("os_dup_test", .{});
735 defer file.close();
736
737 var buf: [7]u8 = undefined;
738 try testing.expectEqualStrings("dupdup2", buf[0..try file.readAll(&buf)]);
620 var buffer: [8]u8 = undefined;
621 try testing.expectEqualStrings("dupdup2", try tmp.dir.readFile("os_dup_test", &buffer));
739622}
740623
741624test "writev longer than IOV_MAX" {
......@@ -966,20 +849,6 @@ test "isatty" {
966849 try expectEqual(posix.isatty(file.handle), false);
967850}
968851
969test "read with empty buffer" {
970 var tmp = tmpDir(.{});
971 defer tmp.cleanup();
972
973 var file = try tmp.dir.createFile("read_empty", .{ .read = true });
974 defer file.close();
975
976 const bytes = try a.alloc(u8, 0);
977 defer a.free(bytes);
978
979 const rc = try posix.read(file.handle, bytes);
980 try expectEqual(rc, 0);
981}
982
983852test "pread with empty buffer" {
984853 var tmp = tmpDir(.{});
985854 defer tmp.cleanup();
lib/std/process/Child.zig+42-15
......@@ -1,5 +1,9 @@
1const std = @import("../std.zig");
1const ChildProcess = @This();
2
23const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5
6const std = @import("../std.zig");
37const unicode = std.unicode;
48const fs = std.fs;
59const process = std.process;
......@@ -11,9 +15,7 @@ const mem = std.mem;
1115const EnvMap = std.process.EnvMap;
1216const maxInt = std.math.maxInt;
1317const assert = std.debug.assert;
14const native_os = builtin.os.tag;
1518const Allocator = std.mem.Allocator;
16const ChildProcess = @This();
1719const ArrayList = std.ArrayList;
1820
1921pub const Id = switch (native_os) {
......@@ -317,16 +319,23 @@ pub fn waitForSpawn(self: *ChildProcess) SpawnError!void {
317319
318320 const err_pipe = self.err_pipe orelse return;
319321 self.err_pipe = null;
320
321322 // Wait for the child to report any errors in or before `execvpe`.
322 if (readIntFd(err_pipe)) |child_err_int| {
323 posix.close(err_pipe);
323 const report = readIntFd(err_pipe);
324 posix.close(err_pipe);
325 if (report) |child_err_int| {
324326 const child_err: SpawnError = @errorCast(@errorFromInt(child_err_int));
325327 self.term = child_err;
326328 return child_err;
327 } else |_| {
328 // Write end closed by CLOEXEC at the time of the `execvpe` call, indicating success!
329 posix.close(err_pipe);
329 } else |read_err| switch (read_err) {
330 error.EndOfStream => {
331 // Write end closed by CLOEXEC at the time of the `execvpe` call,
332 // indicating success.
333 },
334 else => {
335 // Problem reading the error from the error reporting pipe. We
336 // don't know if the child is alive or dead. Better to assume it is
337 // alive so the resource does not risk being leaked.
338 },
330339 }
331340}
332341
......@@ -563,6 +572,10 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
563572 error.BadPathName => unreachable, // Windows-only
564573 error.WouldBlock => unreachable,
565574 error.NetworkNotFound => unreachable, // Windows-only
575 error.Canceled => unreachable, // temporarily in the posix error set
576 error.SharingViolation => unreachable, // Windows-only
577 error.PipeBusy => unreachable, // not a pipe
578 error.AntivirusInterference => unreachable, // Windows-only
566579 else => |e| return e,
567580 }
568581 else
......@@ -1014,8 +1027,14 @@ fn writeIntFd(fd: i32, value: ErrInt) !void {
10141027
10151028fn readIntFd(fd: i32) !ErrInt {
10161029 var buffer: [8]u8 = undefined;
1017 var fr: std.fs.File.Reader = .initStreaming(.{ .handle = fd }, &buffer);
1018 return @intCast(fr.interface.takeInt(u64, .little) catch return error.SystemResources);
1030 var i: usize = 0;
1031 while (i < buffer.len) {
1032 const n = try std.posix.read(fd, buffer[i..]);
1033 if (n == 0) return error.EndOfStream;
1034 i += n;
1035 }
1036 const int = mem.readInt(u64, &buffer, .little);
1037 return @intCast(int);
10191038}
10201039
10211040const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8);
......@@ -1065,16 +1084,24 @@ fn windowsCreateProcessPathExt(
10651084 // or a version with a supported PATHEXT appended. We then try calling CreateProcessW
10661085 // with the found versions in the appropriate order.
10671086
1087 // In the future, child process execution needs to move to Io implementation.
1088 // Under those conditions, here we will have access to lower level directory
1089 // opening function knowing which implementation we are in. Here, we imitate
1090 // that scenario.
1091 var threaded: std.Io.Threaded = .init_single_threaded;
1092 const io = threaded.ioBasic();
1093
10681094 var dir = dir: {
10691095 // needs to be null-terminated
10701096 try dir_buf.append(allocator, 0);
10711097 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
10721098 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
10731099 const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);
1074 break :dir fs.cwd().openDirW(prefixed_path.span().ptr, .{ .iterate = true }) catch
1075 return error.FileNotFound;
1100 break :dir threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{
1101 .iterate = true,
1102 }) catch return error.FileNotFound;
10761103 };
1077 defer dir.close();
1104 defer dir.close(io);
10781105
10791106 // Add wildcard and null-terminator
10801107 try app_buf.append(allocator, '*');
......@@ -1108,7 +1135,7 @@ fn windowsCreateProcessPathExt(
11081135 .Buffer = @constCast(app_name_wildcard.ptr),
11091136 };
11101137 const rc = windows.ntdll.NtQueryDirectoryFile(
1111 dir.fd,
1138 dir.handle,
11121139 null,
11131140 null,
11141141 null,
lib/std/start.zig-37
......@@ -652,7 +652,6 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {
652652 std.os.environ = envp;
653653
654654 std.debug.maybeEnableSegfaultHandler();
655 maybeIgnoreSigpipe();
656655
657656 return callMain();
658657}
......@@ -756,39 +755,3 @@ pub fn call_wWinMain() std.os.windows.INT {
756755 // second parameter hPrevInstance, MSDN: "This parameter is always NULL"
757756 return root.wWinMain(hInstance, null, lpCmdLine, nCmdShow);
758757}
759
760fn maybeIgnoreSigpipe() void {
761 const have_sigpipe_support = switch (builtin.os.tag) {
762 .linux,
763 .plan9,
764 .illumos,
765 .netbsd,
766 .openbsd,
767 .haiku,
768 .macos,
769 .ios,
770 .watchos,
771 .tvos,
772 .visionos,
773 .dragonfly,
774 .freebsd,
775 .serenity,
776 => true,
777
778 else => false,
779 };
780
781 if (have_sigpipe_support and !std.options.keep_sigpipe) {
782 const posix = std.posix;
783 const act: posix.Sigaction = .{
784 // Set handler to a noop function instead of `SIG.IGN` to prevent
785 // leaking signal disposition to a child process.
786 .handler = .{ .handler = noopSigHandler },
787 .mask = posix.sigemptyset(),
788 .flags = 0,
789 };
790 posix.sigaction(posix.SIG.PIPE, &act, null);
791 }
792}
793
794fn noopSigHandler(_: i32) callconv(.c) void {}
lib/std/std.zig-14
......@@ -85,7 +85,6 @@ pub const macho = @import("macho.zig");
8585pub const math = @import("math.zig");
8686pub const mem = @import("mem.zig");
8787pub const meta = @import("meta.zig");
88pub const net = @import("net.zig");
8988pub const os = @import("os.zig");
9089pub const once = @import("once.zig").once;
9190pub const pdb = @import("pdb.zig");
......@@ -145,19 +144,6 @@ pub const Options = struct {
145144
146145 crypto_fork_safety: bool = true,
147146
148 /// By default Zig disables SIGPIPE by setting a "no-op" handler for it. Set this option
149 /// to `true` to prevent that.
150 ///
151 /// Note that we use a "no-op" handler instead of SIG_IGN because it will not be inherited by
152 /// any child process.
153 ///
154 /// SIGPIPE is triggered when a process attempts to write to a broken pipe. By default, SIGPIPE
155 /// will terminate the process instead of exiting. It doesn't trigger the panic handler so in many
156 /// cases it's unclear why the process was terminated. By capturing SIGPIPE instead, functions that
157 /// write to broken pipes will return the EPIPE error (error.BrokenPipe) and the program can handle
158 /// it like any other error.
159 keep_sigpipe: bool = false,
160
161147 /// By default, std.http.Client will support HTTPS connections. Set this option to `true` to
162148 /// disable TLS support.
163149 ///
lib/std/tar.zig+5-5
......@@ -977,7 +977,7 @@ test pipeToFileSystem {
977977 const data = @embedFile("tar/testdata/example.tar");
978978 var reader: std.Io.Reader = .fixed(data);
979979
980 var tmp = testing.tmpDir(.{ .no_follow = true });
980 var tmp = testing.tmpDir(.{ .follow_symlinks = false });
981981 defer tmp.cleanup();
982982 const dir = tmp.dir;
983983
......@@ -1010,7 +1010,7 @@ test "pipeToFileSystem root_dir" {
10101010
10111011 // with strip_components = 1
10121012 {
1013 var tmp = testing.tmpDir(.{ .no_follow = true });
1013 var tmp = testing.tmpDir(.{ .follow_symlinks = false });
10141014 defer tmp.cleanup();
10151015 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };
10161016 defer diagnostics.deinit();
......@@ -1032,7 +1032,7 @@ test "pipeToFileSystem root_dir" {
10321032 // with strip_components = 0
10331033 {
10341034 reader = .fixed(data);
1035 var tmp = testing.tmpDir(.{ .no_follow = true });
1035 var tmp = testing.tmpDir(.{ .follow_symlinks = false });
10361036 defer tmp.cleanup();
10371037 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };
10381038 defer diagnostics.deinit();
......@@ -1084,7 +1084,7 @@ test "pipeToFileSystem strip_components" {
10841084 const data = @embedFile("tar/testdata/example.tar");
10851085 var reader: std.Io.Reader = .fixed(data);
10861086
1087 var tmp = testing.tmpDir(.{ .no_follow = true });
1087 var tmp = testing.tmpDir(.{ .follow_symlinks = false });
10881088 defer tmp.cleanup();
10891089 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };
10901090 defer diagnostics.deinit();
......@@ -1145,7 +1145,7 @@ test "executable bit" {
11451145 for ([_]PipeOptions.ModeMode{ .ignore, .executable_bit_only }) |opt| {
11461146 var reader: std.Io.Reader = .fixed(data);
11471147
1148 var tmp = testing.tmpDir(.{ .no_follow = true });
1148 var tmp = testing.tmpDir(.{ .follow_symlinks = false });
11491149 //defer tmp.cleanup();
11501150
11511151 pipeToFileSystem(tmp.dir, &reader, .{
lib/std/tar/Writer.zig+33-22
......@@ -1,7 +1,9 @@
1const Writer = @This();
2
13const std = @import("std");
4const Io = std.Io;
25const assert = std.debug.assert;
36const testing = std.testing;
4const Writer = @This();
57
68const block_size = @sizeOf(Header);
79
......@@ -14,9 +16,8 @@ pub const Options = struct {
1416 mtime: u64 = 0,
1517};
1618
17underlying_writer: *std.Io.Writer,
19underlying_writer: *Io.Writer,
1820prefix: []const u8 = "",
19mtime_now: u64 = 0,
2021
2122const Error = error{
2223 WriteFailed,
......@@ -36,16 +37,27 @@ pub fn writeDir(w: *Writer, sub_path: []const u8, options: Options) Error!void {
3637 try w.writeHeader(.directory, sub_path, "", 0, options);
3738}
3839
39pub const WriteFileError = std.Io.Writer.FileError || Error || std.fs.File.Reader.SizeError;
40pub const WriteFileError = Io.Writer.FileError || Error || Io.File.Reader.SizeError;
41
42pub fn writeFileTimestamp(
43 w: *Writer,
44 sub_path: []const u8,
45 file_reader: *Io.File.Reader,
46 mtime: Io.Timestamp,
47) WriteFileError!void {
48 return writeFile(w, sub_path, file_reader, @intCast(mtime.toSeconds()));
49}
4050
4151pub fn writeFile(
4252 w: *Writer,
4353 sub_path: []const u8,
44 file_reader: *std.fs.File.Reader,
45 stat_mtime: i128,
54 file_reader: *Io.File.Reader,
55 /// If you want to match the file format's expectations, it wants number of
56 /// seconds since POSIX epoch. Zero is also a great option here to make
57 /// generated tarballs more reproducible.
58 mtime: u64,
4659) WriteFileError!void {
4760 const size = try file_reader.getSize();
48 const mtime: u64 = @intCast(@divFloor(stat_mtime, std.time.ns_per_s));
4961
5062 var header: Header = .{};
5163 try w.setPath(&header, sub_path);
......@@ -58,7 +70,7 @@ pub fn writeFile(
5870 try w.writePadding64(size);
5971}
6072
61pub const WriteFileStreamError = Error || std.Io.Reader.StreamError;
73pub const WriteFileStreamError = Error || Io.Reader.StreamError;
6274
6375/// Writes file reading file content from `reader`. Reads exactly `size` bytes
6476/// from `reader`, or returns `error.EndOfStream`.
......@@ -66,7 +78,7 @@ pub fn writeFileStream(
6678 w: *Writer,
6779 sub_path: []const u8,
6880 size: u64,
69 reader: *std.Io.Reader,
81 reader: *Io.Reader,
7082 options: Options,
7183) WriteFileStreamError!void {
7284 try w.writeHeader(.regular, sub_path, "", size, options);
......@@ -136,15 +148,15 @@ fn writeExtendedHeader(w: *Writer, typeflag: Header.FileType, buffers: []const [
136148 try w.writePadding(len);
137149}
138150
139fn writePadding(w: *Writer, bytes: usize) std.Io.Writer.Error!void {
151fn writePadding(w: *Writer, bytes: usize) Io.Writer.Error!void {
140152 return writePaddingPos(w, bytes % block_size);
141153}
142154
143fn writePadding64(w: *Writer, bytes: u64) std.Io.Writer.Error!void {
155fn writePadding64(w: *Writer, bytes: u64) Io.Writer.Error!void {
144156 return writePaddingPos(w, @intCast(bytes % block_size));
145157}
146158
147fn writePaddingPos(w: *Writer, pos: usize) std.Io.Writer.Error!void {
159fn writePaddingPos(w: *Writer, pos: usize) Io.Writer.Error!void {
148160 if (pos == 0) return;
149161 try w.underlying_writer.splatByteAll(0, block_size - pos);
150162}
......@@ -153,7 +165,7 @@ fn writePaddingPos(w: *Writer, pos: usize) std.Io.Writer.Error!void {
153165/// "reasonable system must not assume that such a block exists when reading an
154166/// archive". Therefore, the Zig standard library recommends to not call this
155167/// function.
156pub fn finishPedantically(w: *Writer) std.Io.Writer.Error!void {
168pub fn finishPedantically(w: *Writer) Io.Writer.Error!void {
157169 try w.underlying_writer.splatByteAll(0, block_size * 2);
158170}
159171
......@@ -236,7 +248,6 @@ pub const Header = extern struct {
236248 }
237249
238250 // Integer number of seconds since January 1, 1970, 00:00 Coordinated Universal Time.
239 // mtime == 0 will use current time
240251 pub fn setMtime(w: *Header, mtime: u64) error{OctalOverflow}!void {
241252 try octal(&w.mtime, mtime);
242253 }
......@@ -248,7 +259,7 @@ pub const Header = extern struct {
248259 try octal(&w.checksum, checksum);
249260 }
250261
251 pub fn write(h: *Header, bw: *std.Io.Writer) error{ OctalOverflow, WriteFailed }!void {
262 pub fn write(h: *Header, bw: *Io.Writer) error{ OctalOverflow, WriteFailed }!void {
252263 try h.updateChecksum();
253264 try bw.writeAll(std.mem.asBytes(h));
254265 }
......@@ -396,14 +407,14 @@ test "write files" {
396407 {
397408 const root = "root";
398409
399 var output: std.Io.Writer.Allocating = .init(testing.allocator);
410 var output: Io.Writer.Allocating = .init(testing.allocator);
400411 var w: Writer = .{ .underlying_writer = &output.writer };
401412 defer output.deinit();
402413 try w.setRoot(root);
403414 for (files) |file|
404415 try w.writeFileBytes(file.path, file.content, .{});
405416
406 var input: std.Io.Reader = .fixed(output.written());
417 var input: Io.Reader = .fixed(output.written());
407418 var it: std.tar.Iterator = .init(&input, .{
408419 .file_name_buffer = &file_name_buffer,
409420 .link_name_buffer = &link_name_buffer,
......@@ -424,7 +435,7 @@ test "write files" {
424435 try testing.expectEqual('/', actual.name[root.len..][0]);
425436 try testing.expectEqualStrings(expected.path, actual.name[root.len + 1 ..]);
426437
427 var content: std.Io.Writer.Allocating = .init(testing.allocator);
438 var content: Io.Writer.Allocating = .init(testing.allocator);
428439 defer content.deinit();
429440 try it.streamRemaining(actual, &content.writer);
430441 try testing.expectEqualSlices(u8, expected.content, content.written());
......@@ -432,15 +443,15 @@ test "write files" {
432443 }
433444 // without root
434445 {
435 var output: std.Io.Writer.Allocating = .init(testing.allocator);
446 var output: Io.Writer.Allocating = .init(testing.allocator);
436447 var w: Writer = .{ .underlying_writer = &output.writer };
437448 defer output.deinit();
438449 for (files) |file| {
439 var content: std.Io.Reader = .fixed(file.content);
450 var content: Io.Reader = .fixed(file.content);
440451 try w.writeFileStream(file.path, file.content.len, &content, .{});
441452 }
442453
443 var input: std.Io.Reader = .fixed(output.written());
454 var input: Io.Reader = .fixed(output.written());
444455 var it: std.tar.Iterator = .init(&input, .{
445456 .file_name_buffer = &file_name_buffer,
446457 .link_name_buffer = &link_name_buffer,
......@@ -452,7 +463,7 @@ test "write files" {
452463 const expected = files[i];
453464 try testing.expectEqualStrings(expected.path, actual.name);
454465
455 var content: std.Io.Writer.Allocating = .init(testing.allocator);
466 var content: Io.Writer.Allocating = .init(testing.allocator);
456467 defer content.deinit();
457468 try it.streamRemaining(actual, &content.writer);
458469 try testing.expectEqualSlices(u8, expected.content, content.written());
lib/std/testing.zig+8-1
......@@ -28,6 +28,9 @@ pub var allocator_instance: std.heap.GeneralPurposeAllocator(.{
2828 break :b .init;
2929};
3030
31pub var io_instance: std.Io.Threaded = undefined;
32pub const io = io_instance.io();
33
3134/// TODO https://github.com/ziglang/zig/issues/5738
3235pub var log_level = std.log.Level.warn;
3336
......@@ -1145,6 +1148,7 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime
11451148 } else |err| switch (err) {
11461149 error.OutOfMemory => {
11471150 if (failing_allocator_inst.allocated_bytes != failing_allocator_inst.freed_bytes) {
1151 const tty_config = std.Io.tty.detectConfig(.stderr());
11481152 print(
11491153 "\nfail_index: {d}/{d}\nallocated bytes: {d}\nfreed bytes: {d}\nallocations: {d}\ndeallocations: {d}\nallocation that was made to fail: {f}",
11501154 .{
......@@ -1154,7 +1158,10 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime
11541158 failing_allocator_inst.freed_bytes,
11551159 failing_allocator_inst.allocations,
11561160 failing_allocator_inst.deallocations,
1157 failing_allocator_inst.getStackTrace(),
1161 std.debug.FormatStackTrace{
1162 .stack_trace = failing_allocator_inst.getStackTrace(),
1163 .tty_config = tty_config,
1164 },
11581165 },
11591166 );
11601167 return error.MemoryLeakDetected;
lib/std/time.zig+3-69
......@@ -8,74 +8,6 @@ const posix = std.posix;
88
99pub const epoch = @import("time/epoch.zig");
1010
11/// Get a calendar timestamp, in seconds, relative to UTC 1970-01-01.
12/// Precision of timing depends on the hardware and operating system.
13/// The return value is signed because it is possible to have a date that is
14/// before the epoch.
15/// See `posix.clock_gettime` for a POSIX timestamp.
16pub fn timestamp() i64 {
17 return @divFloor(milliTimestamp(), ms_per_s);
18}
19
20/// Get a calendar timestamp, in milliseconds, relative to UTC 1970-01-01.
21/// Precision of timing depends on the hardware and operating system.
22/// The return value is signed because it is possible to have a date that is
23/// before the epoch.
24/// See `posix.clock_gettime` for a POSIX timestamp.
25pub fn milliTimestamp() i64 {
26 return @as(i64, @intCast(@divFloor(nanoTimestamp(), ns_per_ms)));
27}
28
29/// Get a calendar timestamp, in microseconds, relative to UTC 1970-01-01.
30/// Precision of timing depends on the hardware and operating system.
31/// The return value is signed because it is possible to have a date that is
32/// before the epoch.
33/// See `posix.clock_gettime` for a POSIX timestamp.
34pub fn microTimestamp() i64 {
35 return @as(i64, @intCast(@divFloor(nanoTimestamp(), ns_per_us)));
36}
37
38/// Get a calendar timestamp, in nanoseconds, relative to UTC 1970-01-01.
39/// Precision of timing depends on the hardware and operating system.
40/// On Windows this has a maximum granularity of 100 nanoseconds.
41/// The return value is signed because it is possible to have a date that is
42/// before the epoch.
43/// See `posix.clock_gettime` for a POSIX timestamp.
44pub fn nanoTimestamp() i128 {
45 switch (builtin.os.tag) {
46 .windows => {
47 // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds and uses the NTFS/Windows epoch,
48 // which is 1601-01-01.
49 const epoch_adj = epoch.windows * (ns_per_s / 100);
50 return @as(i128, windows.ntdll.RtlGetSystemTimePrecise() + epoch_adj) * 100;
51 },
52 .wasi => {
53 var ns: std.os.wasi.timestamp_t = undefined;
54 const err = std.os.wasi.clock_time_get(.REALTIME, 1, &ns);
55 assert(err == .SUCCESS);
56 return ns;
57 },
58 .uefi => {
59 const value, _ = std.os.uefi.system_table.runtime_services.getTime() catch return 0;
60 return value.toEpoch();
61 },
62 else => {
63 const ts = posix.clock_gettime(.REALTIME) catch |err| switch (err) {
64 error.UnsupportedClock, error.Unexpected => return 0, // "Precision of timing depends on hardware and OS".
65 };
66 return (@as(i128, ts.sec) * ns_per_s) + ts.nsec;
67 },
68 }
69}
70
71test milliTimestamp {
72 const time_0 = milliTimestamp();
73 std.Thread.sleep(ns_per_ms);
74 const time_1 = milliTimestamp();
75 const interval = time_1 - time_0;
76 try testing.expect(interval > 0);
77}
78
7911// Divisions of a nanosecond.
8012pub const ns_per_us = 1000;
8113pub const ns_per_ms = 1000 * ns_per_us;
......@@ -268,9 +200,11 @@ pub const Timer = struct {
268200};
269201
270202test Timer {
203 const io = std.testing.io;
204
271205 var timer = try Timer.start();
272206
273 std.Thread.sleep(10 * ns_per_ms);
207 try std.Io.Clock.Duration.sleep(.{ .clock = .awake, .raw = .fromMilliseconds(10) }, io);
274208 const time_0 = timer.read();
275209 try testing.expect(time_0 > 0);
276210
lib/std/unicode.zig-25
......@@ -1809,30 +1809,6 @@ pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) error{InvalidWtf8}!usize
18091809 return utf8ToUtf16LeImpl(wtf16le, wtf8, .can_encode_surrogate_half);
18101810}
18111811
1812fn checkUtf8ToUtf16LeOverflowImpl(utf8: []const u8, utf16le: []const u16, comptime surrogates: Surrogates) !bool {
1813 // Each u8 in UTF-8/WTF-8 correlates to at most one u16 in UTF-16LE/WTF-16LE.
1814 if (utf16le.len >= utf8.len) return false;
1815 const utf16_len = calcUtf16LeLenImpl(utf8, surrogates) catch {
1816 return switch (surrogates) {
1817 .cannot_encode_surrogate_half => error.InvalidUtf8,
1818 .can_encode_surrogate_half => error.InvalidWtf8,
1819 };
1820 };
1821 return utf16_len > utf16le.len;
1822}
1823
1824/// Checks if calling `utf8ToUtf16Le` would overflow. Might fail if utf8 is not
1825/// valid UTF-8.
1826pub fn checkUtf8ToUtf16LeOverflow(utf8: []const u8, utf16le: []const u16) error{InvalidUtf8}!bool {
1827 return checkUtf8ToUtf16LeOverflowImpl(utf8, utf16le, .cannot_encode_surrogate_half);
1828}
1829
1830/// Checks if calling `utf8ToUtf16Le` would overflow. Might fail if wtf8 is not
1831/// valid WTF-8.
1832pub fn checkWtf8ToWtf16LeOverflow(wtf8: []const u8, wtf16le: []const u16) error{InvalidWtf8}!bool {
1833 return checkUtf8ToUtf16LeOverflowImpl(wtf8, wtf16le, .can_encode_surrogate_half);
1834}
1835
18361812/// Surrogate codepoints (U+D800 to U+DFFF) are replaced by the Unicode replacement
18371813/// character (U+FFFD).
18381814/// All surrogate codepoints and the replacement character are encoded as three
......@@ -2039,7 +2015,6 @@ fn testRoundtripWtf8(wtf8: []const u8) !void {
20392015 var wtf16_buf: [32]u16 = undefined;
20402016 const wtf16_len = try wtf8ToWtf16Le(&wtf16_buf, wtf8);
20412017 try testing.expectEqual(wtf16_len, calcWtf16LeLen(wtf8));
2042 try testing.expectEqual(false, checkWtf8ToWtf16LeOverflow(wtf8, &wtf16_buf));
20432018 const wtf16 = wtf16_buf[0..wtf16_len];
20442019
20452020 var roundtripped_buf: [32]u8 = undefined;
lib/std/zig.zig+7-6
......@@ -6,6 +6,7 @@ const std = @import("std.zig");
66const tokenizer = @import("zig/tokenizer.zig");
77const assert = std.debug.assert;
88const Allocator = std.mem.Allocator;
9const Io = std.Io;
910const Writer = std.Io.Writer;
1011
1112pub const ErrorBundle = @import("zig/ErrorBundle.zig");
......@@ -52,9 +53,9 @@ pub const Color = enum {
5253 /// Assume stderr is a terminal.
5354 on,
5455
55 pub fn get_tty_conf(color: Color) std.Io.tty.Config {
56 pub fn get_tty_conf(color: Color) Io.tty.Config {
5657 return switch (color) {
57 .auto => std.Io.tty.detectConfig(std.fs.File.stderr()),
58 .auto => Io.tty.detectConfig(std.fs.File.stderr()),
5859 .on => .escape_codes,
5960 .off => .no_color,
6061 };
......@@ -323,7 +324,7 @@ pub const BuildId = union(enum) {
323324 try std.testing.expectError(error.InvalidBuildIdStyle, parse("yaddaxxx"));
324325 }
325326
326 pub fn format(id: BuildId, writer: *std.Io.Writer) std.Io.Writer.Error!void {
327 pub fn format(id: BuildId, writer: *Writer) Writer.Error!void {
327328 switch (id) {
328329 .none, .fast, .uuid, .sha1, .md5 => {
329330 try writer.writeAll(@tagName(id));
......@@ -558,7 +559,7 @@ test isUnderscore {
558559/// If the source can be UTF-16LE encoded, this function asserts that `gpa`
559560/// will align a byte-sized allocation to at least 2. Allocators that don't do
560561/// this are rare.
561pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *std.fs.File.Reader) ![:0]u8 {
562pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *Io.File.Reader) ![:0]u8 {
562563 var buffer: std.ArrayList(u8) = .empty;
563564 defer buffer.deinit(gpa);
564565
......@@ -620,8 +621,8 @@ pub fn putAstErrorsIntoBundle(
620621 try wip_errors.addZirErrorMessages(zir, tree, tree.source, path);
621622}
622623
623pub fn resolveTargetQueryOrFatal(target_query: std.Target.Query) std.Target {
624 return std.zig.system.resolveTargetQuery(target_query) catch |err|
624pub fn resolveTargetQueryOrFatal(io: Io, target_query: std.Target.Query) std.Target {
625 return std.zig.system.resolveTargetQuery(io, target_query) catch |err|
625626 std.process.fatal("unable to resolve target: {s}", .{@errorName(err)});
626627}
627628
lib/std/zig/ErrorBundle.zig+6-5
......@@ -6,12 +6,13 @@
66//! There is one special encoding for this data structure. If both arrays are
77//! empty, it means there are no errors. This special encoding exists so that
88//! heap allocation is not needed in the common case of no errors.
9const ErrorBundle = @This();
910
1011const std = @import("std");
11const ErrorBundle = @This();
12const Io = std.Io;
13const Writer = std.Io.Writer;
1214const Allocator = std.mem.Allocator;
1315const assert = std.debug.assert;
14const Writer = std.Io.Writer;
1516
1617string_bytes: []const u8,
1718/// The first thing in this array is an `ErrorMessageList`.
......@@ -156,7 +157,7 @@ pub fn nullTerminatedString(eb: ErrorBundle, index: String) [:0]const u8 {
156157}
157158
158159pub const RenderOptions = struct {
159 ttyconf: std.Io.tty.Config,
160 ttyconf: Io.tty.Config,
160161 include_reference_trace: bool = true,
161162 include_source_line: bool = true,
162163 include_log_text: bool = true,
......@@ -190,7 +191,7 @@ fn renderErrorMessageToWriter(
190191 err_msg_index: MessageIndex,
191192 w: *Writer,
192193 kind: []const u8,
193 color: std.Io.tty.Color,
194 color: Io.tty.Color,
194195 indent: usize,
195196) (Writer.Error || std.posix.UnexpectedError)!void {
196197 const ttyconf = options.ttyconf;
......@@ -806,7 +807,7 @@ pub const Wip = struct {
806807 };
807808 defer bundle.deinit(std.testing.allocator);
808809
809 const ttyconf: std.Io.tty.Config = .no_color;
810 const ttyconf: Io.tty.Config = .no_color;
810811
811812 var bundle_buf: Writer.Allocating = .init(std.testing.allocator);
812813 const bundle_bw = &bundle_buf.interface;
lib/std/zig/LibCInstallation.zig+6-6
......@@ -329,7 +329,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
329329 defer search_dir.close();
330330
331331 if (self.include_dir == null) {
332 if (search_dir.accessZ(include_dir_example_file, .{})) |_| {
332 if (search_dir.access(include_dir_example_file, .{})) |_| {
333333 self.include_dir = try allocator.dupeZ(u8, search_path);
334334 } else |err| switch (err) {
335335 error.FileNotFound => {},
......@@ -338,7 +338,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
338338 }
339339
340340 if (self.sys_include_dir == null) {
341 if (search_dir.accessZ(sys_include_dir_example_file, .{})) |_| {
341 if (search_dir.access(sys_include_dir_example_file, .{})) |_| {
342342 self.sys_include_dir = try allocator.dupeZ(u8, search_path);
343343 } else |err| switch (err) {
344344 error.FileNotFound => {},
......@@ -382,7 +382,7 @@ fn findNativeIncludeDirWindows(
382382 };
383383 defer dir.close();
384384
385 dir.accessZ("stdlib.h", .{}) catch |err| switch (err) {
385 dir.access("stdlib.h", .{}) catch |err| switch (err) {
386386 error.FileNotFound => continue,
387387 else => return error.FileSystem,
388388 };
......@@ -429,7 +429,7 @@ fn findNativeCrtDirWindows(
429429 };
430430 defer dir.close();
431431
432 dir.accessZ("ucrt.lib", .{}) catch |err| switch (err) {
432 dir.access("ucrt.lib", .{}) catch |err| switch (err) {
433433 error.FileNotFound => continue,
434434 else => return error.FileSystem,
435435 };
......@@ -496,7 +496,7 @@ fn findNativeKernel32LibDir(
496496 };
497497 defer dir.close();
498498
499 dir.accessZ("kernel32.lib", .{}) catch |err| switch (err) {
499 dir.access("kernel32.lib", .{}) catch |err| switch (err) {
500500 error.FileNotFound => continue,
501501 else => return error.FileSystem,
502502 };
......@@ -531,7 +531,7 @@ fn findNativeMsvcIncludeDir(
531531 };
532532 defer dir.close();
533533
534 dir.accessZ("vcruntime.h", .{}) catch |err| switch (err) {
534 dir.access("vcruntime.h", .{}) catch |err| switch (err) {
535535 error.FileNotFound => return error.LibCStdLibHeaderNotFound,
536536 else => return error.FileSystem,
537537 };
lib/std/zig/system.zig+310-494
......@@ -1,3 +1,14 @@
1const builtin = @import("builtin");
2const std = @import("../std.zig");
3const mem = std.mem;
4const elf = std.elf;
5const fs = std.fs;
6const assert = std.debug.assert;
7const Target = std.Target;
8const native_endian = builtin.cpu.arch.endian();
9const posix = std.posix;
10const Io = std.Io;
11
112pub const NativePaths = @import("system/NativePaths.zig");
213
314pub const windows = @import("system/windows.zig");
......@@ -199,14 +210,14 @@ pub const DetectError = error{
199210 OSVersionDetectionFail,
200211 Unexpected,
201212 ProcessNotFound,
202};
213} || Io.Cancelable;
203214
204215/// Given a `Target.Query`, which specifies in detail which parts of the
205216/// target should be detected natively, which should be standard or default,
206217/// and which are provided explicitly, this function resolves the native
207218/// components by detecting the native system, and then resolves
208219/// standard/default parts relative to that.
209pub fn resolveTargetQuery(query: Target.Query) DetectError!Target {
220pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
210221 // Until https://github.com/ziglang/zig/issues/4592 is implemented (support detecting the
211222 // native CPU architecture as being different than the current target), we use this:
212223 const query_cpu_arch = query.cpu_arch orelse builtin.cpu.arch;
......@@ -356,10 +367,10 @@ pub fn resolveTargetQuery(query: Target.Query) DetectError!Target {
356367 }
357368
358369 var cpu = switch (query.cpu_model) {
359 .native => detectNativeCpuAndFeatures(query_cpu_arch, os, query),
370 .native => detectNativeCpuAndFeatures(io, query_cpu_arch, os, query),
360371 .baseline => Target.Cpu.baseline(query_cpu_arch, os),
361372 .determined_by_arch_os => if (query.cpu_arch == null)
362 detectNativeCpuAndFeatures(query_cpu_arch, os, query)
373 detectNativeCpuAndFeatures(io, query_cpu_arch, os, query)
363374 else
364375 Target.Cpu.baseline(query_cpu_arch, os),
365376 .explicit => |model| model.toCpu(query_cpu_arch),
......@@ -411,7 +422,34 @@ pub fn resolveTargetQuery(query: Target.Query) DetectError!Target {
411422 query.cpu_features_sub,
412423 );
413424
414 var result = try detectAbiAndDynamicLinker(cpu, os, query);
425 var result = detectAbiAndDynamicLinker(io, cpu, os, query) catch |err| switch (err) {
426 error.Canceled => |e| return e,
427 error.Unexpected => |e| return e,
428 error.WouldBlock => return error.Unexpected,
429 error.BrokenPipe => return error.Unexpected,
430 error.ConnectionResetByPeer => return error.Unexpected,
431 error.Timeout => return error.Unexpected,
432 error.NotOpenForReading => return error.Unexpected,
433 error.SocketUnconnected => return error.Unexpected,
434
435 error.AccessDenied,
436 error.ProcessNotFound,
437 error.SymLinkLoop,
438 error.ProcessFdQuotaExceeded,
439 error.SystemFdQuotaExceeded,
440 error.SystemResources,
441 error.IsDir,
442 error.DeviceBusy,
443 error.InputOutput,
444 error.LockViolation,
445 error.FileSystem,
446
447 error.UnableToOpenElfFile,
448 error.UnhelpfulFile,
449 error.InvalidElfFile,
450 error.RelativeShebang,
451 => return defaultAbiAndDynamicLinker(cpu, os, query),
452 };
415453
416454 // These CPU feature hacks have to come after ABI detection.
417455 {
......@@ -483,7 +521,7 @@ fn updateCpuFeatures(
483521 set.removeFeatureSet(sub_set);
484522}
485523
486fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, query: Target.Query) ?Target.Cpu {
524fn detectNativeCpuAndFeatures(io: Io, cpu_arch: Target.Cpu.Arch, os: Target.Os, query: Target.Query) ?Target.Cpu {
487525 // Here we switch on a comptime value rather than `cpu_arch`. This is valid because `cpu_arch`,
488526 // although it is a runtime value, is guaranteed to be one of the architectures in the set
489527 // of the respective switch prong.
......@@ -494,7 +532,7 @@ fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, query: T
494532 }
495533
496534 switch (builtin.os.tag) {
497 .linux => return linux.detectNativeCpuAndFeatures(),
535 .linux => return linux.detectNativeCpuAndFeatures(io),
498536 .macos => return darwin.macos.detectNativeCpuAndFeatures(),
499537 .windows => return windows.detectNativeCpuAndFeatures(),
500538 else => {},
......@@ -506,53 +544,42 @@ fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, query: T
506544}
507545
508546pub const AbiAndDynamicLinkerFromFileError = error{
509 FileSystem,
510 SystemResources,
547 Canceled,
548 AccessDenied,
549 Unexpected,
550 Unseekable,
551 ReadFailed,
552 EndOfStream,
553 NameTooLong,
554 StaticElfFile,
555 InvalidElfFile,
556 StreamTooLong,
557 Timeout,
511558 SymLinkLoop,
559 SystemResources,
512560 ProcessFdQuotaExceeded,
513561 SystemFdQuotaExceeded,
514 UnableToReadElfFile,
515 InvalidElfClass,
516 InvalidElfVersion,
517 InvalidElfEndian,
518 InvalidElfFile,
519 InvalidElfMagic,
520 Unexpected,
521 UnexpectedEndOfFile,
522 NameTooLong,
523562 ProcessNotFound,
524 StaticElfFile,
563 IsDir,
564 WouldBlock,
565 InputOutput,
566 BrokenPipe,
567 ConnectionResetByPeer,
568 NotOpenForReading,
569 SocketUnconnected,
570 LockViolation,
571 FileSystem,
525572};
526573
527pub fn abiAndDynamicLinkerFromFile(
528 file: fs.File,
574fn abiAndDynamicLinkerFromFile(
575 file_reader: *Io.File.Reader,
576 header: *const elf.Header,
529577 cpu: Target.Cpu,
530578 os: Target.Os,
531579 ld_info_list: []const LdInfo,
532580 query: Target.Query,
533581) AbiAndDynamicLinkerFromFileError!Target {
534 var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
535 _ = try preadAtLeast(file, &hdr_buf, 0, hdr_buf.len);
536 const hdr32: *elf.Elf32_Ehdr = @ptrCast(&hdr_buf);
537 const hdr64: *elf.Elf64_Ehdr = @ptrCast(&hdr_buf);
538 if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
539 const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI.DATA]) {
540 elf.ELFDATA2LSB => .little,
541 elf.ELFDATA2MSB => .big,
542 else => return error.InvalidElfEndian,
543 };
544 const need_bswap = elf_endian != native_endian;
545 if (hdr32.e_ident[elf.EI.VERSION] != 1) return error.InvalidElfVersion;
546
547 const is_64 = switch (hdr32.e_ident[elf.EI.CLASS]) {
548 elf.ELFCLASS32 => false,
549 elf.ELFCLASS64 => true,
550 else => return error.InvalidElfClass,
551 };
552 var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff);
553 const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize);
554 const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum);
555
582 const io = file_reader.io;
556583 var result: Target = .{
557584 .cpu = cpu,
558585 .os = os,
......@@ -563,170 +590,90 @@ pub fn abiAndDynamicLinkerFromFile(
563590 var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC
564591 const look_for_ld = query.dynamic_linker.get() == null;
565592
566 var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined;
567 if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile;
568
569 var ph_i: u16 = 0;
570593 var got_dyn_section: bool = false;
571
572 while (ph_i < phnum) {
573 // Reserve some bytes so that we can deref the 64-bit struct fields
574 // even when the ELF file is 32-bits.
575 const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr);
576 const ph_read_byte_len = try preadAtLeast(file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize);
577 var ph_buf_i: usize = 0;
578 while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({
579 ph_i += 1;
580 phoff += phentsize;
581 ph_buf_i += phentsize;
582 }) {
583 const ph32: *elf.Elf32_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i]));
584 const ph64: *elf.Elf64_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i]));
585 const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type);
586 switch (p_type) {
587 elf.PT_INTERP => {
588 got_dyn_section = true;
589
590 if (look_for_ld) {
591 const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
592 const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
593 if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong;
594 const filesz: usize = @intCast(p_filesz);
595 _ = try preadAtLeast(file, result.dynamic_linker.buffer[0..filesz], p_offset, filesz);
596 // PT_INTERP includes a null byte in filesz.
597 const len = filesz - 1;
598 // dynamic_linker.max_byte is "max", not "len".
599 // We know it will fit in u8 because we check against dynamic_linker.buffer.len above.
600 result.dynamic_linker.len = @intCast(len);
601
602 // Use it to determine ABI.
603 const full_ld_path = result.dynamic_linker.buffer[0..len];
604 for (ld_info_list) |ld_info| {
605 const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
606 if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) {
607 result.abi = ld_info.abi;
608 break;
609 }
594 {
595 var it = header.iterateProgramHeaders(file_reader);
596 while (try it.next()) |phdr| switch (phdr.p_type) {
597 elf.PT_INTERP => {
598 got_dyn_section = true;
599
600 if (look_for_ld) {
601 const p_filesz = phdr.p_filesz;
602 if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong;
603 const filesz: usize = @intCast(p_filesz);
604 try file_reader.seekTo(phdr.p_offset);
605 try file_reader.interface.readSliceAll(result.dynamic_linker.buffer[0..filesz]);
606 // PT_INTERP includes a null byte in filesz.
607 const len = filesz - 1;
608 // dynamic_linker.max_byte is "max", not "len".
609 // We know it will fit in u8 because we check against dynamic_linker.buffer.len above.
610 result.dynamic_linker.len = @intCast(len);
611
612 // Use it to determine ABI.
613 const full_ld_path = result.dynamic_linker.buffer[0..len];
614 for (ld_info_list) |ld_info| {
615 const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
616 if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) {
617 result.abi = ld_info.abi;
618 break;
610619 }
611620 }
612 },
613 // We only need this for detecting glibc version.
614 elf.PT_DYNAMIC => {
615 got_dyn_section = true;
616
617 if (builtin.target.os.tag == .linux and result.isGnuLibC() and
618 query.glibc_version == null)
619 {
620 var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);
621 const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);
622 const dyn_size: usize = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn);
623 const dyn_num = p_filesz / dyn_size;
624 var dyn_buf: [16 * @sizeOf(elf.Elf64_Dyn)]u8 align(@alignOf(elf.Elf64_Dyn)) = undefined;
625 var dyn_i: usize = 0;
626 dyn: while (dyn_i < dyn_num) {
627 // Reserve some bytes so that we can deref the 64-bit struct fields
628 // even when the ELF file is 32-bits.
629 const dyn_reserve: usize = @sizeOf(elf.Elf64_Dyn) - @sizeOf(elf.Elf32_Dyn);
630 const dyn_read_byte_len = try preadAtLeast(
631 file,
632 dyn_buf[0 .. dyn_buf.len - dyn_reserve],
633 dyn_off,
634 dyn_size,
635 );
636 var dyn_buf_i: usize = 0;
637 while (dyn_buf_i < dyn_read_byte_len and dyn_i < dyn_num) : ({
638 dyn_i += 1;
639 dyn_off += dyn_size;
640 dyn_buf_i += dyn_size;
641 }) {
642 const dyn32: *elf.Elf32_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i]));
643 const dyn64: *elf.Elf64_Dyn = @ptrCast(@alignCast(&dyn_buf[dyn_buf_i]));
644 const tag = elfInt(is_64, need_bswap, dyn32.d_tag, dyn64.d_tag);
645 const val = elfInt(is_64, need_bswap, dyn32.d_val, dyn64.d_val);
646 if (tag == elf.DT_RUNPATH) {
647 rpath_offset = val;
648 break :dyn;
649 }
650 }
621 }
622 },
623 // We only need this for detecting glibc version.
624 elf.PT_DYNAMIC => {
625 got_dyn_section = true;
626
627 if (builtin.target.os.tag == .linux and result.isGnuLibC() and query.glibc_version == null) {
628 var dyn_it = header.iterateDynamicSection(file_reader, phdr.p_offset, phdr.p_filesz);
629 while (try dyn_it.next()) |dyn| {
630 if (dyn.d_tag == elf.DT_RUNPATH) {
631 rpath_offset = dyn.d_val;
632 break;
651633 }
652634 }
653 },
654 else => continue,
655 }
656 }
635 }
636 },
637 else => continue,
638 };
657639 }
658640
659641 if (!got_dyn_section) {
660642 return error.StaticElfFile;
661643 }
662644
663 if (builtin.target.os.tag == .linux and result.isGnuLibC() and
664 query.glibc_version == null)
665 {
666 const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
667
668 var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);
669 const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);
670 const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);
671
672 var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;
673 if (sh_buf.len < shentsize) return error.InvalidElfFile;
674
675 _ = try preadAtLeast(file, &sh_buf, str_section_off, shentsize);
676 const shstr32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf));
677 const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf));
678 const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);
679 const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);
680 var strtab_buf: [4096:0]u8 = undefined;
681 const shstrtab_len = @min(shstrtab_size, strtab_buf.len);
682 const shstrtab_read_len = try preadAtLeast(file, &strtab_buf, shstrtab_off, shstrtab_len);
683 const shstrtab = strtab_buf[0..shstrtab_read_len];
684
685 const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum);
686 var sh_i: u16 = 0;
687 const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) {
688 // Reserve some bytes so that we can deref the 64-bit struct fields
689 // even when the ELF file is 32-bits.
690 const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr);
691 const sh_read_byte_len = try preadAtLeast(
692 file,
693 sh_buf[0 .. sh_buf.len - sh_reserve],
694 shoff,
695 shentsize,
696 );
697 var sh_buf_i: usize = 0;
698 while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({
699 sh_i += 1;
700 shoff += shentsize;
701 sh_buf_i += shentsize;
702 }) {
703 const sh32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
704 const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
705 const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name);
706 const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0);
707 if (mem.eql(u8, sh_name, ".dynstr")) {
708 break :find_dyn_str .{
709 .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset),
710 .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size),
711 };
712 }
713 }
714 } else null;
715
645 if (builtin.target.os.tag == .linux and result.isGnuLibC() and query.glibc_version == null) {
646 const str_section_off = header.shoff + @as(u64, header.shentsize) * @as(u64, header.shstrndx);
647 try file_reader.seekTo(str_section_off);
648 const shstr = try elf.takeSectionHeader(&file_reader.interface, header.is_64, header.endian);
649 var strtab_buf: [4096]u8 = undefined;
650 const shstrtab = strtab_buf[0..@min(shstr.sh_size, strtab_buf.len)];
651 try file_reader.seekTo(shstr.sh_offset);
652 try file_reader.interface.readSliceAll(shstrtab);
653 const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: {
654 var it = header.iterateSectionHeaders(file_reader);
655 while (try it.next()) |shdr| {
656 const end = mem.findScalarPos(u8, shstrtab, shdr.sh_name, 0) orelse continue;
657 const sh_name = shstrtab[shdr.sh_name..end :0];
658 if (mem.eql(u8, sh_name, ".dynstr")) break :find_dyn_str .{
659 .offset = shdr.sh_offset,
660 .size = shdr.sh_size,
661 };
662 } else break :find_dyn_str null;
663 };
716664 if (dynstr) |ds| {
717665 if (rpath_offset) |rpoff| {
718666 if (rpoff > ds.size) return error.InvalidElfFile;
719667 const rpoff_file = ds.offset + rpoff;
720668 const rp_max_size = ds.size - rpoff;
721669
722 const strtab_len = @min(rp_max_size, strtab_buf.len);
723 const strtab_read_len = try preadAtLeast(file, &strtab_buf, rpoff_file, strtab_len);
724 const strtab = strtab_buf[0..strtab_read_len];
670 try file_reader.seekTo(rpoff_file);
671 const rpath_list = try file_reader.interface.takeSentinel(0);
672 if (rpath_list.len > rp_max_size) return error.StreamTooLong;
725673
726 const rpath_list = mem.sliceTo(strtab, 0);
727674 var it = mem.tokenizeScalar(u8, rpath_list, ':');
728675 while (it.next()) |rpath| {
729 if (glibcVerFromRPath(rpath)) |ver| {
676 if (glibcVerFromRPath(io, rpath)) |ver| {
730677 result.os.version_range.linux.glibc = ver;
731678 return result;
732679 } else |err| switch (err) {
......@@ -741,7 +688,7 @@ pub fn abiAndDynamicLinkerFromFile(
741688 // There is no DT_RUNPATH so we try to find libc.so.6 inside the same
742689 // directory as the dynamic linker.
743690 if (fs.path.dirname(dl_path)) |rpath| {
744 if (glibcVerFromRPath(rpath)) |ver| {
691 if (glibcVerFromRPath(io, rpath)) |ver| {
745692 result.os.version_range.linux.glibc = ver;
746693 return result;
747694 } else |err| switch (err) {
......@@ -755,8 +702,6 @@ pub fn abiAndDynamicLinkerFromFile(
755702 var link_buf: [posix.PATH_MAX]u8 = undefined;
756703 const link_name = posix.readlink(dl_path, &link_buf) catch |err| switch (err) {
757704 error.NameTooLong => unreachable,
758 error.InvalidUtf8 => unreachable, // WASI only
759 error.InvalidWtf8 => unreachable, // Windows only
760705 error.BadPathName => unreachable, // Windows only
761706 error.UnsupportedReparsePointType => unreachable, // Windows only
762707 error.NetworkNotFound => unreachable, // Windows only
......@@ -806,7 +751,7 @@ pub fn abiAndDynamicLinkerFromFile(
806751 @memcpy(path_buf[index..][0..abi.len], abi);
807752 index += abi.len;
808753 const rpath = path_buf[0..index];
809 if (glibcVerFromRPath(rpath)) |ver| {
754 if (glibcVerFromRPath(io, rpath)) |ver| {
810755 result.os.version_range.linux.glibc = ver;
811756 return result;
812757 } else |err| switch (err) {
......@@ -845,29 +790,25 @@ test glibcVerFromLinkName {
845790 try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.37.4.5.so", "ld-"));
846791}
847792
848fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
793fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
849794 var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
850 error.NameTooLong => unreachable,
851 error.InvalidUtf8 => unreachable, // WASI only
852 error.InvalidWtf8 => unreachable, // Windows-only
853 error.BadPathName => unreachable,
854 error.DeviceBusy => unreachable,
855 error.NetworkNotFound => unreachable, // Windows-only
856
857 error.FileNotFound,
858 error.NotDir,
859 error.AccessDenied,
860 error.PermissionDenied,
861 error.NoDevice,
862 => return error.GLibCNotFound,
863
864 error.ProcessNotFound,
865 error.ProcessFdQuotaExceeded,
866 error.SystemFdQuotaExceeded,
867 error.SystemResources,
868 error.SymLinkLoop,
869 error.Unexpected,
870 => |e| return e,
795 error.NameTooLong => return error.Unexpected,
796 error.BadPathName => return error.Unexpected,
797 error.DeviceBusy => return error.Unexpected,
798 error.NetworkNotFound => return error.Unexpected, // Windows-only
799
800 error.FileNotFound => return error.GLibCNotFound,
801 error.NotDir => return error.GLibCNotFound,
802 error.AccessDenied => return error.GLibCNotFound,
803 error.PermissionDenied => return error.GLibCNotFound,
804 error.NoDevice => return error.GLibCNotFound,
805
806 error.ProcessFdQuotaExceeded => |e| return e,
807 error.SystemFdQuotaExceeded => |e| return e,
808 error.SystemResources => |e| return e,
809 error.SymLinkLoop => |e| return e,
810 error.Unexpected => |e| return e,
811 error.Canceled => |e| return e,
871812 };
872813 defer dir.close();
873814
......@@ -879,155 +820,103 @@ fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
879820 // .dynstr section, and finding the max version number of symbols
880821 // that start with "GLIBC_2.".
881822 const glibc_so_basename = "libc.so.6";
882 var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
883 error.NameTooLong => unreachable,
884 error.InvalidUtf8 => unreachable, // WASI only
885 error.InvalidWtf8 => unreachable, // Windows only
886 error.BadPathName => unreachable, // Windows only
887 error.PipeBusy => unreachable, // Windows-only
888 error.SharingViolation => unreachable, // Windows-only
889 error.NetworkNotFound => unreachable, // Windows-only
890 error.AntivirusInterference => unreachable, // Windows-only
891 error.FileLocksNotSupported => unreachable, // No lock requested.
892 error.NoSpaceLeft => unreachable, // read-only
893 error.PathAlreadyExists => unreachable, // read-only
894 error.DeviceBusy => unreachable, // read-only
895 error.FileBusy => unreachable, // read-only
896 error.WouldBlock => unreachable, // not using O_NONBLOCK
897 error.NoDevice => unreachable, // not asking for a special device
898
899 error.AccessDenied,
900 error.PermissionDenied,
901 error.FileNotFound,
902 error.NotDir,
903 error.IsDir,
904 => return error.GLibCNotFound,
905
823 var file = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
824 error.NameTooLong => return error.Unexpected,
825 error.BadPathName => return error.Unexpected,
826 error.PipeBusy => return error.Unexpected, // Windows-only
827 error.SharingViolation => return error.Unexpected, // Windows-only
828 error.NetworkNotFound => return error.Unexpected, // Windows-only
829 error.AntivirusInterference => return error.Unexpected, // Windows-only
830 error.FileLocksNotSupported => return error.Unexpected, // No lock requested.
831 error.NoSpaceLeft => return error.Unexpected, // read-only
832 error.PathAlreadyExists => return error.Unexpected, // read-only
833 error.DeviceBusy => return error.Unexpected, // read-only
834 error.FileBusy => return error.Unexpected, // read-only
835 error.NoDevice => return error.Unexpected, // not asking for a special device
906836 error.FileTooBig => return error.Unexpected,
907
908 error.ProcessNotFound,
909 error.ProcessFdQuotaExceeded,
910 error.SystemFdQuotaExceeded,
911 error.SystemResources,
912 error.SymLinkLoop,
913 error.Unexpected,
914 => |e| return e,
837 error.WouldBlock => return error.Unexpected, // not opened in non-blocking
838
839 error.AccessDenied => return error.GLibCNotFound,
840 error.PermissionDenied => return error.GLibCNotFound,
841 error.FileNotFound => return error.GLibCNotFound,
842 error.NotDir => return error.GLibCNotFound,
843 error.IsDir => return error.GLibCNotFound,
844
845 error.ProcessNotFound => |e| return e,
846 error.ProcessFdQuotaExceeded => |e| return e,
847 error.SystemFdQuotaExceeded => |e| return e,
848 error.SystemResources => |e| return e,
849 error.SymLinkLoop => |e| return e,
850 error.Unexpected => |e| return e,
851 error.Canceled => |e| return e,
915852 };
916 defer f.close();
853 defer file.close();
854
855 // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system.
856 var buffer: [8000]u8 = undefined;
857 var file_reader: Io.File.Reader = .initAdapted(file, io, &buffer);
917858
918 return glibcVerFromSoFile(f) catch |err| switch (err) {
859 return glibcVerFromSoFile(&file_reader) catch |err| switch (err) {
919860 error.InvalidElfMagic,
920861 error.InvalidElfEndian,
921862 error.InvalidElfClass,
922 error.InvalidElfFile,
923863 error.InvalidElfVersion,
924864 error.InvalidGnuLibCVersion,
925 error.UnexpectedEndOfFile,
865 error.EndOfStream,
926866 => return error.GLibCNotFound,
927867
928 error.SystemResources,
929 error.UnableToReadElfFile,
930 error.Unexpected,
931 error.FileSystem,
932 error.ProcessNotFound,
933 => |e| return e,
868 error.ReadFailed => return file_reader.err.?,
869 else => |e| return e,
934870 };
935871}
936872
937fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion {
938 var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;
939 _ = try preadAtLeast(file, &hdr_buf, 0, hdr_buf.len);
940 const hdr32: *elf.Elf32_Ehdr = @ptrCast(&hdr_buf);
941 const hdr64: *elf.Elf64_Ehdr = @ptrCast(&hdr_buf);
942 if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
943 const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI.DATA]) {
944 elf.ELFDATA2LSB => .little,
945 elf.ELFDATA2MSB => .big,
946 else => return error.InvalidElfEndian,
873fn glibcVerFromSoFile(file_reader: *Io.File.Reader) !std.SemanticVersion {
874 const header = try elf.Header.read(&file_reader.interface);
875 const str_section_off = header.shoff + @as(u64, header.shentsize) * @as(u64, header.shstrndx);
876 try file_reader.seekTo(str_section_off);
877 const shstr = try elf.takeSectionHeader(&file_reader.interface, header.is_64, header.endian);
878 var strtab_buf: [4096]u8 = undefined;
879 const shstrtab = strtab_buf[0..@min(shstr.sh_size, strtab_buf.len)];
880 try file_reader.seekTo(shstr.sh_offset);
881 try file_reader.interface.readSliceAll(shstrtab);
882 const dynstr: struct { offset: u64, size: u64 } = find_dyn_str: {
883 var it = header.iterateSectionHeaders(file_reader);
884 while (try it.next()) |shdr| {
885 const end = mem.findScalarPos(u8, shstrtab, shdr.sh_name, 0) orelse continue;
886 const sh_name = shstrtab[shdr.sh_name..end :0];
887 if (mem.eql(u8, sh_name, ".dynstr")) break :find_dyn_str .{
888 .offset = shdr.sh_offset,
889 .size = shdr.sh_size,
890 };
891 } else return error.InvalidGnuLibCVersion;
947892 };
948 const need_bswap = elf_endian != native_endian;
949 if (hdr32.e_ident[elf.EI.VERSION] != 1) return error.InvalidElfVersion;
950
951 const is_64 = switch (hdr32.e_ident[elf.EI.CLASS]) {
952 elf.ELFCLASS32 => false,
953 elf.ELFCLASS64 => true,
954 else => return error.InvalidElfClass,
955 };
956 const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);
957 var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);
958 const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);
959 const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);
960 var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;
961 if (sh_buf.len < shentsize) return error.InvalidElfFile;
962
963 _ = try preadAtLeast(file, &sh_buf, str_section_off, shentsize);
964 const shstr32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf));
965 const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf));
966 const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);
967 const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);
968 var strtab_buf: [4096:0]u8 = undefined;
969 const shstrtab_len = @min(shstrtab_size, strtab_buf.len);
970 const shstrtab_read_len = try preadAtLeast(file, &strtab_buf, shstrtab_off, shstrtab_len);
971 const shstrtab = strtab_buf[0..shstrtab_read_len];
972 const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum);
973 var sh_i: u16 = 0;
974 const dynstr: struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) {
975 // Reserve some bytes so that we can deref the 64-bit struct fields
976 // even when the ELF file is 32-bits.
977 const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr);
978 const sh_read_byte_len = try preadAtLeast(
979 file,
980 sh_buf[0 .. sh_buf.len - sh_reserve],
981 shoff,
982 shentsize,
983 );
984 var sh_buf_i: usize = 0;
985 while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({
986 sh_i += 1;
987 shoff += shentsize;
988 sh_buf_i += shentsize;
989 }) {
990 const sh32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
991 const sh64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf[sh_buf_i]));
992 const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name);
993 const sh_name = mem.sliceTo(shstrtab[sh_name_off..], 0);
994 if (mem.eql(u8, sh_name, ".dynstr")) {
995 break :find_dyn_str .{
996 .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset),
997 .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size),
998 };
999 }
1000 }
1001 } else return error.InvalidGnuLibCVersion;
1002893
1003894 // Here we loop over all the strings in the dynstr string table, assuming that any
1004895 // strings that start with "GLIBC_2." indicate the existence of such a glibc version,
1005896 // and furthermore, that the system-installed glibc is at minimum that version.
1006
1007 // Empirically, glibc 2.34 libc.so .dynstr section is 32441 bytes on my system.
1008 // Here I use double this value plus some headroom. This makes it only need
1009 // a single read syscall here.
1010 var buf: [80000]u8 = undefined;
1011 if (buf.len < dynstr.size) return error.InvalidGnuLibCVersion;
1012
1013 const dynstr_size: usize = @intCast(dynstr.size);
1014 const dynstr_bytes = buf[0..dynstr_size];
1015 _ = try preadAtLeast(file, dynstr_bytes, dynstr.offset, dynstr_bytes.len);
1016 var it = mem.splitScalar(u8, dynstr_bytes, 0);
1017897 var max_ver: std.SemanticVersion = .{ .major = 2, .minor = 2, .patch = 5 };
1018 while (it.next()) |s| {
1019 if (mem.startsWith(u8, s, "GLIBC_2.")) {
1020 const chopped = s["GLIBC_".len..];
1021 const ver = Target.Query.parseVersion(chopped) catch |err| switch (err) {
1022 error.Overflow => return error.InvalidGnuLibCVersion,
1023 error.InvalidVersion => return error.InvalidGnuLibCVersion,
1024 };
1025 switch (ver.order(max_ver)) {
1026 .gt => max_ver = ver,
1027 .lt, .eq => continue,
898 var offset: u64 = 0;
899 try file_reader.seekTo(dynstr.offset);
900 while (offset < dynstr.size) {
901 if (file_reader.interface.takeSentinel(0)) |s| {
902 if (mem.startsWith(u8, s, "GLIBC_2.")) {
903 const chopped = s["GLIBC_".len..];
904 const ver = Target.Query.parseVersion(chopped) catch |err| switch (err) {
905 error.Overflow => return error.InvalidGnuLibCVersion,
906 error.InvalidVersion => return error.InvalidGnuLibCVersion,
907 };
908 switch (ver.order(max_ver)) {
909 .gt => max_ver = ver,
910 .lt, .eq => continue,
911 }
1028912 }
913 offset += s.len + 1;
914 } else |err| switch (err) {
915 error.EndOfStream, error.StreamTooLong => break,
916 error.ReadFailed => |e| return e,
1029917 }
1030918 }
919
1031920 return max_ver;
1032921}
1033922
......@@ -1044,11 +933,7 @@ fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion {
1044933/// answer to these questions, or if there is a shebang line, then it chases the referenced
1045934/// file recursively. If that does not provide the answer, then the function falls back to
1046935/// defaults.
1047fn detectAbiAndDynamicLinker(
1048 cpu: Target.Cpu,
1049 os: Target.Os,
1050 query: Target.Query,
1051) DetectError!Target {
936fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Target.Query) !Target {
1052937 const native_target_has_ld = comptime Target.DynamicLinker.kind(builtin.os.tag) != .none;
1053938 const is_linux = builtin.target.os.tag == .linux;
1054939 const is_illumos = builtin.target.os.tag == .illumos;
......@@ -1111,49 +996,49 @@ fn detectAbiAndDynamicLinker(
1111996
1112997 const ld_info_list = ld_info_list_buffer[0..ld_info_list_len];
1113998
999 var file_reader: Io.File.Reader = undefined;
1000 // According to `man 2 execve`:
1001 //
1002 // The kernel imposes a maximum length on the text
1003 // that follows the "#!" characters at the start of a script;
1004 // characters beyond the limit are ignored.
1005 // Before Linux 5.1, the limit is 127 characters.
1006 // Since Linux 5.1, the limit is 255 characters.
1007 //
1008 // Tests show that bash and zsh consider 255 as total limit,
1009 // *including* "#!" characters and ignoring newline.
1010 // For safety, we set max length as 255 + \n (1).
1011 const max_shebang_line_size = 256;
1012 var file_reader_buffer: [4096]u8 = undefined;
1013 comptime assert(file_reader_buffer.len >= max_shebang_line_size);
1014
11141015 // Best case scenario: the executable is dynamically linked, and we can iterate
11151016 // over our own shared objects and find a dynamic linker.
1116 const elf_file = elf_file: {
1117 // This block looks for a shebang line in /usr/bin/env,
1118 // if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead,
1119 // doing the same logic recursively in case it finds another shebang line.
1017 const header = elf_file: {
1018 // This block looks for a shebang line in "/usr/bin/env". If it finds
1019 // one, then instead of using "/usr/bin/env" as the ELF file to examine,
1020 // it uses the file it references instead, doing the same logic
1021 // recursively in case it finds another shebang line.
11201022
11211023 var file_name: []const u8 = switch (os.tag) {
1122 // Since /usr/bin/env is hard-coded into the shebang line of many portable scripts, it's a
1123 // reasonably reliable path to start with.
1024 // Since /usr/bin/env is hard-coded into the shebang line of many
1025 // portable scripts, it's a reasonably reliable path to start with.
11241026 else => "/usr/bin/env",
11251027 // Haiku does not have a /usr root directory.
11261028 .haiku => "/bin/env",
11271029 };
11281030
1129 // According to `man 2 execve`:
1130 //
1131 // The kernel imposes a maximum length on the text
1132 // that follows the "#!" characters at the start of a script;
1133 // characters beyond the limit are ignored.
1134 // Before Linux 5.1, the limit is 127 characters.
1135 // Since Linux 5.1, the limit is 255 characters.
1136 //
1137 // Tests show that bash and zsh consider 255 as total limit,
1138 // *including* "#!" characters and ignoring newline.
1139 // For safety, we set max length as 255 + \n (1).
1140 var buffer: [255 + 1]u8 = undefined;
11411031 while (true) {
1142 // Interpreter path can be relative on Linux, but
1143 // for simplicity we are asserting it is an absolute path.
11441032 const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) {
1145 error.NoSpaceLeft => unreachable,
1146 error.NameTooLong => unreachable,
1147 error.PathAlreadyExists => unreachable,
1148 error.SharingViolation => unreachable,
1149 error.InvalidUtf8 => unreachable, // WASI only
1150 error.InvalidWtf8 => unreachable, // Windows only
1151 error.BadPathName => unreachable,
1152 error.PipeBusy => unreachable,
1153 error.FileLocksNotSupported => unreachable,
1154 error.WouldBlock => unreachable,
1155 error.FileBusy => unreachable, // opened without write permissions
1156 error.AntivirusInterference => unreachable, // Windows-only error
1033 error.NoSpaceLeft => return error.Unexpected,
1034 error.NameTooLong => return error.Unexpected,
1035 error.PathAlreadyExists => return error.Unexpected,
1036 error.SharingViolation => return error.Unexpected,
1037 error.BadPathName => return error.Unexpected,
1038 error.PipeBusy => return error.Unexpected,
1039 error.FileLocksNotSupported => return error.Unexpected,
1040 error.FileBusy => return error.Unexpected, // opened without write permissions
1041 error.AntivirusInterference => return error.Unexpected, // Windows-only error
11571042
11581043 error.IsDir,
11591044 error.NotDir,
......@@ -1164,87 +1049,71 @@ fn detectAbiAndDynamicLinker(
11641049 error.NetworkNotFound,
11651050 error.FileTooBig,
11661051 error.Unexpected,
1167 => |e| {
1168 std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.", .{@errorName(e)});
1169 return defaultAbiAndDynamicLinker(cpu, os, query);
1170 },
1052 => return error.UnableToOpenElfFile,
11711053
11721054 else => |e| return e,
11731055 };
11741056 var is_elf_file = false;
1175 defer if (is_elf_file == false) file.close();
1176
1177 // Shortest working interpreter path is "#!/i" (4)
1178 // (interpreter is "/i", assuming all paths are absolute, like in above comment).
1179 // ELF magic number length is also 4.
1180 //
1181 // If file is shorter than that, it is definitely not ELF file
1182 // nor file with "shebang" line.
1183 const min_len: usize = 4;
1184
1185 const len = preadAtLeast(file, &buffer, 0, min_len) catch |err| switch (err) {
1186 error.UnexpectedEndOfFile,
1187 error.UnableToReadElfFile,
1188 error.ProcessNotFound,
1189 => return defaultAbiAndDynamicLinker(cpu, os, query),
1057 defer if (!is_elf_file) file.close();
1058
1059 file_reader = .initAdapted(file, io, &file_reader_buffer);
1060 file_name = undefined; // it aliases file_reader_buffer
1061
1062 const header = elf.Header.read(&file_reader.interface) catch |hdr_err| switch (hdr_err) {
1063 error.EndOfStream,
1064 error.InvalidElfMagic,
1065 => {
1066 const shebang_line = file_reader.interface.takeSentinel('\n') catch |err| switch (err) {
1067 error.ReadFailed => return file_reader.err.?,
1068 // It's neither an ELF file nor file with shebang line.
1069 error.EndOfStream, error.StreamTooLong => return error.UnhelpfulFile,
1070 };
1071 if (!mem.startsWith(u8, shebang_line, "#!")) return error.UnhelpfulFile;
1072 // We detected shebang, now parse entire line.
1073
1074 // Trim leading "#!", spaces and tabs.
1075 const trimmed_line = mem.trimStart(u8, shebang_line[2..], &.{ ' ', '\t' });
1076
1077 // This line can have:
1078 // * Interpreter path only,
1079 // * Interpreter path and arguments, all separated by space, tab or NUL character.
1080 // And optionally newline at the end.
1081 const path_maybe_args = mem.trimEnd(u8, trimmed_line, "\n");
1082
1083 // Separate path and args.
1084 const path_end = mem.indexOfAny(u8, path_maybe_args, &.{ ' ', '\t', 0 }) orelse path_maybe_args.len;
1085 const unvalidated_path = path_maybe_args[0..path_end];
1086 file_name = if (fs.path.isAbsolute(unvalidated_path)) unvalidated_path else return error.RelativeShebang;
1087 continue;
1088 },
11901089
1191 else => |e| return e,
1090 error.InvalidElfVersion,
1091 error.InvalidElfClass,
1092 error.InvalidElfEndian,
1093 => return error.InvalidElfFile,
1094
1095 error.ReadFailed => return file_reader.err.?,
11921096 };
1193 const content = buffer[0..len];
1194
1195 if (mem.eql(u8, content[0..4], std.elf.MAGIC)) {
1196 // It is very likely ELF file!
1197 is_elf_file = true;
1198 break :elf_file file;
1199 } else if (mem.eql(u8, content[0..2], "#!")) {
1200 // We detected shebang, now parse entire line.
1201
1202 // Trim leading "#!", spaces and tabs.
1203 const trimmed_line = mem.trimStart(u8, content[2..], &.{ ' ', '\t' });
1204
1205 // This line can have:
1206 // * Interpreter path only,
1207 // * Interpreter path and arguments, all separated by space, tab or NUL character.
1208 // And optionally newline at the end.
1209 const path_maybe_args = mem.trimEnd(u8, trimmed_line, "\n");
1210
1211 // Separate path and args.
1212 const path_end = mem.indexOfAny(u8, path_maybe_args, &.{ ' ', '\t', 0 }) orelse path_maybe_args.len;
1213
1214 file_name = path_maybe_args[0..path_end];
1215 continue;
1216 } else {
1217 // Not a ELF file, not a shell script with "shebang line", invalid duck.
1218 return defaultAbiAndDynamicLinker(cpu, os, query);
1219 }
1097 is_elf_file = true;
1098 break :elf_file header;
12201099 }
12211100 };
1222 defer elf_file.close();
1101 defer file_reader.file.close(io);
12231102
1224 // TODO: inline this function and combine the buffer we already read above to find
1225 // the possible shebang line with the buffer we use for the ELF header.
1226 return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, query) catch |err| switch (err) {
1103 return abiAndDynamicLinkerFromFile(&file_reader, &header, cpu, os, ld_info_list, query) catch |err| switch (err) {
12271104 error.FileSystem,
12281105 error.SystemResources,
12291106 error.SymLinkLoop,
12301107 error.ProcessFdQuotaExceeded,
12311108 error.SystemFdQuotaExceeded,
12321109 error.ProcessNotFound,
1110 error.Canceled,
12331111 => |e| return e,
12341112
1235 error.UnableToReadElfFile,
1236 error.InvalidElfClass,
1237 error.InvalidElfVersion,
1238 error.InvalidElfEndian,
1239 error.InvalidElfFile,
1240 error.InvalidElfMagic,
1241 error.Unexpected,
1242 error.UnexpectedEndOfFile,
1243 error.NameTooLong,
1244 error.StaticElfFile,
1245 // Finally, we fall back on the standard path.
1246 => |e| {
1247 std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.", .{@errorName(e)});
1113 error.ReadFailed => return file_reader.err.?,
1114
1115 else => |e| {
1116 std.log.warn("encountered {t}; falling back to default ABI and dynamic linker", .{e});
12481117 return defaultAbiAndDynamicLinker(cpu, os, query);
12491118 },
12501119 };
......@@ -1269,59 +1138,6 @@ const LdInfo = struct {
12691138 abi: Target.Abi,
12701139};
12711140
1272fn preadAtLeast(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize {
1273 var i: usize = 0;
1274 while (i < min_read_len) {
1275 const len = file.pread(buf[i..], offset + i) catch |err| switch (err) {
1276 error.OperationAborted => unreachable, // Windows-only
1277 error.WouldBlock => unreachable, // Did not request blocking mode
1278 error.Canceled => unreachable, // timerfd is unseekable
1279 error.NotOpenForReading => unreachable,
1280 error.SystemResources => return error.SystemResources,
1281 error.IsDir => return error.UnableToReadElfFile,
1282 error.BrokenPipe => return error.UnableToReadElfFile,
1283 error.Unseekable => return error.UnableToReadElfFile,
1284 error.ConnectionResetByPeer => return error.UnableToReadElfFile,
1285 error.ConnectionTimedOut => return error.UnableToReadElfFile,
1286 error.SocketNotConnected => return error.UnableToReadElfFile,
1287 error.Unexpected => return error.Unexpected,
1288 error.InputOutput => return error.FileSystem,
1289 error.AccessDenied => return error.Unexpected,
1290 error.ProcessNotFound => return error.ProcessNotFound,
1291 error.LockViolation => return error.UnableToReadElfFile,
1292 };
1293 if (len == 0) return error.UnexpectedEndOfFile;
1294 i += len;
1295 }
1296 return i;
1297}
1298
1299fn elfInt(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) {
1300 if (is_64) {
1301 if (need_bswap) {
1302 return @byteSwap(int_64);
1303 } else {
1304 return int_64;
1305 }
1306 } else {
1307 if (need_bswap) {
1308 return @byteSwap(int_32);
1309 } else {
1310 return int_32;
1311 }
1312 }
1313}
1314
1315const builtin = @import("builtin");
1316const std = @import("../std.zig");
1317const mem = std.mem;
1318const elf = std.elf;
1319const fs = std.fs;
1320const assert = std.debug.assert;
1321const Target = std.Target;
1322const native_endian = builtin.cpu.arch.endian();
1323const posix = std.posix;
1324
13251141test {
13261142 _ = NativePaths;
13271143
lib/std/zig/system/linux.zig+7-5
......@@ -1,5 +1,7 @@
1const std = @import("std");
21const builtin = @import("builtin");
2
3const std = @import("std");
4const Io = std.Io;
35const mem = std.mem;
46const fs = std.fs;
57const fmt = std.fmt;
......@@ -344,7 +346,7 @@ fn testParser(
344346 expected_model: *const Target.Cpu.Model,
345347 input: []const u8,
346348) !void {
347 var r: std.Io.Reader = .fixed(input);
349 var r: Io.Reader = .fixed(input);
348350 const result = try parser.parse(arch, &r);
349351 try testing.expectEqual(expected_model, result.?.model);
350352 try testing.expect(expected_model.features.eql(result.?.features));
......@@ -357,7 +359,7 @@ fn testParser(
357359// When all the lines have been analyzed the finalize method is called.
358360fn CpuinfoParser(comptime impl: anytype) type {
359361 return struct {
360 fn parse(arch: Target.Cpu.Arch, reader: *std.Io.Reader) !?Target.Cpu {
362 fn parse(arch: Target.Cpu.Arch, reader: *Io.Reader) !?Target.Cpu {
361363 var obj: impl = .{};
362364 while (try reader.takeDelimiter('\n')) |line| {
363365 const colon_pos = mem.indexOfScalar(u8, line, ':') orelse continue;
......@@ -376,14 +378,14 @@ inline fn getAArch64CpuFeature(comptime feat_reg: []const u8) u64 {
376378 );
377379}
378380
379pub fn detectNativeCpuAndFeatures() ?Target.Cpu {
381pub fn detectNativeCpuAndFeatures(io: Io) ?Target.Cpu {
380382 var file = fs.openFileAbsolute("/proc/cpuinfo", .{}) catch |err| switch (err) {
381383 else => return null,
382384 };
383385 defer file.close();
384386
385387 var buffer: [4096]u8 = undefined; // "flags" lines can get pretty long.
386 var file_reader = file.reader(&buffer);
388 var file_reader = file.reader(io, &buffer);
387389
388390 const current_arch = builtin.cpu.arch;
389391 switch (current_arch) {
src/Builtin.zig+1-1
......@@ -360,7 +360,7 @@ pub fn updateFileOnDisk(file: *File, comp: *Compilation) !void {
360360 file.stat = .{
361361 .size = file.source.?.len,
362362 .inode = 0, // dummy value
363 .mtime = 0, // dummy value
363 .mtime = .zero, // dummy value
364364 };
365365}
366366
src/Compilation.zig+41-28
......@@ -1,7 +1,9 @@
11const Compilation = @This();
2const builtin = @import("builtin");
23
34const std = @import("std");
4const builtin = @import("builtin");
5const Io = std.Io;
6const Writer = std.Io.Writer;
57const fs = std.fs;
68const mem = std.mem;
79const Allocator = std.mem.Allocator;
......@@ -12,7 +14,6 @@ const ThreadPool = std.Thread.Pool;
1214const WaitGroup = std.Thread.WaitGroup;
1315const ErrorBundle = std.zig.ErrorBundle;
1416const fatal = std.process.fatal;
15const Writer = std.Io.Writer;
1617
1718const Value = @import("Value.zig");
1819const Type = @import("Type.zig");
......@@ -54,6 +55,7 @@ gpa: Allocator,
5455/// Not thread-safe - lock `mutex` if potentially accessing from multiple
5556/// threads at once.
5657arena: Allocator,
58io: Io,
5759/// Not every Compilation compiles .zig code! For example you could do `zig build-exe foo.o`.
5860zcu: ?*Zcu,
5961/// Contains different state depending on the `CacheMode` used by this `Compilation`.
......@@ -1076,21 +1078,22 @@ pub const CObject = struct {
10761078 diag.* = undefined;
10771079 }
10781080
1079 pub fn count(diag: Diag) u32 {
1081 pub fn count(diag: *const Diag) u32 {
10801082 var total: u32 = 1;
10811083 for (diag.sub_diags) |sub_diag| total += sub_diag.count();
10821084 return total;
10831085 }
10841086
1085 pub fn addToErrorBundle(diag: Diag, eb: *ErrorBundle.Wip, bundle: Bundle, note: *u32) !void {
1086 const err_msg = try eb.addErrorMessage(try diag.toErrorMessage(eb, bundle, 0));
1087 pub fn addToErrorBundle(diag: *const Diag, io: Io, eb: *ErrorBundle.Wip, bundle: Bundle, note: *u32) !void {
1088 const err_msg = try eb.addErrorMessage(try diag.toErrorMessage(io, eb, bundle, 0));
10871089 eb.extra.items[note.*] = @intFromEnum(err_msg);
10881090 note.* += 1;
1089 for (diag.sub_diags) |sub_diag| try sub_diag.addToErrorBundle(eb, bundle, note);
1091 for (diag.sub_diags) |sub_diag| try sub_diag.addToErrorBundle(io, eb, bundle, note);
10901092 }
10911093
10921094 pub fn toErrorMessage(
1093 diag: Diag,
1095 diag: *const Diag,
1096 io: Io,
10941097 eb: *ErrorBundle.Wip,
10951098 bundle: Bundle,
10961099 notes_len: u32,
......@@ -1117,7 +1120,7 @@ pub const CObject = struct {
11171120 const file = fs.cwd().openFile(file_name, .{}) catch break :source_line 0;
11181121 defer file.close();
11191122 var buffer: [1024]u8 = undefined;
1120 var file_reader = file.reader(&buffer);
1123 var file_reader = file.reader(io, &buffer);
11211124 file_reader.seekTo(diag.src_loc.offset + 1 - diag.src_loc.column) catch break :source_line 0;
11221125 var aw: Writer.Allocating = .init(eb.gpa);
11231126 defer aw.deinit();
......@@ -1155,7 +1158,7 @@ pub const CObject = struct {
11551158 gpa.destroy(bundle);
11561159 }
11571160
1158 pub fn parse(gpa: Allocator, path: []const u8) !*Bundle {
1161 pub fn parse(gpa: Allocator, io: Io, path: []const u8) !*Bundle {
11591162 const BlockId = enum(u32) {
11601163 Meta = 8,
11611164 Diag,
......@@ -1191,7 +1194,7 @@ pub const CObject = struct {
11911194 var buffer: [1024]u8 = undefined;
11921195 const file = try fs.cwd().openFile(path, .{});
11931196 defer file.close();
1194 var file_reader = file.reader(&buffer);
1197 var file_reader = file.reader(io, &buffer);
11951198 var bc = std.zig.llvm.BitcodeReader.init(gpa, .{ .reader = &file_reader.interface });
11961199 defer bc.deinit();
11971200
......@@ -1305,14 +1308,14 @@ pub const CObject = struct {
13051308 return bundle;
13061309 }
13071310
1308 pub fn addToErrorBundle(bundle: Bundle, eb: *ErrorBundle.Wip) !void {
1311 pub fn addToErrorBundle(bundle: Bundle, io: Io, eb: *ErrorBundle.Wip) !void {
13091312 for (bundle.diags) |diag| {
13101313 const notes_len = diag.count() - 1;
1311 try eb.addRootErrorMessage(try diag.toErrorMessage(eb, bundle, notes_len));
1314 try eb.addRootErrorMessage(try diag.toErrorMessage(io, eb, bundle, notes_len));
13121315 if (notes_len > 0) {
13131316 var note = try eb.reserveNotes(notes_len);
13141317 for (diag.sub_diags) |sub_diag|
1315 try sub_diag.addToErrorBundle(eb, bundle, &note);
1318 try sub_diag.addToErrorBundle(io, eb, bundle, &note);
13161319 }
13171320 }
13181321 }
......@@ -1904,7 +1907,7 @@ pub const CreateDiagnostic = union(enum) {
19041907 return error.CreateFail;
19051908 }
19061909};
1907pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options: CreateOptions) error{
1910pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, options: CreateOptions) error{
19081911 OutOfMemory,
19091912 Unexpected,
19101913 CurrentWorkingDirectoryUnlinked,
......@@ -2112,6 +2115,7 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options
21122115 const cache = try arena.create(Cache);
21132116 cache.* = .{
21142117 .gpa = gpa,
2118 .io = io,
21152119 .manifest_dir = options.dirs.local_cache.handle.makeOpenPath("h", .{}) catch |err| {
21162120 return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = "h", .err = err } });
21172121 },
......@@ -2230,6 +2234,7 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options
22302234 comp.* = .{
22312235 .gpa = gpa,
22322236 .arena = arena,
2237 .io = io,
22332238 .zcu = opt_zcu,
22342239 .cache_use = undefined, // populated below
22352240 .bin_file = null, // populated below if necessary
......@@ -3917,13 +3922,14 @@ fn addBuf(list: *std.array_list.Managed([]const u8), buf: []const u8) void {
39173922/// This function is temporally single-threaded.
39183923pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle {
39193924 const gpa = comp.gpa;
3925 const io = comp.io;
39203926
39213927 var bundle: ErrorBundle.Wip = undefined;
39223928 try bundle.init(gpa);
39233929 defer bundle.deinit();
39243930
39253931 for (comp.failed_c_objects.values()) |diag_bundle| {
3926 try diag_bundle.addToErrorBundle(&bundle);
3932 try diag_bundle.addToErrorBundle(io, &bundle);
39273933 }
39283934
39293935 for (comp.failed_win32_resources.values()) |error_bundle| {
......@@ -5308,6 +5314,7 @@ fn docsCopyModule(
53085314 name: []const u8,
53095315 tar_file_writer: *fs.File.Writer,
53105316) !void {
5317 const io = comp.io;
53115318 const root = module.root;
53125319 var mod_dir = d: {
53135320 const root_dir, const sub_path = root.openInfo(comp.dirs);
......@@ -5341,9 +5348,9 @@ fn docsCopyModule(
53415348 };
53425349 defer file.close();
53435350 const stat = try file.stat();
5344 var file_reader: fs.File.Reader = .initSize(file, &buffer, stat.size);
5351 var file_reader: fs.File.Reader = .initSize(file.adaptToNewApi(), io, &buffer, stat.size);
53455352
5346 archiver.writeFile(entry.path, &file_reader, stat.mtime) catch |err| {
5353 archiver.writeFileTimestamp(entry.path, &file_reader, stat.mtime) catch |err| {
53475354 return comp.lockAndSetMiscFailure(.docs_copy, "unable to archive {f}{s}: {t}", .{
53485355 root.fmt(comp), entry.path, err,
53495356 });
......@@ -5363,6 +5370,7 @@ fn workerDocsWasm(comp: *Compilation, parent_prog_node: std.Progress.Node) void
53635370
53645371fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubUpdateError!void {
53655372 const gpa = comp.gpa;
5373 const io = comp.io;
53665374
53675375 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
53685376 defer arena_allocator.deinit();
......@@ -5371,7 +5379,7 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU
53715379 const optimize_mode = std.builtin.OptimizeMode.ReleaseSmall;
53725380 const output_mode = std.builtin.OutputMode.Exe;
53735381 const resolved_target: Package.Module.ResolvedTarget = .{
5374 .result = std.zig.system.resolveTargetQuery(.{
5382 .result = std.zig.system.resolveTargetQuery(io, .{
53755383 .cpu_arch = .wasm32,
53765384 .os_tag = .freestanding,
53775385 .cpu_features_add = std.Target.wasm.featureSet(&.{
......@@ -5447,7 +5455,7 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU
54475455 try root_mod.deps.put(arena, "Walk", walk_mod);
54485456
54495457 var sub_create_diag: CreateDiagnostic = undefined;
5450 const sub_compilation = Compilation.create(gpa, arena, &sub_create_diag, .{
5458 const sub_compilation = Compilation.create(gpa, arena, io, &sub_create_diag, .{
54515459 .dirs = dirs,
54525460 .self_exe_path = comp.self_exe_path,
54535461 .config = config,
......@@ -5665,6 +5673,8 @@ pub fn translateC(
56655673) !CImportResult {
56665674 dev.check(.translate_c_command);
56675675
5676 const gpa = comp.gpa;
5677 const io = comp.io;
56685678 const tmp_basename = std.fmt.hex(std.crypto.random.int(u64));
56695679 const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ tmp_basename;
56705680 const cache_dir = comp.dirs.local_cache.handle;
......@@ -5704,9 +5714,9 @@ pub fn translateC(
57045714
57055715 const mcpu = mcpu: {
57065716 var buf: std.ArrayListUnmanaged(u8) = .empty;
5707 defer buf.deinit(comp.gpa);
5717 defer buf.deinit(gpa);
57085718
5709 try buf.print(comp.gpa, "-mcpu={s}", .{target.cpu.model.name});
5719 try buf.print(gpa, "-mcpu={s}", .{target.cpu.model.name});
57105720
57115721 // TODO better serialization https://github.com/ziglang/zig/issues/4584
57125722 const all_features_list = target.cpu.arch.allFeaturesList();
......@@ -5716,7 +5726,7 @@ pub fn translateC(
57165726 const is_enabled = target.cpu.features.isEnabled(index);
57175727
57185728 const plus_or_minus = "-+"[@intFromBool(is_enabled)];
5719 try buf.print(comp.gpa, "{c}{s}", .{ plus_or_minus, feature.name });
5729 try buf.print(gpa, "{c}{s}", .{ plus_or_minus, feature.name });
57205730 }
57215731 break :mcpu try buf.toOwnedSlice(arena);
57225732 };
......@@ -5729,7 +5739,7 @@ pub fn translateC(
57295739 }
57305740
57315741 var stdout: []u8 = undefined;
5732 try @import("main.zig").translateC(comp.gpa, arena, argv.items, prog_node, &stdout);
5742 try @import("main.zig").translateC(gpa, arena, io, argv.items, prog_node, &stdout);
57335743
57345744 if (out_dep_path) |dep_file_path| add_deps: {
57355745 if (comp.verbose_cimport) log.info("processing dep file at {s}", .{dep_file_path});
......@@ -5765,7 +5775,7 @@ pub fn translateC(
57655775 fatal("unable to read {}-byte translate-c message body: {s}", .{ header.bytes_len, @errorName(err) });
57665776 switch (header.tag) {
57675777 .error_bundle => {
5768 const error_bundle = try std.zig.Server.allocErrorBundle(comp.gpa, body);
5778 const error_bundle = try std.zig.Server.allocErrorBundle(gpa, body);
57695779 return .{
57705780 .digest = undefined,
57715781 .cache_hit = false,
......@@ -6152,6 +6162,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
61526162 log.debug("updating C object: {s}", .{c_object.src.src_path});
61536163
61546164 const gpa = comp.gpa;
6165 const io = comp.io;
61556166
61566167 if (c_object.clearStatus(gpa)) {
61576168 // There was previous failure.
......@@ -6351,7 +6362,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
63516362
63526363 try child.spawn();
63536364
6354 var stderr_reader = child.stderr.?.readerStreaming(&.{});
6365 var stderr_reader = child.stderr.?.readerStreaming(io, &.{});
63556366 const stderr = try stderr_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32)));
63566367
63576368 const term = child.wait() catch |err| {
......@@ -6360,7 +6371,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
63606371
63616372 switch (term) {
63626373 .Exited => |code| if (code != 0) if (out_diag_path) |diag_file_path| {
6363 const bundle = CObject.Diag.Bundle.parse(gpa, diag_file_path) catch |err| {
6374 const bundle = CObject.Diag.Bundle.parse(gpa, io, diag_file_path) catch |err| {
63646375 log.err("{}: failed to parse clang diagnostics: {s}", .{ err, stderr });
63656376 return comp.failCObj(c_object, "clang exited with code {d}", .{code});
63666377 };
......@@ -7805,6 +7816,7 @@ fn buildOutputFromZig(
78057816 defer tracy_trace.end();
78067817
78077818 const gpa = comp.gpa;
7819 const io = comp.io;
78087820 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
78097821 defer arena_allocator.deinit();
78107822 const arena = arena_allocator.allocator();
......@@ -7878,7 +7890,7 @@ fn buildOutputFromZig(
78787890 };
78797891
78807892 var sub_create_diag: CreateDiagnostic = undefined;
7881 const sub_compilation = Compilation.create(gpa, arena, &sub_create_diag, .{
7893 const sub_compilation = Compilation.create(gpa, arena, io, &sub_create_diag, .{
78827894 .dirs = comp.dirs.withoutLocalCache(),
78837895 .cache_mode = .whole,
78847896 .parent_whole_cache = parent_whole_cache,
......@@ -7946,6 +7958,7 @@ pub fn build_crt_file(
79467958 defer tracy_trace.end();
79477959
79487960 const gpa = comp.gpa;
7961 const io = comp.io;
79497962 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
79507963 defer arena_allocator.deinit();
79517964 const arena = arena_allocator.allocator();
......@@ -8014,7 +8027,7 @@ pub fn build_crt_file(
80148027 }
80158028
80168029 var sub_create_diag: CreateDiagnostic = undefined;
8017 const sub_compilation = Compilation.create(gpa, arena, &sub_create_diag, .{
8030 const sub_compilation = Compilation.create(gpa, arena, io, &sub_create_diag, .{
80188031 .dirs = comp.dirs.withoutLocalCache(),
80198032 .self_exe_path = comp.self_exe_path,
80208033 .cache_mode = .whole,
src/IncrementalDebugServer.zig+13-10
......@@ -44,22 +44,24 @@ pub fn spawn(ids: *IncrementalDebugServer) void {
4444}
4545fn runThread(ids: *IncrementalDebugServer) void {
4646 const gpa = ids.zcu.gpa;
47 const io = ids.zcu.comp.io;
4748
4849 var cmd_buf: [1024]u8 = undefined;
4950 var text_out: std.ArrayListUnmanaged(u8) = .empty;
5051 defer text_out.deinit(gpa);
5152
52 const addr = std.net.Address.parseIp6("::", port) catch unreachable;
53 var server = addr.listen(.{}) catch @panic("IncrementalDebugServer: failed to listen");
54 defer server.deinit();
55 const conn = server.accept() catch @panic("IncrementalDebugServer: failed to accept");
56 defer conn.stream.close();
53 const addr: std.Io.net.IpAddress = .{ .ip6 = .loopback(port) };
54 var server = addr.listen(io, .{}) catch @panic("IncrementalDebugServer: failed to listen");
55 defer server.deinit(io);
56 var stream = server.accept(io) catch @panic("IncrementalDebugServer: failed to accept");
57 defer stream.close(io);
5758
58 var stream_reader = conn.stream.reader(&cmd_buf);
59 var stream_reader = stream.reader(io, &cmd_buf);
60 var stream_writer = stream.writer(io, &.{});
5961
6062 while (ids.running.load(.monotonic)) {
61 conn.stream.writeAll("zig> ") catch @panic("IncrementalDebugServer: failed to write");
62 const untrimmed = stream_reader.interface().takeSentinel('\n') catch |err| switch (err) {
63 stream_writer.interface.writeAll("zig> ") catch @panic("IncrementalDebugServer: failed to write");
64 const untrimmed = stream_reader.interface.takeSentinel('\n') catch |err| switch (err) {
6365 error.EndOfStream => break,
6466 else => @panic("IncrementalDebugServer: failed to read command"),
6567 };
......@@ -72,7 +74,7 @@ fn runThread(ids: *IncrementalDebugServer) void {
7274 text_out.clearRetainingCapacity();
7375 {
7476 if (!ids.mutex.tryLock()) {
75 conn.stream.writeAll("waiting for in-progress update to finish...\n") catch @panic("IncrementalDebugServer: failed to write");
77 stream_writer.interface.writeAll("waiting for in-progress update to finish...\n") catch @panic("IncrementalDebugServer: failed to write");
7678 ids.mutex.lock();
7779 }
7880 defer ids.mutex.unlock();
......@@ -81,7 +83,7 @@ fn runThread(ids: *IncrementalDebugServer) void {
8183 handleCommand(ids.zcu, &allocating.writer, cmd, arg) catch @panic("IncrementalDebugServer: out of memory");
8284 }
8385 text_out.append(gpa, '\n') catch @panic("IncrementalDebugServer: out of memory");
84 conn.stream.writeAll(text_out.items) catch @panic("IncrementalDebugServer: failed to write");
86 stream_writer.interface.writeAll(text_out.items) catch @panic("IncrementalDebugServer: failed to write");
8587 }
8688 std.debug.print("closing incremental debug server\n", .{});
8789}
......@@ -373,6 +375,7 @@ fn printType(ty: Type, zcu: *const Zcu, w: anytype) !void {
373375}
374376
375377const std = @import("std");
378const Io = std.Io;
376379const Allocator = std.mem.Allocator;
377380
378381const Compilation = @import("Compilation.zig");
src/Package/Fetch.zig+40-20
......@@ -26,9 +26,13 @@
2626//!
2727//! All of this must be done with only referring to the state inside this struct
2828//! because this work will be done in a dedicated thread.
29const Fetch = @This();
2930
3031const builtin = @import("builtin");
32const native_os = builtin.os.tag;
33
3134const std = @import("std");
35const Io = std.Io;
3236const fs = std.fs;
3337const assert = std.debug.assert;
3438const ascii = std.ascii;
......@@ -36,14 +40,13 @@ const Allocator = std.mem.Allocator;
3640const Cache = std.Build.Cache;
3741const ThreadPool = std.Thread.Pool;
3842const WaitGroup = std.Thread.WaitGroup;
39const Fetch = @This();
4043const git = @import("Fetch/git.zig");
4144const Package = @import("../Package.zig");
4245const Manifest = Package.Manifest;
4346const ErrorBundle = std.zig.ErrorBundle;
44const native_os = builtin.os.tag;
4547
4648arena: std.heap.ArenaAllocator,
49io: Io,
4750location: Location,
4851location_tok: std.zig.Ast.TokenIndex,
4952hash_tok: std.zig.Ast.OptionalTokenIndex,
......@@ -323,6 +326,7 @@ pub const RunError = error{
323326};
324327
325328pub fn run(f: *Fetch) RunError!void {
329 const io = f.io;
326330 const eb = &f.error_bundle;
327331 const arena = f.arena.allocator();
328332 const gpa = f.arena.child_allocator;
......@@ -389,7 +393,7 @@ pub fn run(f: *Fetch) RunError!void {
389393
390394 const file_err = if (dir_err == error.NotDir) e: {
391395 if (fs.cwd().openFile(path_or_url, .{})) |file| {
392 var resource: Resource = .{ .file = file.reader(&server_header_buffer) };
396 var resource: Resource = .{ .file = file.reader(io, &server_header_buffer) };
393397 return f.runResource(path_or_url, &resource, null);
394398 } else |err| break :e err;
395399 } else dir_err;
......@@ -484,7 +488,8 @@ fn runResource(
484488 resource: *Resource,
485489 remote_hash: ?Package.Hash,
486490) RunError!void {
487 defer resource.deinit();
491 const io = f.io;
492 defer resource.deinit(io);
488493 const arena = f.arena.allocator();
489494 const eb = &f.error_bundle;
490495 const s = fs.path.sep_str;
......@@ -697,6 +702,7 @@ fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void {
697702}
698703
699704fn queueJobsForDeps(f: *Fetch) RunError!void {
705 const io = f.io;
700706 assert(f.job_queue.recursive);
701707
702708 // If the package does not have a build.zig.zon file then there are no dependencies.
......@@ -786,6 +792,7 @@ fn queueJobsForDeps(f: *Fetch) RunError!void {
786792 f.job_queue.all_fetches.appendAssumeCapacity(new_fetch);
787793 }
788794 new_fetch.* = .{
795 .io = io,
789796 .arena = std.heap.ArenaAllocator.init(gpa),
790797 .location = location,
791798 .location_tok = dep.location_tok,
......@@ -897,9 +904,9 @@ const Resource = union(enum) {
897904 decompress_buffer: []u8,
898905 };
899906
900 fn deinit(resource: *Resource) void {
907 fn deinit(resource: *Resource, io: Io) void {
901908 switch (resource.*) {
902 .file => |*file_reader| file_reader.file.close(),
909 .file => |*file_reader| file_reader.file.close(io),
903910 .http_request => |*http_request| http_request.request.deinit(),
904911 .git => |*git_resource| {
905912 git_resource.fetch_stream.deinit();
......@@ -909,7 +916,7 @@ const Resource = union(enum) {
909916 resource.* = undefined;
910917 }
911918
912 fn reader(resource: *Resource) *std.Io.Reader {
919 fn reader(resource: *Resource) *Io.Reader {
913920 return switch (resource.*) {
914921 .file => |*file_reader| return &file_reader.interface,
915922 .http_request => |*http_request| return http_request.response.readerDecompressing(
......@@ -985,6 +992,7 @@ const FileType = enum {
985992const init_resource_buffer_size = git.Packet.max_data_length;
986993
987994fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u8) RunError!void {
995 const io = f.io;
988996 const arena = f.arena.allocator();
989997 const eb = &f.error_bundle;
990998
......@@ -995,7 +1003,7 @@ fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u
9951003 f.parent_package_root, path, err,
9961004 }));
9971005 };
998 resource.* = .{ .file = file.reader(reader_buffer) };
1006 resource.* = .{ .file = file.reader(io, reader_buffer) };
9991007 return;
10001008 }
10011009
......@@ -1242,7 +1250,7 @@ fn unpackResource(
12421250 }
12431251}
12441252
1245fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: *std.Io.Reader) RunError!UnpackResult {
1253fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: *Io.Reader) RunError!UnpackResult {
12461254 const eb = &f.error_bundle;
12471255 const arena = f.arena.allocator();
12481256
......@@ -1273,11 +1281,12 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: *std.Io.Reader) RunError!Un
12731281 return res;
12741282}
12751283
1276fn unzip(f: *Fetch, out_dir: fs.Dir, reader: *std.Io.Reader) error{ ReadFailed, OutOfMemory, FetchFailed }!UnpackResult {
1284fn unzip(f: *Fetch, out_dir: fs.Dir, reader: *Io.Reader) error{ ReadFailed, OutOfMemory, FetchFailed }!UnpackResult {
12771285 // We write the entire contents to a file first because zip files
12781286 // must be processed back to front and they could be too large to
12791287 // load into memory.
12801288
1289 const io = f.io;
12811290 const cache_root = f.job_queue.global_cache;
12821291 const prefix = "tmp/";
12831292 const suffix = ".zip";
......@@ -1319,7 +1328,7 @@ fn unzip(f: *Fetch, out_dir: fs.Dir, reader: *std.Io.Reader) error{ ReadFailed,
13191328 f.location_tok,
13201329 try eb.printString("failed writing temporary zip file: {t}", .{err}),
13211330 );
1322 break :b zip_file_writer.moveToReader();
1331 break :b zip_file_writer.moveToReader(io);
13231332 };
13241333
13251334 var diagnostics: std.zip.Diagnostics = .{ .allocator = f.arena.allocator() };
......@@ -1339,7 +1348,10 @@ fn unzip(f: *Fetch, out_dir: fs.Dir, reader: *std.Io.Reader) error{ ReadFailed,
13391348}
13401349
13411350fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!UnpackResult {
1351 const io = f.io;
13421352 const arena = f.arena.allocator();
1353 // TODO don't try to get a gpa from an arena. expose this dependency higher up
1354 // because the backing of arena could be page allocator
13431355 const gpa = f.arena.child_allocator;
13441356 const object_format: git.Oid.Format = resource.want_oid;
13451357
......@@ -1358,7 +1370,7 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!U
13581370 const fetch_reader = &resource.fetch_stream.reader;
13591371 _ = try fetch_reader.streamRemaining(&pack_file_writer.interface);
13601372 try pack_file_writer.interface.flush();
1361 break :b pack_file_writer.moveToReader();
1373 break :b pack_file_writer.moveToReader(io);
13621374 };
13631375
13641376 var index_file = try pack_dir.createFile("pkg.idx", .{ .read = true });
......@@ -1372,7 +1384,7 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!U
13721384 }
13731385
13741386 {
1375 var index_file_reader = index_file.reader(&index_file_buffer);
1387 var index_file_reader = index_file.reader(io, &index_file_buffer);
13761388 const checkout_prog_node = f.prog_node.start("Checkout", 0);
13771389 defer checkout_prog_node.end();
13781390 var repository: git.Repository = undefined;
......@@ -2029,7 +2041,7 @@ const UnpackResult = struct {
20292041 // output errors to string
20302042 var errors = try fetch.error_bundle.toOwnedBundle("");
20312043 defer errors.deinit(gpa);
2032 var aw: std.Io.Writer.Allocating = .init(gpa);
2044 var aw: Io.Writer.Allocating = .init(gpa);
20332045 defer aw.deinit();
20342046 try errors.renderToWriter(.{ .ttyconf = .no_color }, &aw.writer);
20352047 try std.testing.expectEqualStrings(
......@@ -2057,6 +2069,7 @@ test "tarball with duplicate paths" {
20572069 //
20582070
20592071 const gpa = std.testing.allocator;
2072 const io = std.testing.io;
20602073 var tmp = std.testing.tmpDir(.{});
20612074 defer tmp.cleanup();
20622075
......@@ -2067,7 +2080,7 @@ test "tarball with duplicate paths" {
20672080
20682081 // Run tarball fetch, expect to fail
20692082 var fb: TestFetchBuilder = undefined;
2070 var fetch = try fb.build(gpa, tmp.dir, tarball_path);
2083 var fetch = try fb.build(gpa, io, tmp.dir, tarball_path);
20712084 defer fb.deinit();
20722085 try std.testing.expectError(error.FetchFailed, fetch.run());
20732086
......@@ -2089,6 +2102,7 @@ test "tarball with excluded duplicate paths" {
20892102 //
20902103
20912104 const gpa = std.testing.allocator;
2105 const io = std.testing.io;
20922106 var tmp = std.testing.tmpDir(.{});
20932107 defer tmp.cleanup();
20942108
......@@ -2099,7 +2113,7 @@ test "tarball with excluded duplicate paths" {
20992113
21002114 // Run tarball fetch, should succeed
21012115 var fb: TestFetchBuilder = undefined;
2102 var fetch = try fb.build(gpa, tmp.dir, tarball_path);
2116 var fetch = try fb.build(gpa, io, tmp.dir, tarball_path);
21032117 defer fb.deinit();
21042118 try fetch.run();
21052119
......@@ -2133,6 +2147,8 @@ test "tarball without root folder" {
21332147 //
21342148
21352149 const gpa = std.testing.allocator;
2150 const io = std.testing.io;
2151
21362152 var tmp = std.testing.tmpDir(.{});
21372153 defer tmp.cleanup();
21382154
......@@ -2143,7 +2159,7 @@ test "tarball without root folder" {
21432159
21442160 // Run tarball fetch, should succeed
21452161 var fb: TestFetchBuilder = undefined;
2146 var fetch = try fb.build(gpa, tmp.dir, tarball_path);
2162 var fetch = try fb.build(gpa, io, tmp.dir, tarball_path);
21472163 defer fb.deinit();
21482164 try fetch.run();
21492165
......@@ -2164,6 +2180,8 @@ test "tarball without root folder" {
21642180test "set executable bit based on file content" {
21652181 if (!std.fs.has_executable_bit) return error.SkipZigTest;
21662182 const gpa = std.testing.allocator;
2183 const io = std.testing.io;
2184
21672185 var tmp = std.testing.tmpDir(.{});
21682186 defer tmp.cleanup();
21692187
......@@ -2182,7 +2200,7 @@ test "set executable bit based on file content" {
21822200 // -rwxrwxr-x 17 executables/script
21832201
21842202 var fb: TestFetchBuilder = undefined;
2185 var fetch = try fb.build(gpa, tmp.dir, tarball_path);
2203 var fetch = try fb.build(gpa, io, tmp.dir, tarball_path);
21862204 defer fb.deinit();
21872205
21882206 try fetch.run();
......@@ -2232,13 +2250,14 @@ const TestFetchBuilder = struct {
22322250 fn build(
22332251 self: *TestFetchBuilder,
22342252 allocator: std.mem.Allocator,
2253 io: Io,
22352254 cache_parent_dir: std.fs.Dir,
22362255 path_or_url: []const u8,
22372256 ) !*Fetch {
22382257 const cache_dir = try cache_parent_dir.makeOpenPath("zig-global-cache", .{});
22392258
22402259 try self.thread_pool.init(.{ .allocator = allocator });
2241 self.http_client = .{ .allocator = allocator };
2260 self.http_client = .{ .allocator = allocator, .io = io };
22422261 self.global_cache_directory = .{ .handle = cache_dir, .path = null };
22432262
22442263 self.job_queue = .{
......@@ -2254,6 +2273,7 @@ const TestFetchBuilder = struct {
22542273
22552274 self.fetch = .{
22562275 .arena = std.heap.ArenaAllocator.init(allocator),
2276 .io = io,
22572277 .location = .{ .path_or_url = path_or_url },
22582278 .location_tok = 0,
22592279 .hash_tok = .none,
......@@ -2338,7 +2358,7 @@ const TestFetchBuilder = struct {
23382358 if (notes_len > 0) {
23392359 try std.testing.expectEqual(notes_len, em.notes_len);
23402360 }
2341 var aw: std.Io.Writer.Allocating = .init(std.testing.allocator);
2361 var aw: Io.Writer.Allocating = .init(std.testing.allocator);
23422362 defer aw.deinit();
23432363 try errors.renderToWriter(.{ .ttyconf = .no_color }, &aw.writer);
23442364 try std.testing.expectEqualStrings(msg, aw.written());
src/Package/Fetch/git.zig+37-32
......@@ -5,6 +5,7 @@
55//! a package.
66
77const std = @import("std");
8const Io = std.Io;
89const mem = std.mem;
910const testing = std.testing;
1011const Allocator = mem.Allocator;
......@@ -67,8 +68,8 @@ pub const Oid = union(Format) {
6768 };
6869
6970 const Hashing = union(Format) {
70 sha1: std.Io.Writer.Hashing(Sha1),
71 sha256: std.Io.Writer.Hashing(Sha256),
71 sha1: Io.Writer.Hashing(Sha1),
72 sha256: Io.Writer.Hashing(Sha256),
7273
7374 fn init(oid_format: Format, buffer: []u8) Hashing {
7475 return switch (oid_format) {
......@@ -77,7 +78,7 @@ pub const Oid = union(Format) {
7778 };
7879 }
7980
80 fn writer(h: *@This()) *std.Io.Writer {
81 fn writer(h: *@This()) *Io.Writer {
8182 return switch (h.*) {
8283 inline else => |*inner| &inner.writer,
8384 };
......@@ -100,7 +101,7 @@ pub const Oid = union(Format) {
100101 };
101102 }
102103
103 pub fn readBytes(oid_format: Format, reader: *std.Io.Reader) !Oid {
104 pub fn readBytes(oid_format: Format, reader: *Io.Reader) !Oid {
104105 return switch (oid_format) {
105106 inline else => |tag| @unionInit(Oid, @tagName(tag), (try reader.takeArray(tag.byteLength())).*),
106107 };
......@@ -146,7 +147,7 @@ pub const Oid = union(Format) {
146147 } else error.InvalidOid;
147148 }
148149
149 pub fn format(oid: Oid, writer: *std.Io.Writer) std.Io.Writer.Error!void {
150 pub fn format(oid: Oid, writer: *Io.Writer) Io.Writer.Error!void {
150151 try writer.print("{x}", .{oid.slice()});
151152 }
152153
......@@ -594,7 +595,7 @@ pub const Packet = union(enum) {
594595 pub const max_data_length = 65516;
595596
596597 /// Reads a packet in pkt-line format.
597 fn read(reader: *std.Io.Reader) !Packet {
598 fn read(reader: *Io.Reader) !Packet {
598599 const packet: Packet = try .peek(reader);
599600 switch (packet) {
600601 .data => |data| reader.toss(data.len),
......@@ -605,7 +606,7 @@ pub const Packet = union(enum) {
605606
606607 /// Consumes the header of a pkt-line packet and reads any associated data
607608 /// into the reader's buffer, but does not consume the data.
608 fn peek(reader: *std.Io.Reader) !Packet {
609 fn peek(reader: *Io.Reader) !Packet {
609610 const length = std.fmt.parseUnsigned(u16, try reader.take(4), 16) catch return error.InvalidPacket;
610611 switch (length) {
611612 0 => return .flush,
......@@ -618,7 +619,7 @@ pub const Packet = union(enum) {
618619 }
619620
620621 /// Writes a packet in pkt-line format.
621 fn write(packet: Packet, writer: *std.Io.Writer) !void {
622 fn write(packet: Packet, writer: *Io.Writer) !void {
622623 switch (packet) {
623624 .flush => try writer.writeAll("0000"),
624625 .delimiter => try writer.writeAll("0001"),
......@@ -812,7 +813,7 @@ pub const Session = struct {
812813
813814 const CapabilityIterator = struct {
814815 request: std.http.Client.Request,
815 reader: *std.Io.Reader,
816 reader: *Io.Reader,
816817 decompress: std.http.Decompress,
817818
818819 const Capability = struct {
......@@ -869,7 +870,7 @@ pub const Session = struct {
869870 upload_pack_uri.query = null;
870871 upload_pack_uri.fragment = null;
871872
872 var body: std.Io.Writer = .fixed(options.buffer);
873 var body: Io.Writer = .fixed(options.buffer);
873874 try Packet.write(.{ .data = "command=ls-refs\n" }, &body);
874875 if (session.supports_agent) {
875876 try Packet.write(.{ .data = agent_capability }, &body);
......@@ -918,7 +919,7 @@ pub const Session = struct {
918919 pub const RefIterator = struct {
919920 format: Oid.Format,
920921 request: std.http.Client.Request,
921 reader: *std.Io.Reader,
922 reader: *Io.Reader,
922923 decompress: std.http.Decompress,
923924
924925 pub const Ref = struct {
......@@ -986,7 +987,7 @@ pub const Session = struct {
986987 upload_pack_uri.query = null;
987988 upload_pack_uri.fragment = null;
988989
989 var body: std.Io.Writer = .fixed(response_buffer);
990 var body: Io.Writer = .fixed(response_buffer);
990991 try Packet.write(.{ .data = "command=fetch\n" }, &body);
991992 if (session.supports_agent) {
992993 try Packet.write(.{ .data = agent_capability }, &body);
......@@ -1068,8 +1069,8 @@ pub const Session = struct {
10681069
10691070 pub const FetchStream = struct {
10701071 request: std.http.Client.Request,
1071 input: *std.Io.Reader,
1072 reader: std.Io.Reader,
1072 input: *Io.Reader,
1073 reader: Io.Reader,
10731074 err: ?Error = null,
10741075 remaining_len: usize,
10751076 decompress: std.http.Decompress,
......@@ -1094,7 +1095,7 @@ pub const Session = struct {
10941095 _,
10951096 };
10961097
1097 pub fn stream(r: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize {
1098 pub fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
10981099 const fs: *FetchStream = @alignCast(@fieldParentPtr("reader", r));
10991100 const input = fs.input;
11001101 if (fs.remaining_len == 0) {
......@@ -1139,7 +1140,7 @@ const PackHeader = struct {
11391140 const signature = "PACK";
11401141 const supported_version = 2;
11411142
1142 fn read(reader: *std.Io.Reader) !PackHeader {
1143 fn read(reader: *Io.Reader) !PackHeader {
11431144 const actual_signature = reader.take(4) catch |e| switch (e) {
11441145 error.EndOfStream => return error.InvalidHeader,
11451146 else => |other| return other,
......@@ -1202,7 +1203,7 @@ const EntryHeader = union(Type) {
12021203 };
12031204 }
12041205
1205 fn read(format: Oid.Format, reader: *std.Io.Reader) !EntryHeader {
1206 fn read(format: Oid.Format, reader: *Io.Reader) !EntryHeader {
12061207 const InitialByte = packed struct { len: u4, type: u3, has_next: bool };
12071208 const initial: InitialByte = @bitCast(reader.takeByte() catch |e| switch (e) {
12081209 error.EndOfStream => return error.InvalidFormat,
......@@ -1231,7 +1232,7 @@ const EntryHeader = union(Type) {
12311232 }
12321233};
12331234
1234fn readOffsetVarInt(r: *std.Io.Reader) !u64 {
1235fn readOffsetVarInt(r: *Io.Reader) !u64 {
12351236 const Byte = packed struct { value: u7, has_next: bool };
12361237 var b: Byte = @bitCast(try r.takeByte());
12371238 var value: u64 = b.value;
......@@ -1250,7 +1251,7 @@ const IndexHeader = struct {
12501251 const supported_version = 2;
12511252 const size = 4 + 4 + @sizeOf([256]u32);
12521253
1253 fn read(index_header: *IndexHeader, reader: *std.Io.Reader) !void {
1254 fn read(index_header: *IndexHeader, reader: *Io.Reader) !void {
12541255 const sig = try reader.take(4);
12551256 if (!mem.eql(u8, sig, signature)) return error.InvalidHeader;
12561257 const version = try reader.takeInt(u32, .big);
......@@ -1324,7 +1325,7 @@ pub fn indexPack(
13241325 }
13251326 @memset(fan_out_table[fan_out_index..], count);
13261327
1327 var index_hashed_writer = std.Io.Writer.hashed(&index_writer.interface, Oid.Hasher.init(format), &.{});
1328 var index_hashed_writer = Io.Writer.hashed(&index_writer.interface, Oid.Hasher.init(format), &.{});
13281329 const writer = &index_hashed_writer.writer;
13291330 try writer.writeAll(IndexHeader.signature);
13301331 try writer.writeInt(u32, IndexHeader.supported_version, .big);
......@@ -1489,14 +1490,14 @@ fn resolveDeltaChain(
14891490 const delta_header = try EntryHeader.read(format, &pack.interface);
14901491 const delta_data = try readObjectRaw(allocator, &pack.interface, delta_header.uncompressedLength());
14911492 defer allocator.free(delta_data);
1492 var delta_reader: std.Io.Reader = .fixed(delta_data);
1493 var delta_reader: Io.Reader = .fixed(delta_data);
14931494 _ = try delta_reader.takeLeb128(u64); // base object size
14941495 const expanded_size = try delta_reader.takeLeb128(u64);
14951496
14961497 const expanded_alloc_size = std.math.cast(usize, expanded_size) orelse return error.ObjectTooLarge;
14971498 const expanded_data = try allocator.alloc(u8, expanded_alloc_size);
14981499 errdefer allocator.free(expanded_data);
1499 var expanded_delta_stream: std.Io.Writer = .fixed(expanded_data);
1500 var expanded_delta_stream: Io.Writer = .fixed(expanded_data);
15001501 try expandDelta(base_data, &delta_reader, &expanded_delta_stream);
15011502 if (expanded_delta_stream.end != expanded_size) return error.InvalidObject;
15021503
......@@ -1509,9 +1510,9 @@ fn resolveDeltaChain(
15091510/// Reads the complete contents of an object from `reader`. This function may
15101511/// read more bytes than required from `reader`, so the reader position after
15111512/// returning is not reliable.
1512fn readObjectRaw(allocator: Allocator, reader: *std.Io.Reader, size: u64) ![]u8 {
1513fn readObjectRaw(allocator: Allocator, reader: *Io.Reader, size: u64) ![]u8 {
15131514 const alloc_size = std.math.cast(usize, size) orelse return error.ObjectTooLarge;
1514 var aw: std.Io.Writer.Allocating = .init(allocator);
1515 var aw: Io.Writer.Allocating = .init(allocator);
15151516 try aw.ensureTotalCapacity(alloc_size + std.compress.flate.max_window_len);
15161517 defer aw.deinit();
15171518 var decompress: std.compress.flate.Decompress = .init(reader, .zlib, &.{});
......@@ -1523,7 +1524,7 @@ fn readObjectRaw(allocator: Allocator, reader: *std.Io.Reader, size: u64) ![]u8
15231524///
15241525/// The format of the delta data is documented in
15251526/// [pack-format](https://git-scm.com/docs/pack-format).
1526fn expandDelta(base_object: []const u8, delta_reader: *std.Io.Reader, writer: *std.Io.Writer) !void {
1527fn expandDelta(base_object: []const u8, delta_reader: *Io.Reader, writer: *Io.Writer) !void {
15271528 while (true) {
15281529 const inst: packed struct { value: u7, copy: bool } = @bitCast(delta_reader.takeByte() catch |e| switch (e) {
15291530 error.EndOfStream => return,
......@@ -1576,7 +1577,7 @@ fn expandDelta(base_object: []const u8, delta_reader: *std.Io.Reader, writer: *s
15761577/// - SHA-1: `dd582c0720819ab7130b103635bd7271b9fd4feb`
15771578/// - SHA-256: `7f444a92bd4572ee4a28b2c63059924a9ca1829138553ef3e7c41ee159afae7a`
15781579/// 4. `git checkout $commit`
1579fn runRepositoryTest(comptime format: Oid.Format, head_commit: []const u8) !void {
1580fn runRepositoryTest(io: Io, comptime format: Oid.Format, head_commit: []const u8) !void {
15801581 const testrepo_pack = @embedFile("git/testdata/testrepo-" ++ @tagName(format) ++ ".pack");
15811582
15821583 var git_dir = testing.tmpDir(.{});
......@@ -1586,7 +1587,7 @@ fn runRepositoryTest(comptime format: Oid.Format, head_commit: []const u8) !void
15861587 try pack_file.writeAll(testrepo_pack);
15871588
15881589 var pack_file_buffer: [2000]u8 = undefined;
1589 var pack_file_reader = pack_file.reader(&pack_file_buffer);
1590 var pack_file_reader = pack_file.reader(io, &pack_file_buffer);
15901591
15911592 var index_file = try git_dir.dir.createFile("testrepo.idx", .{ .read = true });
15921593 defer index_file.close();
......@@ -1608,7 +1609,7 @@ fn runRepositoryTest(comptime format: Oid.Format, head_commit: []const u8) !void
16081609 try testing.expectEqualSlices(u8, testrepo_idx, index_file_data);
16091610 }
16101611
1611 var index_file_reader = index_file.reader(&index_file_buffer);
1612 var index_file_reader = index_file.reader(io, &index_file_buffer);
16121613 var repository: Repository = undefined;
16131614 try repository.init(testing.allocator, format, &pack_file_reader, &index_file_reader);
16141615 defer repository.deinit();
......@@ -1687,11 +1688,11 @@ fn runRepositoryTest(comptime format: Oid.Format, head_commit: []const u8) !void
16871688const skip_checksums = true;
16881689
16891690test "SHA-1 packfile indexing and checkout" {
1690 try runRepositoryTest(.sha1, "dd582c0720819ab7130b103635bd7271b9fd4feb");
1691 try runRepositoryTest(std.testing.io, .sha1, "dd582c0720819ab7130b103635bd7271b9fd4feb");
16911692}
16921693
16931694test "SHA-256 packfile indexing and checkout" {
1694 try runRepositoryTest(.sha256, "7f444a92bd4572ee4a28b2c63059924a9ca1829138553ef3e7c41ee159afae7a");
1695 try runRepositoryTest(std.testing.io, .sha256, "7f444a92bd4572ee4a28b2c63059924a9ca1829138553ef3e7c41ee159afae7a");
16951696}
16961697
16971698/// Checks out a commit of a packfile. Intended for experimenting with and
......@@ -1699,6 +1700,10 @@ test "SHA-256 packfile indexing and checkout" {
16991700pub fn main() !void {
17001701 const allocator = std.heap.smp_allocator;
17011702
1703 var threaded: Io.Threaded = .init(allocator);
1704 defer threaded.deinit();
1705 const io = threaded.io();
1706
17021707 const args = try std.process.argsAlloc(allocator);
17031708 defer std.process.argsFree(allocator, args);
17041709 if (args.len != 5) {
......@@ -1710,7 +1715,7 @@ pub fn main() !void {
17101715 var pack_file = try std.fs.cwd().openFile(args[2], .{});
17111716 defer pack_file.close();
17121717 var pack_file_buffer: [4096]u8 = undefined;
1713 var pack_file_reader = pack_file.reader(&pack_file_buffer);
1718 var pack_file_reader = pack_file.reader(io, &pack_file_buffer);
17141719
17151720 const commit = try Oid.parse(format, args[3]);
17161721 var worktree = try std.fs.cwd().makeOpenPath(args[4], .{});
......@@ -1727,7 +1732,7 @@ pub fn main() !void {
17271732 try indexPack(allocator, format, &pack_file_reader, &index_file_writer);
17281733
17291734 std.debug.print("Starting checkout...\n", .{});
1730 var index_file_reader = index_file.reader(&index_file_buffer);
1735 var index_file_reader = index_file.reader(io, &index_file_buffer);
17311736 var repository: Repository = undefined;
17321737 try repository.init(allocator, format, &pack_file_reader, &index_file_reader);
17331738 defer repository.deinit();
src/Zcu.zig+19-13
......@@ -4,9 +4,12 @@
44//!
55//! Each `Compilation` has exactly one or zero `Zcu`, depending on whether
66//! there is or is not any zig source code, respectively.
7const Zcu = @This();
8const builtin = @import("builtin");
79
810const std = @import("std");
9const builtin = @import("builtin");
11const Io = std.Io;
12const Writer = std.Io.Writer;
1013const mem = std.mem;
1114const Allocator = std.mem.Allocator;
1215const assert = std.debug.assert;
......@@ -15,9 +18,7 @@ const BigIntConst = std.math.big.int.Const;
1518const BigIntMutable = std.math.big.int.Mutable;
1619const Target = std.Target;
1720const Ast = std.zig.Ast;
18const Writer = std.Io.Writer;
1921
20const Zcu = @This();
2122const Compilation = @import("Compilation.zig");
2223const Cache = std.Build.Cache;
2324pub const Value = @import("Value.zig");
......@@ -1037,10 +1038,15 @@ pub const File = struct {
10371038 stat: Cache.File.Stat,
10381039 };
10391040
1040 pub const GetSourceError = error{ OutOfMemory, FileTooBig } || std.fs.File.OpenError || std.fs.File.ReadError;
1041 pub const GetSourceError = error{
1042 OutOfMemory,
1043 FileTooBig,
1044 Streaming,
1045 } || std.fs.File.OpenError || std.fs.File.ReadError;
10411046
10421047 pub fn getSource(file: *File, zcu: *const Zcu) GetSourceError!Source {
10431048 const gpa = zcu.gpa;
1049 const io = zcu.comp.io;
10441050
10451051 if (file.source) |source| return .{
10461052 .bytes = source,
......@@ -1061,7 +1067,7 @@ pub const File = struct {
10611067 const source = try gpa.allocSentinel(u8, @intCast(stat.size), 0);
10621068 errdefer gpa.free(source);
10631069
1064 var file_reader = f.reader(&.{});
1070 var file_reader = f.reader(io, &.{});
10651071 file_reader.size = stat.size;
10661072 file_reader.interface.readSliceAll(source) catch return file_reader.err.?;
10671073
......@@ -2859,9 +2865,9 @@ comptime {
28592865 }
28602866}
28612867
2862pub fn loadZirCache(gpa: Allocator, cache_file: std.fs.File) !Zir {
2868pub fn loadZirCache(gpa: Allocator, io: Io, cache_file: std.fs.File) !Zir {
28632869 var buffer: [2000]u8 = undefined;
2864 var file_reader = cache_file.reader(&buffer);
2870 var file_reader = cache_file.reader(io, &buffer);
28652871 return result: {
28662872 const header = file_reader.interface.takeStructPointer(Zir.Header) catch |err| break :result err;
28672873 break :result loadZirCacheBody(gpa, header.*, &file_reader.interface);
......@@ -2871,7 +2877,7 @@ pub fn loadZirCache(gpa: Allocator, cache_file: std.fs.File) !Zir {
28712877 };
28722878}
28732879
2874pub fn loadZirCacheBody(gpa: Allocator, header: Zir.Header, cache_br: *std.Io.Reader) !Zir {
2880pub fn loadZirCacheBody(gpa: Allocator, header: Zir.Header, cache_br: *Io.Reader) !Zir {
28752881 var instructions: std.MultiArrayList(Zir.Inst) = .{};
28762882 errdefer instructions.deinit(gpa);
28772883
......@@ -2940,7 +2946,7 @@ pub fn saveZirCache(gpa: Allocator, cache_file: std.fs.File, stat: std.fs.File.S
29402946
29412947 .stat_size = stat.size,
29422948 .stat_inode = stat.inode,
2943 .stat_mtime = stat.mtime,
2949 .stat_mtime = stat.mtime.toNanoseconds(),
29442950 };
29452951 var vecs = [_][]const u8{
29462952 @ptrCast((&header)[0..1]),
......@@ -2969,7 +2975,7 @@ pub fn saveZoirCache(cache_file: std.fs.File, stat: std.fs.File.Stat, zoir: Zoir
29692975
29702976 .stat_size = stat.size,
29712977 .stat_inode = stat.inode,
2972 .stat_mtime = stat.mtime,
2978 .stat_mtime = stat.mtime.toNanoseconds(),
29732979 };
29742980 var vecs = [_][]const u8{
29752981 @ptrCast((&header)[0..1]),
......@@ -2988,7 +2994,7 @@ pub fn saveZoirCache(cache_file: std.fs.File, stat: std.fs.File.Stat, zoir: Zoir
29882994 };
29892995}
29902996
2991pub fn loadZoirCacheBody(gpa: Allocator, header: Zoir.Header, cache_br: *std.Io.Reader) !Zoir {
2997pub fn loadZoirCacheBody(gpa: Allocator, header: Zoir.Header, cache_br: *Io.Reader) !Zoir {
29922998 var zoir: Zoir = .{
29932999 .nodes = .empty,
29943000 .extra = &.{},
......@@ -4283,7 +4289,7 @@ const FormatAnalUnit = struct {
42834289 zcu: *Zcu,
42844290};
42854291
4286fn formatAnalUnit(data: FormatAnalUnit, writer: *std.Io.Writer) std.Io.Writer.Error!void {
4292fn formatAnalUnit(data: FormatAnalUnit, writer: *Io.Writer) Io.Writer.Error!void {
42874293 const zcu = data.zcu;
42884294 const ip = &zcu.intern_pool;
42894295 switch (data.unit.unwrap()) {
......@@ -4309,7 +4315,7 @@ fn formatAnalUnit(data: FormatAnalUnit, writer: *std.Io.Writer) std.Io.Writer.Er
43094315
43104316const FormatDependee = struct { dependee: InternPool.Dependee, zcu: *Zcu };
43114317
4312fn formatDependee(data: FormatDependee, writer: *std.Io.Writer) std.Io.Writer.Error!void {
4318fn formatDependee(data: FormatDependee, writer: *Io.Writer) Io.Writer.Error!void {
43134319 const zcu = data.zcu;
43144320 const ip = &zcu.intern_pool;
43154321 switch (data.dependee) {
src/Zcu/PerThread.zig+9-8
......@@ -87,6 +87,7 @@ pub fn updateFile(
8787 const zcu = pt.zcu;
8888 const comp = zcu.comp;
8989 const gpa = zcu.gpa;
90 const io = comp.io;
9091
9192 // In any case we need to examine the stat of the file to determine the course of action.
9293 var source_file = f: {
......@@ -127,7 +128,7 @@ pub fn updateFile(
127128 .astgen_failure, .success => lock: {
128129 const unchanged_metadata =
129130 stat.size == file.stat.size and
130 stat.mtime == file.stat.mtime and
131 stat.mtime.nanoseconds == file.stat.mtime.nanoseconds and
131132 stat.inode == file.stat.inode;
132133
133134 if (unchanged_metadata) {
......@@ -173,8 +174,6 @@ pub fn updateFile(
173174 .lock = lock,
174175 }) catch |err| switch (err) {
175176 error.NotDir => unreachable, // no dir components
176 error.InvalidUtf8 => unreachable, // it's a hex encoded name
177 error.InvalidWtf8 => unreachable, // it's a hex encoded name
178177 error.BadPathName => unreachable, // it's a hex encoded name
179178 error.NameTooLong => unreachable, // it's a fixed size name
180179 error.PipeBusy => unreachable, // it's not a pipe
......@@ -255,7 +254,7 @@ pub fn updateFile(
255254
256255 const source = try gpa.allocSentinel(u8, @intCast(stat.size), 0);
257256 defer if (file.source == null) gpa.free(source);
258 var source_fr = source_file.reader(&.{});
257 var source_fr = source_file.reader(io, &.{});
259258 source_fr.size = stat.size;
260259 source_fr.interface.readSliceAll(source) catch |err| switch (err) {
261260 error.ReadFailed => return source_fr.err.?,
......@@ -353,6 +352,7 @@ fn loadZirZoirCache(
353352 assert(file.getMode() == mode);
354353
355354 const gpa = zcu.gpa;
355 const io = zcu.comp.io;
356356
357357 const Header = switch (mode) {
358358 .zig => Zir.Header,
......@@ -360,7 +360,7 @@ fn loadZirZoirCache(
360360 };
361361
362362 var buffer: [2000]u8 = undefined;
363 var cache_fr = cache_file.reader(&buffer);
363 var cache_fr = cache_file.reader(io, &buffer);
364364 cache_fr.size = stat.size;
365365 const cache_br = &cache_fr.interface;
366366
......@@ -375,7 +375,7 @@ fn loadZirZoirCache(
375375
376376 const unchanged_metadata =
377377 stat.size == header.stat_size and
378 stat.mtime == header.stat_mtime and
378 stat.mtime.nanoseconds == header.stat_mtime and
379379 stat.inode == header.stat_inode;
380380
381381 if (!unchanged_metadata) {
......@@ -2436,6 +2436,7 @@ fn updateEmbedFileInner(
24362436 const tid = pt.tid;
24372437 const zcu = pt.zcu;
24382438 const gpa = zcu.gpa;
2439 const io = zcu.comp.io;
24392440 const ip = &zcu.intern_pool;
24402441
24412442 var file = f: {
......@@ -2450,7 +2451,7 @@ fn updateEmbedFileInner(
24502451 const old_stat = ef.stat;
24512452 const unchanged_metadata =
24522453 stat.size == old_stat.size and
2453 stat.mtime == old_stat.mtime and
2454 stat.mtime.nanoseconds == old_stat.mtime.nanoseconds and
24542455 stat.inode == old_stat.inode;
24552456 if (unchanged_metadata) return;
24562457 }
......@@ -2464,7 +2465,7 @@ fn updateEmbedFileInner(
24642465 const old_len = string_bytes.mutate.len;
24652466 errdefer string_bytes.shrinkRetainingCapacity(old_len);
24662467 const bytes = (try string_bytes.addManyAsSlice(size_plus_one))[0];
2467 var fr = file.reader(&.{});
2468 var fr = file.reader(io, &.{});
24682469 fr.size = stat.size;
24692470 fr.interface.readSliceAll(bytes[0..size]) catch |err| switch (err) {
24702471 error.ReadFailed => return fr.err.?,
src/codegen/llvm.zig+8-8
......@@ -782,10 +782,10 @@ pub const Object = struct {
782782 pub const EmitOptions = struct {
783783 pre_ir_path: ?[]const u8,
784784 pre_bc_path: ?[]const u8,
785 bin_path: ?[*:0]const u8,
786 asm_path: ?[*:0]const u8,
787 post_ir_path: ?[*:0]const u8,
788 post_bc_path: ?[*:0]const u8,
785 bin_path: ?[:0]const u8,
786 asm_path: ?[:0]const u8,
787 post_ir_path: ?[:0]const u8,
788 post_bc_path: ?[]const u8,
789789
790790 is_debug: bool,
791791 is_small: bool,
......@@ -989,7 +989,7 @@ pub const Object = struct {
989989 options.post_ir_path == null and options.post_bc_path == null) return;
990990
991991 if (options.post_bc_path) |path| {
992 var file = std.fs.cwd().createFileZ(path, .{}) catch |err|
992 var file = std.fs.cwd().createFile(path, .{}) catch |err|
993993 return diags.fail("failed to create '{s}': {s}", .{ path, @errorName(err) });
994994 defer file.close();
995995
......@@ -1098,8 +1098,8 @@ pub const Object = struct {
10981098 // though it's clearly not ready and produces multiple miscompilations in our std tests.
10991099 .allow_machine_outliner = !comp.root_mod.resolved_target.result.cpu.arch.isRISCV(),
11001100 .asm_filename = null,
1101 .bin_filename = options.bin_path,
1102 .llvm_ir_filename = options.post_ir_path,
1101 .bin_filename = if (options.bin_path) |x| x.ptr else null,
1102 .llvm_ir_filename = if (options.post_ir_path) |x| x.ptr else null,
11031103 .bitcode_filename = null,
11041104
11051105 // `.coverage` value is only used when `.sancov` is enabled.
......@@ -1146,7 +1146,7 @@ pub const Object = struct {
11461146 lowered_options.time_report_out = &time_report_c_str;
11471147 }
11481148
1149 lowered_options.asm_filename = options.asm_path;
1149 lowered_options.asm_filename = if (options.asm_path) |x| x.ptr else null;
11501150 if (target_machine.emitToFile(module, &error_message, &lowered_options)) {
11511151 defer llvm.disposeMessage(error_message);
11521152 return diags.fail("LLVM failed to emit asm={s} bin={s} ir={s} bc={s}: {s}", .{
src/codegen/wasm/Emit.zig+14-2
......@@ -188,8 +188,8 @@ pub fn lowerToCode(emit: *Emit) Error!void {
188188 .fromInterned(fn_info.return_type),
189189 target,
190190 ).?;
191 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.call_indirect));
192191 if (is_obj) {
192 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.call_indirect));
193193 try wasm.out_relocs.append(gpa, .{
194194 .offset = @intCast(code.items.len),
195195 .pointee = .{ .type_index = func_ty_index },
......@@ -198,7 +198,19 @@ pub fn lowerToCode(emit: *Emit) Error!void {
198198 });
199199 code.appendNTimesAssumeCapacity(0, 5);
200200 } else {
201 const index: Wasm.Flush.FuncTypeIndex = .fromTypeIndex(func_ty_index, &wasm.flush_buffer);
201 const index: Wasm.Flush.FuncTypeIndex = @enumFromInt(wasm.flush_buffer.func_types.getIndex(func_ty_index) orelse {
202 // In this case we tried to call a function pointer for
203 // which the type signature does not match any function
204 // body or function import in the entire wasm executable.
205 //
206 // Since there is no way to create a reference to a
207 // function without it being in the function table or
208 // import table, this instruction is unreachable.
209 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.@"unreachable"));
210 inst += 1;
211 continue :loop tags[inst];
212 });
213 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.call_indirect));
202214 writeUleb128(code, @intFromEnum(index));
203215 }
204216 writeUleb128(code, @as(u32, 0)); // table index
src/fmt.zig+12-4
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23const mem = std.mem;
34const fs = std.fs;
45const process = std.process;
......@@ -34,13 +35,14 @@ const Fmt = struct {
3435 color: Color,
3536 gpa: Allocator,
3637 arena: Allocator,
38 io: Io,
3739 out_buffer: std.Io.Writer.Allocating,
3840 stdout_writer: *fs.File.Writer,
3941
4042 const SeenMap = std.AutoHashMap(fs.File.INode, void);
4143};
4244
43pub fn run(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
45pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !void {
4446 var color: Color = .auto;
4547 var stdin_flag = false;
4648 var check_flag = false;
......@@ -99,7 +101,7 @@ pub fn run(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
99101
100102 const stdin: fs.File = .stdin();
101103 var stdio_buffer: [1024]u8 = undefined;
102 var file_reader: fs.File.Reader = stdin.reader(&stdio_buffer);
104 var file_reader: fs.File.Reader = stdin.reader(io, &stdio_buffer);
103105 const source_code = std.zig.readSourceFileToEndAlloc(gpa, &file_reader) catch |err| {
104106 fatal("unable to read stdin: {}", .{err});
105107 };
......@@ -165,6 +167,7 @@ pub fn run(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
165167 var fmt: Fmt = .{
166168 .gpa = gpa,
167169 .arena = arena,
170 .io = io,
168171 .seen = .init(gpa),
169172 .any_error = false,
170173 .check_ast = check_ast_flag,
......@@ -255,6 +258,8 @@ fn fmtPathFile(
255258 dir: fs.Dir,
256259 sub_path: []const u8,
257260) !void {
261 const io = fmt.io;
262
258263 const source_file = try dir.openFile(sub_path, .{});
259264 var file_closed = false;
260265 errdefer if (!file_closed) source_file.close();
......@@ -265,7 +270,7 @@ fn fmtPathFile(
265270 return error.IsDir;
266271
267272 var read_buffer: [1024]u8 = undefined;
268 var file_reader: fs.File.Reader = source_file.reader(&read_buffer);
273 var file_reader: fs.File.Reader = source_file.reader(io, &read_buffer);
269274 file_reader.size = stat.size;
270275
271276 const gpa = fmt.gpa;
......@@ -363,5 +368,8 @@ pub fn main() !void {
363368 var arena_instance = std.heap.ArenaAllocator.init(gpa);
364369 const arena = arena_instance.allocator();
365370 const args = try process.argsAlloc(arena);
366 return run(gpa, arena, args[1..]);
371 var threaded: std.Io.Threaded = .init(gpa);
372 defer threaded.deinit();
373 const io = threaded.io();
374 return run(gpa, arena, io, args[1..]);
367375}
src/libs/freebsd.zig+4-1
......@@ -426,6 +426,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
426426 }
427427
428428 const gpa = comp.gpa;
429 const io = comp.io;
429430
430431 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
431432 defer arena_allocator.deinit();
......@@ -438,6 +439,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
438439 // Use the global cache directory.
439440 var cache: Cache = .{
440441 .gpa = gpa,
442 .io = io,
441443 .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}),
442444 };
443445 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
......@@ -1017,6 +1019,7 @@ fn buildSharedLib(
10171019 const tracy = trace(@src());
10181020 defer tracy.end();
10191021
1022 const io = comp.io;
10201023 const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover });
10211024 const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 };
10221025 const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?);
......@@ -1071,7 +1074,7 @@ fn buildSharedLib(
10711074 const misc_task: Compilation.MiscTask = .@"freebsd libc shared object";
10721075
10731076 var sub_create_diag: Compilation.CreateDiagnostic = undefined;
1074 const sub_compilation = Compilation.create(comp.gpa, arena, &sub_create_diag, .{
1077 const sub_compilation = Compilation.create(comp.gpa, arena, io, &sub_create_diag, .{
10751078 .dirs = comp.dirs.withoutLocalCache(),
10761079 .thread_pool = comp.thread_pool,
10771080 .self_exe_path = comp.self_exe_path,
src/libs/glibc.zig+4-1
......@@ -666,6 +666,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
666666 }
667667
668668 const gpa = comp.gpa;
669 const io = comp.io;
669670
670671 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
671672 defer arena_allocator.deinit();
......@@ -677,6 +678,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
677678 // Use the global cache directory.
678679 var cache: Cache = .{
679680 .gpa = gpa,
681 .io = io,
680682 .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}),
681683 };
682684 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
......@@ -1175,6 +1177,7 @@ fn buildSharedLib(
11751177 const tracy = trace(@src());
11761178 defer tracy.end();
11771179
1180 const io = comp.io;
11781181 const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover });
11791182 const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 };
11801183 const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?);
......@@ -1229,7 +1232,7 @@ fn buildSharedLib(
12291232 const misc_task: Compilation.MiscTask = .@"glibc shared object";
12301233
12311234 var sub_create_diag: Compilation.CreateDiagnostic = undefined;
1232 const sub_compilation = Compilation.create(comp.gpa, arena, &sub_create_diag, .{
1235 const sub_compilation = Compilation.create(comp.gpa, arena, io, &sub_create_diag, .{
12331236 .dirs = comp.dirs.withoutLocalCache(),
12341237 .thread_pool = comp.thread_pool,
12351238 .self_exe_path = comp.self_exe_path,
src/libs/libcxx.zig+4-2
......@@ -120,6 +120,7 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError!
120120 defer arena_allocator.deinit();
121121 const arena = arena_allocator.allocator();
122122
123 const io = comp.io;
123124 const root_name = "c++";
124125 const output_mode = .Lib;
125126 const link_mode = .static;
......@@ -254,7 +255,7 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError!
254255 const misc_task: Compilation.MiscTask = .libcxx;
255256
256257 var sub_create_diag: Compilation.CreateDiagnostic = undefined;
257 const sub_compilation = Compilation.create(comp.gpa, arena, &sub_create_diag, .{
258 const sub_compilation = Compilation.create(comp.gpa, arena, io, &sub_create_diag, .{
258259 .dirs = comp.dirs.withoutLocalCache(),
259260 .self_exe_path = comp.self_exe_path,
260261 .cache_mode = .whole,
......@@ -309,6 +310,7 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
309310 defer arena_allocator.deinit();
310311 const arena = arena_allocator.allocator();
311312
313 const io = comp.io;
312314 const root_name = "c++abi";
313315 const output_mode = .Lib;
314316 const link_mode = .static;
......@@ -446,7 +448,7 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
446448 const misc_task: Compilation.MiscTask = .libcxxabi;
447449
448450 var sub_create_diag: Compilation.CreateDiagnostic = undefined;
449 const sub_compilation = Compilation.create(comp.gpa, arena, &sub_create_diag, .{
451 const sub_compilation = Compilation.create(comp.gpa, arena, io, &sub_create_diag, .{
450452 .dirs = comp.dirs.withoutLocalCache(),
451453 .self_exe_path = comp.self_exe_path,
452454 .cache_mode = .whole,
src/libs/libtsan.zig+2-1
......@@ -25,6 +25,7 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo
2525 defer arena_allocator.deinit();
2626 const arena = arena_allocator.allocator();
2727
28 const io = comp.io;
2829 const target = comp.getTarget();
2930 const root_name = switch (target.os.tag) {
3031 // On Apple platforms, we use the same name as LLVM because the
......@@ -277,7 +278,7 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo
277278 const misc_task: Compilation.MiscTask = .libtsan;
278279
279280 var sub_create_diag: Compilation.CreateDiagnostic = undefined;
280 const sub_compilation = Compilation.create(comp.gpa, arena, &sub_create_diag, .{
281 const sub_compilation = Compilation.create(comp.gpa, arena, io, &sub_create_diag, .{
281282 .dirs = comp.dirs.withoutLocalCache(),
282283 .thread_pool = comp.thread_pool,
283284 .self_exe_path = comp.self_exe_path,
src/libs/libunwind.zig+2-1
......@@ -26,6 +26,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
2626 defer arena_allocator.deinit();
2727 const arena = arena_allocator.allocator();
2828
29 const io = comp.io;
2930 const output_mode = .Lib;
3031 const target = &comp.root_mod.resolved_target.result;
3132 const unwind_tables: std.builtin.UnwindTables =
......@@ -143,7 +144,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
143144 const misc_task: Compilation.MiscTask = .libunwind;
144145
145146 var sub_create_diag: Compilation.CreateDiagnostic = undefined;
146 const sub_compilation = Compilation.create(comp.gpa, arena, &sub_create_diag, .{
147 const sub_compilation = Compilation.create(comp.gpa, arena, io, &sub_create_diag, .{
147148 .dirs = comp.dirs.withoutLocalCache(),
148149 .self_exe_path = comp.self_exe_path,
149150 .config = config,
src/libs/mingw.zig+3-1
......@@ -235,6 +235,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
235235 dev.check(.build_import_lib);
236236
237237 const gpa = comp.gpa;
238 const io = comp.io;
238239
239240 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
240241 defer arena_allocator.deinit();
......@@ -255,6 +256,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
255256 // Use the global cache directory.
256257 var cache: Cache = .{
257258 .gpa = gpa,
259 .io = io,
258260 .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}),
259261 };
260262 cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
......@@ -302,7 +304,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
302304 .output = .{ .to_list = .{ .arena = .init(gpa) } },
303305 };
304306 defer diagnostics.deinit();
305 var aro_comp = aro.Compilation.init(gpa, arena, &diagnostics, std.fs.cwd());
307 var aro_comp = aro.Compilation.init(gpa, arena, io, &diagnostics, std.fs.cwd());
306308 defer aro_comp.deinit();
307309
308310 aro_comp.target = target.*;
src/libs/musl.zig+2-1
......@@ -26,6 +26,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro
2626 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
2727 defer arena_allocator.deinit();
2828 const arena = arena_allocator.allocator();
29 const io = comp.io;
2930
3031 switch (in_crt_file) {
3132 .crt1_o => {
......@@ -246,7 +247,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro
246247 const misc_task: Compilation.MiscTask = .@"musl libc.so";
247248
248249 var sub_create_diag: Compilation.CreateDiagnostic = undefined;
249 const sub_compilation = Compilation.create(comp.gpa, arena, &sub_create_diag, .{
250 const sub_compilation = Compilation.create(comp.gpa, arena, io, &sub_create_diag, .{
250251 .dirs = comp.dirs.withoutLocalCache(),
251252 .self_exe_path = comp.self_exe_path,
252253 .cache_mode = .whole,
src/libs/netbsd.zig+4-1
......@@ -372,6 +372,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
372372 }
373373
374374 const gpa = comp.gpa;
375 const io = comp.io;
375376
376377 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
377378 defer arena_allocator.deinit();
......@@ -383,6 +384,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
383384 // Use the global cache directory.
384385 var cache: Cache = .{
385386 .gpa = gpa,
387 .io = io,
386388 .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}),
387389 };
388390 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
......@@ -680,6 +682,7 @@ fn buildSharedLib(
680682 const tracy = trace(@src());
681683 defer tracy.end();
682684
685 const io = comp.io;
683686 const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover });
684687 const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 };
685688 const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?);
......@@ -733,7 +736,7 @@ fn buildSharedLib(
733736 const misc_task: Compilation.MiscTask = .@"netbsd libc shared object";
734737
735738 var sub_create_diag: Compilation.CreateDiagnostic = undefined;
736 const sub_compilation = Compilation.create(comp.gpa, arena, &sub_create_diag, .{
739 const sub_compilation = Compilation.create(comp.gpa, arena, io, &sub_create_diag, .{
737740 .dirs = comp.dirs.withoutLocalCache(),
738741 .thread_pool = comp.thread_pool,
739742 .self_exe_path = comp.self_exe_path,
src/link.zig+11-20
......@@ -1,19 +1,22 @@
1const std = @import("std");
2const build_options = @import("build_options");
31const builtin = @import("builtin");
2const build_options = @import("build_options");
3
4const std = @import("std");
5const Io = std.Io;
46const assert = std.debug.assert;
57const fs = std.fs;
68const mem = std.mem;
79const log = std.log.scoped(.link);
8const trace = @import("tracy.zig").trace;
9const wasi_libc = @import("libs/wasi_libc.zig");
10
1110const Allocator = std.mem.Allocator;
1211const Cache = std.Build.Cache;
1312const Path = std.Build.Cache.Path;
1413const Directory = std.Build.Cache.Directory;
1514const Compilation = @import("Compilation.zig");
1615const LibCInstallation = std.zig.LibCInstallation;
16
17const trace = @import("tracy.zig").trace;
18const wasi_libc = @import("libs/wasi_libc.zig");
19
1720const Zcu = @import("Zcu.zig");
1821const InternPool = @import("InternPool.zig");
1922const Type = @import("Type.zig");
......@@ -572,6 +575,7 @@ pub const File = struct {
572575 dev.check(.make_writable);
573576 const comp = base.comp;
574577 const gpa = comp.gpa;
578 const io = comp.io;
575579 switch (base.tag) {
576580 .lld => assert(base.file == null),
577581 .elf, .macho, .wasm => {
......@@ -616,22 +620,9 @@ pub const File = struct {
616620 &coff.mf
617621 else
618622 unreachable;
619 var attempt: u5 = 0;
620 mf.file = while (true) break base.emit.root_dir.handle.openFile(base.emit.sub_path, .{
623 mf.file = .adaptFromNewApi(try Io.Dir.openFile(base.emit.root_dir.handle.adaptToNewApi(), io, base.emit.sub_path, .{
621624 .mode = .read_write,
622 }) catch |err| switch (err) {
623 error.AccessDenied => switch (builtin.os.tag) {
624 .windows => {
625 if (attempt == 13) return error.AccessDenied;
626 // give the kernel a chance to finish closing the executable handle
627 std.os.windows.kernel32.Sleep(@as(u32, 1) << attempt >> 1);
628 attempt += 1;
629 continue;
630 },
631 else => return error.AccessDenied,
632 },
633 else => |e| return e,
634 };
625 }));
635626 base.file = mf.file;
636627 try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1]));
637628 },
src/link/Coff.zig+1-1
......@@ -610,7 +610,7 @@ fn create(
610610 .Obj => false,
611611 };
612612 const machine = target.toCoffMachine();
613 const timestamp: u32 = if (options.repro) 0 else @truncate(@as(u64, @bitCast(std.time.timestamp())));
613 const timestamp: u32 = 0;
614614 const major_subsystem_version = options.major_subsystem_version orelse 6;
615615 const minor_subsystem_version = options.minor_subsystem_version orelse 0;
616616 const magic: std.coff.OptionalHeader.Magic = switch (target.ptrBitWidth()) {
src/link/Lld.zig+6-8
......@@ -1613,11 +1613,9 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {
16131613 }
16141614}
16151615
1616fn spawnLld(
1617 comp: *Compilation,
1618 arena: Allocator,
1619 argv: []const []const u8,
1620) !void {
1616fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !void {
1617 const io = comp.io;
1618
16211619 if (comp.verbose_link) {
16221620 // Skip over our own name so that the LLD linker name is the first argv item.
16231621 Compilation.dump_argv(argv[1..]);
......@@ -1649,7 +1647,7 @@ fn spawnLld(
16491647 child.stderr_behavior = .Pipe;
16501648
16511649 child.spawn() catch |err| break :term err;
1652 var stderr_reader = child.stderr.?.readerStreaming(&.{});
1650 var stderr_reader = child.stderr.?.readerStreaming(io, &.{});
16531651 stderr = try stderr_reader.interface.allocRemaining(comp.gpa, .unlimited);
16541652 break :term child.wait();
16551653 }) catch |first_err| term: {
......@@ -1659,7 +1657,7 @@ fn spawnLld(
16591657 const rand_int = std.crypto.random.int(u64);
16601658 const rsp_path = "tmp" ++ s ++ std.fmt.hex(rand_int) ++ ".rsp";
16611659
1662 const rsp_file = try comp.dirs.local_cache.handle.createFileZ(rsp_path, .{});
1660 const rsp_file = try comp.dirs.local_cache.handle.createFile(rsp_path, .{});
16631661 defer comp.dirs.local_cache.handle.deleteFileZ(rsp_path) catch |err|
16641662 log.warn("failed to delete response file {s}: {s}", .{ rsp_path, @errorName(err) });
16651663 {
......@@ -1699,7 +1697,7 @@ fn spawnLld(
16991697 rsp_child.stderr_behavior = .Pipe;
17001698
17011699 rsp_child.spawn() catch |err| break :err err;
1702 var stderr_reader = rsp_child.stderr.?.readerStreaming(&.{});
1700 var stderr_reader = rsp_child.stderr.?.readerStreaming(io, &.{});
17031701 stderr = try stderr_reader.interface.allocRemaining(comp.gpa, .unlimited);
17041702 break :term rsp_child.wait() catch |err| break :err err;
17051703 }
src/link/MachO.zig+7-9
......@@ -915,7 +915,7 @@ pub fn readArMagic(file: std.fs.File, offset: usize, buffer: *[Archive.SARMAG]u8
915915 return buffer[0..Archive.SARMAG];
916916}
917917
918fn addObject(self: *MachO, path: Path, handle: File.HandleIndex, offset: u64) !void {
918fn addObject(self: *MachO, path: Path, handle_index: File.HandleIndex, offset: u64) !void {
919919 const tracy = trace(@src());
920920 defer tracy.end();
921921
......@@ -929,17 +929,15 @@ fn addObject(self: *MachO, path: Path, handle: File.HandleIndex, offset: u64) !v
929929 });
930930 errdefer gpa.free(abs_path);
931931
932 const mtime: u64 = mtime: {
933 const file = self.getFileHandle(handle);
934 const stat = file.stat() catch break :mtime 0;
935 break :mtime @as(u64, @intCast(@divFloor(stat.mtime, 1_000_000_000)));
936 };
937 const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
932 const file = self.getFileHandle(handle_index);
933 const stat = try file.stat();
934 const mtime = stat.mtime.toSeconds();
935 const index: File.Index = @intCast(try self.files.addOne(gpa));
938936 self.files.set(index, .{ .object = .{
939937 .offset = offset,
940938 .path = abs_path,
941 .file_handle = handle,
942 .mtime = mtime,
939 .file_handle = handle_index,
940 .mtime = @intCast(mtime),
943941 .index = index,
944942 } });
945943 try self.objects.append(gpa, index);
src/link/MappedFile.zig+8-6
......@@ -16,11 +16,13 @@ writers: std.SinglyLinkedList,
1616
1717pub const growth_factor = 4;
1818
19pub const Error = std.posix.MMapError ||
20 std.posix.MRemapError ||
21 std.fs.File.SetEndPosError ||
22 std.fs.File.CopyRangeError ||
23 error{NotFile};
19pub const Error = std.posix.MMapError || std.posix.MRemapError || std.fs.File.SetEndPosError || error{
20 NotFile,
21 SystemResources,
22 IsDir,
23 Unseekable,
24 NoSpaceLeft,
25};
2426
2527pub fn init(file: std.fs.File, gpa: std.mem.Allocator) !MappedFile {
2628 var mf: MappedFile = .{
......@@ -402,7 +404,7 @@ pub const Node = extern struct {
402404
403405 const w: *Writer = @fieldParentPtr("interface", interface);
404406 const copy_size: usize = @intCast(w.mf.copyFileRange(
405 file_reader.file,
407 .adaptFromNewApi(file_reader.file),
406408 file_reader.pos,
407409 w.ni.fileLocation(w.mf, true).offset + interface.end,
408410 limit.minInt(interface.unusedCapacityLen()),
src/link/Wasm.zig+14-6
......@@ -3029,18 +3029,22 @@ fn openParseObjectReportingFailure(wasm: *Wasm, path: Path) void {
30293029fn parseObject(wasm: *Wasm, obj: link.Input.Object) !void {
30303030 log.debug("parseObject {f}", .{obj.path});
30313031 const gpa = wasm.base.comp.gpa;
3032 const io = wasm.base.comp.io;
30323033 const gc_sections = wasm.base.gc_sections;
30333034
30343035 defer obj.file.close();
30353036
3037 var file_reader = obj.file.reader(io, &.{});
3038
30363039 try wasm.objects.ensureUnusedCapacity(gpa, 1);
3037 const stat = try obj.file.stat();
3038 const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig;
3040 const size = std.math.cast(usize, try file_reader.getSize()) orelse return error.FileTooBig;
30393041
30403042 const file_contents = try gpa.alloc(u8, size);
30413043 defer gpa.free(file_contents);
30423044
3043 const n = try obj.file.preadAll(file_contents, 0);
3045 const n = file_reader.interface.readSliceShort(file_contents) catch |err| switch (err) {
3046 error.ReadFailed => return file_reader.err.?,
3047 };
30443048 if (n != file_contents.len) return error.UnexpectedEndOfFile;
30453049
30463050 var ss: Object.ScratchSpace = .{};
......@@ -3053,17 +3057,21 @@ fn parseObject(wasm: *Wasm, obj: link.Input.Object) !void {
30533057fn parseArchive(wasm: *Wasm, obj: link.Input.Object) !void {
30543058 log.debug("parseArchive {f}", .{obj.path});
30553059 const gpa = wasm.base.comp.gpa;
3060 const io = wasm.base.comp.io;
30563061 const gc_sections = wasm.base.gc_sections;
30573062
30583063 defer obj.file.close();
30593064
3060 const stat = try obj.file.stat();
3061 const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig;
3065 var file_reader = obj.file.reader(io, &.{});
3066
3067 const size = std.math.cast(usize, try file_reader.getSize()) orelse return error.FileTooBig;
30623068
30633069 const file_contents = try gpa.alloc(u8, size);
30643070 defer gpa.free(file_contents);
30653071
3066 const n = try obj.file.preadAll(file_contents, 0);
3072 const n = file_reader.interface.readSliceShort(file_contents) catch |err| switch (err) {
3073 error.ReadFailed => return file_reader.err.?,
3074 };
30673075 if (n != file_contents.len) return error.UnexpectedEndOfFile;
30683076
30693077 var archive = try Archive.parse(gpa, file_contents);
src/link/Wasm/Flush.zig+8-3
......@@ -1064,9 +1064,14 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
10641064 }
10651065
10661066 // Finally, write the entire binary into the file.
1067 const file = wasm.base.file.?;
1068 try file.pwriteAll(binary_bytes.items, 0);
1069 try file.setEndPos(binary_bytes.items.len);
1067 var file_writer = wasm.base.file.?.writer(&.{});
1068 file_writer.interface.writeAll(binary_bytes.items) catch |err| switch (err) {
1069 error.WriteFailed => return file_writer.err.?,
1070 };
1071 file_writer.end() catch |err| switch (err) {
1072 error.WriteFailed => return file_writer.err.?,
1073 else => |e| return e,
1074 };
10701075}
10711076
10721077const VirtualAddrs = struct {
src/main.zig+88-75
......@@ -1,5 +1,8 @@
1const std = @import("std");
21const builtin = @import("builtin");
2const native_os = builtin.os.tag;
3
4const std = @import("std");
5const Io = std.Io;
36const assert = std.debug.assert;
47const fs = std.fs;
58const mem = std.mem;
......@@ -10,7 +13,6 @@ const Color = std.zig.Color;
1013const warn = std.log.warn;
1114const ThreadPool = std.Thread.Pool;
1215const cleanExit = std.process.cleanExit;
13const native_os = builtin.os.tag;
1416const Cache = std.Build.Cache;
1517const Path = std.Build.Cache.Path;
1618const Directory = std.Build.Cache.Directory;
......@@ -245,26 +247,30 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
245247 }
246248 }
247249
250 var threaded: Io.Threaded = .init(gpa);
251 defer threaded.deinit();
252 const io = threaded.io();
253
248254 const cmd = args[1];
249255 const cmd_args = args[2..];
250256 if (mem.eql(u8, cmd, "build-exe")) {
251257 dev.check(.build_exe_command);
252 return buildOutputType(gpa, arena, args, .{ .build = .Exe });
258 return buildOutputType(gpa, arena, io, args, .{ .build = .Exe });
253259 } else if (mem.eql(u8, cmd, "build-lib")) {
254260 dev.check(.build_lib_command);
255 return buildOutputType(gpa, arena, args, .{ .build = .Lib });
261 return buildOutputType(gpa, arena, io, args, .{ .build = .Lib });
256262 } else if (mem.eql(u8, cmd, "build-obj")) {
257263 dev.check(.build_obj_command);
258 return buildOutputType(gpa, arena, args, .{ .build = .Obj });
264 return buildOutputType(gpa, arena, io, args, .{ .build = .Obj });
259265 } else if (mem.eql(u8, cmd, "test")) {
260266 dev.check(.test_command);
261 return buildOutputType(gpa, arena, args, .zig_test);
267 return buildOutputType(gpa, arena, io, args, .zig_test);
262268 } else if (mem.eql(u8, cmd, "test-obj")) {
263269 dev.check(.test_command);
264 return buildOutputType(gpa, arena, args, .zig_test_obj);
270 return buildOutputType(gpa, arena, io, args, .zig_test_obj);
265271 } else if (mem.eql(u8, cmd, "run")) {
266272 dev.check(.run_command);
267 return buildOutputType(gpa, arena, args, .run);
273 return buildOutputType(gpa, arena, io, args, .run);
268274 } else if (mem.eql(u8, cmd, "dlltool") or
269275 mem.eql(u8, cmd, "ranlib") or
270276 mem.eql(u8, cmd, "lib") or
......@@ -274,7 +280,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
274280 return process.exit(try llvmArMain(arena, args));
275281 } else if (mem.eql(u8, cmd, "build")) {
276282 dev.check(.build_command);
277 return cmdBuild(gpa, arena, cmd_args);
283 return cmdBuild(gpa, arena, io, cmd_args);
278284 } else if (mem.eql(u8, cmd, "clang") or
279285 mem.eql(u8, cmd, "-cc1") or mem.eql(u8, cmd, "-cc1as"))
280286 {
......@@ -288,16 +294,16 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
288294 return process.exit(try lldMain(arena, args, true));
289295 } else if (mem.eql(u8, cmd, "cc")) {
290296 dev.check(.cc_command);
291 return buildOutputType(gpa, arena, args, .cc);
297 return buildOutputType(gpa, arena, io, args, .cc);
292298 } else if (mem.eql(u8, cmd, "c++")) {
293299 dev.check(.cc_command);
294 return buildOutputType(gpa, arena, args, .cpp);
300 return buildOutputType(gpa, arena, io, args, .cpp);
295301 } else if (mem.eql(u8, cmd, "translate-c")) {
296302 dev.check(.translate_c_command);
297 return buildOutputType(gpa, arena, args, .translate_c);
303 return buildOutputType(gpa, arena, io, args, .translate_c);
298304 } else if (mem.eql(u8, cmd, "rc")) {
299305 const use_server = cmd_args.len > 0 and std.mem.eql(u8, cmd_args[0], "--zig-integration");
300 return jitCmd(gpa, arena, cmd_args, .{
306 return jitCmd(gpa, arena, io, cmd_args, .{
301307 .cmd_name = "resinator",
302308 .root_src_path = "resinator/main.zig",
303309 .depend_on_aro = true,
......@@ -306,22 +312,22 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
306312 });
307313 } else if (mem.eql(u8, cmd, "fmt")) {
308314 dev.check(.fmt_command);
309 return @import("fmt.zig").run(gpa, arena, cmd_args);
315 return @import("fmt.zig").run(gpa, arena, io, cmd_args);
310316 } else if (mem.eql(u8, cmd, "objcopy")) {
311 return jitCmd(gpa, arena, cmd_args, .{
317 return jitCmd(gpa, arena, io, cmd_args, .{
312318 .cmd_name = "objcopy",
313319 .root_src_path = "objcopy.zig",
314320 });
315321 } else if (mem.eql(u8, cmd, "fetch")) {
316 return cmdFetch(gpa, arena, cmd_args);
322 return cmdFetch(gpa, arena, io, cmd_args);
317323 } else if (mem.eql(u8, cmd, "libc")) {
318 return jitCmd(gpa, arena, cmd_args, .{
324 return jitCmd(gpa, arena, io, cmd_args, .{
319325 .cmd_name = "libc",
320326 .root_src_path = "libc.zig",
321327 .prepend_zig_lib_dir_path = true,
322328 });
323329 } else if (mem.eql(u8, cmd, "std")) {
324 return jitCmd(gpa, arena, cmd_args, .{
330 return jitCmd(gpa, arena, io, cmd_args, .{
325331 .cmd_name = "std",
326332 .root_src_path = "std-docs.zig",
327333 .prepend_zig_lib_dir_path = true,
......@@ -332,7 +338,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
332338 return cmdInit(gpa, arena, cmd_args);
333339 } else if (mem.eql(u8, cmd, "targets")) {
334340 dev.check(.targets_command);
335 const host = std.zig.resolveTargetQueryOrFatal(.{});
341 const host = std.zig.resolveTargetQueryOrFatal(io, .{});
336342 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
337343 try @import("print_targets.zig").cmdTargets(arena, cmd_args, &stdout_writer.interface, &host);
338344 return stdout_writer.interface.flush();
......@@ -342,16 +348,18 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
342348 return;
343349 } else if (mem.eql(u8, cmd, "env")) {
344350 dev.check(.env_command);
351 const host = std.zig.resolveTargetQueryOrFatal(io, .{});
345352 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
346353 try @import("print_env.zig").cmdEnv(
347354 arena,
348355 &stdout_writer.interface,
349356 args,
350357 if (native_os == .wasi) wasi_preopens,
358 &host,
351359 );
352360 return stdout_writer.interface.flush();
353361 } else if (mem.eql(u8, cmd, "reduce")) {
354 return jitCmd(gpa, arena, cmd_args, .{
362 return jitCmd(gpa, arena, io, cmd_args, .{
355363 .cmd_name = "reduce",
356364 .root_src_path = "reduce.zig",
357365 });
......@@ -362,13 +370,13 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
362370 dev.check(.help_command);
363371 return fs.File.stdout().writeAll(usage);
364372 } else if (mem.eql(u8, cmd, "ast-check")) {
365 return cmdAstCheck(arena, cmd_args);
373 return cmdAstCheck(arena, io, cmd_args);
366374 } else if (mem.eql(u8, cmd, "detect-cpu")) {
367 return cmdDetectCpu(cmd_args);
375 return cmdDetectCpu(io, cmd_args);
368376 } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "changelist")) {
369 return cmdChangelist(arena, cmd_args);
377 return cmdChangelist(arena, io, cmd_args);
370378 } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "dump-zir")) {
371 return cmdDumpZir(arena, cmd_args);
379 return cmdDumpZir(arena, io, cmd_args);
372380 } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "llvm-ints")) {
373381 return cmdDumpLlvmInts(gpa, arena, cmd_args);
374382 } else {
......@@ -735,7 +743,7 @@ const ArgMode = union(enum) {
735743const Listen = union(enum) {
736744 none,
737745 stdio: if (dev.env.supports(.stdio_listen)) void else noreturn,
738 ip4: if (dev.env.supports(.network_listen)) std.net.Ip4Address else noreturn,
746 ip4: if (dev.env.supports(.network_listen)) Io.net.Ip4Address else noreturn,
739747};
740748
741749const ArgsIterator = struct {
......@@ -792,6 +800,7 @@ const CliModule = struct {
792800fn buildOutputType(
793801 gpa: Allocator,
794802 arena: Allocator,
803 io: Io,
795804 all_args: []const []const u8,
796805 arg_mode: ArgMode,
797806) !void {
......@@ -1328,7 +1337,7 @@ fn buildOutputType(
13281337 const host, const port_text = mem.cutScalar(u8, next_arg, ':') orelse .{ next_arg, "14735" };
13291338 const port = std.fmt.parseInt(u16, port_text, 10) catch |err|
13301339 fatal("invalid port number: '{s}': {s}", .{ port_text, @errorName(err) });
1331 listen = .{ .ip4 = std.net.Ip4Address.parse(host, port) catch |err|
1340 listen = .{ .ip4 = Io.net.Ip4Address.parse(host, port) catch |err|
13321341 fatal("invalid host: '{s}': {s}", .{ host, @errorName(err) }) };
13331342 }
13341343 } else if (mem.eql(u8, arg, "--listen=-")) {
......@@ -3017,7 +3026,7 @@ fn buildOutputType(
30173026 create_module.opts.emit_bin = emit_bin != .no;
30183027 create_module.opts.any_c_source_files = create_module.c_source_files.items.len != 0;
30193028
3020 const main_mod = try createModule(gpa, arena, &create_module, 0, null, color);
3029 const main_mod = try createModule(gpa, arena, io, &create_module, 0, null, color);
30213030 for (create_module.modules.keys(), create_module.modules.values()) |key, cli_mod| {
30223031 if (cli_mod.resolved == null)
30233032 fatal("module '{s}' declared but not used", .{key});
......@@ -3311,7 +3320,7 @@ fn buildOutputType(
33113320 var file_writer = f.writer(&.{});
33123321 var buffer: [1000]u8 = undefined;
33133322 var hasher = file_writer.interface.hashed(Cache.Hasher.init("0123456789abcdef"), &buffer);
3314 var stdin_reader = fs.File.stdin().readerStreaming(&.{});
3323 var stdin_reader = fs.File.stdin().readerStreaming(io, &.{});
33153324 _ = hasher.writer.sendFileAll(&stdin_reader, .unlimited) catch |err| switch (err) {
33163325 error.WriteFailed => fatal("failed to write {s}: {t}", .{ dump_path, file_writer.err.? }),
33173326 else => fatal("failed to pipe stdin to {s}: {t}", .{ dump_path, err }),
......@@ -3367,7 +3376,7 @@ fn buildOutputType(
33673376 try create_module.rpath_list.appendSlice(arena, rpath_dedup.keys());
33683377
33693378 var create_diag: Compilation.CreateDiagnostic = undefined;
3370 const comp = Compilation.create(gpa, arena, &create_diag, .{
3379 const comp = Compilation.create(gpa, arena, io, &create_diag, .{
33713380 .dirs = dirs,
33723381 .thread_pool = &thread_pool,
33733382 .self_exe_path = switch (native_os) {
......@@ -3542,7 +3551,7 @@ fn buildOutputType(
35423551 switch (listen) {
35433552 .none => {},
35443553 .stdio => {
3545 var stdin_reader = fs.File.stdin().reader(&stdin_buffer);
3554 var stdin_reader = fs.File.stdin().reader(io, &stdin_buffer);
35463555 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
35473556 try serve(
35483557 comp,
......@@ -3557,22 +3566,22 @@ fn buildOutputType(
35573566 return cleanExit();
35583567 },
35593568 .ip4 => |ip4_addr| {
3560 const addr: std.net.Address = .{ .in = ip4_addr };
3569 const addr: Io.net.IpAddress = .{ .ip4 = ip4_addr };
35613570
3562 var server = try addr.listen(.{
3571 var server = try addr.listen(io, .{
35633572 .reuse_address = true,
35643573 });
3565 defer server.deinit();
3574 defer server.deinit(io);
35663575
3567 const conn = try server.accept();
3568 defer conn.stream.close();
3576 var stream = try server.accept(io);
3577 defer stream.close(io);
35693578
3570 var input = conn.stream.reader(&stdin_buffer);
3571 var output = conn.stream.writer(&stdout_buffer);
3579 var input = stream.reader(io, &stdin_buffer);
3580 var output = stream.writer(io, &stdout_buffer);
35723581
35733582 try serve(
35743583 comp,
3575 input.interface(),
3584 &input.interface,
35763585 &output.interface,
35773586 test_exec_args.items,
35783587 self_exe_path,
......@@ -3646,6 +3655,7 @@ fn buildOutputType(
36463655 comp,
36473656 gpa,
36483657 arena,
3658 io,
36493659 test_exec_args.items,
36503660 self_exe_path,
36513661 arg_mode,
......@@ -3704,6 +3714,7 @@ const CreateModule = struct {
37043714fn createModule(
37053715 gpa: Allocator,
37063716 arena: Allocator,
3717 io: Io,
37073718 create_module: *CreateModule,
37083719 index: usize,
37093720 parent: ?*Package.Module,
......@@ -3777,7 +3788,7 @@ fn createModule(
37773788 }
37783789
37793790 const target_query = std.zig.parseTargetQueryOrReportFatalError(arena, target_parse_options);
3780 const target = std.zig.resolveTargetQueryOrFatal(target_query);
3791 const target = std.zig.resolveTargetQueryOrFatal(io, target_query);
37813792 break :t .{
37823793 .result = target,
37833794 .is_native_os = target_query.isNativeOs(),
......@@ -4022,7 +4033,7 @@ fn createModule(
40224033 for (cli_mod.deps) |dep| {
40234034 const dep_index = create_module.modules.getIndex(dep.value) orelse
40244035 fatal("module '{s}' depends on non-existent module '{s}'", .{ name, dep.key });
4025 const dep_mod = try createModule(gpa, arena, create_module, dep_index, mod, color);
4036 const dep_mod = try createModule(gpa, arena, io, create_module, dep_index, mod, color);
40264037 try mod.deps.put(arena, dep.key, dep_mod);
40274038 }
40284039
......@@ -4039,8 +4050,8 @@ fn saveState(comp: *Compilation, incremental: bool) void {
40394050
40404051fn serve(
40414052 comp: *Compilation,
4042 in: *std.Io.Reader,
4043 out: *std.Io.Writer,
4053 in: *Io.Reader,
4054 out: *Io.Writer,
40444055 test_exec_args: []const ?[]const u8,
40454056 self_exe_path: ?[]const u8,
40464057 arg_mode: ArgMode,
......@@ -4126,6 +4137,7 @@ fn serve(
41264137 // comp,
41274138 // gpa,
41284139 // arena,
4140 // io,
41294141 // test_exec_args,
41304142 // self_exe_path.?,
41314143 // arg_mode,
......@@ -4280,6 +4292,7 @@ fn runOrTest(
42804292 comp: *Compilation,
42814293 gpa: Allocator,
42824294 arena: Allocator,
4295 io: Io,
42834296 test_exec_args: []const ?[]const u8,
42844297 self_exe_path: []const u8,
42854298 arg_mode: ArgMode,
......@@ -4334,7 +4347,7 @@ fn runOrTest(
43344347 std.debug.lockStdErr();
43354348 const err = process.execve(gpa, argv.items, &env_map);
43364349 std.debug.unlockStdErr();
4337 try warnAboutForeignBinaries(arena, arg_mode, target, link_libc);
4350 try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc);
43384351 const cmd = try std.mem.join(arena, " ", argv.items);
43394352 fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd });
43404353 } else if (process.can_spawn) {
......@@ -4355,7 +4368,7 @@ fn runOrTest(
43554368 break :t child.spawnAndWait();
43564369 };
43574370 const term = term_result catch |err| {
4358 try warnAboutForeignBinaries(arena, arg_mode, target, link_libc);
4371 try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc);
43594372 const cmd = try std.mem.join(arena, " ", argv.items);
43604373 fatal("the following command failed with '{s}':\n{s}", .{ @errorName(err), cmd });
43614374 };
......@@ -4521,6 +4534,8 @@ fn cmdTranslateC(
45214534) !void {
45224535 dev.check(.translate_c_command);
45234536
4537 const io = comp.io;
4538
45244539 assert(comp.c_source_files.len == 1);
45254540 const c_source_file = comp.c_source_files[0];
45264541
......@@ -4584,7 +4599,7 @@ fn cmdTranslateC(
45844599 };
45854600 defer zig_file.close();
45864601 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
4587 var file_reader = zig_file.reader(&.{});
4602 var file_reader = zig_file.reader(io, &.{});
45884603 _ = try stdout_writer.interface.sendFileAll(&file_reader, .unlimited);
45894604 try stdout_writer.interface.flush();
45904605 return cleanExit();
......@@ -4594,11 +4609,12 @@ fn cmdTranslateC(
45944609pub fn translateC(
45954610 gpa: Allocator,
45964611 arena: Allocator,
4612 io: Io,
45974613 argv: []const []const u8,
45984614 prog_node: std.Progress.Node,
45994615 capture: ?*[]u8,
46004616) !void {
4601 try jitCmd(gpa, arena, argv, .{
4617 try jitCmd(gpa, arena, io, argv, .{
46024618 .cmd_name = "translate-c",
46034619 .root_src_path = "translate-c/main.zig",
46044620 .depend_on_aro = true,
......@@ -4755,7 +4771,7 @@ test sanitizeExampleName {
47554771 try std.testing.expectEqualStrings("test_project", try sanitizeExampleName(arena, "test project"));
47564772}
47574773
4758fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4774fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !void {
47594775 dev.check(.build_command);
47604776
47614777 var build_file: ?[]const u8 = null;
......@@ -4983,7 +4999,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
49834999 .arch_os_abi = triple,
49845000 });
49855001 break :t .{
4986 .result = std.zig.resolveTargetQueryOrFatal(target_query),
5002 .result = std.zig.resolveTargetQueryOrFatal(io, target_query),
49875003 .is_native_os = false,
49885004 .is_native_abi = false,
49895005 .is_explicit_dynamic_linker = false,
......@@ -4991,7 +5007,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
49915007 }
49925008 }
49935009 break :t .{
4994 .result = std.zig.resolveTargetQueryOrFatal(.{}),
5010 .result = std.zig.resolveTargetQueryOrFatal(io, .{}),
49955011 .is_native_os = true,
49965012 .is_native_abi = true,
49975013 .is_explicit_dynamic_linker = false,
......@@ -5046,8 +5062,9 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
50465062 // Prevents bootstrap from depending on a bunch of unnecessary stuff.
50475063 var http_client: if (dev.env.supports(.fetch_command)) std.http.Client else struct {
50485064 allocator: Allocator,
5065 io: Io,
50495066 fn deinit(_: @This()) void {}
5050 } = .{ .allocator = gpa };
5067 } = .{ .allocator = gpa, .io = io };
50515068 defer http_client.deinit();
50525069
50535070 var unlazy_set: Package.Fetch.JobQueue.UnlazySet = .{};
......@@ -5139,6 +5156,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
51395156
51405157 var fetch: Package.Fetch = .{
51415158 .arena = std.heap.ArenaAllocator.init(gpa),
5159 .io = io,
51425160 .location = .{ .relative_path = phantom_package_root },
51435161 .location_tok = 0,
51445162 .hash_tok = .none,
......@@ -5261,7 +5279,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
52615279 try root_mod.deps.put(arena, "@build", build_mod);
52625280
52635281 var create_diag: Compilation.CreateDiagnostic = undefined;
5264 const comp = Compilation.create(gpa, arena, &create_diag, .{
5282 const comp = Compilation.create(gpa, arena, io, &create_diag, .{
52655283 .libc_installation = libc_installation,
52665284 .dirs = dirs,
52675285 .root_name = "build",
......@@ -5400,6 +5418,7 @@ const JitCmdOptions = struct {
54005418fn jitCmd(
54015419 gpa: Allocator,
54025420 arena: Allocator,
5421 io: Io,
54035422 args: []const []const u8,
54045423 options: JitCmdOptions,
54055424) !void {
......@@ -5412,7 +5431,7 @@ fn jitCmd(
54125431
54135432 const target_query: std.Target.Query = .{};
54145433 const resolved_target: Package.Module.ResolvedTarget = .{
5415 .result = std.zig.resolveTargetQueryOrFatal(target_query),
5434 .result = std.zig.resolveTargetQueryOrFatal(io, target_query),
54165435 .is_native_os = true,
54175436 .is_native_abi = true,
54185437 .is_explicit_dynamic_linker = false,
......@@ -5504,7 +5523,7 @@ fn jitCmd(
55045523 }
55055524
55065525 var create_diag: Compilation.CreateDiagnostic = undefined;
5507 const comp = Compilation.create(gpa, arena, &create_diag, .{
5526 const comp = Compilation.create(gpa, arena, io, &create_diag, .{
55085527 .dirs = dirs,
55095528 .root_name = options.cmd_name,
55105529 .config = config,
......@@ -5584,7 +5603,7 @@ fn jitCmd(
55845603 try child.spawn();
55855604
55865605 if (options.capture) |ptr| {
5587 var stdout_reader = child.stdout.?.readerStreaming(&.{});
5606 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
55885607 ptr.* = try stdout_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32)));
55895608 }
55905609
......@@ -6039,10 +6058,7 @@ const usage_ast_check =
60396058 \\
60406059;
60416060
6042fn cmdAstCheck(
6043 arena: Allocator,
6044 args: []const []const u8,
6045) !void {
6061fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {
60466062 dev.check(.ast_check_command);
60476063
60486064 const Zir = std.zig.Zir;
......@@ -6090,7 +6106,7 @@ fn cmdAstCheck(
60906106 };
60916107 } else fs.File.stdin();
60926108 defer if (zig_source_path != null) f.close();
6093 var file_reader: fs.File.Reader = f.reader(&stdin_buffer);
6109 var file_reader: fs.File.Reader = f.reader(io, &stdin_buffer);
60946110 break :s std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err| {
60956111 fatal("unable to load file '{s}' for ast-check: {s}", .{ display_path, @errorName(err) });
60966112 };
......@@ -6209,7 +6225,7 @@ fn cmdAstCheck(
62096225 }
62106226}
62116227
6212fn cmdDetectCpu(args: []const []const u8) !void {
6228fn cmdDetectCpu(io: Io, args: []const []const u8) !void {
62136229 dev.check(.detect_cpu_command);
62146230
62156231 const detect_cpu_usage =
......@@ -6254,7 +6270,7 @@ fn cmdDetectCpu(args: []const []const u8) !void {
62546270 const cpu = try detectNativeCpuWithLLVM(builtin.cpu.arch, name, features);
62556271 try printCpu(cpu);
62566272 } else {
6257 const host_target = std.zig.resolveTargetQueryOrFatal(.{});
6273 const host_target = std.zig.resolveTargetQueryOrFatal(io, .{});
62586274 try printCpu(host_target.cpu);
62596275 }
62606276}
......@@ -6385,10 +6401,7 @@ fn cmdDumpLlvmInts(
63856401}
63866402
63876403/// This is only enabled for debug builds.
6388fn cmdDumpZir(
6389 arena: Allocator,
6390 args: []const []const u8,
6391) !void {
6404fn cmdDumpZir(arena: Allocator, io: Io, args: []const []const u8) !void {
63926405 dev.check(.dump_zir_command);
63936406
63946407 const Zir = std.zig.Zir;
......@@ -6400,7 +6413,7 @@ fn cmdDumpZir(
64006413 };
64016414 defer f.close();
64026415
6403 const zir = try Zcu.loadZirCache(arena, f);
6416 const zir = try Zcu.loadZirCache(arena, io, f);
64046417 var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer);
64056418 const stdout_bw = &stdout_writer.interface;
64066419 {
......@@ -6432,10 +6445,7 @@ fn cmdDumpZir(
64326445}
64336446
64346447/// This is only enabled for debug builds.
6435fn cmdChangelist(
6436 arena: Allocator,
6437 args: []const []const u8,
6438) !void {
6448fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void {
64396449 dev.check(.changelist_command);
64406450
64416451 const color: Color = .auto;
......@@ -6448,7 +6458,7 @@ fn cmdChangelist(
64486458 var f = fs.cwd().openFile(old_source_path, .{}) catch |err|
64496459 fatal("unable to open old source file '{s}': {s}", .{ old_source_path, @errorName(err) });
64506460 defer f.close();
6451 var file_reader: fs.File.Reader = f.reader(&stdin_buffer);
6461 var file_reader: fs.File.Reader = f.reader(io, &stdin_buffer);
64526462 break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err|
64536463 fatal("unable to read old source file '{s}': {s}", .{ old_source_path, @errorName(err) });
64546464 };
......@@ -6456,7 +6466,7 @@ fn cmdChangelist(
64566466 var f = fs.cwd().openFile(new_source_path, .{}) catch |err|
64576467 fatal("unable to open new source file '{s}': {s}", .{ new_source_path, @errorName(err) });
64586468 defer f.close();
6459 var file_reader: fs.File.Reader = f.reader(&stdin_buffer);
6469 var file_reader: fs.File.Reader = f.reader(io, &stdin_buffer);
64606470 break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err|
64616471 fatal("unable to read new source file '{s}': {s}", .{ new_source_path, @errorName(err) });
64626472 };
......@@ -6521,13 +6531,14 @@ fn prefixedIntArg(arg: []const u8, prefix: []const u8) ?u64 {
65216531}
65226532
65236533fn warnAboutForeignBinaries(
6534 io: Io,
65246535 arena: Allocator,
65256536 arg_mode: ArgMode,
65266537 target: *const std.Target,
65276538 link_libc: bool,
65286539) !void {
65296540 const host_query: std.Target.Query = .{};
6530 const host_target = std.zig.resolveTargetQueryOrFatal(host_query);
6541 const host_target = std.zig.resolveTargetQueryOrFatal(io, host_query);
65316542
65326543 switch (std.zig.system.getExternalExecutor(&host_target, target, .{ .link_libc = link_libc })) {
65336544 .native => return,
......@@ -6812,6 +6823,7 @@ const usage_fetch =
68126823fn cmdFetch(
68136824 gpa: Allocator,
68146825 arena: Allocator,
6826 io: Io,
68156827 args: []const []const u8,
68166828) !void {
68176829 dev.check(.fetch_command);
......@@ -6867,7 +6879,7 @@ fn cmdFetch(
68676879 try thread_pool.init(.{ .allocator = gpa });
68686880 defer thread_pool.deinit();
68696881
6870 var http_client: std.http.Client = .{ .allocator = gpa };
6882 var http_client: std.http.Client = .{ .allocator = gpa, .io = io };
68716883 defer http_client.deinit();
68726884
68736885 try http_client.initDefaultProxies(arena);
......@@ -6900,6 +6912,7 @@ fn cmdFetch(
69006912
69016913 var fetch: Package.Fetch = .{
69026914 .arena = std.heap.ArenaAllocator.init(gpa),
6915 .io = io,
69036916 .location = .{ .path_or_url = path_or_url },
69046917 .location_tok = 0,
69056918 .hash_tok = .none,
......@@ -7080,7 +7093,7 @@ fn cmdFetch(
70807093 try fixups.append_string_after_node.put(gpa, manifest.version_node, dependencies_text);
70817094 }
70827095
7083 var aw: std.Io.Writer.Allocating = .init(gpa);
7096 var aw: Io.Writer.Allocating = .init(gpa);
70847097 defer aw.deinit();
70857098 try ast.render(gpa, &aw.writer, fixups);
70867099 const rendered = aw.written();
src/print_env.zig+1-2
......@@ -14,6 +14,7 @@ pub fn cmdEnv(
1414 .wasi => std.fs.wasi.Preopens,
1515 else => void,
1616 },
17 host: *const std.Target,
1718) !void {
1819 const override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena);
1920 const override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena);
......@@ -38,8 +39,6 @@ pub fn cmdEnv(
3839 const zig_lib_dir = dirs.zig_lib.path orelse "";
3940 const zig_std_dir = try dirs.zig_lib.join(arena, &.{"std"});
4041 const global_cache_dir = dirs.global_cache.path orelse "";
41
42 const host = try std.zig.system.resolveTargetQuery(.{});
4342 const triple = try host.zigTriple(arena);
4443
4544 var serializer: std.zon.Serializer = .{ .writer = out };
test/src/Cases.zig+6-11
......@@ -370,6 +370,10 @@ fn addFromDirInner(
370370 const resolved_target = b.resolveTargetQuery(target_query);
371371 const target = &resolved_target.result;
372372 for (backends) |backend| {
373 if (backend == .selfhosted and target.cpu.arch == .wasm32) {
374 // https://github.com/ziglang/zig/issues/25684
375 continue;
376 }
373377 if (backend == .selfhosted and
374378 target.cpu.arch != .aarch64 and target.cpu.arch != .wasm32 and target.cpu.arch != .x86_64 and target.cpu.arch != .spirv64)
375379 {
......@@ -455,8 +459,7 @@ pub fn lowerToBuildSteps(
455459 parent_step: *std.Build.Step,
456460 options: CaseTestOptions,
457461) void {
458 const host = std.zig.system.resolveTargetQuery(.{}) catch |err|
459 std.debug.panic("unable to detect native host: {s}\n", .{@errorName(err)});
462 const host = b.resolveTargetQuery(.{});
460463 const cases_dir_path = b.build_root.join(b.allocator, &.{ "test", "cases" }) catch @panic("OOM");
461464
462465 for (self.cases.items) |case| {
......@@ -587,7 +590,7 @@ pub fn lowerToBuildSteps(
587590 },
588591 .Execution => |expected_stdout| no_exec: {
589592 const run = if (case.target.result.ofmt == .c) run_step: {
590 if (getExternalExecutor(&host, &case.target.result, .{ .link_libc = true }) != .native) {
593 if (getExternalExecutor(&host.result, &case.target.result, .{ .link_libc = true }) != .native) {
591594 // We wouldn't be able to run the compiled C code.
592595 break :no_exec;
593596 }
......@@ -972,14 +975,6 @@ const TestManifest = struct {
972975 }
973976};
974977
975fn resolveTargetQuery(query: std.Target.Query) std.Build.ResolvedTarget {
976 return .{
977 .query = query,
978 .target = std.zig.system.resolveTargetQuery(query) catch
979 @panic("unable to resolve target query"),
980 };
981}
982
983978fn knownFileExtension(filename: []const u8) bool {
984979 // List taken from `Compilation.classifyFileExt` in the compiler.
985980 for ([_][]const u8{
test/src/convert-stack-trace.zig+7-1
......@@ -32,6 +32,12 @@ pub fn main() !void {
3232 const args = try std.process.argsAlloc(arena);
3333 if (args.len != 2) std.process.fatal("usage: convert-stack-trace path/to/test/output", .{});
3434
35 const gpa = arena;
36
37 var threaded: std.Io.Threaded = .init(gpa);
38 defer threaded.deinit();
39 const io = threaded.io();
40
3541 var read_buf: [1024]u8 = undefined;
3642 var write_buf: [1024]u8 = undefined;
3743
......@@ -40,7 +46,7 @@ pub fn main() !void {
4046
4147 const out_file: std.fs.File = .stdout();
4248
43 var in_fr = in_file.reader(&read_buf);
49 var in_fr = in_file.reader(io, &read_buf);
4450 var out_fw = out_file.writer(&write_buf);
4551
4652 const w = &out_fw.interface;
test/standalone/child_process/child.zig+10-3
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23
34// 42 is expected by parent; other values result in test failure
45var exit_code: u8 = 42;
......@@ -6,12 +7,17 @@ var exit_code: u8 = 42;
67pub fn main() !void {
78 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
89 const arena = arena_state.allocator();
9 try run(arena);
10
11 var threaded: std.Io.Threaded = .init(arena);
12 defer threaded.deinit();
13 const io = threaded.io();
14
15 try run(arena, io);
1016 arena_state.deinit();
1117 std.process.exit(exit_code);
1218}
1319
14fn run(allocator: std.mem.Allocator) !void {
20fn run(allocator: std.mem.Allocator, io: Io) !void {
1521 var args = try std.process.argsWithAllocator(allocator);
1622 defer args.deinit();
1723 _ = args.next() orelse unreachable; // skip binary name
......@@ -33,7 +39,8 @@ fn run(allocator: std.mem.Allocator) !void {
3339 const hello_stdin = "hello from stdin";
3440 var buf: [hello_stdin.len]u8 = undefined;
3541 const stdin: std.fs.File = .stdin();
36 const n = try stdin.readAll(&buf);
42 var reader = stdin.reader(io, &.{});
43 const n = try reader.interface.readSliceShort(&buf);
3744 if (!std.mem.eql(u8, buf[0..n], hello_stdin)) {
3845 testError("stdin: '{s}'; want '{s}'", .{ buf[0..n], hello_stdin });
3946 }
test/standalone/child_process/main.zig+5-1
......@@ -20,6 +20,10 @@ pub fn main() !void {
2020 };
2121 defer if (needs_free) gpa.free(child_path);
2222
23 var threaded: std.Io.Threaded = .init(gpa);
24 defer threaded.deinit();
25 const io = threaded.io();
26
2327 var child = std.process.Child.init(&.{ child_path, "hello arg" }, gpa);
2428 child.stdin_behavior = .Pipe;
2529 child.stdout_behavior = .Pipe;
......@@ -32,7 +36,7 @@ pub fn main() !void {
3236
3337 const hello_stdout = "hello from stdout";
3438 var buf: [hello_stdout.len]u8 = undefined;
35 var stdout_reader = child.stdout.?.readerStreaming(&.{});
39 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
3640 const n = try stdout_reader.interface.readSliceShort(&buf);
3741 if (!std.mem.eql(u8, buf[0..n], hello_stdout)) {
3842 testError("child stdout: '{s}'; want '{s}'", .{ buf[0..n], hello_stdout });
test/standalone/coff_dwarf/main.zig+5-1
......@@ -11,10 +11,14 @@ pub fn main() void {
1111 var di: std.debug.SelfInfo = .init;
1212 defer di.deinit(gpa);
1313
14 var threaded: std.Io.Threaded = .init(gpa);
15 defer threaded.deinit();
16 const io = threaded.io();
17
1418 var add_addr: usize = undefined;
1519 _ = add(1, 2, &add_addr);
1620
17 const symbol = di.getSymbol(gpa, add_addr) catch |err| fatal("failed to get symbol: {t}", .{err});
21 const symbol = di.getSymbol(gpa, io, add_addr) catch |err| fatal("failed to get symbol: {t}", .{err});
1822 defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
1923
2024 if (symbol.name == null) fatal("failed to resolve symbol name", .{});
test/standalone/libfuzzer/main.zig+5-1
......@@ -15,6 +15,10 @@ pub fn main() !void {
1515 defer args.deinit();
1616 _ = args.skip(); // executable name
1717
18 var threaded: std.Io.Threaded = .init(gpa);
19 defer threaded.deinit();
20 const io = threaded.io();
21
1822 const cache_dir_path = args.next() orelse @panic("expected cache directory path argument");
1923 var cache_dir = try std.fs.cwd().openDir(cache_dir_path, .{});
2024 defer cache_dir.close();
......@@ -30,7 +34,7 @@ pub fn main() !void {
3034 defer coverage_file.close();
3135
3236 var read_buf: [@sizeOf(abi.SeenPcsHeader)]u8 = undefined;
33 var r = coverage_file.reader(&read_buf);
37 var r = coverage_file.reader(io, &read_buf);
3438 const pcs_header = r.interface.takeStruct(abi.SeenPcsHeader, native_endian) catch return r.err.?;
3539
3640 if (pcs_header.pcs_len == 0)
test/standalone/posix/sigaction.zig+10-14
......@@ -17,12 +17,12 @@ fn test_sigaction() !void {
1717 return; // https://github.com/ziglang/zig/issues/15381
1818 }
1919
20 const test_signo = std.posix.SIG.URG; // URG only because it is ignored by default in debuggers
20 const test_signo: std.posix.SIG = .URG; // URG only because it is ignored by default in debuggers
2121
2222 const S = struct {
2323 var handler_called_count: u32 = 0;
2424
25 fn handler(sig: i32, info: *const std.posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
25 fn handler(sig: std.posix.SIG, info: *const std.posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
2626 _ = ctx_ptr;
2727 // Check that we received the correct signal.
2828 const info_sig = switch (native_os) {
......@@ -80,20 +80,18 @@ fn test_sigaction() !void {
8080}
8181
8282fn test_sigset_bits() !void {
83 const NO_SIG: i32 = 0;
84
8583 const S = struct {
86 var expected_sig: i32 = undefined;
87 var seen_sig: i32 = NO_SIG;
84 var expected_sig: std.posix.SIG = undefined;
85 var seen_sig: ?std.posix.SIG = null;
8886
89 fn handler(sig: i32, info: *const std.posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
87 fn handler(sig: std.posix.SIG, info: *const std.posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) void {
9088 _ = ctx_ptr;
9189
9290 const info_sig = switch (native_os) {
9391 .netbsd => info.info.signo,
9492 else => info.signo,
9593 };
96 if (seen_sig == NO_SIG and sig == expected_sig and sig == info_sig) {
94 if (seen_sig == null and sig == expected_sig and sig == info_sig) {
9795 seen_sig = sig;
9896 }
9997 }
......@@ -107,11 +105,9 @@ fn test_sigset_bits() !void {
107105 // big-endian), try sending a blocked signal to make sure the mask matches the
108106 // signal. (Send URG and CHLD because they're ignored by default in the
109107 // debugger, vs. USR1 or other named signals)
110 inline for ([_]i32{ std.posix.SIG.URG, std.posix.SIG.CHLD, 62, 94, 126 }) |test_signo| {
111 if (test_signo >= std.posix.NSIG) continue;
112
108 inline for ([_]std.posix.SIG{ .URG, .CHLD }) |test_signo| {
113109 S.expected_sig = test_signo;
114 S.seen_sig = NO_SIG;
110 S.seen_sig = null;
115111
116112 const sa: std.posix.Sigaction = .{
117113 .handler = .{ .sigaction = &S.handler },
......@@ -135,14 +131,14 @@ fn test_sigset_bits() !void {
135131 switch (std.posix.errno(rc)) {
136132 .SUCCESS => {
137133 // See that the signal is blocked, then unblocked
138 try std.testing.expectEqual(NO_SIG, S.seen_sig);
134 try std.testing.expectEqual(null, S.seen_sig);
139135 std.posix.sigprocmask(std.posix.SIG.UNBLOCK, &block_one, null);
140136 try std.testing.expectEqual(test_signo, S.seen_sig);
141137 },
142138 .INVAL => {
143139 // Signal won't get delviered. Just clean up.
144140 std.posix.sigprocmask(std.posix.SIG.UNBLOCK, &block_one, null);
145 try std.testing.expectEqual(NO_SIG, S.seen_sig);
141 try std.testing.expectEqual(null, S.seen_sig);
146142 },
147143 else => |errno| return std.posix.unexpectedErrno(errno),
148144 }
test/standalone/simple/cat/main.zig+6-2
......@@ -9,6 +9,10 @@ pub fn main() !void {
99 defer arena_instance.deinit();
1010 const arena = arena_instance.allocator();
1111
12 var threaded: std.Io.Threaded = .init(arena);
13 defer threaded.deinit();
14 const io = threaded.io();
15
1216 const args = try std.process.argsAlloc(arena);
1317
1418 const exe = args[0];
......@@ -16,7 +20,7 @@ pub fn main() !void {
1620 var stdout_buffer: [4096]u8 = undefined;
1721 var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer);
1822 const stdout = &stdout_writer.interface;
19 var stdin_reader = fs.File.stdin().readerStreaming(&.{});
23 var stdin_reader = fs.File.stdin().readerStreaming(io, &.{});
2024
2125 const cwd = fs.cwd();
2226
......@@ -32,7 +36,7 @@ pub fn main() !void {
3236 defer file.close();
3337
3438 catted_anything = true;
35 var file_reader = file.reader(&.{});
39 var file_reader = file.reader(io, &.{});
3640 _ = try stdout.sendFileAll(&file_reader, .unlimited);
3741 try stdout.flush();
3842 }
test/standalone/test_obj_link_run/build.zig+1
......@@ -11,6 +11,7 @@ pub fn build(b: *std.Build) void {
1111 if (is_windows) {
1212 test_obj.linkSystemLibrary("ntdll");
1313 test_obj.linkSystemLibrary("kernel32");
14 test_obj.linkSystemLibrary("ws2_32");
1415 }
1516
1617 const test_exe_mod = b.createModule(.{
test/standalone/windows_spawn/main.zig+1-1
......@@ -224,7 +224,7 @@ fn renameExe(dir: std.fs.Dir, old_sub_path: []const u8, new_sub_path: []const u8
224224 error.AccessDenied => {
225225 if (attempt == 13) return error.AccessDenied;
226226 // give the kernel a chance to finish closing the executable handle
227 std.os.windows.kernel32.Sleep(@as(u32, 1) << attempt >> 1);
227 _ = std.os.windows.kernel32.SleepEx(@as(u32, 1) << attempt >> 1, std.os.windows.FALSE);
228228 attempt += 1;
229229 continue;
230230 },
tools/docgen.zig+7-1
......@@ -36,6 +36,12 @@ pub fn main() !void {
3636 var args_it = try process.argsWithAllocator(arena);
3737 if (!args_it.skip()) @panic("expected self arg");
3838
39 const gpa = arena;
40
41 var threaded: std.Io.Threaded = .init(gpa);
42 defer threaded.deinit();
43 const io = threaded.io();
44
3945 var opt_code_dir: ?[]const u8 = null;
4046 var opt_input: ?[]const u8 = null;
4147 var opt_output: ?[]const u8 = null;
......@@ -77,7 +83,7 @@ pub fn main() !void {
7783 var code_dir = try fs.cwd().openDir(code_dir_path, .{});
7884 defer code_dir.close();
7985
80 var in_file_reader = in_file.reader(&.{});
86 var in_file_reader = in_file.reader(io, &.{});
8187 const input_file_bytes = try in_file_reader.interface.allocRemaining(arena, .limited(max_doc_file_size));
8288
8389 var tokenizer = Tokenizer.init(input_path, input_file_bytes);
tools/doctest.zig+26-18
......@@ -1,5 +1,8 @@
11const builtin = @import("builtin");
2
23const std = @import("std");
4const Io = std.Io;
5const Writer = std.Io.Writer;
36const fatal = std.process.fatal;
47const mem = std.mem;
58const fs = std.fs;
......@@ -7,7 +10,6 @@ const process = std.process;
710const Allocator = std.mem.Allocator;
811const testing = std.testing;
912const getExternalExecutor = std.zig.system.getExternalExecutor;
10const Writer = std.Io.Writer;
1113
1214const max_doc_file_size = 10 * 1024 * 1024;
1315
......@@ -36,6 +38,12 @@ pub fn main() !void {
3638 var args_it = try process.argsWithAllocator(arena);
3739 if (!args_it.skip()) fatal("missing argv[0]", .{});
3840
41 const gpa = arena;
42
43 var threaded: std.Io.Threaded = .init(gpa);
44 defer threaded.deinit();
45 const io = threaded.io();
46
3947 var opt_input: ?[]const u8 = null;
4048 var opt_output: ?[]const u8 = null;
4149 var opt_zig: ?[]const u8 = null;
......@@ -93,6 +101,7 @@ pub fn main() !void {
93101 try printSourceBlock(arena, out, source, fs.path.basename(input_path));
94102 try printOutput(
95103 arena,
104 io,
96105 out,
97106 code,
98107 tmp_dir_path,
......@@ -109,6 +118,7 @@ pub fn main() !void {
109118
110119fn printOutput(
111120 arena: Allocator,
121 io: Io,
112122 out: *Writer,
113123 code: Code,
114124 /// Relative to this process' cwd.
......@@ -123,11 +133,11 @@ fn printOutput(
123133 var env_map = try process.getEnvMap(arena);
124134 try env_map.put("CLICOLOR_FORCE", "1");
125135
126 const host = try std.zig.system.resolveTargetQuery(.{});
136 const host = try std.zig.system.resolveTargetQuery(io, .{});
127137 const obj_ext = builtin.object_format.fileExt(builtin.cpu.arch);
128138 const print = std.debug.print;
129139
130 var shell_buffer: std.Io.Writer.Allocating = .init(arena);
140 var shell_buffer: Writer.Allocating = .init(arena);
131141 defer shell_buffer.deinit();
132142 const shell_out = &shell_buffer.writer;
133143
......@@ -238,7 +248,7 @@ fn printOutput(
238248 const target_query = try std.Target.Query.parse(.{
239249 .arch_os_abi = code.target_str orelse "native",
240250 });
241 const target = try std.zig.system.resolveTargetQuery(target_query);
251 const target = try std.zig.system.resolveTargetQuery(io, target_query);
242252
243253 const path_to_exe = try std.fmt.allocPrint(arena, "./{s}{s}", .{
244254 code_name, target.exeFileExt(),
......@@ -316,9 +326,7 @@ fn printOutput(
316326 const target_query = try std.Target.Query.parse(.{
317327 .arch_os_abi = triple,
318328 });
319 const target = try std.zig.system.resolveTargetQuery(
320 target_query,
321 );
329 const target = try std.zig.system.resolveTargetQuery(io, target_query);
322330 switch (getExternalExecutor(&host, &target, .{
323331 .link_libc = code.link_libc,
324332 })) {
......@@ -1397,7 +1405,7 @@ test "printShell" {
13971405 \\</samp></pre></figure>
13981406 ;
13991407
1400 var buffer: std.Io.Writer.Allocating = .init(test_allocator);
1408 var buffer: Writer.Allocating = .init(test_allocator);
14011409 defer buffer.deinit();
14021410
14031411 try printShell(&buffer.writer, shell_out, false);
......@@ -1414,7 +1422,7 @@ test "printShell" {
14141422 \\</samp></pre></figure>
14151423 ;
14161424
1417 var buffer: std.Io.Writer.Allocating = .init(test_allocator);
1425 var buffer: Writer.Allocating = .init(test_allocator);
14181426 defer buffer.deinit();
14191427
14201428 try printShell(&buffer.writer, shell_out, false);
......@@ -1428,7 +1436,7 @@ test "printShell" {
14281436 \\</samp></pre></figure>
14291437 ;
14301438
1431 var buffer: std.Io.Writer.Allocating = .init(test_allocator);
1439 var buffer: Writer.Allocating = .init(test_allocator);
14321440 defer buffer.deinit();
14331441
14341442 try printShell(&buffer.writer, shell_out, false);
......@@ -1447,7 +1455,7 @@ test "printShell" {
14471455 \\</samp></pre></figure>
14481456 ;
14491457
1450 var buffer: std.Io.Writer.Allocating = .init(test_allocator);
1458 var buffer: Writer.Allocating = .init(test_allocator);
14511459 defer buffer.deinit();
14521460
14531461 try printShell(&buffer.writer, shell_out, false);
......@@ -1468,7 +1476,7 @@ test "printShell" {
14681476 \\</samp></pre></figure>
14691477 ;
14701478
1471 var buffer: std.Io.Writer.Allocating = .init(test_allocator);
1479 var buffer: Writer.Allocating = .init(test_allocator);
14721480 defer buffer.deinit();
14731481
14741482 try printShell(&buffer.writer, shell_out, false);
......@@ -1487,7 +1495,7 @@ test "printShell" {
14871495 \\</samp></pre></figure>
14881496 ;
14891497
1490 var buffer: std.Io.Writer.Allocating = .init(test_allocator);
1498 var buffer: Writer.Allocating = .init(test_allocator);
14911499 defer buffer.deinit();
14921500
14931501 try printShell(&buffer.writer, shell_out, false);
......@@ -1510,7 +1518,7 @@ test "printShell" {
15101518 \\</samp></pre></figure>
15111519 ;
15121520
1513 var buffer: std.Io.Writer.Allocating = .init(test_allocator);
1521 var buffer: Writer.Allocating = .init(test_allocator);
15141522 defer buffer.deinit();
15151523
15161524 try printShell(&buffer.writer, shell_out, false);
......@@ -1532,7 +1540,7 @@ test "printShell" {
15321540 \\</samp></pre></figure>
15331541 ;
15341542
1535 var buffer: std.Io.Writer.Allocating = .init(test_allocator);
1543 var buffer: Writer.Allocating = .init(test_allocator);
15361544 defer buffer.deinit();
15371545
15381546 try printShell(&buffer.writer, shell_out, false);
......@@ -1549,7 +1557,7 @@ test "printShell" {
15491557 \\</samp></pre></figure>
15501558 ;
15511559
1552 var buffer: std.Io.Writer.Allocating = .init(test_allocator);
1560 var buffer: Writer.Allocating = .init(test_allocator);
15531561 defer buffer.deinit();
15541562
15551563 try printShell(&buffer.writer, shell_out, false);
......@@ -1568,7 +1576,7 @@ test "printShell" {
15681576 \\</samp></pre></figure>
15691577 ;
15701578
1571 var buffer: std.Io.Writer.Allocating = .init(test_allocator);
1579 var buffer: Writer.Allocating = .init(test_allocator);
15721580 defer buffer.deinit();
15731581
15741582 try printShell(&buffer.writer, shell_out, false);
......@@ -1583,7 +1591,7 @@ test "printShell" {
15831591 \\</samp></pre></figure>
15841592 ;
15851593
1586 var buffer: std.Io.Writer.Allocating = .init(test_allocator);
1594 var buffer: Writer.Allocating = .init(test_allocator);
15871595 defer buffer.deinit();
15881596
15891597 try printShell(&buffer.writer, shell_out, false);
tools/fetch_them_macos_headers.zig+11-5
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23const fs = std.fs;
34const mem = std.mem;
45const process = std.process;
......@@ -85,8 +86,12 @@ pub fn main() anyerror!void {
8586 } else try argv.append(arg);
8687 }
8788
89 var threaded: Io.Threaded = .init(gpa);
90 defer threaded.deinit();
91 const io = threaded.io();
92
8893 const sysroot_path = sysroot orelse blk: {
89 const target = try std.zig.system.resolveTargetQuery(.{});
94 const target = try std.zig.system.resolveTargetQuery(io, .{});
9095 break :blk std.zig.system.darwin.getSdk(allocator, &target) orelse
9196 fatal("no SDK found; you can provide one explicitly with '--sysroot' flag", .{});
9297 };
......@@ -114,12 +119,13 @@ pub fn main() anyerror!void {
114119 .arch = arch,
115120 .os_ver = os_ver,
116121 };
117 try fetchTarget(allocator, argv.items, sysroot_path, target, version, tmp);
122 try fetchTarget(allocator, io, argv.items, sysroot_path, target, version, tmp);
118123 }
119124}
120125
121126fn fetchTarget(
122127 arena: Allocator,
128 io: Io,
123129 args: []const []const u8,
124130 sysroot: []const u8,
125131 target: Target,
......@@ -190,7 +196,7 @@ fn fetchTarget(
190196 var dirs = std.StringHashMap(fs.Dir).init(arena);
191197 try dirs.putNoClobber(".", dest_dir);
192198
193 var headers_list_file_reader = headers_list_file.reader(&.{});
199 var headers_list_file_reader = headers_list_file.reader(io, &.{});
194200 const headers_list_str = try headers_list_file_reader.interface.allocRemaining(arena, .unlimited);
195201 const prefix = "/usr/include";
196202
......@@ -263,8 +269,8 @@ const Version = struct {
263269
264270 pub fn format(
265271 v: Version,
266 writer: *std.Io.Writer,
267 ) std.Io.Writer.Error!void {
272 writer: *Io.Writer,
273 ) Io.Writer.Error!void {
268274 try writer.print("{d}.{d}.{d}", .{ v.major, v.minor, v.patch });
269275 }
270276};
tools/gen_macos_headers_c.zig+2-2
......@@ -33,7 +33,7 @@ pub fn main() anyerror!void {
3333
3434 if (positionals.items.len != 1) fatal("expected one positional argument: [dir]", .{});
3535
36 var dir = try std.fs.cwd().openDir(positionals.items[0], .{ .no_follow = true });
36 var dir = try std.fs.cwd().openDir(positionals.items[0], .{ .follow_symlinks = false });
3737 defer dir.close();
3838 var paths = std.array_list.Managed([]const u8).init(arena);
3939 try findHeaders(arena, dir, "", &paths);
......@@ -73,7 +73,7 @@ fn findHeaders(
7373 switch (entry.kind) {
7474 .directory => {
7575 const path = try std.fs.path.join(arena, &.{ prefix, entry.name });
76 var subdir = try dir.openDir(entry.name, .{ .no_follow = true });
76 var subdir = try dir.openDir(entry.name, .{ .follow_symlinks = false });
7777 defer subdir.close();
7878 try findHeaders(arena, subdir, path, paths);
7979 },
tools/generate_c_size_and_align_checks.zig+5-1
......@@ -39,8 +39,12 @@ pub fn main() !void {
3939 std.process.exit(1);
4040 }
4141
42 var threaded: std.Io.Threaded = .init(gpa);
43 defer threaded.deinit();
44 const io = threaded.io();
45
4246 const query = try std.Target.Query.parse(.{ .arch_os_abi = args[1] });
43 const target = try std.zig.system.resolveTargetQuery(query);
47 const target = try std.zig.system.resolveTargetQuery(io, query);
4448
4549 var buffer: [2000]u8 = undefined;
4650 var stdout_writer = std.fs.File.stdout().writerStreaming(&buffer);
tools/incr-check.zig+23-11
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const Io = std.Io;
23const Allocator = std.mem.Allocator;
34const Cache = std.Build.Cache;
45
......@@ -11,6 +12,12 @@ pub fn main() !void {
1112 defer arena_instance.deinit();
1213 const arena = arena_instance.allocator();
1314
15 const gpa = arena;
16
17 var threaded: Io.Threaded = .init(gpa);
18 defer threaded.deinit();
19 const io = threaded.io();
20
1421 var opt_zig_exe: ?[]const u8 = null;
1522 var opt_input_file_name: ?[]const u8 = null;
1623 var opt_lib_dir: ?[]const u8 = null;
......@@ -53,7 +60,7 @@ pub fn main() !void {
5360 const input_file_name = opt_input_file_name orelse fatal("missing input file\n{s}", .{usage});
5461
5562 const input_file_bytes = try std.fs.cwd().readFileAlloc(input_file_name, arena, .limited(std.math.maxInt(u32)));
56 const case = try Case.parse(arena, input_file_bytes);
63 const case = try Case.parse(arena, io, input_file_bytes);
5764
5865 // Check now: if there are any targets using the `cbe` backend, we need the lib dir.
5966 if (opt_lib_dir == null) {
......@@ -86,22 +93,21 @@ pub fn main() !void {
8693 else
8794 null;
8895
89 const host = try std.zig.system.resolveTargetQuery(.{});
96 const host = try std.zig.system.resolveTargetQuery(io, .{});
9097
9198 const debug_log_verbose = debug_zcu or debug_dwarf or debug_link;
9299
93100 for (case.targets) |target| {
94101 const target_prog_node = node: {
95102 var name_buf: [std.Progress.Node.max_name_len]u8 = undefined;
96 const name = std.fmt.bufPrint(&name_buf, "{s}-{s}", .{ target.query, @tagName(target.backend) }) catch &name_buf;
103 const name = std.fmt.bufPrint(&name_buf, "{s}-{t}", .{ target.query, target.backend }) catch &name_buf;
97104 break :node prog_node.start(name, case.updates.len);
98105 };
99106 defer target_prog_node.end();
100107
101108 if (debug_log_verbose) {
102 std.log.scoped(.status).info("target: '{s}-{s}'", .{ target.query, @tagName(target.backend) });
109 std.log.scoped(.status).info("target: '{s}-{t}'", .{ target.query, target.backend });
103110 }
104
105111 var child_args: std.ArrayListUnmanaged([]const u8) = .empty;
106112 try child_args.appendSlice(arena, &.{
107113 resolved_zig_exe,
......@@ -114,8 +120,10 @@ pub fn main() !void {
114120 ".local-cache",
115121 "--global-cache-dir",
116122 ".global-cache",
117 "--listen=-",
118123 });
124 if (target.resolved.os.tag == .windows) try child_args.append(arena, "-lws2_32");
125 try child_args.append(arena, "--listen=-");
126
119127 if (opt_resolved_lib_dir) |resolved_lib_dir| {
120128 try child_args.appendSlice(arena, &.{ "--zig-lib-dir", resolved_lib_dir });
121129 }
......@@ -167,8 +175,12 @@ pub fn main() !void {
167175 target.query,
168176 "-I",
169177 opt_resolved_lib_dir.?, // verified earlier
170 "-o",
171178 });
179
180 if (target.resolved.os.tag == .windows)
181 try cc_child_args.append(arena, "-lws2_32");
182
183 try cc_child_args.append(arena, "-o");
172184 }
173185
174186 var eval: Eval = .{
......@@ -186,7 +198,7 @@ pub fn main() !void {
186198
187199 try child.spawn();
188200
189 var poller = std.Io.poll(arena, Eval.StreamEnum, .{
201 var poller = Io.poll(arena, Eval.StreamEnum, .{
190202 .stdout = child.stdout.?,
191203 .stderr = child.stderr.?,
192204 });
......@@ -226,7 +238,7 @@ const Eval = struct {
226238 cc_child_args: *std.ArrayListUnmanaged([]const u8),
227239
228240 const StreamEnum = enum { stdout, stderr };
229 const Poller = std.Io.Poller(StreamEnum);
241 const Poller = Io.Poller(StreamEnum);
230242
231243 /// Currently this function assumes the previous updates have already been written.
232244 fn write(eval: *Eval, update: Case.Update) void {
......@@ -647,7 +659,7 @@ const Case = struct {
647659 msg: []const u8,
648660 };
649661
650 fn parse(arena: Allocator, bytes: []const u8) !Case {
662 fn parse(arena: Allocator, io: Io, bytes: []const u8) !Case {
651663 const fatal = std.process.fatal;
652664
653665 var targets: std.ArrayListUnmanaged(Target) = .empty;
......@@ -683,7 +695,7 @@ const Case = struct {
683695 },
684696 }) catch fatal("line {d}: invalid target query '{s}'", .{ line_n, query });
685697
686 const resolved = try std.zig.system.resolveTargetQuery(parsed_query);
698 const resolved = try std.zig.system.resolveTargetQuery(io, parsed_query);
687699
688700 try targets.append(arena, .{
689701 .query = query,
tools/migrate_langref.zig+7-1
......@@ -13,10 +13,16 @@ pub fn main() !void {
1313 defer arena_instance.deinit();
1414 const arena = arena_instance.allocator();
1515
16 const gpa = arena;
17
1618 const args = try std.process.argsAlloc(arena);
1719 const input_file = args[1];
1820 const output_file = args[2];
1921
22 var threaded: std.Io.Threaded = .init(gpa);
23 defer threaded.deinit();
24 const io = threaded.io();
25
2026 var in_file = try fs.cwd().openFile(input_file, .{ .mode = .read_only });
2127 defer in_file.close();
2228
......@@ -28,7 +34,7 @@ pub fn main() !void {
2834 var out_dir = try fs.cwd().openDir(fs.path.dirname(output_file).?, .{});
2935 defer out_dir.close();
3036
31 var in_file_reader = in_file.reader(&.{});
37 var in_file_reader = in_file.reader(io, &.{});
3238 const input_file_bytes = try in_file_reader.interface.allocRemaining(arena, .unlimited);
3339
3440 var tokenizer = Tokenizer.init(input_file, input_file_bytes);