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...@@ -413,7 +413,6 @@ set(ZIG_STAGE2_SOURCES
413 lib/std/Thread/Futex.zig413 lib/std/Thread/Futex.zig
414 lib/std/Thread/Mutex.zig414 lib/std/Thread/Mutex.zig
415 lib/std/Thread/Pool.zig415 lib/std/Thread/Pool.zig
416 lib/std/Thread/ResetEvent.zig
417 lib/std/Thread/WaitGroup.zig416 lib/std/Thread/WaitGroup.zig
418 lib/std/array_hash_map.zig417 lib/std/array_hash_map.zig
419 lib/std/array_list.zig418 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...@@ -76,23 +76,15 @@ This produces a `zig2` executable in the current working directory. This is a
76[without LLVM extensions](https://github.com/ziglang/zig/issues/16270), and is76[without LLVM extensions](https://github.com/ziglang/zig/issues/16270), and is
77therefore lacking these features:77therefore lacking these features:
78- Release mode optimizations78- 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)
83- [Some ELF linking features](https://github.com/ziglang/zig/issues/17749)79- [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)
85- [Some WebAssembly linking features](https://github.com/ziglang/zig/issues/17750)81- [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)
87- [Ability to create static archives from object files](https://github.com/ziglang/zig/issues/9828)82- [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)
88- Ability to compile C, C++, Objective-C, and Objective-C++ files84- 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 be86Even when built this way, Zig provides an LLVM backend that produces bitcode
91useful for creating system packages of Zig projects using the system C87files, which may be optimized and compiled into object files via a system Clang
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
96package. This can be used to produce system packages of Zig applications88package. This can be used to produce system packages of Zig applications
97without the Zig package dependency on LLVM.89without 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\...@@ -95,7 +95,7 @@ Enter-VsDevShell -VsInstallPath "C:\Program Files (x86)\Microsoft Visual Studio\
95CheckLastExitCode95CheckLastExitCode
9696
97Write-Output "Build and run behavior tests with msvc..."97Write-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.lib98& 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
99CheckLastExitCode99CheckLastExitCode
100100
101& .\test-x86_64-windows-msvc.exe101& .\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\...@@ -113,7 +113,7 @@ Enter-VsDevShell -VsInstallPath "C:\Program Files (x86)\Microsoft Visual Studio\
113CheckLastExitCode113CheckLastExitCode
114114
115Write-Output "Build and run behavior tests with msvc..."115Write-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.lib116& 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
117CheckLastExitCode117CheckLastExitCode
118118
119& .\test-x86_64-windows-msvc.exe119& .\test-x86_64-windows-msvc.exe
lib/compiler/aro/aro/Compilation.zig+32-28
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const assert = std.debug.assert;3const assert = std.debug.assert;
3const EpochSeconds = std.time.epoch.EpochSeconds;4const EpochSeconds = std.time.epoch.EpochSeconds;
4const mem = std.mem;5const mem = std.mem;
...@@ -113,7 +114,7 @@ pub const Environment = struct {...@@ -113,7 +114,7 @@ pub const Environment = struct {
113 if (parsed > max_timestamp) return error.InvalidEpoch;114 if (parsed > max_timestamp) return error.InvalidEpoch;
114 return .{ .provided = parsed };115 return .{ .provided = parsed };
115 } else {116 } 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;
117 return .{ .system = std.math.clamp(timestamp, 0, max_timestamp) };118 return .{ .system = std.math.clamp(timestamp, 0, max_timestamp) };
118 }119 }
119 }120 }
...@@ -124,6 +125,7 @@ const Compilation = @This();...@@ -124,6 +125,7 @@ const Compilation = @This();
124gpa: Allocator,125gpa: Allocator,
125/// Allocations in this arena live all the way until `Compilation.deinit`.126/// Allocations in this arena live all the way until `Compilation.deinit`.
126arena: Allocator,127arena: Allocator,
128io: Io,
127diagnostics: *Diagnostics,129diagnostics: *Diagnostics,
128130
129code_gen_options: CodeGenOptions = .default,131code_gen_options: CodeGenOptions = .default,
...@@ -157,10 +159,11 @@ type_store: TypeStore = .{},...@@ -157,10 +159,11 @@ type_store: TypeStore = .{},
157ms_cwd_source_id: ?Source.Id = null,159ms_cwd_source_id: ?Source.Id = null,
158cwd: std.fs.Dir,160cwd: 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 {
161 return .{163 return .{
162 .gpa = gpa,164 .gpa = gpa,
163 .arena = arena,165 .arena = arena,
166 .io = io,
164 .diagnostics = diagnostics,167 .diagnostics = diagnostics,
165 .cwd = cwd,168 .cwd = cwd,
166 };169 };
...@@ -168,10 +171,11 @@ pub fn init(gpa: Allocator, arena: Allocator, diagnostics: *Diagnostics, cwd: st...@@ -168,10 +171,11 @@ pub fn init(gpa: Allocator, arena: Allocator, diagnostics: *Diagnostics, cwd: st
168171
169/// Initialize Compilation with default environment,172/// Initialize Compilation with default environment,
170/// pragma handlers and emulation mode set to target.173/// 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 {
172 var comp: Compilation = .{175 var comp: Compilation = .{
173 .gpa = gpa,176 .gpa = gpa,
174 .arena = arena,177 .arena = arena,
178 .io = io,
175 .diagnostics = diagnostics,179 .diagnostics = diagnostics,
176 .environment = try Environment.loadAll(gpa),180 .environment = try Environment.loadAll(gpa),
177 .cwd = cwd,181 .cwd = cwd,
...@@ -222,14 +226,14 @@ pub const SystemDefinesMode = enum {...@@ -222,14 +226,14 @@ pub const SystemDefinesMode = enum {
222 include_system_defines,226 include_system_defines,
223};227};
224228
225fn generateSystemDefines(comp: *Compilation, w: *std.Io.Writer) !void {229fn generateSystemDefines(comp: *Compilation, w: *Io.Writer) !void {
226 const define = struct {230 const define = struct {
227 fn define(_w: *std.Io.Writer, name: []const u8) !void {231 fn define(_w: *Io.Writer, name: []const u8) !void {
228 try _w.print("#define {s} 1\n", .{name});232 try _w.print("#define {s} 1\n", .{name});
229 }233 }
230 }.define;234 }.define;
231 const defineStd = struct {235 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 {
233 if (is_gnu) {237 if (is_gnu) {
234 try _w.print("#define {s} 1\n", .{name});238 try _w.print("#define {s} 1\n", .{name});
235 }239 }
...@@ -956,7 +960,7 @@ fn generateSystemDefines(comp: *Compilation, w: *std.Io.Writer) !void {...@@ -956,7 +960,7 @@ fn generateSystemDefines(comp: *Compilation, w: *std.Io.Writer) !void {
956pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode) AddSourceError!Source {960pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode) AddSourceError!Source {
957 try comp.type_store.initNamedTypes(comp);961 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);
960 defer allocating.deinit();964 defer allocating.deinit();
961965
962 comp.writeBuiltinMacros(system_defines_mode, &allocating.writer) catch |err| switch (err) {966 comp.writeBuiltinMacros(system_defines_mode, &allocating.writer) catch |err| switch (err) {
...@@ -970,7 +974,7 @@ pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefi...@@ -970,7 +974,7 @@ pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefi
970 return comp.addSourceFromOwnedBuffer("<builtin>", contents, .user);974 return comp.addSourceFromOwnedBuffer("<builtin>", contents, .user);
971}975}
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 {
974 if (system_defines_mode == .include_system_defines) {978 if (system_defines_mode == .include_system_defines) {
975 try w.writeAll(979 try w.writeAll(
976 \\#define __VERSION__ "Aro980 \\#define __VERSION__ "Aro
...@@ -1018,7 +1022,7 @@ fn writeBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode...@@ -1018,7 +1022,7 @@ fn writeBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode
1018 }1022 }
1019}1023}
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 {
1022 const denormMin = semantics.chooseValue(1026 const denormMin = semantics.chooseValue(
1023 []const u8,1027 []const u8,
1024 .{1028 .{
...@@ -1093,7 +1097,7 @@ fn generateFloatMacros(w: *std.Io.Writer, prefix: []const u8, semantics: target_...@@ -1093,7 +1097,7 @@ fn generateFloatMacros(w: *std.Io.Writer, prefix: []const u8, semantics: target_
1093 try w.print("#define __{s}_MIN__ {s}{s}\n", .{ prefix, min, ext });1097 try w.print("#define __{s}_MIN__ {s}{s}\n", .{ prefix, min, ext });
1094}1098}
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 {
1097 try w.print("#define {s} ", .{name});1101 try w.print("#define {s} ", .{name});
1098 try qt.print(comp, w);1102 try qt.print(comp, w);
1099 try w.writeByte('\n');1103 try w.writeByte('\n');
...@@ -1128,7 +1132,7 @@ fn generateFastOrLeastType(...@@ -1128,7 +1132,7 @@ fn generateFastOrLeastType(
1128 bits: usize,1132 bits: usize,
1129 kind: enum { least, fast },1133 kind: enum { least, fast },
1130 signedness: std.builtin.Signedness,1134 signedness: std.builtin.Signedness,
1131 w: *std.Io.Writer,1135 w: *Io.Writer,
1132) !void {1136) !void {
1133 const ty = comp.intLeastN(bits, signedness); // defining the fast types as the least types is permitted1137 const ty = comp.intLeastN(bits, signedness); // defining the fast types as the least types is permitted
11341138
...@@ -1158,7 +1162,7 @@ fn generateFastOrLeastType(...@@ -1158,7 +1162,7 @@ fn generateFastOrLeastType(
1158 try comp.generateFmt(prefix, w, ty);1162 try comp.generateFmt(prefix, w, ty);
1159}1163}
11601164
1161fn generateFastAndLeastWidthTypes(comp: *Compilation, w: *std.Io.Writer) !void {1165fn generateFastAndLeastWidthTypes(comp: *Compilation, w: *Io.Writer) !void {
1162 const sizes = [_]usize{ 8, 16, 32, 64 };1166 const sizes = [_]usize{ 8, 16, 32, 64 };
1163 for (sizes) |size| {1167 for (sizes) |size| {
1164 try comp.generateFastOrLeastType(size, .least, .signed, w);1168 try comp.generateFastOrLeastType(size, .least, .signed, w);
...@@ -1168,7 +1172,7 @@ fn generateFastAndLeastWidthTypes(comp: *Compilation, w: *std.Io.Writer) !void {...@@ -1168,7 +1172,7 @@ fn generateFastAndLeastWidthTypes(comp: *Compilation, w: *std.Io.Writer) !void {
1168 }1172 }
1169}1173}
11701174
1171fn generateExactWidthTypes(comp: *Compilation, w: *std.Io.Writer) !void {1175fn generateExactWidthTypes(comp: *Compilation, w: *Io.Writer) !void {
1172 try comp.generateExactWidthType(w, .schar);1176 try comp.generateExactWidthType(w, .schar);
11731177
1174 if (QualType.short.sizeof(comp) > QualType.char.sizeof(comp)) {1178 if (QualType.short.sizeof(comp) > QualType.char.sizeof(comp)) {
...@@ -1216,7 +1220,7 @@ fn generateExactWidthTypes(comp: *Compilation, w: *std.Io.Writer) !void {...@@ -1216,7 +1220,7 @@ fn generateExactWidthTypes(comp: *Compilation, w: *std.Io.Writer) !void {
1216 }1220 }
1217}1221}
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 {
1220 const unsigned = qt.signedness(comp) == .unsigned;1224 const unsigned = qt.signedness(comp) == .unsigned;
1221 const modifier = qt.formatModifier(comp);1225 const modifier = qt.formatModifier(comp);
1222 const formats = if (unsigned) "ouxX" else "di";1226 const formats = if (unsigned) "ouxX" else "di";
...@@ -1225,7 +1229,7 @@ fn generateFmt(comp: *const Compilation, prefix: []const u8, w: *std.Io.Writer,...@@ -1225,7 +1229,7 @@ fn generateFmt(comp: *const Compilation, prefix: []const u8, w: *std.Io.Writer,
1225 }1229 }
1226}1230}
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 {
1229 return w.print("#define {s}_C_SUFFIX__ {s}\n", .{ prefix, qt.intValueSuffix(comp) });1233 return w.print("#define {s}_C_SUFFIX__ {s}\n", .{ prefix, qt.intValueSuffix(comp) });
1230}1234}
12311235
...@@ -1233,7 +1237,7 @@ fn generateSuffixMacro(comp: *const Compilation, prefix: []const u8, w: *std.Io....@@ -1233,7 +1237,7 @@ fn generateSuffixMacro(comp: *const Compilation, prefix: []const u8, w: *std.Io.
1233/// Name macro (e.g. #define __UINT32_TYPE__ unsigned int)1237/// Name macro (e.g. #define __UINT32_TYPE__ unsigned int)
1234/// Format strings (e.g. #define __UINT32_FMTu__ "u")1238/// Format strings (e.g. #define __UINT32_FMTu__ "u")
1235/// Suffix macro (e.g. #define __UINT32_C_SUFFIX__ U)1239/// 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 {
1237 var qt = original_qt;1241 var qt = original_qt;
1238 const width = qt.sizeof(comp) * 8;1242 const width = qt.sizeof(comp) * 8;
1239 const unsigned = qt.signedness(comp) == .unsigned;1243 const unsigned = qt.signedness(comp) == .unsigned;
...@@ -1266,7 +1270,7 @@ pub fn hasHalfPrecisionFloatABI(comp: *const Compilation) bool {...@@ -1266,7 +1270,7 @@ pub fn hasHalfPrecisionFloatABI(comp: *const Compilation) bool {
1266 return comp.langopts.allow_half_args_and_returns or target_util.hasHalfPrecisionFloatABI(comp.target);1270 return comp.langopts.allow_half_args_and_returns or target_util.hasHalfPrecisionFloatABI(comp.target);
1267}1271}
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 {
1270 const unsigned = qt.signedness(comp) == .unsigned;1274 const unsigned = qt.signedness(comp) == .unsigned;
1271 const max: u128 = switch (qt.bitSizeof(comp)) {1275 const max: u128 = switch (qt.bitSizeof(comp)) {
1272 8 => if (unsigned) std.math.maxInt(u8) else std.math.maxInt(i8),1276 8 => if (unsigned) std.math.maxInt(u8) else std.math.maxInt(i8),
...@@ -1290,7 +1294,7 @@ pub fn wcharMax(comp: *const Compilation) u32 {...@@ -1290,7 +1294,7 @@ pub fn wcharMax(comp: *const Compilation) u32 {
1290 };1294 };
1291}1295}
12921296
1293fn generateExactWidthIntMax(comp: *Compilation, w: *std.Io.Writer, original_qt: QualType) !void {1297fn generateExactWidthIntMax(comp: *Compilation, w: *Io.Writer, original_qt: QualType) !void {
1294 var qt = original_qt;1298 var qt = original_qt;
1295 const bit_count: u8 = @intCast(qt.sizeof(comp) * 8);1299 const bit_count: u8 = @intCast(qt.sizeof(comp) * 8);
1296 const unsigned = qt.signedness(comp) == .unsigned;1300 const unsigned = qt.signedness(comp) == .unsigned;
...@@ -1307,16 +1311,16 @@ fn generateExactWidthIntMax(comp: *Compilation, w: *std.Io.Writer, original_qt:...@@ -1307,16 +1311,16 @@ fn generateExactWidthIntMax(comp: *Compilation, w: *std.Io.Writer, original_qt:
1307 return comp.generateIntMax(w, name, qt);1311 return comp.generateIntMax(w, name, qt);
1308}1312}
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 {
1311 try w.print("#define __{s}_WIDTH__ {d}\n", .{ name, qt.sizeof(comp) * 8 });1315 try w.print("#define __{s}_WIDTH__ {d}\n", .{ name, qt.sizeof(comp) * 8 });
1312}1316}
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 {
1315 try comp.generateIntMax(w, name, qt);1319 try comp.generateIntMax(w, name, qt);
1316 try comp.generateIntWidth(w, name, qt);1320 try comp.generateIntWidth(w, name, qt);
1317}1321}
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 {
1320 try w.print("#define {s} {d}\n", .{ name, qt.sizeof(comp) });1324 try w.print("#define {s} {d}\n", .{ name, qt.sizeof(comp) });
1321}1325}
13221326
...@@ -1797,7 +1801,7 @@ pub const IncludeType = enum {...@@ -1797,7 +1801,7 @@ pub const IncludeType = enum {
1797 angle_brackets,1801 angle_brackets,
1798};1802};
17991803
1800fn getPathContents(comp: *Compilation, path: []const u8, limit: std.Io.Limit) ![]u8 {1804fn getPathContents(comp: *Compilation, path: []const u8, limit: Io.Limit) ![]u8 {
1801 if (mem.indexOfScalar(u8, path, 0) != null) {1805 if (mem.indexOfScalar(u8, path, 0) != null) {
1802 return error.FileNotFound;1806 return error.FileNotFound;
1803 }1807 }
...@@ -1807,11 +1811,12 @@ fn getPathContents(comp: *Compilation, path: []const u8, limit: std.Io.Limit) ![...@@ -1807,11 +1811,12 @@ fn getPathContents(comp: *Compilation, path: []const u8, limit: std.Io.Limit) ![
1807 return comp.getFileContents(file, limit);1811 return comp.getFileContents(file, limit);
1808}1812}
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;
1811 var file_buf: [4096]u8 = undefined;1816 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);
1815 defer allocating.deinit();1820 defer allocating.deinit();
1816 if (file_reader.getSize()) |size| {1821 if (file_reader.getSize()) |size| {
1817 const limited_size = limit.minInt64(size);1822 const limited_size = limit.minInt64(size);
...@@ -1838,7 +1843,7 @@ pub fn findEmbed(...@@ -1838,7 +1843,7 @@ pub fn findEmbed(
1838 includer_token_source: Source.Id,1843 includer_token_source: Source.Id,
1839 /// angle bracket vs quotes1844 /// angle bracket vs quotes
1840 include_type: IncludeType,1845 include_type: IncludeType,
1841 limit: std.Io.Limit,1846 limit: Io.Limit,
1842 opt_dep_file: ?*DepFile,1847 opt_dep_file: ?*DepFile,
1843) !?[]u8 {1848) !?[]u8 {
1844 if (std.fs.path.isAbsolute(filename)) {1849 if (std.fs.path.isAbsolute(filename)) {
...@@ -2002,8 +2007,7 @@ pub fn locSlice(comp: *const Compilation, loc: Source.Location) []const u8 {...@@ -2002,8 +2007,7 @@ pub fn locSlice(comp: *const Compilation, loc: Source.Location) []const u8 {
2002pub fn getSourceMTimeUncached(comp: *const Compilation, source_id: Source.Id) ?u64 {2007pub fn getSourceMTimeUncached(comp: *const Compilation, source_id: Source.Id) ?u64 {
2003 const source = comp.getSource(source_id);2008 const source = comp.getSource(source_id);
2004 if (comp.cwd.statFile(source.path)) |stat| {2009 if (comp.cwd.statFile(source.path)) |stat| {
2005 const mtime = @divTrunc(stat.mtime, std.time.ns_per_s);2010 return std.math.cast(u64, stat.mtime.toSeconds());
2006 return std.math.cast(u64, mtime);
2007 } else |_| {2011 } else |_| {
2008 return null;2012 return null;
2009 }2013 }
lib/compiler/aro/aro/Driver.zig+3-3
...@@ -273,6 +273,7 @@ pub fn parseArgs(...@@ -273,6 +273,7 @@ pub fn parseArgs(
273 macro_buf: *std.ArrayList(u8),273 macro_buf: *std.ArrayList(u8),
274 args: []const []const u8,274 args: []const []const u8,
275) (Compilation.Error || std.Io.Writer.Error)!bool {275) (Compilation.Error || std.Io.Writer.Error)!bool {
276 const io = d.comp.io;
276 var i: usize = 1;277 var i: usize = 1;
277 var comment_arg: []const u8 = "";278 var comment_arg: []const u8 = "";
278 var hosted: ?bool = null;279 var hosted: ?bool = null;
...@@ -772,7 +773,7 @@ pub fn parseArgs(...@@ -772,7 +773,7 @@ pub fn parseArgs(
772 opts.arch_os_abi, @errorName(e),773 opts.arch_os_abi, @errorName(e),
773 }),774 }),
774 };775 };
775 d.comp.target = std.zig.system.resolveTargetQuery(query) catch |e| {776 d.comp.target = std.zig.system.resolveTargetQuery(io, query) catch |e| {
776 return d.fatal("unable to resolve target: {s}", .{errorDescription(e)});777 return d.fatal("unable to resolve target: {s}", .{errorDescription(e)});
777 };778 };
778 }779 }
...@@ -916,8 +917,7 @@ pub fn errorDescription(e: anyerror) []const u8 {...@@ -916,8 +917,7 @@ pub fn errorDescription(e: anyerror) []const u8 {
916 error.NotDir => "is not a directory",917 error.NotDir => "is not a directory",
917 error.NotOpenForReading => "file is not open for reading",918 error.NotOpenForReading => "file is not open for reading",
918 error.NotOpenForWriting => "file is not open for writing",919 error.NotOpenForWriting => "file is not open for writing",
919 error.InvalidUtf8 => "path is not valid UTF-8",920 error.BadPathName => "bad path name",
920 error.InvalidWtf8 => "path is not valid WTF-8",
921 error.FileBusy => "file is busy",921 error.FileBusy => "file is busy",
922 error.NameTooLong => "file name is too long",922 error.NameTooLong => "file name is too long",
923 error.AccessDenied => "access denied",923 error.AccessDenied => "access denied",
lib/compiler/build_runner.zig+23-14
...@@ -1,5 +1,8 @@...@@ -1,5 +1,8 @@
1const std = @import("std");1const runner = @This();
2const builtin = @import("builtin");2const builtin = @import("builtin");
3
4const std = @import("std");
5const Io = std.Io;
3const assert = std.debug.assert;6const assert = std.debug.assert;
4const fmt = std.fmt;7const fmt = std.fmt;
5const mem = std.mem;8const mem = std.mem;
...@@ -11,7 +14,6 @@ const WebServer = std.Build.WebServer;...@@ -11,7 +14,6 @@ const WebServer = std.Build.WebServer;
11const Allocator = std.mem.Allocator;14const Allocator = std.mem.Allocator;
12const fatal = std.process.fatal;15const fatal = std.process.fatal;
13const Writer = std.Io.Writer;16const Writer = std.Io.Writer;
14const runner = @This();
15const tty = std.Io.tty;17const tty = std.Io.tty;
1618
17pub const root = @import("@build");19pub const root = @import("@build");
...@@ -38,6 +40,10 @@ pub fn main() !void {...@@ -38,6 +40,10 @@ pub fn main() !void {
3840
39 const args = try process.argsAlloc(arena);41 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
41 // skip my own exe name47 // skip my own exe name
42 var arg_idx: usize = 1;48 var arg_idx: usize = 1;
4349
...@@ -68,8 +74,10 @@ pub fn main() !void {...@@ -68,8 +74,10 @@ pub fn main() !void {
68 };74 };
6975
70 var graph: std.Build.Graph = .{76 var graph: std.Build.Graph = .{
77 .io = io,
71 .arena = arena,78 .arena = arena,
72 .cache = .{79 .cache = .{
80 .io = io,
73 .gpa = arena,81 .gpa = arena,
74 .manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),82 .manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),
75 },83 },
...@@ -79,7 +87,7 @@ pub fn main() !void {...@@ -79,7 +87,7 @@ pub fn main() !void {
79 .zig_lib_directory = zig_lib_directory,87 .zig_lib_directory = zig_lib_directory,
80 .host = .{88 .host = .{
81 .query = .{},89 .query = .{},
82 .result = try std.zig.system.resolveTargetQuery(.{}),90 .result = try std.zig.system.resolveTargetQuery(io, .{}),
83 },91 },
84 .time_report = false,92 .time_report = false,
85 };93 };
...@@ -116,7 +124,7 @@ pub fn main() !void {...@@ -116,7 +124,7 @@ pub fn main() !void {
116 var watch = false;124 var watch = false;
117 var fuzz: ?std.Build.Fuzz.Mode = null;125 var fuzz: ?std.Build.Fuzz.Mode = null;
118 var debounce_interval_ms: u16 = 50;126 var debounce_interval_ms: u16 = 50;
119 var webui_listen: ?std.net.Address = null;127 var webui_listen: ?Io.net.IpAddress = null;
120128
121 if (try std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(arena)) |str| {129 if (try std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(arena)) |str| {
122 if (std.meta.stringToEnum(ErrorStyle, str)) |style| {130 if (std.meta.stringToEnum(ErrorStyle, str)) |style| {
...@@ -283,11 +291,11 @@ pub fn main() !void {...@@ -283,11 +291,11 @@ pub fn main() !void {
283 });291 });
284 };292 };
285 } else if (mem.eql(u8, arg, "--webui")) {293 } 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) };
287 } else if (mem.startsWith(u8, arg, "--webui=")) {295 } else if (mem.startsWith(u8, arg, "--webui=")) {
288 const addr_str = arg["--webui=".len..];296 const addr_str = arg["--webui=".len..];
289 if (std.mem.eql(u8, addr_str, "-")) fatal("web interface cannot listen on stdio", .{});297 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| {
291 fatal("invalid web UI address '{s}': {s}", .{ addr_str, @errorName(err) });299 fatal("invalid web UI address '{s}': {s}", .{ addr_str, @errorName(err) });
292 };300 };
293 } else if (mem.eql(u8, arg, "--debug-log")) {301 } else if (mem.eql(u8, arg, "--debug-log")) {
...@@ -329,14 +337,10 @@ pub fn main() !void {...@@ -329,14 +337,10 @@ pub fn main() !void {
329 watch = true;337 watch = true;
330 } else if (mem.eql(u8, arg, "--time-report")) {338 } else if (mem.eql(u8, arg, "--time-report")) {
331 graph.time_report = true;339 graph.time_report = true;
332 if (webui_listen == null) {340 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
333 webui_listen = std.net.Address.parseIp("::1", 0) catch unreachable;
334 }
335 } else if (mem.eql(u8, arg, "--fuzz")) {341 } else if (mem.eql(u8, arg, "--fuzz")) {
336 fuzz = .{ .forever = undefined };342 fuzz = .{ .forever = undefined };
337 if (webui_listen == null) {343 if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
338 webui_listen = std.net.Address.parseIp("::1", 0) catch unreachable;
339 }
340 } else if (mem.startsWith(u8, arg, "--fuzz=")) {344 } else if (mem.startsWith(u8, arg, "--fuzz=")) {
341 const value = arg["--fuzz=".len..];345 const value = arg["--fuzz=".len..];
342 if (value.len == 0) fatal("missing argument to --fuzz", .{});346 if (value.len == 0) fatal("missing argument to --fuzz", .{});
...@@ -545,13 +549,15 @@ pub fn main() !void {...@@ -545,13 +549,15 @@ pub fn main() !void {
545549
546 var w: Watch = w: {550 var w: Watch = w: {
547 if (!watch) break :w undefined;551 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});
549 break :w try .init();553 break :w try .init();
550 };554 };
551555
552 try run.thread_pool.init(thread_pool_options);556 try run.thread_pool.init(thread_pool_options);
553 defer run.thread_pool.deinit();557 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
555 run.web_server = if (webui_listen) |listen_address| ws: {561 run.web_server = if (webui_listen) |listen_address| ws: {
556 if (builtin.single_threaded) unreachable; // `fatal` above562 if (builtin.single_threaded) unreachable; // `fatal` above
557 break :ws .init(.{563 break :ws .init(.{
...@@ -563,11 +569,12 @@ pub fn main() !void {...@@ -563,11 +569,12 @@ pub fn main() !void {
563 .root_prog_node = main_progress_node,569 .root_prog_node = main_progress_node,
564 .watch = watch,570 .watch = watch,
565 .listen_address = listen_address,571 .listen_address = listen_address,
572 .base_timestamp = now,
566 });573 });
567 } else null;574 } else null;
568575
569 if (run.web_server) |*ws| {576 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});
571 }578 }
572579
573 rebuild: while (true) : (if (run.error_style.clearOnUpdate()) {580 rebuild: while (true) : (if (run.error_style.clearOnUpdate()) {
...@@ -750,6 +757,7 @@ fn runStepNames(...@@ -750,6 +757,7 @@ fn runStepNames(
750 fuzz: ?std.Build.Fuzz.Mode,757 fuzz: ?std.Build.Fuzz.Mode,
751) !void {758) !void {
752 const gpa = run.gpa;759 const gpa = run.gpa;
760 const io = b.graph.io;
753 const step_stack = &run.step_stack;761 const step_stack = &run.step_stack;
754 const thread_pool = &run.thread_pool;762 const thread_pool = &run.thread_pool;
755763
...@@ -853,6 +861,7 @@ fn runStepNames(...@@ -853,6 +861,7 @@ fn runStepNames(
853 assert(mode == .limit);861 assert(mode == .limit);
854 var f = std.Build.Fuzz.init(862 var f = std.Build.Fuzz.init(
855 gpa,863 gpa,
864 io,
856 thread_pool,865 thread_pool,
857 step_stack.keys(),866 step_stack.keys(),
858 parent_prog_node,867 parent_prog_node,
lib/compiler/libc.zig+5-1
...@@ -29,6 +29,10 @@ pub fn main() !void {...@@ -29,6 +29,10 @@ pub fn main() !void {
29 const arena = arena_instance.allocator();29 const arena = arena_instance.allocator();
30 const gpa = arena;30 const gpa = arena;
3131
32 var threaded: std.Io.Threaded = .init(gpa);
33 defer threaded.deinit();
34 const io = threaded.io();
35
32 const args = try std.process.argsAlloc(arena);36 const args = try std.process.argsAlloc(arena);
33 const zig_lib_directory = args[1];37 const zig_lib_directory = args[1];
3438
...@@ -66,7 +70,7 @@ pub fn main() !void {...@@ -66,7 +70,7 @@ pub fn main() !void {
66 const target_query = std.zig.parseTargetQueryOrReportFatalError(gpa, .{70 const target_query = std.zig.parseTargetQueryOrReportFatalError(gpa, .{
67 .arch_os_abi = target_arch_os_abi,71 .arch_os_abi = target_arch_os_abi,
68 });72 });
69 const target = std.zig.resolveTargetQueryOrFatal(target_query);73 const target = std.zig.resolveTargetQueryOrFatal(io, target_query);
7074
71 if (print_includes) {75 if (print_includes) {
72 const libc_installation: ?*LibCInstallation = libc: {76 const libc_installation: ?*LibCInstallation = libc: {
lib/compiler/objcopy.zig+6-3
...@@ -29,7 +29,6 @@ pub fn main() !void {...@@ -29,7 +29,6 @@ pub fn main() !void {
29}29}
3030
31fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {31fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
32 _ = gpa;
33 var i: usize = 0;32 var i: usize = 0;
34 var opt_out_fmt: ?std.Target.ObjectFormat = null;33 var opt_out_fmt: ?std.Target.ObjectFormat = null;
35 var opt_input: ?[]const u8 = null;34 var opt_input: ?[]const u8 = null;
...@@ -148,12 +147,16 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void...@@ -148,12 +147,16 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
148 const input = opt_input orelse fatal("expected input parameter", .{});147 const input = opt_input orelse fatal("expected input parameter", .{});
149 const output = opt_output orelse fatal("expected output parameter", .{});148 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
151 const input_file = fs.cwd().openFile(input, .{}) catch |err| fatal("failed to open {s}: {t}", .{ input, err });154 const input_file = fs.cwd().openFile(input, .{}) catch |err| fatal("failed to open {s}: {t}", .{ input, err });
152 defer input_file.close();155 defer input_file.close();
153156
154 const stat = input_file.stat() catch |err| fatal("failed to stat {s}: {t}", .{ input, err });157 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
158 const elf_hdr = std.elf.Header.read(&in.interface) catch |err| switch (err) {161 const elf_hdr = std.elf.Header.read(&in.interface) catch |err| switch (err) {
159 error.ReadFailed => fatal("unable to read {s}: {t}", .{ input, in.err.? }),162 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...@@ -218,7 +221,7 @@ fn cmdObjCopy(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
218 try out.end();221 try out.end();
219222
220 if (listen) {223 if (listen) {
221 var stdin_reader = fs.File.stdin().reader(&stdin_buffer);224 var stdin_reader = fs.File.stdin().reader(io, &stdin_buffer);
222 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);225 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
223 var server = try Server.init(.{226 var server = try Server.init(.{
224 .in = &stdin_reader.interface,227 .in = &stdin_reader.interface,
lib/compiler/resinator/compile.zig+15-8
...@@ -1,6 +1,12 @@...@@ -1,6 +1,12 @@
1const std = @import("std");
2const builtin = @import("builtin");1const builtin = @import("builtin");
2const native_endian = builtin.cpu.arch.endian();
3
4const std = @import("std");
5const Io = std.Io;
3const Allocator = std.mem.Allocator;6const Allocator = std.mem.Allocator;
7const WORD = std.os.windows.WORD;
8const DWORD = std.os.windows.DWORD;
9
4const Node = @import("ast.zig").Node;10const Node = @import("ast.zig").Node;
5const lex = @import("lex.zig");11const lex = @import("lex.zig");
6const Parser = @import("parse.zig").Parser;12const Parser = @import("parse.zig").Parser;
...@@ -17,8 +23,6 @@ const res = @import("res.zig");...@@ -17,8 +23,6 @@ const res = @import("res.zig");
17const ico = @import("ico.zig");23const ico = @import("ico.zig");
18const ani = @import("ani.zig");24const ani = @import("ani.zig");
19const bmp = @import("bmp.zig");25const bmp = @import("bmp.zig");
20const WORD = std.os.windows.WORD;
21const DWORD = std.os.windows.DWORD;
22const utils = @import("utils.zig");26const utils = @import("utils.zig");
23const NameOrOrdinal = res.NameOrOrdinal;27const NameOrOrdinal = res.NameOrOrdinal;
24const SupportedCodePage = @import("code_pages.zig").SupportedCodePage;28const SupportedCodePage = @import("code_pages.zig").SupportedCodePage;
...@@ -28,7 +32,6 @@ const windows1252 = @import("windows1252.zig");...@@ -28,7 +32,6 @@ const windows1252 = @import("windows1252.zig");
28const lang = @import("lang.zig");32const lang = @import("lang.zig");
29const code_pages = @import("code_pages.zig");33const code_pages = @import("code_pages.zig");
30const errors = @import("errors.zig");34const errors = @import("errors.zig");
31const native_endian = builtin.cpu.arch.endian();
3235
33pub const CompileOptions = struct {36pub const CompileOptions = struct {
34 cwd: std.fs.Dir,37 cwd: std.fs.Dir,
...@@ -77,7 +80,7 @@ pub const Dependencies = struct {...@@ -77,7 +80,7 @@ pub const Dependencies = struct {
77 }80 }
78};81};
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 {
81 var lexer = lex.Lexer.init(source, .{84 var lexer = lex.Lexer.init(source, .{
82 .default_code_page = options.default_code_page,85 .default_code_page = options.default_code_page,
83 .source_mappings = options.source_mappings,86 .source_mappings = options.source_mappings,
...@@ -166,10 +169,11 @@ pub fn compile(allocator: Allocator, source: []const u8, writer: *std.Io.Writer,...@@ -166,10 +169,11 @@ pub fn compile(allocator: Allocator, source: []const u8, writer: *std.Io.Writer,
166 defer arena_allocator.deinit();169 defer arena_allocator.deinit();
167 const arena = arena_allocator.allocator();170 const arena = arena_allocator.allocator();
168171
169 var compiler = Compiler{172 var compiler: Compiler = .{
170 .source = source,173 .source = source,
171 .arena = arena,174 .arena = arena,
172 .allocator = allocator,175 .allocator = allocator,
176 .io = io,
173 .cwd = options.cwd,177 .cwd = options.cwd,
174 .diagnostics = options.diagnostics,178 .diagnostics = options.diagnostics,
175 .dependencies = options.dependencies,179 .dependencies = options.dependencies,
...@@ -191,6 +195,7 @@ pub const Compiler = struct {...@@ -191,6 +195,7 @@ pub const Compiler = struct {
191 source: []const u8,195 source: []const u8,
192 arena: Allocator,196 arena: Allocator,
193 allocator: Allocator,197 allocator: Allocator,
198 io: Io,
194 cwd: std.fs.Dir,199 cwd: std.fs.Dir,
195 state: State = .{},200 state: State = .{},
196 diagnostics: *Diagnostics,201 diagnostics: *Diagnostics,
...@@ -409,7 +414,7 @@ pub const Compiler = struct {...@@ -409,7 +414,7 @@ pub const Compiler = struct {
409 }414 }
410 }415 }
411416
412 var first_error: ?std.fs.File.OpenError = null;417 var first_error: ?(std.fs.File.OpenError || std.fs.File.StatError) = null;
413 for (self.search_dirs) |search_dir| {418 for (self.search_dirs) |search_dir| {
414 if (utils.openFileNotDir(search_dir.dir, path, .{})) |file| {419 if (utils.openFileNotDir(search_dir.dir, path, .{})) |file| {
415 errdefer file.close();420 errdefer file.close();
...@@ -496,6 +501,8 @@ pub const Compiler = struct {...@@ -496,6 +501,8 @@ pub const Compiler = struct {
496 }501 }
497502
498 pub fn writeResourceExternal(self: *Compiler, node: *Node.ResourceExternal, writer: *std.Io.Writer) !void {503 pub fn writeResourceExternal(self: *Compiler, node: *Node.ResourceExternal, writer: *std.Io.Writer) !void {
504 const io = self.io;
505
499 // Init header with data size zero for now, will need to fill it in later506 // Init header with data size zero for now, will need to fill it in later
500 var header = try self.resourceHeader(node.id, node.type, .{});507 var header = try self.resourceHeader(node.id, node.type, .{});
501 defer header.deinit(self.allocator);508 defer header.deinit(self.allocator);
...@@ -582,7 +589,7 @@ pub const Compiler = struct {...@@ -582,7 +589,7 @@ pub const Compiler = struct {
582 };589 };
583 defer file_handle.close();590 defer file_handle.close();
584 var file_buffer: [2048]u8 = undefined;591 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
587 if (maybe_predefined_type) |predefined_type| {594 if (maybe_predefined_type) |predefined_type| {
588 switch (predefined_type) {595 switch (predefined_type) {
lib/compiler/resinator/cvtres.zig+11-4
...@@ -1,5 +1,7 @@...@@ -1,5 +1,7 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const Allocator = std.mem.Allocator;3const Allocator = std.mem.Allocator;
4
3const res = @import("res.zig");5const res = @import("res.zig");
4const NameOrOrdinal = res.NameOrOrdinal;6const NameOrOrdinal = res.NameOrOrdinal;
5const MemoryFlags = res.MemoryFlags;7const MemoryFlags = res.MemoryFlags;
...@@ -169,8 +171,7 @@ pub fn parseNameOrOrdinal(allocator: Allocator, reader: *std.Io.Reader) !NameOrO...@@ -169,8 +171,7 @@ pub fn parseNameOrOrdinal(allocator: Allocator, reader: *std.Io.Reader) !NameOrO
169171
170pub const CoffOptions = struct {172pub const CoffOptions = struct {
171 target: std.coff.IMAGE.FILE.MACHINE = .AMD64,173 target: std.coff.IMAGE.FILE.MACHINE = .AMD64,
172 /// If true, zeroes will be written to all timestamp fields174 timestamp: i64 = 0,
173 reproducible: bool = true,
174 /// If true, the MEM_WRITE flag will not be set in the .rsrc section header175 /// If true, the MEM_WRITE flag will not be set in the .rsrc section header
175 read_only: bool = false,176 read_only: bool = false,
176 /// If non-null, a symbol with this name and storage class EXTERNAL will be added to the symbol table.177 /// 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 {...@@ -188,7 +189,13 @@ pub const Diagnostics = union {
188 overflow_resource: usize,189 overflow_resource: usize,
189};190};
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 {
192 var resource_tree = ResourceTree.init(allocator, options);199 var resource_tree = ResourceTree.init(allocator, options);
193 defer resource_tree.deinit();200 defer resource_tree.deinit();
194201
...@@ -215,7 +222,7 @@ pub fn writeCoff(allocator: Allocator, writer: *std.Io.Writer, resources: []cons...@@ -215,7 +222,7 @@ pub fn writeCoff(allocator: Allocator, writer: *std.Io.Writer, resources: []cons
215 const pointer_to_rsrc02_data = pointer_to_relocations + relocations_len;222 const pointer_to_rsrc02_data = pointer_to_relocations + relocations_len;
216 const pointer_to_symbol_table = pointer_to_rsrc02_data + lengths.rsrc02;223 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;
219 const size_of_optional_header = 0;226 const size_of_optional_header = 0;
220 const machine_type: std.coff.IMAGE.FILE.MACHINE = options.target;227 const machine_type: std.coff.IMAGE.FILE.MACHINE = options.target;
221 const flags = std.coff.Header.Flags{228 const flags = std.coff.Header.Flags{
lib/compiler/resinator/errors.zig+27-9
...@@ -1,5 +1,11 @@...@@ -1,5 +1,11 @@
1const builtin = @import("builtin");
2const native_endian = builtin.cpu.arch.endian();
3
1const std = @import("std");4const std = @import("std");
5const Io = std.Io;
2const assert = std.debug.assert;6const assert = std.debug.assert;
7const Allocator = std.mem.Allocator;
8
3const Token = @import("lex.zig").Token;9const Token = @import("lex.zig").Token;
4const SourceMappings = @import("source_mapping.zig").SourceMappings;10const SourceMappings = @import("source_mapping.zig").SourceMappings;
5const utils = @import("utils.zig");11const utils = @import("utils.zig");
...@@ -11,19 +17,19 @@ const parse = @import("parse.zig");...@@ -11,19 +17,19 @@ const parse = @import("parse.zig");
11const lang = @import("lang.zig");17const lang = @import("lang.zig");
12const code_pages = @import("code_pages.zig");18const code_pages = @import("code_pages.zig");
13const SupportedCodePage = code_pages.SupportedCodePage;19const SupportedCodePage = code_pages.SupportedCodePage;
14const builtin = @import("builtin");
15const native_endian = builtin.cpu.arch.endian();
1620
17pub const Diagnostics = struct {21pub const Diagnostics = struct {
18 errors: std.ArrayList(ErrorDetails) = .empty,22 errors: std.ArrayList(ErrorDetails) = .empty,
19 /// Append-only, cannot handle removing strings.23 /// Append-only, cannot handle removing strings.
20 /// Expects to own all strings within the list.24 /// Expects to own all strings within the list.
21 strings: std.ArrayList([]const u8) = .empty,25 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 {
25 return .{30 return .{
26 .allocator = allocator,31 .allocator = allocator,
32 .io = io,
27 };33 };
28 }34 }
2935
...@@ -62,10 +68,11 @@ pub const Diagnostics = struct {...@@ -62,10 +68,11 @@ pub const Diagnostics = struct {
62 }68 }
6369
64 pub fn renderToStdErr(self: *Diagnostics, cwd: std.fs.Dir, source: []const u8, tty_config: std.Io.tty.Config, source_mappings: ?SourceMappings) void {70 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;
65 const stderr = std.debug.lockStderrWriter(&.{});72 const stderr = std.debug.lockStderrWriter(&.{});
66 defer std.debug.unlockStderrWriter();73 defer std.debug.unlockStderrWriter();
67 for (self.errors.items) |err_details| {74 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;
69 }76 }
70 }77 }
7178
...@@ -167,9 +174,9 @@ pub const ErrorDetails = struct {...@@ -167,9 +174,9 @@ pub const ErrorDetails = struct {
167 filename_string_index: FilenameStringIndex,174 filename_string_index: FilenameStringIndex,
168175
169 pub const FilenameStringIndex = std.meta.Int(.unsigned, 32 - @bitSizeOf(FileOpenErrorEnum));176 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 {
173 return switch (err) {180 return switch (err) {
174 inline else => |e| @field(ErrorDetails.FileOpenError.FileOpenErrorEnum, @errorName(e)),181 inline else => |e| @field(ErrorDetails.FileOpenError.FileOpenErrorEnum, @errorName(e)),
175 };182 };
...@@ -894,7 +901,16 @@ fn cellCount(code_page: SupportedCodePage, source: []const u8, start_index: usiz...@@ -894,7 +901,16 @@ fn cellCount(code_page: SupportedCodePage, source: []const u8, start_index: usiz
894901
895const truncated_str = "<...truncated...>";902const 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 {
898 if (err_details.type == .hint) return;914 if (err_details.type == .hint) return;
899915
900 const source_line_start = err_details.token.getLineStartForErrorDisplay(source);916 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,...@@ -989,6 +1005,7 @@ pub fn renderErrorMessage(writer: *std.Io.Writer, tty_config: std.Io.tty.Config,
989 var initial_lines_err: ?anyerror = null;1005 var initial_lines_err: ?anyerror = null;
990 var file_reader_buf: [max_source_line_bytes * 2]u8 = undefined;1006 var file_reader_buf: [max_source_line_bytes * 2]u8 = undefined;
991 var corresponding_lines: ?CorrespondingLines = CorrespondingLines.init(1007 var corresponding_lines: ?CorrespondingLines = CorrespondingLines.init(
1008 io,
992 cwd,1009 cwd,
993 err_details,1010 err_details,
994 source_line_for_display.line,1011 source_line_for_display.line,
...@@ -1084,6 +1101,7 @@ const CorrespondingLines = struct {...@@ -1084,6 +1101,7 @@ const CorrespondingLines = struct {
1084 code_page: SupportedCodePage,1101 code_page: SupportedCodePage,
10851102
1086 pub fn init(1103 pub fn init(
1104 io: Io,
1087 cwd: std.fs.Dir,1105 cwd: std.fs.Dir,
1088 err_details: ErrorDetails,1106 err_details: ErrorDetails,
1089 line_for_comparison: []const u8,1107 line_for_comparison: []const u8,
...@@ -1108,7 +1126,7 @@ const CorrespondingLines = struct {...@@ -1108,7 +1126,7 @@ const CorrespondingLines = struct {
1108 .code_page = err_details.code_page,1126 .code_page = err_details.code_page,
1109 .file_reader = undefined,1127 .file_reader = undefined,
1110 };1128 };
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);
1112 errdefer corresponding_lines.deinit();1130 errdefer corresponding_lines.deinit();
11131131
1114 try corresponding_lines.writeLineFromStreamVerbatim(1132 try corresponding_lines.writeLineFromStreamVerbatim(
lib/compiler/resinator/main.zig+90-73
...@@ -1,5 +1,9 @@...@@ -1,5 +1,9 @@
1const std = @import("std");
2const builtin = @import("builtin");1const builtin = @import("builtin");
2
3const std = @import("std");
4const Io = std.Io;
5const Allocator = std.mem.Allocator;
6
3const removeComments = @import("comments.zig").removeComments;7const removeComments = @import("comments.zig").removeComments;
4const parseAndRemoveLineCommands = @import("source_mapping.zig").parseAndRemoveLineCommands;8const parseAndRemoveLineCommands = @import("source_mapping.zig").parseAndRemoveLineCommands;
5const compile = @import("compile.zig").compile;9const compile = @import("compile.zig").compile;
...@@ -16,19 +20,18 @@ const aro = @import("aro");...@@ -16,19 +20,18 @@ const aro = @import("aro");
16const compiler_util = @import("../util.zig");20const compiler_util = @import("../util.zig");
1721
18pub fn main() !void {22pub fn main() !void {
19 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;23 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
20 defer std.debug.assert(gpa.deinit() == .ok);24 defer std.debug.assert(debug_allocator.deinit() == .ok);
21 const allocator = gpa.allocator();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);
24 defer arena_state.deinit();28 defer arena_state.deinit();
25 const arena = arena_state.allocator();29 const arena = arena_state.allocator();
2630
27 const stderr = std.fs.File.stderr();31 const stderr = std.fs.File.stderr();
28 const stderr_config = std.Io.tty.detectConfig(stderr);32 const stderr_config = std.Io.tty.detectConfig(stderr);
2933
30 const args = try std.process.argsAlloc(allocator);34 const args = try std.process.argsAlloc(arena);
31 defer std.process.argsFree(allocator, args);
3235
33 if (args.len < 2) {36 if (args.len < 2) {
34 try renderErrorMessage(std.debug.lockStderrWriter(&.{}), stderr_config, .err, "expected zig lib dir as first argument", .{});37 try renderErrorMessage(std.debug.lockStderrWriter(&.{}), stderr_config, .err, "expected zig lib dir as first argument", .{});
...@@ -59,11 +62,11 @@ pub fn main() !void {...@@ -59,11 +62,11 @@ pub fn main() !void {
59 };62 };
6063
61 var options = options: {64 var options = options: {
62 var cli_diagnostics = cli.Diagnostics.init(allocator);65 var cli_diagnostics = cli.Diagnostics.init(gpa);
63 defer cli_diagnostics.deinit();66 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) {
65 error.ParseError => {68 error.ParseError => {
66 try error_handler.emitCliDiagnostics(allocator, cli_args, &cli_diagnostics);69 try error_handler.emitCliDiagnostics(gpa, cli_args, &cli_diagnostics);
67 std.process.exit(1);70 std.process.exit(1);
68 },71 },
69 else => |e| return e,72 else => |e| return e,
...@@ -84,6 +87,10 @@ pub fn main() !void {...@@ -84,6 +87,10 @@ pub fn main() !void {
84 };87 };
85 defer options.deinit();88 defer options.deinit();
8689
90 var threaded: std.Io.Threaded = .init(gpa);
91 defer threaded.deinit();
92 const io = threaded.io();
93
87 if (options.print_help_and_exit) {94 if (options.print_help_and_exit) {
88 try cli.writeUsage(stdout, "zig rc");95 try cli.writeUsage(stdout, "zig rc");
89 try stdout.flush();96 try stdout.flush();
...@@ -99,12 +106,13 @@ pub fn main() !void {...@@ -99,12 +106,13 @@ pub fn main() !void {
99 try stdout.flush();106 try stdout.flush();
100 }107 }
101108
102 var dependencies = Dependencies.init(allocator);109 var dependencies = Dependencies.init(gpa);
103 defer dependencies.deinit();110 defer dependencies.deinit();
104 const maybe_dependencies: ?*Dependencies = if (options.depfile_path != null) &dependencies else null;111 const maybe_dependencies: ?*Dependencies = if (options.depfile_path != null) &dependencies else null;
105112
106 var include_paths = LazyIncludePaths{113 var include_paths = LazyIncludePaths{
107 .arena = arena,114 .arena = arena,
115 .io = io,
108 .auto_includes_option = options.auto_includes,116 .auto_includes_option = options.auto_includes,
109 .zig_lib_dir = zig_lib_dir,117 .zig_lib_dir = zig_lib_dir,
110 .target_machine_type = options.coff_options.target,118 .target_machine_type = options.coff_options.target,
...@@ -112,12 +120,12 @@ pub fn main() !void {...@@ -112,12 +120,12 @@ pub fn main() !void {
112120
113 const full_input = full_input: {121 const full_input = full_input: {
114 if (options.input_format == .rc and options.preprocess != .no) {122 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);
116 errdefer preprocessed_buf.deinit();124 errdefer preprocessed_buf.deinit();
117125
118 // We're going to throw away everything except the final preprocessed output anyway,126 // We're going to throw away everything except the final preprocessed output anyway,
119 // so we can use a scoped arena for everything else.127 // 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);
121 defer aro_arena_state.deinit();129 defer aro_arena_state.deinit();
122 const aro_arena = aro_arena_state.allocator();130 const aro_arena = aro_arena_state.allocator();
123131
...@@ -129,12 +137,12 @@ pub fn main() !void {...@@ -129,12 +137,12 @@ pub fn main() !void {
129 .color = stderr_config,137 .color = stderr_config,
130 } } },138 } } },
131 true => .{ .output = .{ .to_list = .{139 true => .{ .output = .{ .to_list = .{
132 .arena = .init(allocator),140 .arena = .init(gpa),
133 } } },141 } } },
134 };142 };
135 defer diagnostics.deinit();143 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());
138 defer comp.deinit();146 defer comp.deinit();
139147
140 var argv: std.ArrayList([]const u8) = .empty;148 var argv: std.ArrayList([]const u8) = .empty;
...@@ -159,20 +167,20 @@ pub fn main() !void {...@@ -159,20 +167,20 @@ pub fn main() !void {
159167
160 preprocess.preprocess(&comp, &preprocessed_buf.writer, argv.items, maybe_dependencies) catch |err| switch (err) {168 preprocess.preprocess(&comp, &preprocessed_buf.writer, argv.items, maybe_dependencies) catch |err| switch (err) {
161 error.GeneratedSourceError => {169 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);
163 std.process.exit(1);171 std.process.exit(1);
164 },172 },
165 // ArgError can occur if e.g. the .rc file is not found173 // ArgError can occur if e.g. the .rc file is not found
166 error.ArgError, error.PreprocessError => {174 error.ArgError, error.PreprocessError => {
167 try error_handler.emitAroDiagnostics(allocator, "failed during preprocessing", &comp);175 try error_handler.emitAroDiagnostics(gpa, "failed during preprocessing", &comp);
168 std.process.exit(1);176 std.process.exit(1);
169 },177 },
170 error.FileTooBig => {178 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", .{});
172 std.process.exit(1);180 std.process.exit(1);
173 },181 },
174 error.WriteFailed => {182 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", .{});
176 std.process.exit(1);184 std.process.exit(1);
177 },185 },
178 error.OutOfMemory => |e| return e,186 error.OutOfMemory => |e| return e,
...@@ -182,22 +190,22 @@ pub fn main() !void {...@@ -182,22 +190,22 @@ pub fn main() !void {
182 } else {190 } else {
183 switch (options.input_source) {191 switch (options.input_source) {
184 .stdio => |file| {192 .stdio => |file| {
185 var file_reader = file.reader(&.{});193 var file_reader = file.reader(io, &.{});
186 break :full_input file_reader.interface.allocRemaining(allocator, .unlimited) catch |err| {194 break :full_input file_reader.interface.allocRemaining(gpa, .unlimited) catch |err| {
187 try error_handler.emitMessage(allocator, .err, "unable to read input from stdin: {s}", .{@errorName(err)});195 try error_handler.emitMessage(gpa, .err, "unable to read input from stdin: {s}", .{@errorName(err)});
188 std.process.exit(1);196 std.process.exit(1);
189 };197 };
190 },198 },
191 .filename => |input_filename| {199 .filename => |input_filename| {
192 break :full_input std.fs.cwd().readFileAlloc(input_filename, allocator, .unlimited) catch |err| {200 break :full_input std.fs.cwd().readFileAlloc(input_filename, gpa, .unlimited) catch |err| {
193 try error_handler.emitMessage(allocator, .err, "unable to read input file path '{s}': {s}", .{ input_filename, @errorName(err) });201 try error_handler.emitMessage(gpa, .err, "unable to read input file path '{s}': {s}", .{ input_filename, @errorName(err) });
194 std.process.exit(1);202 std.process.exit(1);
195 };203 };
196 },204 },
197 }205 }
198 }206 }
199 };207 };
200 defer allocator.free(full_input);208 defer gpa.free(full_input);
201209
202 if (options.preprocess == .only) {210 if (options.preprocess == .only) {
203 switch (options.output_source) {211 switch (options.output_source) {
...@@ -221,55 +229,55 @@ pub fn main() !void {...@@ -221,55 +229,55 @@ pub fn main() !void {
221 }229 }
222 else if (options.input_format == .res)230 else if (options.input_format == .res)
223 IoStream.fromIoSource(options.input_source, .input) catch |err| {231 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) });
225 std.process.exit(1);233 std.process.exit(1);
226 }234 }
227 else235 else
228 IoStream.fromIoSource(options.output_source, .output) catch |err| {236 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) });
230 std.process.exit(1);238 std.process.exit(1);
231 };239 };
232 defer res_stream.deinit(allocator);240 defer res_stream.deinit(gpa);
233241
234 const res_data = res_data: {242 const res_data = res_data: {
235 if (options.input_format != .res) {243 if (options.input_format != .res) {
236 // Note: We still want to run this when no-preprocess is set because:244 // Note: We still want to run this when no-preprocess is set because:
237 // 1. We want to print accurate line numbers after removing multiline comments245 // 1. We want to print accurate line numbers after removing multiline comments
238 // 2. We want to be able to handle an already-preprocessed input with #line commands in it246 // 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) {
240 error.InvalidLineCommand => {248 error.InvalidLineCommand => {
241 // TODO: Maybe output the invalid line command249 // 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", .{});
243 if (options.preprocess == .no) {251 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>\"", .{});
245 } else {253 } 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", .{});
247 }255 }
248 std.process.exit(1);256 std.process.exit(1);
249 },257 },
250 error.LineNumberOverflow => {258 error.LineNumberOverflow => {
251 // TODO: Better error message259 // 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)});
253 std.process.exit(1);261 std.process.exit(1);
254 },262 },
255 error.OutOfMemory => |e| return e,263 error.OutOfMemory => |e| return e,
256 };264 };
257 defer mapping_results.mappings.deinit(allocator);265 defer mapping_results.mappings.deinit(gpa);
258266
259 const default_code_page = options.default_code_page orelse .windows1252;267 const default_code_page = options.default_code_page orelse .windows1252;
260 const has_disjoint_code_page = hasDisjointCodePage(mapping_results.result, &mapping_results.mappings, default_code_page);268 const has_disjoint_code_page = hasDisjointCodePage(mapping_results.result, &mapping_results.mappings, default_code_page);
261269
262 const final_input = try removeComments(mapping_results.result, mapping_results.result, &mapping_results.mappings);270 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);
265 defer diagnostics.deinit();273 defer diagnostics.deinit();
266274
267 var output_buffer: [4096]u8 = undefined;275 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);
269 defer res_stream_writer.deinit(&res_stream.source);277 defer res_stream_writer.deinit(&res_stream.source);
270 const output_buffered_stream = res_stream_writer.interface();278 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, .{
273 .cwd = std.fs.cwd(),281 .cwd = std.fs.cwd(),
274 .diagnostics = &diagnostics,282 .diagnostics = &diagnostics,
275 .source_mappings = &mapping_results.mappings,283 .source_mappings = &mapping_results.mappings,
...@@ -287,7 +295,7 @@ pub fn main() !void {...@@ -287,7 +295,7 @@ pub fn main() !void {
287 .warn_instead_of_error_on_invalid_code_page = options.warn_instead_of_error_on_invalid_code_page,295 .warn_instead_of_error_on_invalid_code_page = options.warn_instead_of_error_on_invalid_code_page,
288 }) catch |err| switch (err) {296 }) catch |err| switch (err) {
289 error.ParseError, error.CompileError => {297 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);
291 // Delete the output file on error299 // Delete the output file on error
292 res_stream.cleanupAfterError();300 res_stream.cleanupAfterError();
293 std.process.exit(1);301 std.process.exit(1);
...@@ -305,7 +313,7 @@ pub fn main() !void {...@@ -305,7 +313,7 @@ pub fn main() !void {
305 // write the depfile313 // write the depfile
306 if (options.depfile_path) |depfile_path| {314 if (options.depfile_path) |depfile_path| {
307 var depfile = std.fs.cwd().createFile(depfile_path, .{}) catch |err| {315 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) });
309 std.process.exit(1);317 std.process.exit(1);
310 };318 };
311 defer depfile.close();319 defer depfile.close();
...@@ -332,41 +340,41 @@ pub fn main() !void {...@@ -332,41 +340,41 @@ pub fn main() !void {
332340
333 if (options.output_format != .coff) return;341 if (options.output_format != .coff) return;
334342
335 break :res_data res_stream.source.readAll(allocator) catch |err| {343 break :res_data res_stream.source.readAll(gpa, io) catch |err| {
336 try error_handler.emitMessage(allocator, .err, "unable to read res from '{s}': {s}", .{ res_stream.name, @errorName(err) });344 try error_handler.emitMessage(gpa, .err, "unable to read res from '{s}': {s}", .{ res_stream.name, @errorName(err) });
337 std.process.exit(1);345 std.process.exit(1);
338 };346 };
339 };347 };
340 // No need to keep the res_data around after parsing the resources from it348 // 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
343 std.debug.assert(options.output_format == .coff);351 std.debug.assert(options.output_format == .coff);
344352
345 // TODO: Maybe use a buffered file reader instead of reading file into memory -> fbs353 // TODO: Maybe use a buffered file reader instead of reading file into memory -> fbs
346 var res_reader: std.Io.Reader = .fixed(res_data.bytes);354 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| {
348 // TODO: Better errors356 // 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) });
350 std.process.exit(1);358 std.process.exit(1);
351 };359 };
352 };360 };
353 defer resources.deinit();361 defer resources.deinit();
354362
355 var coff_stream = IoStream.fromIoSource(options.output_source, .output) catch |err| {363 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) });
357 std.process.exit(1);365 std.process.exit(1);
358 };366 };
359 defer coff_stream.deinit(allocator);367 defer coff_stream.deinit(gpa);
360368
361 var coff_output_buffer: [4096]u8 = undefined;369 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
364 var cvtres_diagnostics: cvtres.Diagnostics = .{ .none = {} };372 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| {
366 switch (err) {374 switch (err) {
367 error.DuplicateResource => {375 error.DuplicateResource => {
368 const duplicate_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];376 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}]", .{
370 duplicate_resource.name_value,378 duplicate_resource.name_value,
371 fmtResourceType(duplicate_resource.type_value),379 fmtResourceType(duplicate_resource.type_value),
372 duplicate_resource.language,380 duplicate_resource.language,
...@@ -374,8 +382,8 @@ pub fn main() !void {...@@ -374,8 +382,8 @@ pub fn main() !void {
374 },382 },
375 error.ResourceDataTooLong => {383 error.ResourceDataTooLong => {
376 const overflow_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];384 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", .{});385 try error_handler.emitMessage(gpa, .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}]", .{386 try error_handler.emitMessage(gpa, .note, "the resource with the invalid size is [id: {f}, type: {f}, language: {f}]", .{
379 overflow_resource.name_value,387 overflow_resource.name_value,
380 fmtResourceType(overflow_resource.type_value),388 fmtResourceType(overflow_resource.type_value),
381 overflow_resource.language,389 overflow_resource.language,
...@@ -383,15 +391,15 @@ pub fn main() !void {...@@ -383,15 +391,15 @@ pub fn main() !void {
383 },391 },
384 error.TotalResourceDataTooLong => {392 error.TotalResourceDataTooLong => {
385 const overflow_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];393 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", .{});394 try error_handler.emitMessage(gpa, .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}]", .{395 try error_handler.emitMessage(gpa, .note, "size overflow occurred when attempting to write this resource: [id: {f}, type: {f}, language: {f}]", .{
388 overflow_resource.name_value,396 overflow_resource.name_value,
389 fmtResourceType(overflow_resource.type_value),397 fmtResourceType(overflow_resource.type_value),
390 overflow_resource.language,398 overflow_resource.language,
391 });399 });
392 },400 },
393 else => {401 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) });
395 },403 },
396 }404 }
397 // Delete the output file on error405 // Delete the output file on error
...@@ -423,7 +431,7 @@ const IoStream = struct {...@@ -423,7 +431,7 @@ const IoStream = struct {
423 };431 };
424 }432 }
425433
426 pub fn deinit(self: *IoStream, allocator: std.mem.Allocator) void {434 pub fn deinit(self: *IoStream, allocator: Allocator) void {
427 self.source.deinit(allocator);435 self.source.deinit(allocator);
428 }436 }
429437
...@@ -458,7 +466,7 @@ const IoStream = struct {...@@ -458,7 +466,7 @@ const IoStream = struct {
458 }466 }
459 }467 }
460468
461 pub fn deinit(self: *Source, allocator: std.mem.Allocator) void {469 pub fn deinit(self: *Source, allocator: Allocator) void {
462 switch (self.*) {470 switch (self.*) {
463 .file => |file| file.close(),471 .file => |file| file.close(),
464 .stdio => {},472 .stdio => {},
...@@ -471,18 +479,18 @@ const IoStream = struct {...@@ -471,18 +479,18 @@ const IoStream = struct {
471 bytes: []const u8,479 bytes: []const u8,
472 needs_free: bool,480 needs_free: bool,
473481
474 pub fn deinit(self: Data, allocator: std.mem.Allocator) void {482 pub fn deinit(self: Data, allocator: Allocator) void {
475 if (self.needs_free) {483 if (self.needs_free) {
476 allocator.free(self.bytes);484 allocator.free(self.bytes);
477 }485 }
478 }486 }
479 };487 };
480488
481 pub fn readAll(self: Source, allocator: std.mem.Allocator) !Data {489 pub fn readAll(self: Source, allocator: Allocator, io: Io) !Data {
482 return switch (self) {490 return switch (self) {
483 inline .file, .stdio => |file| .{491 inline .file, .stdio => |file| .{
484 .bytes = b: {492 .bytes = b: {
485 var file_reader = file.reader(&.{});493 var file_reader = file.reader(io, &.{});
486 break :b try file_reader.interface.allocRemaining(allocator, .unlimited);494 break :b try file_reader.interface.allocRemaining(allocator, .unlimited);
487 },495 },
488 .needs_free = true,496 .needs_free = true,
...@@ -496,7 +504,7 @@ const IoStream = struct {...@@ -496,7 +504,7 @@ const IoStream = struct {
496 file: std.fs.File.Writer,504 file: std.fs.File.Writer,
497 allocating: std.Io.Writer.Allocating,505 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
501 pub fn interface(this: *@This()) *std.Io.Writer {509 pub fn interface(this: *@This()) *std.Io.Writer {
502 return switch (this.*) {510 return switch (this.*) {
...@@ -514,7 +522,7 @@ const IoStream = struct {...@@ -514,7 +522,7 @@ const IoStream = struct {
514 }522 }
515 };523 };
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 {
518 return switch (source.*) {526 return switch (source.*) {
519 .file, .stdio => |file| .{ .file = file.writer(buffer) },527 .file, .stdio => |file| .{ .file = file.writer(buffer) },
520 .memory => |*list| .{ .allocating = .fromArrayList(allocator, list) },528 .memory => |*list| .{ .allocating = .fromArrayList(allocator, list) },
...@@ -525,17 +533,20 @@ const IoStream = struct {...@@ -525,17 +533,20 @@ const IoStream = struct {
525};533};
526534
527const LazyIncludePaths = struct {535const LazyIncludePaths = struct {
528 arena: std.mem.Allocator,536 arena: Allocator,
537 io: Io,
529 auto_includes_option: cli.Options.AutoIncludes,538 auto_includes_option: cli.Options.AutoIncludes,
530 zig_lib_dir: []const u8,539 zig_lib_dir: []const u8,
531 target_machine_type: std.coff.IMAGE.FILE.MACHINE,540 target_machine_type: std.coff.IMAGE.FILE.MACHINE,
532 resolved_include_paths: ?[]const []const u8 = null,541 resolved_include_paths: ?[]const []const u8 = null,
533542
534 pub fn get(self: *LazyIncludePaths, error_handler: *ErrorHandler) ![]const []const u8 {543 pub fn get(self: *LazyIncludePaths, error_handler: *ErrorHandler) ![]const []const u8 {
544 const io = self.io;
545
535 if (self.resolved_include_paths) |include_paths|546 if (self.resolved_include_paths) |include_paths|
536 return include_paths;547 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) {
539 error.OutOfMemory => |e| return e,550 error.OutOfMemory => |e| return e,
540 else => |e| {551 else => |e| {
541 switch (e) {552 switch (e) {
...@@ -556,7 +567,13 @@ const LazyIncludePaths = struct {...@@ -556,7 +567,13 @@ const LazyIncludePaths = struct {
556 }567 }
557};568};
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 {
560 if (auto_includes_option == .none) return &[_][]const u8{};577 if (auto_includes_option == .none) return &[_][]const u8{};
561578
562 const includes_arch: std.Target.Cpu.Arch = switch (target_machine_type) {579 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...@@ -600,7 +617,7 @@ fn getIncludePaths(arena: std.mem.Allocator, auto_includes_option: cli.Options.A
600 .cpu_arch = includes_arch,617 .cpu_arch = includes_arch,
601 .abi = .msvc,618 .abi = .msvc,
602 };619 };
603 const target = std.zig.resolveTargetQueryOrFatal(target_query);620 const target = std.zig.resolveTargetQueryOrFatal(io, target_query);
604 const is_native_abi = target_query.isNativeAbi();621 const is_native_abi = target_query.isNativeAbi();
605 const detected_libc = std.zig.LibCDirs.detect(arena, zig_lib_dir, &target, is_native_abi, true, null) catch {622 const detected_libc = std.zig.LibCDirs.detect(arena, zig_lib_dir, &target, is_native_abi, true, null) catch {
606 if (includes == .any) {623 if (includes == .any) {
...@@ -626,7 +643,7 @@ fn getIncludePaths(arena: std.mem.Allocator, auto_includes_option: cli.Options.A...@@ -626,7 +643,7 @@ fn getIncludePaths(arena: std.mem.Allocator, auto_includes_option: cli.Options.A
626 .cpu_arch = includes_arch,643 .cpu_arch = includes_arch,
627 .abi = .gnu,644 .abi = .gnu,
628 };645 };
629 const target = std.zig.resolveTargetQueryOrFatal(target_query);646 const target = std.zig.resolveTargetQueryOrFatal(io, target_query);
630 const is_native_abi = target_query.isNativeAbi();647 const is_native_abi = target_query.isNativeAbi();
631 const detected_libc = std.zig.LibCDirs.detect(arena, zig_lib_dir, &target, is_native_abi, true, null) catch |err| switch (err) {648 const detected_libc = std.zig.LibCDirs.detect(arena, zig_lib_dir, &target, is_native_abi, true, null) catch |err| switch (err) {
632 error.OutOfMemory => |e| return e,649 error.OutOfMemory => |e| return e,
...@@ -647,7 +664,7 @@ const ErrorHandler = union(enum) {...@@ -647,7 +664,7 @@ const ErrorHandler = union(enum) {
647664
648 pub fn emitCliDiagnostics(665 pub fn emitCliDiagnostics(
649 self: *ErrorHandler,666 self: *ErrorHandler,
650 allocator: std.mem.Allocator,667 allocator: Allocator,
651 args: []const []const u8,668 args: []const []const u8,
652 diagnostics: *cli.Diagnostics,669 diagnostics: *cli.Diagnostics,
653 ) !void {670 ) !void {
...@@ -666,7 +683,7 @@ const ErrorHandler = union(enum) {...@@ -666,7 +683,7 @@ const ErrorHandler = union(enum) {
666683
667 pub fn emitAroDiagnostics(684 pub fn emitAroDiagnostics(
668 self: *ErrorHandler,685 self: *ErrorHandler,
669 allocator: std.mem.Allocator,686 allocator: Allocator,
670 fail_msg: []const u8,687 fail_msg: []const u8,
671 comp: *aro.Compilation,688 comp: *aro.Compilation,
672 ) !void {689 ) !void {
...@@ -692,7 +709,7 @@ const ErrorHandler = union(enum) {...@@ -692,7 +709,7 @@ const ErrorHandler = union(enum) {
692709
693 pub fn emitDiagnostics(710 pub fn emitDiagnostics(
694 self: *ErrorHandler,711 self: *ErrorHandler,
695 allocator: std.mem.Allocator,712 allocator: Allocator,
696 cwd: std.fs.Dir,713 cwd: std.fs.Dir,
697 source: []const u8,714 source: []const u8,
698 diagnostics: *Diagnostics,715 diagnostics: *Diagnostics,
...@@ -713,7 +730,7 @@ const ErrorHandler = union(enum) {...@@ -713,7 +730,7 @@ const ErrorHandler = union(enum) {
713730
714 pub fn emitMessage(731 pub fn emitMessage(
715 self: *ErrorHandler,732 self: *ErrorHandler,
716 allocator: std.mem.Allocator,733 allocator: Allocator,
717 msg_type: @import("utils.zig").ErrorMessageType,734 msg_type: @import("utils.zig").ErrorMessageType,
718 comptime format: []const u8,735 comptime format: []const u8,
719 args: anytype,736 args: anytype,
...@@ -738,7 +755,7 @@ const ErrorHandler = union(enum) {...@@ -738,7 +755,7 @@ const ErrorHandler = union(enum) {
738};755};
739756
740fn cliDiagnosticsToErrorBundle(757fn cliDiagnosticsToErrorBundle(
741 gpa: std.mem.Allocator,758 gpa: Allocator,
742 diagnostics: *cli.Diagnostics,759 diagnostics: *cli.Diagnostics,
743) !ErrorBundle {760) !ErrorBundle {
744 @branchHint(.cold);761 @branchHint(.cold);
...@@ -783,7 +800,7 @@ fn cliDiagnosticsToErrorBundle(...@@ -783,7 +800,7 @@ fn cliDiagnosticsToErrorBundle(
783}800}
784801
785fn diagnosticsToErrorBundle(802fn diagnosticsToErrorBundle(
786 gpa: std.mem.Allocator,803 gpa: Allocator,
787 source: []const u8,804 source: []const u8,
788 diagnostics: *Diagnostics,805 diagnostics: *Diagnostics,
789 mappings: SourceMappings,806 mappings: SourceMappings,
...@@ -870,7 +887,7 @@ fn diagnosticsToErrorBundle(...@@ -870,7 +887,7 @@ fn diagnosticsToErrorBundle(
870 return try bundle.toOwnedBundle("");887 return try bundle.toOwnedBundle("");
871}888}
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 {
874 @branchHint(.cold);891 @branchHint(.cold);
875 var bundle: ErrorBundle.Wip = undefined;892 var bundle: ErrorBundle.Wip = undefined;
876 try bundle.init(allocator);893 try bundle.init(allocator);
lib/compiler/resinator/utils.zig+5-1
...@@ -26,7 +26,11 @@ pub const UncheckedSliceWriter = struct {...@@ -26,7 +26,11 @@ pub const UncheckedSliceWriter = struct {
26/// Cross-platform 'std.fs.Dir.openFile' wrapper that will always return IsDir if26/// Cross-platform 'std.fs.Dir.openFile' wrapper that will always return IsDir if
27/// a directory is attempted to be opened.27/// a directory is attempted to be opened.
28/// TODO: Remove once https://github.com/ziglang/zig/issues/5732 is addressed.28/// 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 {
30 const file = try cwd.openFile(path, flags);34 const file = try cwd.openFile(path, flags);
31 errdefer file.close();35 errdefer file.close();
32 // https://github.com/ziglang/zig/issues/573236 // https://github.com/ziglang/zig/issues/5732
lib/compiler/test_runner.zig+10-13
...@@ -2,6 +2,7 @@...@@ -2,6 +2,7 @@
2const builtin = @import("builtin");2const builtin = @import("builtin");
33
4const std = @import("std");4const std = @import("std");
5const Io = std.Io;
5const fatal = std.process.fatal;6const fatal = std.process.fatal;
6const testing = std.testing;7const testing = std.testing;
7const assert = std.debug.assert;8const assert = std.debug.assert;
...@@ -12,10 +13,11 @@ pub const std_options: std.Options = .{...@@ -12,10 +13,11 @@ pub const std_options: std.Options = .{
12};13};
1314
14var log_err_count: usize = 0;15var log_err_count: usize = 0;
15var fba = std.heap.FixedBufferAllocator.init(&fba_buffer);16var fba: std.heap.FixedBufferAllocator = .init(&fba_buffer);
16var fba_buffer: [8192]u8 = undefined;17var fba_buffer: [8192]u8 = undefined;
17var stdin_buffer: [4096]u8 = undefined;18var stdin_buffer: [4096]u8 = undefined;
18var stdout_buffer: [4096]u8 = undefined;19var stdout_buffer: [4096]u8 = undefined;
20var runner_threaded_io: Io.Threaded = .init_single_threaded;
1921
20/// Keep in sync with logic in `std.Build.addRunArtifact` which decides whether22/// Keep in sync with logic in `std.Build.addRunArtifact` which decides whether
21/// the test runner will communicate with the build runner via `std.zig.Server`.23/// the test runner will communicate with the build runner via `std.zig.Server`.
...@@ -63,8 +65,6 @@ pub fn main() void {...@@ -63,8 +65,6 @@ pub fn main() void {
63 fuzz_abi.fuzzer_init(.fromSlice(cache_dir));65 fuzz_abi.fuzzer_init(.fromSlice(cache_dir));
64 }66 }
6567
66 fba.reset();
67
68 if (listen) {68 if (listen) {
69 return mainServer() catch @panic("internal test runner failure");69 return mainServer() catch @panic("internal test runner failure");
70 } else {70 } else {
...@@ -74,7 +74,7 @@ pub fn main() void {...@@ -74,7 +74,7 @@ pub fn main() void {
7474
75fn mainServer() !void {75fn mainServer() !void {
76 @disableInstrumentation();76 @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);
78 var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer);78 var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer);
79 var server = try std.zig.Server.init(.{79 var server = try std.zig.Server.init(.{
80 .in = &stdin_reader.interface,80 .in = &stdin_reader.interface,
...@@ -131,6 +131,7 @@ fn mainServer() !void {...@@ -131,6 +131,7 @@ fn mainServer() !void {
131131
132 .run_test => {132 .run_test => {
133 testing.allocator_instance = .{};133 testing.allocator_instance = .{};
134 testing.io_instance = .init(testing.allocator);
134 log_err_count = 0;135 log_err_count = 0;
135 const index = try server.receiveBody_u32();136 const index = try server.receiveBody_u32();
136 const test_fn = builtin.test_functions[index];137 const test_fn = builtin.test_functions[index];
...@@ -152,6 +153,7 @@ fn mainServer() !void {...@@ -152,6 +153,7 @@ fn mainServer() !void {
152 break :s .fail;153 break :s .fail;
153 },154 },
154 };155 };
156 testing.io_instance.deinit();
155 const leak_count = testing.allocator_instance.detectLeaks();157 const leak_count = testing.allocator_instance.detectLeaks();
156 testing.allocator_instance.deinitWithoutLeakChecks();158 testing.allocator_instance.deinitWithoutLeakChecks();
157 try server.serveTestResults(.{159 try server.serveTestResults(.{
...@@ -228,18 +230,13 @@ fn mainTerminal() void {...@@ -228,18 +230,13 @@ fn mainTerminal() void {
228 });230 });
229 const have_tty = std.fs.File.stderr().isTty();231 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
236 var leaks: usize = 0;233 var leaks: usize = 0;
237 for (test_fn_list, 0..) |test_fn, i| {234 for (test_fn_list, 0..) |test_fn, i| {
238 testing.allocator_instance = .{};235 testing.allocator_instance = .{};
236 testing.io_instance = .init(testing.allocator);
239 defer {237 defer {
240 if (testing.allocator_instance.deinit() == .leak) {238 testing.io_instance.deinit();
241 leaks += 1;239 if (testing.allocator_instance.deinit() == .leak) leaks += 1;
242 }
243 }240 }
244 testing.log_level = .warn;241 testing.log_level = .warn;
245242
...@@ -326,7 +323,7 @@ pub fn mainSimple() anyerror!void {...@@ -326,7 +323,7 @@ pub fn mainSimple() anyerror!void {
326 .stage2_aarch64, .stage2_riscv64 => true,323 .stage2_aarch64, .stage2_riscv64 => true,
327 else => false,324 else => false,
328 };325 };
329 // is the backend capable of calling `std.Io.Writer.print`?326 // is the backend capable of calling `Io.Writer.print`?
330 const enable_print = switch (builtin.zig_backend) {327 const enable_print = switch (builtin.zig_backend) {
331 .stage2_aarch64, .stage2_riscv64 => true,328 .stage2_aarch64, .stage2_riscv64 => true,
332 else => false,329 else => false,
lib/compiler/translate-c/main.zig+5-1
...@@ -18,6 +18,10 @@ pub fn main() u8 {...@@ -18,6 +18,10 @@ pub fn main() u8 {
18 defer arena_instance.deinit();18 defer arena_instance.deinit();
19 const arena = arena_instance.allocator();19 const arena = arena_instance.allocator();
2020
21 var threaded: std.Io.Threaded = .init(gpa);
22 defer threaded.deinit();
23 const io = threaded.io();
24
21 var args = process.argsAlloc(arena) catch {25 var args = process.argsAlloc(arena) catch {
22 std.debug.print("ran out of memory allocating arguments\n", .{});26 std.debug.print("ran out of memory allocating arguments\n", .{});
23 if (fast_exit) process.exit(1);27 if (fast_exit) process.exit(1);
...@@ -42,7 +46,7 @@ pub fn main() u8 {...@@ -42,7 +46,7 @@ pub fn main() u8 {
42 };46 };
43 defer diagnostics.deinit();47 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) {
46 error.OutOfMemory => {50 error.OutOfMemory => {
47 std.debug.print("ran out of memory initializing C compilation\n", .{});51 std.debug.print("ran out of memory initializing C compilation\n", .{});
48 if (fast_exit) process.exit(1);52 if (fast_exit) process.exit(1);
lib/std/Build.zig+9-3
...@@ -1,5 +1,7 @@...@@ -1,5 +1,7 @@
1const std = @import("std.zig");
2const builtin = @import("builtin");1const builtin = @import("builtin");
2
3const std = @import("std.zig");
4const Io = std.Io;
3const fs = std.fs;5const fs = std.fs;
4const mem = std.mem;6const mem = std.mem;
5const debug = std.debug;7const debug = std.debug;
...@@ -110,6 +112,7 @@ pub const ReleaseMode = enum {...@@ -110,6 +112,7 @@ pub const ReleaseMode = enum {
110/// Shared state among all Build instances.112/// Shared state among all Build instances.
111/// Settings that are here rather than in Build are not configurable per-package.113/// Settings that are here rather than in Build are not configurable per-package.
112pub const Graph = struct {114pub const Graph = struct {
115 io: Io,
113 arena: Allocator,116 arena: Allocator,
114 system_library_options: std.StringArrayHashMapUnmanaged(SystemLibraryMode) = .empty,117 system_library_options: std.StringArrayHashMapUnmanaged(SystemLibraryMode) = .empty,
115 system_package_mode: bool = false,118 system_package_mode: bool = false,
...@@ -1834,6 +1837,8 @@ pub fn runAllowFail(...@@ -1834,6 +1837,8 @@ pub fn runAllowFail(
1834 if (!process.can_spawn)1837 if (!process.can_spawn)
1835 return error.ExecNotSupported;1838 return error.ExecNotSupported;
18361839
1840 const io = b.graph.io;
1841
1837 const max_output_size = 400 * 1024;1842 const max_output_size = 400 * 1024;
1838 var child = std.process.Child.init(argv, b.allocator);1843 var child = std.process.Child.init(argv, b.allocator);
1839 child.stdin_behavior = .Ignore;1844 child.stdin_behavior = .Ignore;
...@@ -1844,7 +1849,7 @@ pub fn runAllowFail(...@@ -1844,7 +1849,7 @@ pub fn runAllowFail(
1844 try Step.handleVerbose2(b, null, child.env_map, argv);1849 try Step.handleVerbose2(b, null, child.env_map, argv);
1845 try child.spawn();1850 try child.spawn();
18461851
1847 var stdout_reader = child.stdout.?.readerStreaming(&.{});1852 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
1848 const stdout = stdout_reader.interface.allocRemaining(b.allocator, .limited(max_output_size)) catch {1853 const stdout = stdout_reader.interface.allocRemaining(b.allocator, .limited(max_output_size)) catch {
1849 return error.ReadFailure;1854 return error.ReadFailure;
1850 };1855 };
...@@ -2666,9 +2671,10 @@ pub fn resolveTargetQuery(b: *Build, query: Target.Query) ResolvedTarget {...@@ -2666,9 +2671,10 @@ pub fn resolveTargetQuery(b: *Build, query: Target.Query) ResolvedTarget {
2666 // Hot path. This is faster than querying the native CPU and OS again.2671 // Hot path. This is faster than querying the native CPU and OS again.
2667 return b.graph.host;2672 return b.graph.host;
2668 }2673 }
2674 const io = b.graph.io;
2669 return .{2675 return .{
2670 .query = query,2676 .query = query,
2671 .result = std.zig.system.resolveTargetQuery(query) catch2677 .result = std.zig.system.resolveTargetQuery(io, query) catch
2672 @panic("unable to resolve target query"),2678 @panic("unable to resolve target query"),
2673 };2679 };
2674}2680}
lib/std/Build/Cache.zig+44-28
...@@ -3,8 +3,10 @@...@@ -3,8 +3,10 @@
3//! not to withstand attacks using specially-crafted input.3//! not to withstand attacks using specially-crafted input.
44
5const Cache = @This();5const Cache = @This();
6const std = @import("std");
7const builtin = @import("builtin");6const builtin = @import("builtin");
7
8const std = @import("std");
9const Io = std.Io;
8const crypto = std.crypto;10const crypto = std.crypto;
9const fs = std.fs;11const fs = std.fs;
10const assert = std.debug.assert;12const assert = std.debug.assert;
...@@ -15,10 +17,11 @@ const Allocator = std.mem.Allocator;...@@ -15,10 +17,11 @@ const Allocator = std.mem.Allocator;
15const log = std.log.scoped(.cache);17const log = std.log.scoped(.cache);
1618
17gpa: Allocator,19gpa: Allocator,
20io: Io,
18manifest_dir: fs.Dir,21manifest_dir: fs.Dir,
19hash: HashHelper = .{},22hash: HashHelper = .{},
20/// This value is accessed from multiple threads, protected by mutex.23/// This value is accessed from multiple threads, protected by mutex.
21recent_problematic_timestamp: i128 = 0,24recent_problematic_timestamp: Io.Timestamp = .zero,
22mutex: std.Thread.Mutex = .{},25mutex: std.Thread.Mutex = .{},
2326
24/// A set of strings such as the zig library directory or project source root, which27/// A set of strings such as the zig library directory or project source root, which
...@@ -152,7 +155,7 @@ pub const File = struct {...@@ -152,7 +155,7 @@ pub const File = struct {
152 pub const Stat = struct {155 pub const Stat = struct {
153 inode: fs.File.INode,156 inode: fs.File.INode,
154 size: u64,157 size: u64,
155 mtime: i128,158 mtime: Io.Timestamp,
156159
157 pub fn fromFs(fs_stat: fs.File.Stat) Stat {160 pub fn fromFs(fs_stat: fs.File.Stat) Stat {
158 return .{161 return .{
...@@ -327,7 +330,7 @@ pub const Manifest = struct {...@@ -327,7 +330,7 @@ pub const Manifest = struct {
327 diagnostic: Diagnostic = .none,330 diagnostic: Diagnostic = .none,
328 /// Keeps track of the last time we performed a file system write to observe331 /// Keeps track of the last time we performed a file system write to observe
329 /// what time the file system thinks it is, according to its own granularity.332 /// 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
332 pub const Diagnostic = union(enum) {335 pub const Diagnostic = union(enum) {
333 none,336 none,
...@@ -661,9 +664,10 @@ pub const Manifest = struct {...@@ -661,9 +664,10 @@ pub const Manifest = struct {
661 },664 },
662 } {665 } {
663 const gpa = self.cache.gpa;666 const gpa = self.cache.gpa;
667 const io = self.cache.io;
664 const input_file_count = self.files.entries.len;668 const input_file_count = self.files.entries.len;
665 var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded669 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.
667 const limit: std.Io.Limit = .limited(manifest_file_size_max);671 const limit: std.Io.Limit = .limited(manifest_file_size_max);
668 const file_contents = manifest_reader.interface.allocRemaining(gpa, limit) catch |err| switch (err) {672 const file_contents = manifest_reader.interface.allocRemaining(gpa, limit) catch |err| switch (err) {
669 error.OutOfMemory => return error.OutOfMemory,673 error.OutOfMemory => return error.OutOfMemory,
...@@ -724,7 +728,7 @@ pub const Manifest = struct {...@@ -724,7 +728,7 @@ pub const Manifest = struct {
724 file.stat = .{728 file.stat = .{
725 .size = stat_size,729 .size = stat_size,
726 .inode = stat_inode,730 .inode = stat_inode,
727 .mtime = stat_mtime,731 .mtime = .{ .nanoseconds = stat_mtime },
728 };732 };
729 file.bin_digest = file_bin_digest;733 file.bin_digest = file_bin_digest;
730 break :f file;734 break :f file;
...@@ -743,7 +747,7 @@ pub const Manifest = struct {...@@ -743,7 +747,7 @@ pub const Manifest = struct {
743 .stat = .{747 .stat = .{
744 .size = stat_size,748 .size = stat_size,
745 .inode = stat_inode,749 .inode = stat_inode,
746 .mtime = stat_mtime,750 .mtime = .{ .nanoseconds = stat_mtime },
747 },751 },
748 .bin_digest = file_bin_digest,752 .bin_digest = file_bin_digest,
749 };753 };
...@@ -776,7 +780,7 @@ pub const Manifest = struct {...@@ -776,7 +780,7 @@ pub const Manifest = struct {
776 return error.CacheCheckFailed;780 return error.CacheCheckFailed;
777 };781 };
778 const size_match = actual_stat.size == cache_hash_file.stat.size;782 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;
780 const inode_match = actual_stat.inode == cache_hash_file.stat.inode;784 const inode_match = actual_stat.inode == cache_hash_file.stat.inode;
781785
782 if (!size_match or !mtime_match or !inode_match) {786 if (!size_match or !mtime_match or !inode_match) {
...@@ -788,7 +792,7 @@ pub const Manifest = struct {...@@ -788,7 +792,7 @@ pub const Manifest = struct {
788792
789 if (self.isProblematicTimestamp(cache_hash_file.stat.mtime)) {793 if (self.isProblematicTimestamp(cache_hash_file.stat.mtime)) {
790 // The actual file has an unreliable timestamp, force it to be hashed794 // 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;
792 cache_hash_file.stat.inode = 0;796 cache_hash_file.stat.inode = 0;
793 }797 }
794798
...@@ -844,10 +848,10 @@ pub const Manifest = struct {...@@ -844,10 +848,10 @@ pub const Manifest = struct {
844 }848 }
845 }849 }
846850
847 fn isProblematicTimestamp(man: *Manifest, file_time: i128) bool {851 fn isProblematicTimestamp(man: *Manifest, timestamp: Io.Timestamp) bool {
848 // If the file_time is prior to the most recent problematic timestamp852 // If the file_time is prior to the most recent problematic timestamp
849 // then we don't need to access the filesystem.853 // 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)
851 return false;855 return false;
852856
853 // Next we will check the globally shared Cache timestamp, which is accessed857 // Next we will check the globally shared Cache timestamp, which is accessed
...@@ -857,7 +861,7 @@ pub const Manifest = struct {...@@ -857,7 +861,7 @@ pub const Manifest = struct {
857861
858 // Save the global one to our local one to avoid locking next time.862 // Save the global one to our local one to avoid locking next time.
859 man.recent_problematic_timestamp = man.cache.recent_problematic_timestamp;863 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)
861 return false;865 return false;
862866
863 // This flag prevents multiple filesystem writes for the same hit() call.867 // This flag prevents multiple filesystem writes for the same hit() call.
...@@ -875,7 +879,7 @@ pub const Manifest = struct {...@@ -875,7 +879,7 @@ pub const Manifest = struct {
875 man.cache.recent_problematic_timestamp = man.recent_problematic_timestamp;879 man.cache.recent_problematic_timestamp = man.recent_problematic_timestamp;
876 }880 }
877881
878 return file_time >= man.recent_problematic_timestamp;882 return timestamp.nanoseconds >= man.recent_problematic_timestamp.nanoseconds;
879 }883 }
880884
881 fn populateFileHash(self: *Manifest, ch_file: *File) !void {885 fn populateFileHash(self: *Manifest, ch_file: *File) !void {
...@@ -900,7 +904,7 @@ pub const Manifest = struct {...@@ -900,7 +904,7 @@ pub const Manifest = struct {
900904
901 if (self.isProblematicTimestamp(ch_file.stat.mtime)) {905 if (self.isProblematicTimestamp(ch_file.stat.mtime)) {
902 // The actual file has an unreliable timestamp, force it to be hashed906 // The actual file has an unreliable timestamp, force it to be hashed
903 ch_file.stat.mtime = 0;907 ch_file.stat.mtime = .zero;
904 ch_file.stat.inode = 0;908 ch_file.stat.inode = 0;
905 }909 }
906910
...@@ -1036,7 +1040,7 @@ pub const Manifest = struct {...@@ -1036,7 +1040,7 @@ pub const Manifest = struct {
10361040
1037 if (self.isProblematicTimestamp(new_file.stat.mtime)) {1041 if (self.isProblematicTimestamp(new_file.stat.mtime)) {
1038 // The actual file has an unreliable timestamp, force it to be hashed1042 // The actual file has an unreliable timestamp, force it to be hashed
1039 new_file.stat.mtime = 0;1043 new_file.stat.mtime = .zero;
1040 new_file.stat.inode = 0;1044 new_file.stat.inode = 0;
1041 }1045 }
10421046
...@@ -1301,7 +1305,7 @@ fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) fs.File.PReadErro...@@ -1301,7 +1305,7 @@ fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) fs.File.PReadErro
1301}1305}
13021306
1303// Create/Write a file, close it, then grab its stat.mtime timestamp.1307// 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 {
1305 const test_out_file = "test-filetimestamp.tmp";1309 const test_out_file = "test-filetimestamp.tmp";
13061310
1307 var file = try dir.createFile(test_out_file, .{1311 var file = try dir.createFile(test_out_file, .{
...@@ -1317,6 +1321,8 @@ fn testGetCurrentFileTimestamp(dir: fs.Dir) !i128 {...@@ -1317,6 +1321,8 @@ fn testGetCurrentFileTimestamp(dir: fs.Dir) !i128 {
1317}1321}
13181322
1319test "cache file and then recall it" {1323test "cache file and then recall it" {
1324 const io = std.testing.io;
1325
1320 var tmp = testing.tmpDir(.{});1326 var tmp = testing.tmpDir(.{});
1321 defer tmp.cleanup();1327 defer tmp.cleanup();
13221328
...@@ -1327,15 +1333,16 @@ test "cache file and then recall it" {...@@ -1327,15 +1333,16 @@ test "cache file and then recall it" {
13271333
1328 // Wait for file timestamps to tick1334 // Wait for file timestamps to tick
1329 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);1335 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
1330 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {1336 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
1331 std.Thread.sleep(1);1337 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
1332 }1338 }
13331339
1334 var digest1: HexDigest = undefined;1340 var digest1: HexDigest = undefined;
1335 var digest2: HexDigest = undefined;1341 var digest2: HexDigest = undefined;
13361342
1337 {1343 {
1338 var cache = Cache{1344 var cache: Cache = .{
1345 .io = io,
1339 .gpa = testing.allocator,1346 .gpa = testing.allocator,
1340 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),1347 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
1341 };1348 };
...@@ -1378,6 +1385,8 @@ test "cache file and then recall it" {...@@ -1378,6 +1385,8 @@ test "cache file and then recall it" {
1378}1385}
13791386
1380test "check that changing a file makes cache fail" {1387test "check that changing a file makes cache fail" {
1388 const io = std.testing.io;
1389
1381 var tmp = testing.tmpDir(.{});1390 var tmp = testing.tmpDir(.{});
1382 defer tmp.cleanup();1391 defer tmp.cleanup();
13831392
...@@ -1390,15 +1399,16 @@ test "check that changing a file makes cache fail" {...@@ -1390,15 +1399,16 @@ test "check that changing a file makes cache fail" {
13901399
1391 // Wait for file timestamps to tick1400 // Wait for file timestamps to tick
1392 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);1401 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
1393 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {1402 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
1394 std.Thread.sleep(1);1403 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
1395 }1404 }
13961405
1397 var digest1: HexDigest = undefined;1406 var digest1: HexDigest = undefined;
1398 var digest2: HexDigest = undefined;1407 var digest2: HexDigest = undefined;
13991408
1400 {1409 {
1401 var cache = Cache{1410 var cache: Cache = .{
1411 .io = io,
1402 .gpa = testing.allocator,1412 .gpa = testing.allocator,
1403 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),1413 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
1404 };1414 };
...@@ -1447,6 +1457,8 @@ test "check that changing a file makes cache fail" {...@@ -1447,6 +1457,8 @@ test "check that changing a file makes cache fail" {
1447}1457}
14481458
1449test "no file inputs" {1459test "no file inputs" {
1460 const io = testing.io;
1461
1450 var tmp = testing.tmpDir(.{});1462 var tmp = testing.tmpDir(.{});
1451 defer tmp.cleanup();1463 defer tmp.cleanup();
14521464
...@@ -1455,7 +1467,8 @@ test "no file inputs" {...@@ -1455,7 +1467,8 @@ test "no file inputs" {
1455 var digest1: HexDigest = undefined;1467 var digest1: HexDigest = undefined;
1456 var digest2: HexDigest = undefined;1468 var digest2: HexDigest = undefined;
14571469
1458 var cache = Cache{1470 var cache: Cache = .{
1471 .io = io,
1459 .gpa = testing.allocator,1472 .gpa = testing.allocator,
1460 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),1473 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
1461 };1474 };
...@@ -1490,6 +1503,8 @@ test "no file inputs" {...@@ -1490,6 +1503,8 @@ test "no file inputs" {
1490}1503}
14911504
1492test "Manifest with files added after initial hash work" {1505test "Manifest with files added after initial hash work" {
1506 const io = std.testing.io;
1507
1493 var tmp = testing.tmpDir(.{});1508 var tmp = testing.tmpDir(.{});
1494 defer tmp.cleanup();1509 defer tmp.cleanup();
14951510
...@@ -1502,8 +1517,8 @@ test "Manifest with files added after initial hash work" {...@@ -1502,8 +1517,8 @@ test "Manifest with files added after initial hash work" {
15021517
1503 // Wait for file timestamps to tick1518 // Wait for file timestamps to tick
1504 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);1519 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
1505 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {1520 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time.nanoseconds) {
1506 std.Thread.sleep(1);1521 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
1507 }1522 }
15081523
1509 var digest1: HexDigest = undefined;1524 var digest1: HexDigest = undefined;
...@@ -1511,7 +1526,8 @@ test "Manifest with files added after initial hash work" {...@@ -1511,7 +1526,8 @@ test "Manifest with files added after initial hash work" {
1511 var digest3: HexDigest = undefined;1526 var digest3: HexDigest = undefined;
15121527
1513 {1528 {
1514 var cache = Cache{1529 var cache: Cache = .{
1530 .io = io,
1515 .gpa = testing.allocator,1531 .gpa = testing.allocator,
1516 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),1532 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
1517 };1533 };
...@@ -1552,8 +1568,8 @@ test "Manifest with files added after initial hash work" {...@@ -1552,8 +1568,8 @@ test "Manifest with files added after initial hash work" {
15521568
1553 // Wait for file timestamps to tick1569 // Wait for file timestamps to tick
1554 const initial_time2 = try testGetCurrentFileTimestamp(tmp.dir);1570 const initial_time2 = try testGetCurrentFileTimestamp(tmp.dir);
1555 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time2) {1571 while ((try testGetCurrentFileTimestamp(tmp.dir)).nanoseconds == initial_time2.nanoseconds) {
1556 std.Thread.sleep(1);1572 try std.Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io);
1557 }1573 }
15581574
1559 {1575 {
lib/std/Build/Cache/Path.zig+6-4
...@@ -1,5 +1,7 @@...@@ -1,5 +1,7 @@
1const Path = @This();1const Path = @This();
2
2const std = @import("../../std.zig");3const std = @import("../../std.zig");
4const Io = std.Io;
3const assert = std.debug.assert;5const assert = std.debug.assert;
4const fs = std.fs;6const fs = std.fs;
5const Allocator = std.mem.Allocator;7const Allocator = std.mem.Allocator;
...@@ -119,7 +121,7 @@ pub fn atomicFile(...@@ -119,7 +121,7 @@ pub fn atomicFile(
119 return p.root_dir.handle.atomicFile(joined_path, options);121 return p.root_dir.handle.atomicFile(joined_path, options);
120}122}
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 {
123 var buf: [fs.max_path_bytes]u8 = undefined;125 var buf: [fs.max_path_bytes]u8 = undefined;
124 const joined_path = if (p.sub_path.len == 0) sub_path else p: {126 const joined_path = if (p.sub_path.len == 0) sub_path else p: {
125 break :p std.fmt.bufPrint(&buf, "{s}" ++ fs.path.sep_str ++ "{s}", .{127 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) {...@@ -151,7 +153,7 @@ pub fn fmtEscapeString(path: Path) std.fmt.Alt(Path, formatEscapeString) {
151 return .{ .data = path };153 return .{ .data = path };
152}154}
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 {
155 if (path.root_dir.path) |p| {157 if (path.root_dir.path) |p| {
156 try std.zig.stringEscape(p, writer);158 try std.zig.stringEscape(p, writer);
157 if (path.sub_path.len > 0) try std.zig.stringEscape(fs.path.sep_str, writer);159 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) {...@@ -167,7 +169,7 @@ pub fn fmtEscapeChar(path: Path) std.fmt.Alt(Path, formatEscapeChar) {
167}169}
168170
169/// Deprecated, use double quoted escape to print paths.171/// 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 {
171 if (path.root_dir.path) |p| {173 if (path.root_dir.path) |p| {
172 for (p) |byte| try std.zig.charEscape(byte, writer);174 for (p) |byte| try std.zig.charEscape(byte, writer);
173 if (path.sub_path.len > 0) try writer.writeByte(fs.path.sep);175 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!...@@ -177,7 +179,7 @@ pub fn formatEscapeChar(path: Path, writer: *std.Io.Writer) std.Io.Writer.Error!
177 }179 }
178}180}
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 {
181 if (std.fs.path.isAbsolute(self.sub_path)) {183 if (std.fs.path.isAbsolute(self.sub_path)) {
182 try writer.writeAll(self.sub_path);184 try writer.writeAll(self.sub_path);
183 return;185 return;
lib/std/Build/Fuzz.zig+6-1
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("../std.zig");1const std = @import("../std.zig");
2const Io = std.Io;
2const Build = std.Build;3const Build = std.Build;
3const Cache = Build.Cache;4const Cache = Build.Cache;
4const Step = std.Build.Step;5const Step = std.Build.Step;
...@@ -14,6 +15,7 @@ const Fuzz = @This();...@@ -14,6 +15,7 @@ const Fuzz = @This();
14const build_runner = @import("root");15const build_runner = @import("root");
1516
16gpa: Allocator,17gpa: Allocator,
18io: Io,
17mode: Mode,19mode: Mode,
1820
19/// Allocated into `gpa`.21/// Allocated into `gpa`.
...@@ -75,6 +77,7 @@ const CoverageMap = struct {...@@ -75,6 +77,7 @@ const CoverageMap = struct {
7577
76pub fn init(78pub fn init(
77 gpa: Allocator,79 gpa: Allocator,
80 io: Io,
78 thread_pool: *std.Thread.Pool,81 thread_pool: *std.Thread.Pool,
79 all_steps: []const *Build.Step,82 all_steps: []const *Build.Step,
80 root_prog_node: std.Progress.Node,83 root_prog_node: std.Progress.Node,
...@@ -111,6 +114,7 @@ pub fn init(...@@ -111,6 +114,7 @@ pub fn init(
111114
112 return .{115 return .{
113 .gpa = gpa,116 .gpa = gpa,
117 .io = io,
114 .mode = mode,118 .mode = mode,
115 .run_steps = run_steps,119 .run_steps = run_steps,
116 .wait_group = .{},120 .wait_group = .{},
...@@ -484,6 +488,7 @@ fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReporte...@@ -484,6 +488,7 @@ fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReporte
484488
485pub fn waitAndPrintReport(fuzz: *Fuzz) void {489pub fn waitAndPrintReport(fuzz: *Fuzz) void {
486 assert(fuzz.mode == .limit);490 assert(fuzz.mode == .limit);
491 const io = fuzz.io;
487492
488 fuzz.wait_group.wait();493 fuzz.wait_group.wait();
489 fuzz.wait_group.reset();494 fuzz.wait_group.reset();
...@@ -506,7 +511,7 @@ pub fn waitAndPrintReport(fuzz: *Fuzz) void {...@@ -506,7 +511,7 @@ pub fn waitAndPrintReport(fuzz: *Fuzz) void {
506511
507 const fuzz_abi = std.Build.abi.fuzz;512 const fuzz_abi = std.Build.abi.fuzz;
508 var rbuf: [0x1000]u8 = undefined;513 var rbuf: [0x1000]u8 = undefined;
509 var r = coverage_file.reader(&rbuf);514 var r = coverage_file.reader(io, &rbuf);
510515
511 var header: fuzz_abi.SeenPcsHeader = undefined;516 var header: fuzz_abi.SeenPcsHeader = undefined;
512 r.interface.readSliceAll(std.mem.asBytes(&header)) catch |err| {517 r.interface.readSliceAll(std.mem.asBytes(&header)) catch |err| {
lib/std/Build/Step.zig+11-8
...@@ -1,9 +1,11 @@...@@ -1,9 +1,11 @@
1const Step = @This();1const Step = @This();
2const builtin = @import("builtin");
3
2const std = @import("../std.zig");4const std = @import("../std.zig");
5const Io = std.Io;
3const Build = std.Build;6const Build = std.Build;
4const Allocator = std.mem.Allocator;7const Allocator = std.mem.Allocator;
5const assert = std.debug.assert;8const assert = std.debug.assert;
6const builtin = @import("builtin");
7const Cache = Build.Cache;9const Cache = Build.Cache;
8const Path = Cache.Path;10const Path = Cache.Path;
9const ArrayList = std.ArrayList;11const ArrayList = std.ArrayList;
...@@ -327,7 +329,7 @@ pub fn cast(step: *Step, comptime T: type) ?*T {...@@ -327,7 +329,7 @@ pub fn cast(step: *Step, comptime T: type) ?*T {
327}329}
328330
329/// For debugging purposes, prints identifying information about this Step.331/// 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 {
331 if (step.debug_stack_trace.instruction_addresses.len > 0) {333 if (step.debug_stack_trace.instruction_addresses.len > 0) {
332 w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {};334 w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {};
333 std.debug.writeStackTrace(&step.debug_stack_trace, w, tty_config) catch {};335 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...@@ -382,7 +384,7 @@ pub fn addError(step: *Step, comptime fmt: []const u8, args: anytype) error{OutO
382384
383pub const ZigProcess = struct {385pub const ZigProcess = struct {
384 child: std.process.Child,386 child: std.process.Child,
385 poller: std.Io.Poller(StreamEnum),387 poller: Io.Poller(StreamEnum),
386 progress_ipc_fd: if (std.Progress.have_ipc) ?std.posix.fd_t else void,388 progress_ipc_fd: if (std.Progress.have_ipc) ?std.posix.fd_t else void,
387389
388 pub const StreamEnum = enum { stdout, stderr };390 pub const StreamEnum = enum { stdout, stderr };
...@@ -458,7 +460,7 @@ pub fn evalZigProcess(...@@ -458,7 +460,7 @@ pub fn evalZigProcess(
458 const zp = try gpa.create(ZigProcess);460 const zp = try gpa.create(ZigProcess);
459 zp.* = .{461 zp.* = .{
460 .child = child,462 .child = child,
461 .poller = std.Io.poll(gpa, ZigProcess.StreamEnum, .{463 .poller = Io.poll(gpa, ZigProcess.StreamEnum, .{
462 .stdout = child.stdout.?,464 .stdout = child.stdout.?,
463 .stderr = child.stderr.?,465 .stderr = child.stderr.?,
464 }),466 }),
...@@ -505,11 +507,12 @@ pub fn evalZigProcess(...@@ -505,11 +507,12 @@ pub fn evalZigProcess(
505}507}
506508
507/// Wrapper around `std.fs.Dir.updateFile` that handles verbose and error output.509/// 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 {
509 const b = s.owner;511 const b = s.owner;
512 const io = b.graph.io;
510 const src_path = src_lazy_path.getPath3(b, s);513 const src_path = src_lazy_path.getPath3(b, s);
511 try handleVerbose(b, null, &.{ "install", "-C", b.fmt("{f}", .{src_path}), dest_path });514 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| {
513 return s.fail("unable to update file from '{f}' to '{s}': {s}", .{516 return s.fail("unable to update file from '{f}' to '{s}': {s}", .{
514 src_path, dest_path, @errorName(err),517 src_path, dest_path, @errorName(err),
515 });518 });
...@@ -738,7 +741,7 @@ pub fn allocPrintCmd2(...@@ -738,7 +741,7 @@ pub fn allocPrintCmd2(
738 argv: []const []const u8,741 argv: []const []const u8,
739) Allocator.Error![]u8 {742) Allocator.Error![]u8 {
740 const shell = struct {743 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 {
742 for (string) |c| {745 for (string) |c| {
743 if (switch (c) {746 if (switch (c) {
744 else => true,747 else => true,
...@@ -772,7 +775,7 @@ pub fn allocPrintCmd2(...@@ -772,7 +775,7 @@ pub fn allocPrintCmd2(
772 }775 }
773 };776 };
774777
775 var aw: std.Io.Writer.Allocating = .init(gpa);778 var aw: Io.Writer.Allocating = .init(gpa);
776 defer aw.deinit();779 defer aw.deinit();
777 const writer = &aw.writer;780 const writer = &aw.writer;
778 if (opt_cwd) |cwd| writer.print("cd {s} && ", .{cwd}) catch return error.OutOfMemory;781 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 {...@@ -1701,7 +1701,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
1701 // This prevents a warning, that should probably be upgraded to an error in Zig's1701 // This prevents a warning, that should probably be upgraded to an error in Zig's
1702 // CLI parsing code, when the linker sees an -L directory that does not exist.1702 // 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", .{})) |_| {
1705 try zig_args.appendSlice(&.{1705 try zig_args.appendSlice(&.{
1706 "-L", b.pathJoin(&.{ search_prefix, "lib" }),1706 "-L", b.pathJoin(&.{ search_prefix, "lib" }),
1707 });1707 });
...@@ -1712,7 +1712,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {...@@ -1712,7 +1712,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
1712 }),1712 }),
1713 }1713 }
17141714
1715 if (prefix_dir.accessZ("include", .{})) |_| {1715 if (prefix_dir.access("include", .{})) |_| {
1716 try zig_args.appendSlice(&.{1716 try zig_args.appendSlice(&.{
1717 "-I", b.pathJoin(&.{ search_prefix, "include" }),1717 "-I", b.pathJoin(&.{ search_prefix, "include" }),
1718 });1718 });
lib/std/Build/Step/Options.zig+5-1
...@@ -532,12 +532,16 @@ const Arg = struct {...@@ -532,12 +532,16 @@ const Arg = struct {
532test Options {532test Options {
533 if (builtin.os.tag == .wasi) return error.SkipZigTest;533 if (builtin.os.tag == .wasi) return error.SkipZigTest;
534534
535 const io = std.testing.io;
536
535 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);537 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
536 defer arena.deinit();538 defer arena.deinit();
537539
538 var graph: std.Build.Graph = .{540 var graph: std.Build.Graph = .{
541 .io = io,
539 .arena = arena.allocator(),542 .arena = arena.allocator(),
540 .cache = .{543 .cache = .{
544 .io = io,
541 .gpa = arena.allocator(),545 .gpa = arena.allocator(),
542 .manifest_dir = std.fs.cwd(),546 .manifest_dir = std.fs.cwd(),
543 },547 },
...@@ -546,7 +550,7 @@ test Options {...@@ -546,7 +550,7 @@ test Options {
546 .global_cache_root = .{ .path = "test", .handle = std.fs.cwd() },550 .global_cache_root = .{ .path = "test", .handle = std.fs.cwd() },
547 .host = .{551 .host = .{
548 .query = .{},552 .query = .{},
549 .result = try std.zig.system.resolveTargetQuery(.{}),553 .result = try std.zig.system.resolveTargetQuery(io, .{}),
550 },554 },
551 .zig_lib_directory = std.Build.Cache.Directory.cwd(),555 .zig_lib_directory = std.Build.Cache.Directory.cwd(),
552 .time_report = false,556 .time_report = false,
lib/std/Build/Step/Run.zig+8-5
...@@ -761,6 +761,7 @@ const IndexedOutput = struct {...@@ -761,6 +761,7 @@ const IndexedOutput = struct {
761};761};
762fn make(step: *Step, options: Step.MakeOptions) !void {762fn make(step: *Step, options: Step.MakeOptions) !void {
763 const b = step.owner;763 const b = step.owner;
764 const io = b.graph.io;
764 const arena = b.allocator;765 const arena = b.allocator;
765 const run: *Run = @fieldParentPtr("step", step);766 const run: *Run = @fieldParentPtr("step", step);
766 const has_side_effects = run.hasSideEffects();767 const has_side_effects = run.hasSideEffects();
...@@ -834,7 +835,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -834,7 +835,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
834 defer file.close();835 defer file.close();
835836
836 var buf: [1024]u8 = undefined;837 var buf: [1024]u8 = undefined;
837 var file_reader = file.reader(&buf);838 var file_reader = file.reader(io, &buf);
838 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {839 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {
839 error.ReadFailed => return step.fail(840 error.ReadFailed => return step.fail(
840 "failed to read from '{f}': {t}",841 "failed to read from '{f}': {t}",
...@@ -1067,6 +1068,7 @@ pub fn rerunInFuzzMode(...@@ -1067,6 +1068,7 @@ pub fn rerunInFuzzMode(
1067) !void {1068) !void {
1068 const step = &run.step;1069 const step = &run.step;
1069 const b = step.owner;1070 const b = step.owner;
1071 const io = b.graph.io;
1070 const arena = b.allocator;1072 const arena = b.allocator;
1071 var argv_list: std.ArrayList([]const u8) = .empty;1073 var argv_list: std.ArrayList([]const u8) = .empty;
1072 for (run.argv.items) |arg| {1074 for (run.argv.items) |arg| {
...@@ -1093,7 +1095,7 @@ pub fn rerunInFuzzMode(...@@ -1093,7 +1095,7 @@ pub fn rerunInFuzzMode(
1093 defer file.close();1095 defer file.close();
10941096
1095 var buf: [1024]u8 = undefined;1097 var buf: [1024]u8 = undefined;
1096 var file_reader = file.reader(&buf);1098 var file_reader = file.reader(io, &buf);
1097 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {1099 _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {
1098 error.ReadFailed => return file_reader.err.?,1100 error.ReadFailed => return file_reader.err.?,
1099 error.WriteFailed => return error.OutOfMemory,1101 error.WriteFailed => return error.OutOfMemory,
...@@ -2090,6 +2092,7 @@ fn sendRunFuzzTestMessage(...@@ -2090,6 +2092,7 @@ fn sendRunFuzzTestMessage(
20902092
2091fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {2093fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {
2092 const b = run.step.owner;2094 const b = run.step.owner;
2095 const io = b.graph.io;
2093 const arena = b.allocator;2096 const arena = b.allocator;
20942097
2095 try child.spawn();2098 try child.spawn();
...@@ -2113,7 +2116,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {...@@ -2113,7 +2116,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {
2113 defer file.close();2116 defer file.close();
2114 // TODO https://github.com/ziglang/zig/issues/239552117 // TODO https://github.com/ziglang/zig/issues/23955
2115 var read_buffer: [1024]u8 = undefined;2118 var read_buffer: [1024]u8 = undefined;
2116 var file_reader = file.reader(&read_buffer);2119 var file_reader = file.reader(io, &read_buffer);
2117 var write_buffer: [1024]u8 = undefined;2120 var write_buffer: [1024]u8 = undefined;
2118 var stdin_writer = child.stdin.?.writer(&write_buffer);2121 var stdin_writer = child.stdin.?.writer(&write_buffer);
2119 _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {2122 _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
...@@ -2159,7 +2162,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {...@@ -2159,7 +2162,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {
2159 stdout_bytes = try poller.toOwnedSlice(.stdout);2162 stdout_bytes = try poller.toOwnedSlice(.stdout);
2160 stderr_bytes = try poller.toOwnedSlice(.stderr);2163 stderr_bytes = try poller.toOwnedSlice(.stderr);
2161 } else {2164 } else {
2162 var stdout_reader = stdout.readerStreaming(&.{});2165 var stdout_reader = stdout.readerStreaming(io, &.{});
2163 stdout_bytes = stdout_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) {2166 stdout_bytes = stdout_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) {
2164 error.OutOfMemory => return error.OutOfMemory,2167 error.OutOfMemory => return error.OutOfMemory,
2165 error.ReadFailed => return stdout_reader.err.?,2168 error.ReadFailed => return stdout_reader.err.?,
...@@ -2167,7 +2170,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {...@@ -2167,7 +2170,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {
2167 };2170 };
2168 }2171 }
2169 } else if (child.stderr) |stderr| {2172 } else if (child.stderr) |stderr| {
2170 var stderr_reader = stderr.readerStreaming(&.{});2173 var stderr_reader = stderr.readerStreaming(io, &.{});
2171 stderr_bytes = stderr_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) {2174 stderr_bytes = stderr_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) {
2172 error.OutOfMemory => return error.OutOfMemory,2175 error.OutOfMemory => return error.OutOfMemory,
2173 error.ReadFailed => return stderr_reader.err.?,2176 error.ReadFailed => return stderr_reader.err.?,
lib/std/Build/Step/UpdateSourceFiles.zig+13-11
...@@ -3,11 +3,13 @@...@@ -3,11 +3,13 @@
3//! not be used during the normal build process, but as a utility run by a3//! not be used during the normal build process, but as a utility run by a
4//! developer with intention to update source files, which will then be4//! developer with intention to update source files, which will then be
5//! committed to version control.5//! committed to version control.
6const UpdateSourceFiles = @This();
7
6const std = @import("std");8const std = @import("std");
9const Io = std.Io;
7const Step = std.Build.Step;10const Step = std.Build.Step;
8const fs = std.fs;11const fs = std.fs;
9const ArrayList = std.ArrayList;12const ArrayList = std.ArrayList;
10const UpdateSourceFiles = @This();
1113
12step: Step,14step: Step,
13output_source_files: std.ArrayListUnmanaged(OutputSourceFile),15output_source_files: std.ArrayListUnmanaged(OutputSourceFile),
...@@ -70,22 +72,21 @@ pub fn addBytesToSource(usf: *UpdateSourceFiles, bytes: []const u8, sub_path: []...@@ -70,22 +72,21 @@ pub fn addBytesToSource(usf: *UpdateSourceFiles, bytes: []const u8, sub_path: []
70fn make(step: *Step, options: Step.MakeOptions) !void {72fn make(step: *Step, options: Step.MakeOptions) !void {
71 _ = options;73 _ = options;
72 const b = step.owner;74 const b = step.owner;
75 const io = b.graph.io;
73 const usf: *UpdateSourceFiles = @fieldParentPtr("step", step);76 const usf: *UpdateSourceFiles = @fieldParentPtr("step", step);
7477
75 var any_miss = false;78 var any_miss = false;
76 for (usf.output_source_files.items) |output_source_file| {79 for (usf.output_source_files.items) |output_source_file| {
77 if (fs.path.dirname(output_source_file.sub_path)) |dirname| {80 if (fs.path.dirname(output_source_file.sub_path)) |dirname| {
78 b.build_root.handle.makePath(dirname) catch |err| {81 b.build_root.handle.makePath(dirname) catch |err| {
79 return step.fail("unable to make path '{f}{s}': {s}", .{82 return step.fail("unable to make path '{f}{s}': {t}", .{ b.build_root, dirname, err });
80 b.build_root, dirname, @errorName(err),
81 });
82 };83 };
83 }84 }
84 switch (output_source_file.contents) {85 switch (output_source_file.contents) {
85 .bytes => |bytes| {86 .bytes => |bytes| {
86 b.build_root.handle.writeFile(.{ .sub_path = output_source_file.sub_path, .data = bytes }) catch |err| {87 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 return step.fail("unable to write file '{f}{s}': {t}", .{
88 b.build_root, output_source_file.sub_path, @errorName(err),89 b.build_root, output_source_file.sub_path, err,
89 });90 });
90 };91 };
91 any_miss = true;92 any_miss = true;
...@@ -94,15 +95,16 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -94,15 +95,16 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
94 if (!step.inputs.populated()) try step.addWatchInput(file_source);95 if (!step.inputs.populated()) try step.addWatchInput(file_source);
9596
96 const source_path = file_source.getPath2(b, step);97 const source_path = file_source.getPath2(b, step);
97 const prev_status = fs.Dir.updateFile(98 const prev_status = Io.Dir.updateFile(
98 fs.cwd(),99 .cwd(),
100 io,
99 source_path,101 source_path,
100 b.build_root.handle,102 b.build_root.handle.adaptToNewApi(),
101 output_source_file.sub_path,103 output_source_file.sub_path,
102 .{},104 .{},
103 ) catch |err| {105 ) catch |err| {
104 return step.fail("unable to update file from '{s}' to '{f}{s}': {s}", .{106 return step.fail("unable to update file from '{s}' to '{f}{s}': {t}", .{
105 source_path, b.build_root, output_source_file.sub_path, @errorName(err),107 source_path, b.build_root, output_source_file.sub_path, err,
106 });108 });
107 };109 };
108 any_miss = any_miss or prev_status == .stale;110 any_miss = any_miss or prev_status == .stale;
lib/std/Build/Step/WriteFile.zig+13-23
...@@ -2,6 +2,7 @@...@@ -2,6 +2,7 @@
2//! the local cache which has a set of files that have either been generated2//! the local cache which has a set of files that have either been generated
3//! during the build, or are copied from the source package.3//! during the build, or are copied from the source package.
4const std = @import("std");4const std = @import("std");
5const Io = std.Io;
5const Step = std.Build.Step;6const Step = std.Build.Step;
6const fs = std.fs;7const fs = std.fs;
7const ArrayList = std.ArrayList;8const ArrayList = std.ArrayList;
...@@ -174,6 +175,7 @@ fn maybeUpdateName(write_file: *WriteFile) void {...@@ -174,6 +175,7 @@ fn maybeUpdateName(write_file: *WriteFile) void {
174fn make(step: *Step, options: Step.MakeOptions) !void {175fn make(step: *Step, options: Step.MakeOptions) !void {
175 _ = options;176 _ = options;
176 const b = step.owner;177 const b = step.owner;
178 const io = b.graph.io;
177 const arena = b.allocator;179 const arena = b.allocator;
178 const gpa = arena;180 const gpa = arena;
179 const write_file: *WriteFile = @fieldParentPtr("step", step);181 const write_file: *WriteFile = @fieldParentPtr("step", step);
...@@ -264,40 +266,27 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -264,40 +266,27 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
264 };266 };
265 defer cache_dir.close();267 defer cache_dir.close();
266268
267 const cwd = fs.cwd();
268
269 for (write_file.files.items) |file| {269 for (write_file.files.items) |file| {
270 if (fs.path.dirname(file.sub_path)) |dirname| {270 if (fs.path.dirname(file.sub_path)) |dirname| {
271 cache_dir.makePath(dirname) catch |err| {271 cache_dir.makePath(dirname) catch |err| {
272 return step.fail("unable to make path '{f}{s}{c}{s}': {s}", .{272 return step.fail("unable to make path '{f}{s}{c}{s}': {t}", .{
273 b.cache_root, cache_path, fs.path.sep, dirname, @errorName(err),273 b.cache_root, cache_path, fs.path.sep, dirname, err,
274 });274 });
275 };275 };
276 }276 }
277 switch (file.contents) {277 switch (file.contents) {
278 .bytes => |bytes| {278 .bytes => |bytes| {
279 cache_dir.writeFile(.{ .sub_path = file.sub_path, .data = bytes }) catch |err| {279 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}", .{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, @errorName(err),281 b.cache_root, cache_path, fs.path.sep, file.sub_path, err,
282 });282 });
283 };283 };
284 },284 },
285 .copy => |file_source| {285 .copy => |file_source| {
286 const source_path = file_source.getPath2(b, step);286 const source_path = file_source.getPath2(b, step);
287 const prev_status = fs.Dir.updateFile(287 const prev_status = Io.Dir.updateFile(.cwd(), io, source_path, cache_dir.adaptToNewApi(), file.sub_path, .{}) catch |err| {
288 cwd,288 return step.fail("unable to update file from '{s}' to '{f}{s}{c}{s}': {t}", .{
289 source_path,289 source_path, b.cache_root, cache_path, fs.path.sep, file.sub_path, err,
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),
301 });290 });
302 };291 };
303 // At this point we already will mark the step as a cache miss.292 // 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 {...@@ -331,10 +320,11 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
331 switch (entry.kind) {320 switch (entry.kind) {
332 .directory => try cache_dir.makePath(dest_path),321 .directory => try cache_dir.makePath(dest_path),
333 .file => {322 .file => {
334 const prev_status = fs.Dir.updateFile(323 const prev_status = Io.Dir.updateFile(
335 src_entry_path.root_dir.handle,324 src_entry_path.root_dir.handle.adaptToNewApi(),
325 io,
336 src_entry_path.sub_path,326 src_entry_path.sub_path,
337 cache_dir,327 cache_dir.adaptToNewApi(),
338 dest_path,328 dest_path,
339 .{},329 .{},
340 ) catch |err| {330 ) catch |err| {
lib/std/Build/WebServer.zig+47-31
...@@ -2,15 +2,16 @@ gpa: Allocator,...@@ -2,15 +2,16 @@ gpa: Allocator,
2thread_pool: *std.Thread.Pool,2thread_pool: *std.Thread.Pool,
3graph: *const Build.Graph,3graph: *const Build.Graph,
4all_steps: []const *Build.Step,4all_steps: []const *Build.Step,
5listen_address: std.net.Address,5listen_address: net.IpAddress,
6ttyconf: std.Io.tty.Config,6ttyconf: Io.tty.Config,
7root_prog_node: std.Progress.Node,7root_prog_node: std.Progress.Node,
8watch: bool,8watch: bool,
99
10tcp_server: ?std.net.Server,10tcp_server: ?net.Server,
11serve_thread: ?std.Thread,11serve_thread: ?std.Thread,
1212
13base_timestamp: i128,13/// Uses `Io.Clock.awake`.
14base_timestamp: Io.Timestamp,
14/// The "step name" data which trails `abi.Hello`, for the steps in `all_steps`.15/// The "step name" data which trails `abi.Hello`, for the steps in `all_steps`.
15step_names_trailing: []u8,16step_names_trailing: []u8,
1617
...@@ -42,6 +43,8 @@ runner_request: ?RunnerRequest,...@@ -42,6 +43,8 @@ runner_request: ?RunnerRequest,
42/// on a fixed interval of this many milliseconds.43/// on a fixed interval of this many milliseconds.
43const default_update_interval_ms = 500;44const default_update_interval_ms = 500;
4445
46pub const base_clock: Io.Clock = .awake;
47
45/// Thread-safe. Triggers updates to be sent to connected WebSocket clients; see `update_id`.48/// Thread-safe. Triggers updates to be sent to connected WebSocket clients; see `update_id`.
46pub fn notifyUpdate(ws: *WebServer) void {49pub fn notifyUpdate(ws: *WebServer) void {
47 _ = ws.update_id.rmw(.Add, 1, .release);50 _ = ws.update_id.rmw(.Add, 1, .release);
...@@ -53,15 +56,17 @@ pub const Options = struct {...@@ -53,15 +56,17 @@ pub const Options = struct {
53 thread_pool: *std.Thread.Pool,56 thread_pool: *std.Thread.Pool,
54 graph: *const std.Build.Graph,57 graph: *const std.Build.Graph,
55 all_steps: []const *Build.Step,58 all_steps: []const *Build.Step,
56 ttyconf: std.Io.tty.Config,59 ttyconf: Io.tty.Config,
57 root_prog_node: std.Progress.Node,60 root_prog_node: std.Progress.Node,
58 watch: bool,61 watch: bool,
59 listen_address: std.net.Address,62 listen_address: net.IpAddress,
63 base_timestamp: Io.Clock.Timestamp,
60};64};
61pub fn init(opts: Options) WebServer {65pub 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`
63 // instead of threads, so that the web server can function in single-threaded builds.67 // instead of threads, so that the web server can function in single-threaded builds.
64 comptime assert(!builtin.single_threaded);68 comptime assert(!builtin.single_threaded);
69 assert(opts.base_timestamp.clock == base_clock);
6570
66 const all_steps = opts.all_steps;71 const all_steps = opts.all_steps;
6772
...@@ -106,7 +111,7 @@ pub fn init(opts: Options) WebServer {...@@ -106,7 +111,7 @@ pub fn init(opts: Options) WebServer {
106 .tcp_server = null,111 .tcp_server = null,
107 .serve_thread = null,112 .serve_thread = null,
108113
109 .base_timestamp = std.time.nanoTimestamp(),114 .base_timestamp = opts.base_timestamp.raw,
110 .step_names_trailing = step_names_trailing,115 .step_names_trailing = step_names_trailing,
111116
112 .step_status_bits = step_status_bits,117 .step_status_bits = step_status_bits,
...@@ -147,32 +152,34 @@ pub fn deinit(ws: *WebServer) void {...@@ -147,32 +152,34 @@ pub fn deinit(ws: *WebServer) void {
147pub fn start(ws: *WebServer) error{AlreadyReported}!void {152pub fn start(ws: *WebServer) error{AlreadyReported}!void {
148 assert(ws.tcp_server == null);153 assert(ws.tcp_server == null);
149 assert(ws.serve_thread == null);154 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| {
152 log.err("failed to listen to port {d}: {s}", .{ ws.listen_address.getPort(), @errorName(err) });158 log.err("failed to listen to port {d}: {s}", .{ ws.listen_address.getPort(), @errorName(err) });
153 return error.AlreadyReported;159 return error.AlreadyReported;
154 };160 };
155 ws.serve_thread = std.Thread.spawn(.{}, serve, .{ws}) catch |err| {161 ws.serve_thread = std.Thread.spawn(.{}, serve, .{ws}) catch |err| {
156 log.err("unable to spawn web server thread: {s}", .{@errorName(err)});162 log.err("unable to spawn web server thread: {s}", .{@errorName(err)});
157 ws.tcp_server.?.deinit();163 ws.tcp_server.?.deinit(io);
158 ws.tcp_server = null;164 ws.tcp_server = null;
159 return error.AlreadyReported;165 return error.AlreadyReported;
160 };166 };
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});
163 if (ws.listen_address.getPort() == 0) {169 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});
165 }171 }
166}172}
167fn serve(ws: *WebServer) void {173fn serve(ws: *WebServer) void {
174 const io = ws.graph.io;
168 while (true) {175 while (true) {
169 const connection = ws.tcp_server.?.accept() catch |err| {176 var stream = ws.tcp_server.?.accept(io) catch |err| {
170 log.err("failed to accept connection: {s}", .{@errorName(err)});177 log.err("failed to accept connection: {s}", .{@errorName(err)});
171 return;178 return;
172 };179 };
173 _ = std.Thread.spawn(.{}, accept, .{ ws, connection }) catch |err| {180 _ = std.Thread.spawn(.{}, accept, .{ ws, stream }) catch |err| {
174 log.err("unable to spawn connection thread: {s}", .{@errorName(err)});181 log.err("unable to spawn connection thread: {s}", .{@errorName(err)});
175 connection.stream.close();182 stream.close(io);
176 continue;183 continue;
177 };184 };
178 }185 }
...@@ -227,6 +234,7 @@ pub fn finishBuild(ws: *WebServer, opts: struct {...@@ -227,6 +234,7 @@ pub fn finishBuild(ws: *WebServer, opts: struct {
227234
228 ws.fuzz = Fuzz.init(235 ws.fuzz = Fuzz.init(
229 ws.gpa,236 ws.gpa,
237 ws.graph.io,
230 ws.thread_pool,238 ws.thread_pool,
231 ws.all_steps,239 ws.all_steps,
232 ws.root_prog_node,240 ws.root_prog_node,
...@@ -241,17 +249,24 @@ pub fn finishBuild(ws: *WebServer, opts: struct {...@@ -241,17 +249,24 @@ pub fn finishBuild(ws: *WebServer, opts: struct {
241}249}
242250
243pub fn now(s: *const WebServer) i64 {251pub 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());
245}255}
246256
247fn accept(ws: *WebServer, connection: std.net.Server.Connection) void {257fn accept(ws: *WebServer, stream: net.Stream) void {
248 defer connection.stream.close();258 const io = ws.graph.io;
249259 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 }
250 var send_buffer: [4096]u8 = undefined;265 var send_buffer: [4096]u8 = undefined;
251 var recv_buffer: [4096]u8 = undefined;266 var recv_buffer: [4096]u8 = undefined;
252 var connection_reader = connection.stream.reader(&recv_buffer);267 var connection_reader = stream.reader(io, &recv_buffer);
253 var connection_writer = connection.stream.writer(&send_buffer);268 var connection_writer = stream.writer(io, &send_buffer);
254 var server: http.Server = .init(connection_reader.interface(), &connection_writer.interface);269 var server: http.Server = .init(&connection_reader.interface, &connection_writer.interface);
255270
256 while (true) {271 while (true) {
257 var request = server.receiveHead() catch |err| switch (err) {272 var request = server.receiveHead() catch |err| switch (err) {
...@@ -466,12 +481,9 @@ pub fn serveFile(...@@ -466,12 +481,9 @@ pub fn serveFile(
466 },481 },
467 });482 });
468}483}
469pub fn serveTarFile(484pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []const Cache.Path) !void {
470 ws: *WebServer,
471 request: *http.Server.Request,
472 paths: []const Cache.Path,
473) !void {
474 const gpa = ws.gpa;485 const gpa = ws.gpa;
486 const io = ws.graph.io;
475487
476 var send_buffer: [0x4000]u8 = undefined;488 var send_buffer: [0x4000]u8 = undefined;
477 var response = try request.respondStreaming(&send_buffer, .{489 var response = try request.respondStreaming(&send_buffer, .{
...@@ -496,7 +508,7 @@ pub fn serveTarFile(...@@ -496,7 +508,7 @@ pub fn serveTarFile(
496 defer file.close();508 defer file.close();
497 const stat = try file.stat();509 const stat = try file.stat();
498 var read_buffer: [1024]u8 = undefined;510 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
501 // TODO: this logic is completely bogus -- obviously so, because `path.root_dir.path` can513 // TODO: this logic is completely bogus -- obviously so, because `path.root_dir.path` can
502 // be cwd-relative. This is also related to why linkification doesn't work in the fuzzer UI:514 // be cwd-relative. This is also related to why linkification doesn't work in the fuzzer UI:
...@@ -508,7 +520,7 @@ pub fn serveTarFile(...@@ -508,7 +520,7 @@ pub fn serveTarFile(
508 if (cached_cwd_path == null) cached_cwd_path = try std.process.getCwdAlloc(gpa);520 if (cached_cwd_path == null) cached_cwd_path = try std.process.getCwdAlloc(gpa);
509 break :cwd cached_cwd_path.?;521 break :cwd cached_cwd_path.?;
510 };522 };
511 try archiver.writeFile(path.sub_path, &file_reader, stat.mtime);523 try archiver.writeFile(path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds()));
512 }524 }
513525
514 // intentionally not calling `archiver.finishPedantically`526 // intentionally not calling `archiver.finishPedantically`
...@@ -516,6 +528,7 @@ pub fn serveTarFile(...@@ -516,6 +528,7 @@ pub fn serveTarFile(
516}528}
517529
518fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.OptimizeMode) !Cache.Path {530fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.OptimizeMode) !Cache.Path {
531 const io = ws.graph.io;
519 const root_name = "build-web";532 const root_name = "build-web";
520 const arch_os_abi = "wasm32-freestanding";533 const arch_os_abi = "wasm32-freestanding";
521 const cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext";534 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...@@ -565,7 +578,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
565 child.stderr_behavior = .Pipe;578 child.stderr_behavior = .Pipe;
566 try child.spawn();579 try child.spawn();
567580
568 var poller = std.Io.poll(gpa, enum { stdout, stderr }, .{581 var poller = Io.poll(gpa, enum { stdout, stderr }, .{
569 .stdout = child.stdout.?,582 .stdout = child.stdout.?,
570 .stderr = child.stderr.?,583 .stderr = child.stderr.?,
571 });584 });
...@@ -659,7 +672,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -659,7 +672,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
659 };672 };
660 const bin_name = try std.zig.binNameAlloc(arena, .{673 const bin_name = try std.zig.binNameAlloc(arena, .{
661 .root_name = root_name,674 .root_name = root_name,
662 .target = &(std.zig.system.resolveTargetQuery(std.Build.parseTargetQuery(.{675 .target = &(std.zig.system.resolveTargetQuery(io, std.Build.parseTargetQuery(.{
663 .arch_os_abi = arch_os_abi,676 .arch_os_abi = arch_os_abi,
664 .cpu_features = cpu_features,677 .cpu_features = cpu_features,
665 }) catch unreachable) catch unreachable),678 }) catch unreachable) catch unreachable),
...@@ -841,7 +854,10 @@ const cache_control_header: http.Header = .{...@@ -841,7 +854,10 @@ const cache_control_header: http.Header = .{
841};854};
842855
843const builtin = @import("builtin");856const builtin = @import("builtin");
857
844const std = @import("std");858const std = @import("std");
859const Io = std.Io;
860const net = std.Io.net;
845const assert = std.debug.assert;861const assert = std.debug.assert;
846const mem = std.mem;862const mem = std.mem;
847const log = std.log.scoped(.web_server);863const log = std.log.scoped(.web_server);
lib/std/Io.zig+1096
...@@ -548,8 +548,1104 @@ pub fn PollFiles(comptime StreamEnum: type) type {...@@ -548,8 +548,1104 @@ pub fn PollFiles(comptime StreamEnum: type) type {
548}548}
549549
550test {550test {
551 _ = net;
551 _ = Reader;552 _ = Reader;
552 _ = Writer;553 _ = Writer;
553 _ = tty;554 _ = tty;
555 _ = Evented;
556 _ = Threaded;
554 _ = @import("Io/test.zig");557 _ = @import("Io/test.zig");
555}558}
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();...@@ -5,7 +5,7 @@ const Writer = @This();
5const std = @import("../std.zig");5const std = @import("../std.zig");
6const assert = std.debug.assert;6const assert = std.debug.assert;
7const Limit = std.Io.Limit;7const Limit = std.Io.Limit;
8const File = std.fs.File;8const File = std.Io.File;
9const testing = std.testing;9const testing = std.testing;
10const Allocator = std.mem.Allocator;10const Allocator = std.mem.Allocator;
11const ArrayList = std.ArrayList;11const ArrayList = std.ArrayList;
...@@ -2827,6 +2827,8 @@ pub const Allocating = struct {...@@ -2827,6 +2827,8 @@ pub const Allocating = struct {
2827};2827};
28282828
2829test "discarding sendFile" {2829test "discarding sendFile" {
2830 const io = testing.io;
2831
2830 var tmp_dir = testing.tmpDir(.{});2832 var tmp_dir = testing.tmpDir(.{});
2831 defer tmp_dir.cleanup();2833 defer tmp_dir.cleanup();
28322834
...@@ -2837,7 +2839,7 @@ test "discarding sendFile" {...@@ -2837,7 +2839,7 @@ test "discarding sendFile" {
2837 try file_writer.interface.writeByte('h');2839 try file_writer.interface.writeByte('h');
2838 try file_writer.interface.flush();2840 try file_writer.interface.flush();
28392841
2840 var file_reader = file_writer.moveToReader();2842 var file_reader = file_writer.moveToReader(io);
2841 try file_reader.seekTo(0);2843 try file_reader.seekTo(0);
28422844
2843 var w_buffer: [256]u8 = undefined;2845 var w_buffer: [256]u8 = undefined;
...@@ -2847,6 +2849,8 @@ test "discarding sendFile" {...@@ -2847,6 +2849,8 @@ test "discarding sendFile" {
2847}2849}
28482850
2849test "allocating sendFile" {2851test "allocating sendFile" {
2852 const io = testing.io;
2853
2850 var tmp_dir = testing.tmpDir(.{});2854 var tmp_dir = testing.tmpDir(.{});
2851 defer tmp_dir.cleanup();2855 defer tmp_dir.cleanup();
28522856
...@@ -2857,7 +2861,7 @@ test "allocating sendFile" {...@@ -2857,7 +2861,7 @@ test "allocating sendFile" {
2857 try file_writer.interface.writeAll("abcd");2861 try file_writer.interface.writeAll("abcd");
2858 try file_writer.interface.flush();2862 try file_writer.interface.flush();
28592863
2860 var file_reader = file_writer.moveToReader();2864 var file_reader = file_writer.moveToReader(io);
2861 try file_reader.seekTo(0);2865 try file_reader.seekTo(0);
2862 try file_reader.interface.fill(2);2866 try file_reader.interface.fill(2);
28632867
...@@ -2869,6 +2873,8 @@ test "allocating sendFile" {...@@ -2869,6 +2873,8 @@ test "allocating sendFile" {
2869}2873}
28702874
2871test sendFileReading {2875test sendFileReading {
2876 const io = testing.io;
2877
2872 var tmp_dir = testing.tmpDir(.{});2878 var tmp_dir = testing.tmpDir(.{});
2873 defer tmp_dir.cleanup();2879 defer tmp_dir.cleanup();
28742880
...@@ -2879,7 +2885,7 @@ test sendFileReading {...@@ -2879,7 +2885,7 @@ test sendFileReading {
2879 try file_writer.interface.writeAll("abcd");2885 try file_writer.interface.writeAll("abcd");
2880 try file_writer.interface.flush();2886 try file_writer.interface.flush();
28812887
2882 var file_reader = file_writer.moveToReader();2888 var file_reader = file_writer.moveToReader(io);
2883 try file_reader.seekTo(0);2889 try file_reader.seekTo(0);
2884 try file_reader.interface.fill(2);2890 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 @@...@@ -1,21 +1,28 @@
1const builtin = @import("builtin");
2const native_endian = builtin.cpu.arch.endian();
3
1const std = @import("std");4const std = @import("std");
2const DefaultPrng = std.Random.DefaultPrng;5const Io = std.Io;
6const testing = std.testing;
3const expect = std.testing.expect;7const expect = std.testing.expect;
4const expectEqual = std.testing.expectEqual;8const expectEqual = std.testing.expectEqual;
5const expectError = std.testing.expectError;9const expectError = std.testing.expectError;
10const DefaultPrng = std.Random.DefaultPrng;
6const mem = std.mem;11const mem = std.mem;
7const fs = std.fs;12const fs = std.fs;
8const File = std.fs.File;13const File = std.fs.File;
9const native_endian = @import("builtin").target.cpu.arch.endian();14const assert = std.debug.assert;
1015
11const tmpDir = std.testing.tmpDir;16const tmpDir = std.testing.tmpDir;
1217
13test "write a file, read it, then delete it" {18test "write a file, read it, then delete it" {
19 const io = testing.io;
20
14 var tmp = tmpDir(.{});21 var tmp = tmpDir(.{});
15 defer tmp.cleanup();22 defer tmp.cleanup();
1623
17 var data: [1024]u8 = undefined;24 var data: [1024]u8 = undefined;
18 var prng = DefaultPrng.init(std.testing.random_seed);25 var prng = DefaultPrng.init(testing.random_seed);
19 const random = prng.random();26 const random = prng.random();
20 random.bytes(data[0..]);27 random.bytes(data[0..]);
21 const tmp_file_name = "temp_test_file.txt";28 const tmp_file_name = "temp_test_file.txt";
...@@ -45,9 +52,9 @@ test "write a file, read it, then delete it" {...@@ -45,9 +52,9 @@ test "write a file, read it, then delete it" {
45 try expectEqual(expected_file_size, file_size);52 try expectEqual(expected_file_size, file_size);
4653
47 var file_buffer: [1024]u8 = undefined;54 var file_buffer: [1024]u8 = undefined;
48 var file_reader = file.reader(&file_buffer);55 var file_reader = file.reader(io, &file_buffer);
49 const contents = try file_reader.interface.allocRemaining(std.testing.allocator, .limited(2 * 1024));56 const contents = try file_reader.interface.allocRemaining(testing.allocator, .limited(2 * 1024));
50 defer std.testing.allocator.free(contents);57 defer testing.allocator.free(contents);
5158
52 try expect(mem.eql(u8, contents[0.."begin".len], "begin"));59 try expect(mem.eql(u8, contents[0.."begin".len], "begin"));
53 try expect(mem.eql(u8, contents["begin".len .. contents.len - "end".len], &data));60 try expect(mem.eql(u8, contents["begin".len .. contents.len - "end".len], &data));
...@@ -89,18 +96,18 @@ test "setEndPos" {...@@ -89,18 +96,18 @@ test "setEndPos" {
89 defer file.close();96 defer file.close();
9097
91 // Verify that the file size changes and the file offset is not moved98 // Verify that the file size changes and the file offset is not moved
92 try std.testing.expect((try file.getEndPos()) == 0);99 try expect((try file.getEndPos()) == 0);
93 try std.testing.expect((try file.getPos()) == 0);100 try expect((try file.getPos()) == 0);
94 try file.setEndPos(8192);101 try file.setEndPos(8192);
95 try std.testing.expect((try file.getEndPos()) == 8192);102 try expect((try file.getEndPos()) == 8192);
96 try std.testing.expect((try file.getPos()) == 0);103 try expect((try file.getPos()) == 0);
97 try file.seekTo(100);104 try file.seekTo(100);
98 try file.setEndPos(4096);105 try file.setEndPos(4096);
99 try std.testing.expect((try file.getEndPos()) == 4096);106 try expect((try file.getEndPos()) == 4096);
100 try std.testing.expect((try file.getPos()) == 100);107 try expect((try file.getPos()) == 100);
101 try file.setEndPos(0);108 try file.setEndPos(0);
102 try std.testing.expect((try file.getEndPos()) == 0);109 try expect((try file.getEndPos()) == 0);
103 try std.testing.expect((try file.getPos()) == 100);110 try expect((try file.getPos()) == 100);
104}111}
105112
106test "updateTimes" {113test "updateTimes" {
...@@ -114,10 +121,90 @@ test "updateTimes" {...@@ -114,10 +121,90 @@ test "updateTimes" {
114 const stat_old = try file.stat();121 const stat_old = try file.stat();
115 // Set atime and mtime to 5s before122 // Set atime and mtime to 5s before
116 try file.updateTimes(123 try file.updateTimes(
117 stat_old.atime - 5 * std.time.ns_per_s,124 stat_old.atime.subDuration(.fromSeconds(5)),
118 stat_old.mtime - 5 * std.time.ns_per_s,125 stat_old.mtime.subDuration(.fromSeconds(5)),
119 );126 );
120 const stat_new = try file.stat();127 const stat_new = try file.stat();
121 try expect(stat_new.atime < stat_old.atime);128 try expect(stat_new.atime.nanoseconds < stat_old.atime.nanoseconds);
122 try expect(stat_new.mtime < stat_old.mtime);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 }
123}210}
lib/std/Progress.zig+5-7
...@@ -392,7 +392,7 @@ var global_progress: Progress = .{...@@ -392,7 +392,7 @@ var global_progress: Progress = .{
392 .terminal = undefined,392 .terminal = undefined,
393 .terminal_mode = .off,393 .terminal_mode = .off,
394 .update_thread = null,394 .update_thread = null,
395 .redraw_event = .{},395 .redraw_event = .unset,
396 .refresh_rate_ns = undefined,396 .refresh_rate_ns = undefined,
397 .initial_delay_ns = undefined,397 .initial_delay_ns = undefined,
398 .rows = 0,398 .rows = 0,
...@@ -493,7 +493,7 @@ pub fn start(options: Options) Node {...@@ -493,7 +493,7 @@ pub fn start(options: Options) Node {
493 .mask = posix.sigemptyset(),493 .mask = posix.sigemptyset(),
494 .flags = (posix.SA.SIGINFO | posix.SA.RESTART),494 .flags = (posix.SA.SIGINFO | posix.SA.RESTART),
495 };495 };
496 posix.sigaction(posix.SIG.WINCH, &act, null);496 posix.sigaction(.WINCH, &act, null);
497 }497 }
498498
499 if (switch (global_progress.terminal_mode) {499 if (switch (global_progress.terminal_mode) {
...@@ -523,9 +523,7 @@ pub fn setStatus(new_status: Status) void {...@@ -523,9 +523,7 @@ pub fn setStatus(new_status: Status) void {
523523
524/// Returns whether a resize is needed to learn the terminal size.524/// Returns whether a resize is needed to learn the terminal size.
525fn wait(timeout_ns: u64) bool {525fn wait(timeout_ns: u64) bool {
526 const resize_flag = if (global_progress.redraw_event.timedWait(timeout_ns)) |_|526 const resize_flag = if (global_progress.redraw_event.timedWait(timeout_ns)) |_| true else |err| switch (err) {
527 true
528 else |err| switch (err) {
529 error.Timeout => false,527 error.Timeout => false,
530 };528 };
531 global_progress.redraw_event.reset();529 global_progress.redraw_event.reset();
...@@ -1537,10 +1535,10 @@ fn maybeUpdateSize(resize_flag: bool) void {...@@ -1537,10 +1535,10 @@ fn maybeUpdateSize(resize_flag: bool) void {
1537 }1535 }
1538}1536}
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 {
1541 _ = info;1539 _ = info;
1542 _ = ctx_ptr;1540 _ = ctx_ptr;
1543 assert(sig == posix.SIG.WINCH);1541 assert(sig == .WINCH);
1544 global_progress.redraw_event.set();1542 global_progress.redraw_event.set();
1545}1543}
15461544
lib/std/Random.zig+6
...@@ -58,6 +58,12 @@ pub fn bytes(r: Random, buf: []u8) void {...@@ -58,6 +58,12 @@ pub fn bytes(r: Random, buf: []u8) void {
58 r.fillFn(r.ptr, buf);58 r.fillFn(r.ptr, buf);
59}59}
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
61pub fn boolean(r: Random) bool {67pub fn boolean(r: Random) bool {
62 return r.int(u1) != 0;68 return r.int(u1) != 0;
63}69}
lib/std/Target/Query.zig+7-5
...@@ -612,6 +612,8 @@ fn versionEqualOpt(a: ?SemanticVersion, b: ?SemanticVersion) bool {...@@ -612,6 +612,8 @@ fn versionEqualOpt(a: ?SemanticVersion, b: ?SemanticVersion) bool {
612}612}
613613
614test parse {614test parse {
615 const io = std.testing.io;
616
615 if (builtin.target.isGnuLibC()) {617 if (builtin.target.isGnuLibC()) {
616 var query = try Query.parse(.{});618 var query = try Query.parse(.{});
617 query.setGnuLibCVersion(2, 1, 1);619 query.setGnuLibCVersion(2, 1, 1);
...@@ -654,7 +656,7 @@ test parse {...@@ -654,7 +656,7 @@ test parse {
654 .arch_os_abi = "x86_64-linux-gnu",656 .arch_os_abi = "x86_64-linux-gnu",
655 .cpu_features = "x86_64-sse-sse2-avx-cx8",657 .cpu_features = "x86_64-sse-sse2-avx-cx8",
656 });658 });
657 const target = try std.zig.system.resolveTargetQuery(query);659 const target = try std.zig.system.resolveTargetQuery(io, query);
658660
659 try std.testing.expect(target.os.tag == .linux);661 try std.testing.expect(target.os.tag == .linux);
660 try std.testing.expect(target.abi == .gnu);662 try std.testing.expect(target.abi == .gnu);
...@@ -679,7 +681,7 @@ test parse {...@@ -679,7 +681,7 @@ test parse {
679 .arch_os_abi = "arm-linux-musleabihf",681 .arch_os_abi = "arm-linux-musleabihf",
680 .cpu_features = "generic+v8a",682 .cpu_features = "generic+v8a",
681 });683 });
682 const target = try std.zig.system.resolveTargetQuery(query);684 const target = try std.zig.system.resolveTargetQuery(io, query);
683685
684 try std.testing.expect(target.os.tag == .linux);686 try std.testing.expect(target.os.tag == .linux);
685 try std.testing.expect(target.abi == .musleabihf);687 try std.testing.expect(target.abi == .musleabihf);
...@@ -696,7 +698,7 @@ test parse {...@@ -696,7 +698,7 @@ test parse {
696 .arch_os_abi = "aarch64-linux.3.10...4.4.1-gnu.2.27",698 .arch_os_abi = "aarch64-linux.3.10...4.4.1-gnu.2.27",
697 .cpu_features = "generic+v8a",699 .cpu_features = "generic+v8a",
698 });700 });
699 const target = try std.zig.system.resolveTargetQuery(query);701 const target = try std.zig.system.resolveTargetQuery(io, query);
700702
701 try std.testing.expect(target.cpu.arch == .aarch64);703 try std.testing.expect(target.cpu.arch == .aarch64);
702 try std.testing.expect(target.os.tag == .linux);704 try std.testing.expect(target.os.tag == .linux);
...@@ -719,7 +721,7 @@ test parse {...@@ -719,7 +721,7 @@ test parse {
719 const query = try Query.parse(.{721 const query = try Query.parse(.{
720 .arch_os_abi = "aarch64-linux.3.10...4.4.1-android.30",722 .arch_os_abi = "aarch64-linux.3.10...4.4.1-android.30",
721 });723 });
722 const target = try std.zig.system.resolveTargetQuery(query);724 const target = try std.zig.system.resolveTargetQuery(io, query);
723725
724 try std.testing.expect(target.cpu.arch == .aarch64);726 try std.testing.expect(target.cpu.arch == .aarch64);
725 try std.testing.expect(target.os.tag == .linux);727 try std.testing.expect(target.os.tag == .linux);
...@@ -740,7 +742,7 @@ test parse {...@@ -740,7 +742,7 @@ test parse {
740 const query = try Query.parse(.{742 const query = try Query.parse(.{
741 .arch_os_abi = "x86-windows.xp...win8-msvc",743 .arch_os_abi = "x86-windows.xp...win8-msvc",
742 });744 });
743 const target = try std.zig.system.resolveTargetQuery(query);745 const target = try std.zig.system.resolveTargetQuery(io, query);
744746
745 try std.testing.expect(target.cpu.arch == .x86);747 try std.testing.expect(target.cpu.arch == .x86);
746 try std.testing.expect(target.os.tag == .windows);748 try std.testing.expect(target.os.tag == .windows);
lib/std/Thread.zig+235-67
...@@ -10,9 +10,9 @@ const target = builtin.target;...@@ -10,9 +10,9 @@ const target = builtin.target;
10const native_os = builtin.os.tag;10const native_os = builtin.os.tag;
11const posix = std.posix;11const posix = std.posix;
12const windows = std.os.windows;12const windows = std.os.windows;
13const testing = std.testing;
1314
14pub const Futex = @import("Thread/Futex.zig");15pub const Futex = @import("Thread/Futex.zig");
15pub const ResetEvent = @import("Thread/ResetEvent.zig");
16pub const Mutex = @import("Thread/Mutex.zig");16pub const Mutex = @import("Thread/Mutex.zig");
17pub const Semaphore = @import("Thread/Semaphore.zig");17pub const Semaphore = @import("Thread/Semaphore.zig");
18pub const Condition = @import("Thread/Condition.zig");18pub const Condition = @import("Thread/Condition.zig");
...@@ -22,81 +22,122 @@ pub const WaitGroup = @import("Thread/WaitGroup.zig");...@@ -22,81 +22,122 @@ pub const WaitGroup = @import("Thread/WaitGroup.zig");
2222
23pub const use_pthreads = native_os != .windows and native_os != .wasi and builtin.link_libc;23pub 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.25/// A thread-safe logical boolean value which can be `set` and `unset`.
26pub fn sleep(nanoseconds: u64) void {26///
27 if (builtin.os.tag == .windows) {27/// It can also block threads until the value is set with cancelation via timed
28 const big_ms_from_ns = nanoseconds / std.time.ns_per_ms;28/// waits. Statically initializable; four bytes on all targets.
29 const ms = math.cast(windows.DWORD, big_ms_from_ns) orelse math.maxInt(windows.DWORD);29pub const ResetEvent = enum(u32) {
30 windows.kernel32.Sleep(ms);30 unset = 0,
31 return;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;
32 }49 }
3350
34 if (builtin.os.tag == .wasi) {51 /// Blocks the calling thread until `set` is called.
35 const w = std.os.wasi;52 ///
36 const userdata: w.userdata_t = 0x0123_45678;53 /// This is effectively a more efficient version of `while (!isSet()) {}`.
37 const clock: w.subscription_clock_t = .{54 ///
38 .id = .MONOTONIC,55 /// The memory accesses before the `set` can be said to happen before `wait` returns.
39 .timeout = nanoseconds,56 pub fn wait(re: *ResetEvent) void {
40 .precision = 0,57 if (builtin.single_threaded) switch (re.*) {
41 .flags = 0,58 .unset => unreachable, // Deadlock, no other threads to wake us up.
59 .waiting => unreachable, // Invalid state.
60 .is_set => return,
42 };61 };
43 const in: w.subscription_t = .{62 if (!re.isSet()) return timedWaitInner(re, null) catch |err| switch (err) {
44 .userdata = userdata,63 error.Timeout => unreachable, // No timeout specified.
45 .u = .{
46 .tag = .CLOCK,
47 .u = .{ .clock = clock },
48 },
49 };64 };
50
51 var event: w.event_t = undefined;
52 var nevents: usize = undefined;
53 _ = w.poll_oneoff(&in, &event, 1, &nevents);
54 return;
55 }65 }
5666
57 if (builtin.os.tag == .uefi) {67 /// Blocks the calling thread until `set` is called, or until the
58 const boot_services = std.os.uefi.system_table.boot_services.?;68 /// corresponding timeout expires, returning `error.Timeout`.
59 const us_from_ns = nanoseconds / std.time.ns_per_us;69 ///
60 const us = math.cast(usize, us_from_ns) orelse math.maxInt(usize);70 /// This is effectively a more efficient version of `while (!isSet()) {}`.
61 boot_services.stall(us) catch unreachable;71 ///
62 return;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);
63 }81 }
6482
65 const s = nanoseconds / std.time.ns_per_s;83 fn timedWaitInner(re: *ResetEvent, timeout: ?u64) error{Timeout}!void {
66 const ns = nanoseconds % std.time.ns_per_s;84 @branchHint(.cold);
6785
68 // Newer kernel ports don't have old `nanosleep()` and `clock_nanosleep()` has been around86 // Try to set the state from `unset` to `waiting` to indicate to the
69 // since Linux 2.6 and glibc 2.1 anyway.87 // `set` thread that others are blocked on the ResetEvent. Avoid using
70 if (builtin.os.tag == .linux) {88 // any strict barriers until we know the ResetEvent is set.
71 const linux = std.os.linux;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 = .{94 // Wait until the ResetEvent is set since the state is waiting.
74 .sec = std.math.cast(linux.time_t, s) orelse std.math.maxInt(linux.time_t),95 if (state == .waiting) {
75 .nsec = std.math.cast(linux.time_t, ns) orelse std.math.maxInt(linux.time_t),96 var futex_deadline = Futex.Deadline.init(timeout);
76 };97 while (true) {
77 var rem: linux.timespec = undefined;98 const wait_result = futex_deadline.wait(@ptrCast(re), @intFromEnum(ResetEvent.waiting));
7899
79 while (true) {100 // Check if the ResetEvent was set before possibly reporting error.Timeout below.
80 switch (linux.E.init(linux.clock_nanosleep(.MONOTONIC, .{ .ABSTIME = false }, &req, &rem))) {101 state = @atomicLoad(ResetEvent, re, .acquire);
81 .SUCCESS => return,102 if (state != .waiting) break;
82 .INTR => {103
83 req = rem;104 try wait_result;
84 continue;
85 },
86 .FAULT => unreachable,
87 .INVAL => unreachable,
88 .OPNOTSUPP => unreachable,
89 else => return,
90 }105 }
91 }106 }
107
108 assert(state == .is_set);
92 }109 }
93110
94 posix.nanosleep(s, ns);111 /// Marks the logical boolean as `set` and unblocks any threads in `wait`
95}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 {129 /// Unmarks the ResetEvent as if `set` was never called.
98 sleep(1);130 ///
99}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
101const Thread = @This();142const Thread = @This();
102const Impl = if (native_os == .windows)143const Impl = if (native_os == .windows)
...@@ -130,6 +171,7 @@ pub const SetNameError = error{...@@ -130,6 +171,7 @@ pub const SetNameError = error{
130 NameTooLong,171 NameTooLong,
131 Unsupported,172 Unsupported,
132 Unexpected,173 Unexpected,
174 InvalidWtf8,
133} || posix.PrctlError || posix.WriteError || std.fs.File.OpenError || std.fmt.BufPrintError;175} || posix.PrctlError || posix.WriteError || std.fs.File.OpenError || std.fmt.BufPrintError;
134176
135pub fn setName(self: Thread, name: []const u8) SetNameError!void {177pub 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...@@ -277,10 +319,13 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co
277 var buf: [32]u8 = undefined;319 var buf: [32]u8 = undefined;
278 const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});320 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
280 const file = try std.fs.cwd().openFile(path, .{});325 const file = try std.fs.cwd().openFile(path, .{});
281 defer file.close();326 defer file.close();
282327
283 var file_reader = file.readerStreaming(&.{});328 var file_reader = file.readerStreaming(io, &.{});
284 const data_len = file_reader.interface.readSliceShort(buffer_ptr[0 .. max_name_len + 1]) catch |err| switch (err) {329 const data_len = file_reader.interface.readSliceShort(buffer_ptr[0 .. max_name_len + 1]) catch |err| switch (err) {
285 error.ReadFailed => return file_reader.err.?,330 error.ReadFailed => return file_reader.err.?,
286 };331 };
...@@ -385,6 +430,8 @@ pub const CpuCountError = error{...@@ -385,6 +430,8 @@ pub const CpuCountError = error{
385};430};
386431
387/// Returns the platforms view on the number of logical CPU cores available.432/// Returns the platforms view on the number of logical CPU cores available.
433///
434/// Returned value guaranteed to be >= 1.
388pub fn getCpuCount() CpuCountError!usize {435pub fn getCpuCount() CpuCountError!usize {
389 return try Impl.getCpuCount();436 return try Impl.getCpuCount();
390}437}
...@@ -963,7 +1010,7 @@ const WasiThreadImpl = struct {...@@ -963,7 +1010,7 @@ const WasiThreadImpl = struct {
963 @call(.auto, f, w.args) catch |err| {1010 @call(.auto, f, w.args) catch |err| {
964 std.debug.print("error: {s}\n", .{@errorName(err)});1011 std.debug.print("error: {s}\n", .{@errorName(err)});
965 if (@errorReturnTrace()) |trace| {1012 if (@errorReturnTrace()) |trace| {
966 std.debug.dumpStackTrace(trace.*);1013 std.debug.dumpStackTrace(trace);
967 }1014 }
968 };1015 };
969 },1016 },
...@@ -1652,9 +1699,9 @@ test "setName, getName" {...@@ -1652,9 +1699,9 @@ test "setName, getName" {
1652 if (builtin.single_threaded) return error.SkipZigTest;1699 if (builtin.single_threaded) return error.SkipZigTest;
16531700
1654 const Context = struct {1701 const Context = struct {
1655 start_wait_event: ResetEvent = .{},1702 start_wait_event: ResetEvent = .unset,
1656 test_done_event: ResetEvent = .{},1703 test_done_event: ResetEvent = .unset,
1657 thread_done_event: ResetEvent = .{},1704 thread_done_event: ResetEvent = .unset,
16581705
1659 done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),1706 done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
1660 thread: Thread = undefined,1707 thread: Thread = undefined,
...@@ -1721,7 +1768,7 @@ test join {...@@ -1721,7 +1768,7 @@ test join {
1721 if (builtin.single_threaded) return error.SkipZigTest;1768 if (builtin.single_threaded) return error.SkipZigTest;
17221769
1723 var value: usize = 0;1770 var value: usize = 0;
1724 var event = ResetEvent{};1771 var event: ResetEvent = .unset;
17251772
1726 const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });1773 const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });
1727 thread.join();1774 thread.join();
...@@ -1733,7 +1780,7 @@ test detach {...@@ -1733,7 +1780,7 @@ test detach {
1733 if (builtin.single_threaded) return error.SkipZigTest;1780 if (builtin.single_threaded) return error.SkipZigTest;
17341781
1735 var value: usize = 0;1782 var value: usize = 0;
1736 var event = ResetEvent{};1783 var event: ResetEvent = .unset;
17371784
1738 const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });1785 const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event });
1739 thread.detach();1786 thread.detach();
...@@ -1778,3 +1825,124 @@ fn testTls() !void {...@@ -1778,3 +1825,124 @@ fn testTls() !void {
1778 x += 1;1825 x += 1;
1779 if (x != 1235) return error.TlsBadEndValue;1826 if (x != 1235) return error.TlsBadEndValue;
1780}1827}
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 {...@@ -123,14 +123,9 @@ const SingleThreadedImpl = struct {
123 fn wait(self: *Impl, mutex: *Mutex, timeout: ?u64) error{Timeout}!void {123 fn wait(self: *Impl, mutex: *Mutex, timeout: ?u64) error{Timeout}!void {
124 _ = self;124 _ = self;
125 _ = mutex;125 _ = mutex;
126
127 // There are no other threads to wake us up.126 // There are no other threads to wake us up.
128 // So if we wait without a timeout we would never wake up.127 // So if we wait without a timeout we would never wake up.
129 const timeout_ns = timeout orelse {128 assert(timeout != null); // Deadlock detected.
130 unreachable; // deadlock detected
131 };
132
133 std.Thread.sleep(timeout_ns);
134 return error.Timeout;129 return error.Timeout;
135 }130 }
136131
...@@ -323,6 +318,8 @@ test "wait and signal" {...@@ -323,6 +318,8 @@ test "wait and signal" {
323 return error.SkipZigTest;318 return error.SkipZigTest;
324 }319 }
325320
321 const io = testing.io;
322
326 const num_threads = 4;323 const num_threads = 4;
327324
328 const MultiWait = struct {325 const MultiWait = struct {
...@@ -348,7 +345,7 @@ test "wait and signal" {...@@ -348,7 +345,7 @@ test "wait and signal" {
348 }345 }
349346
350 while (true) {347 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
353 multi_wait.mutex.lock();350 multi_wait.mutex.lock();
354 defer multi_wait.mutex.unlock();351 defer multi_wait.mutex.unlock();
...@@ -368,6 +365,8 @@ test signal {...@@ -368,6 +365,8 @@ test signal {
368 return error.SkipZigTest;365 return error.SkipZigTest;
369 }366 }
370367
368 const io = testing.io;
369
371 const num_threads = 4;370 const num_threads = 4;
372371
373 const SignalTest = struct {372 const SignalTest = struct {
...@@ -405,7 +404,7 @@ test signal {...@@ -405,7 +404,7 @@ test signal {
405 }404 }
406405
407 while (true) {406 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
410 signal_test.mutex.lock();409 signal_test.mutex.lock();
411 defer signal_test.mutex.unlock();410 defer signal_test.mutex.unlock();
lib/std/Thread/Futex.zig+1-1
...@@ -116,7 +116,7 @@ const SingleThreadedImpl = struct {...@@ -116,7 +116,7 @@ const SingleThreadedImpl = struct {
116 unreachable; // deadlock detected116 unreachable; // deadlock detected
117 };117 };
118118
119 std.Thread.sleep(delay);119 _ = delay;
120 return error.Timeout;120 return error.Timeout;
121 }121 }
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;...@@ -7,11 +7,15 @@ const is_waiting: usize = 1 << 0;
7const one_pending: usize = 1 << 1;7const one_pending: usize = 1 << 1;
88
9state: std.atomic.Value(usize) = std.atomic.Value(usize).init(0),9state: std.atomic.Value(usize) = std.atomic.Value(usize).init(0),
10event: std.Thread.ResetEvent = .{},10event: std.Thread.ResetEvent = .unset,
1111
12pub fn start(self: *WaitGroup) void {12pub fn start(self: *WaitGroup) void {
13 const state = self.state.fetchAdd(one_pending, .monotonic);13 return startStateless(&self.state);
14 assert((state / one_pending) < (std.math.maxInt(usize) / one_pending));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));
15}19}
1620
17pub fn startMany(self: *WaitGroup, n: usize) void {21pub fn startMany(self: *WaitGroup, n: usize) void {
...@@ -28,13 +32,20 @@ pub fn finish(self: *WaitGroup) void {...@@ -28,13 +32,20 @@ pub fn finish(self: *WaitGroup) void {
28 }32 }
29}33}
3034
31pub fn wait(self: *WaitGroup) void {35pub fn finishStateless(state: *std.atomic.Value(usize), event: *std.Thread.ResetEvent) void {
32 const state = self.state.fetchAdd(is_waiting, .acquire);36 const prev_state = state.fetchSub(one_pending, .acq_rel);
33 assert(state & is_waiting == 0);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) {41pub fn wait(wg: *WaitGroup) void {
36 self.event.wait();42 return waitStateless(&wg.state, &wg.event);
37 }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();
38}49}
3950
40pub fn reset(self: *WaitGroup) void {51pub fn reset(self: *WaitGroup) void {
lib/std/Uri.zig+36-16
...@@ -1,45 +1,48 @@...@@ -1,45 +1,48 @@
1//! Uniform Resource Identifier (URI) parsing roughly adhering to <https://tools.ietf.org/html/rfc3986>.1//! Uniform Resource Identifier (URI) parsing roughly adhering to
2//! Does not do perfect grammar and character class checking, but should be robust against URIs in the wild.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
4const std = @import("std.zig");5const std = @import("std.zig");
5const testing = std.testing;6const testing = std.testing;
6const Uri = @This();7const Uri = @This();
7const Allocator = std.mem.Allocator;8const Allocator = std.mem.Allocator;
8const Writer = std.Io.Writer;9const Writer = std.Io.Writer;
10const HostName = std.Io.net.HostName;
911
10scheme: []const u8,12scheme: []const u8,
11user: ?Component = null,13user: ?Component = null,
12password: ?Component = null,14password: ?Component = null,
15/// If non-null, already validated.
13host: ?Component = null,16host: ?Component = null,
14port: ?u16 = null,17port: ?u16 = null,
15path: Component = Component.empty,18path: Component = Component.empty,
16query: ?Component = null,19query: ?Component = null,
17fragment: ?Component = null,20fragment: ?Component = null,
1821
19pub const host_name_max = 255;22pub const GetHostError = error{UriMissingHost};
2023
21/// Returned value may point into `buffer` or be the original string.24/// Returned value may point into `buffer` or be the original string.
22///25///
23/// Suggested buffer length: `host_name_max`.
24///
25/// See also:26/// See also:
26/// * `getHostAlloc`27/// * `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 {
28 const component = uri.host orelse return error.UriMissingHost;29 const component = uri.host orelse return error.UriMissingHost;
29 return component.toRaw(buffer) catch |err| switch (err) {30 const bytes = component.toRaw(buffer) catch |err| switch (err) {
30 error.NoSpaceLeft => return error.UriHostTooLong,31 error.NoSpaceLeft => unreachable, // `host` already validated.
31 };32 };
33 return .{ .bytes = bytes };
32}34}
3335
36pub const GetHostAllocError = GetHostError || error{OutOfMemory};
37
34/// Returned value may point into `buffer` or be the original string.38/// Returned value may point into `buffer` or be the original string.
35///39///
36/// See also:40/// See also:
37/// * `getHost`41/// * `getHost`
38pub fn getHostAlloc(uri: Uri, arena: Allocator) error{ UriMissingHost, UriHostTooLong, OutOfMemory }![]const u8 {42pub fn getHostAlloc(uri: Uri, arena: Allocator) GetHostAllocError!HostName {
39 const component = uri.host orelse return error.UriMissingHost;43 const component = uri.host orelse return error.UriMissingHost;
40 const result = try component.toRawMaybeAlloc(arena);44 const bytes = try component.toRawMaybeAlloc(arena);
41 if (result.len > host_name_max) return error.UriHostTooLong;45 return .{ .bytes = bytes };
42 return result;
43}46}
4447
45pub const Component = union(enum) {48pub const Component = union(enum) {
...@@ -194,7 +197,12 @@ pub fn percentDecodeInPlace(buffer: []u8) []u8 {...@@ -194,7 +197,12 @@ pub fn percentDecodeInPlace(buffer: []u8) []u8 {
194 return percentDecodeBackwards(buffer, buffer);197 return percentDecodeBackwards(buffer, buffer);
195}198}
196199
197pub const ParseError = error{ UnexpectedCharacter, InvalidFormat, InvalidPort };200pub const ParseError = error{
201 UnexpectedCharacter,
202 InvalidFormat,
203 InvalidPort,
204 InvalidHostName,
205};
198206
199/// Parses the URI or returns an error. This function is not compliant, but is required to parse207/// Parses the URI or returns an error. This function is not compliant, but is required to parse
200/// some forms of URIs in the wild, such as HTTP Location headers.208/// 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...@@ -397,7 +405,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE
397 .scheme = new_parsed.scheme,405 .scheme = new_parsed.scheme,
398 .user = new_parsed.user,406 .user = new_parsed.user,
399 .password = new_parsed.password,407 .password = new_parsed.password,
400 .host = new_parsed.host,408 .host = try validateHostComponent(new_parsed.host),
401 .port = new_parsed.port,409 .port = new_parsed.port,
402 .path = remove_dot_segments(new_path),410 .path = remove_dot_segments(new_path),
403 .query = new_parsed.query,411 .query = new_parsed.query,
...@@ -408,7 +416,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE...@@ -408,7 +416,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE
408 .scheme = base.scheme,416 .scheme = base.scheme,
409 .user = new_parsed.user,417 .user = new_parsed.user,
410 .password = new_parsed.password,418 .password = new_parsed.password,
411 .host = host,419 .host = try validateHostComponent(host),
412 .port = new_parsed.port,420 .port = new_parsed.port,
413 .path = remove_dot_segments(new_path),421 .path = remove_dot_segments(new_path),
414 .query = new_parsed.query,422 .query = new_parsed.query,
...@@ -430,7 +438,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE...@@ -430,7 +438,7 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE
430 .scheme = base.scheme,438 .scheme = base.scheme,
431 .user = base.user,439 .user = base.user,
432 .password = base.password,440 .password = base.password,
433 .host = base.host,441 .host = try validateHostComponent(base.host),
434 .port = base.port,442 .port = base.port,
435 .path = path,443 .path = path,
436 .query = query,444 .query = query,
...@@ -438,6 +446,18 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE...@@ -438,6 +446,18 @@ pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceE
438 };446 };
439}447}
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
441/// In-place implementation of RFC 3986, Section 5.2.4.461/// In-place implementation of RFC 3986, Section 5.2.4.
442fn remove_dot_segments(path: []u8) Component {462fn remove_dot_segments(path: []u8) Component {
443 var in_i: usize = 0;463 var in_i: usize = 0;
lib/std/builtin.zig-13
...@@ -37,19 +37,6 @@ pub const subsystem: ?std.Target.SubSystem = blk: {...@@ -37,19 +37,6 @@ pub const subsystem: ?std.Target.SubSystem = blk: {
37pub const StackTrace = struct {37pub const StackTrace = struct {
38 index: usize,38 index: usize,
39 instruction_addresses: []usize,39 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 }
53};40};
5441
55/// This data structure is used by the Zig language code generation and42/// This data structure is used by the Zig language code generation and
lib/std/c.zig+423-413
...@@ -1,12 +1,14 @@...@@ -1,12 +1,14 @@
1const std = @import("std");
2const builtin = @import("builtin");1const 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");
3const c = @This();8const c = @This();
4const maxInt = std.math.maxInt;9const maxInt = std.math.maxInt;
5const assert = std.debug.assert;10const assert = std.debug.assert;
6const page_size = std.heap.page_size_min;11const page_size = std.heap.page_size_min;
7const native_abi = builtin.abi;
8const native_arch = builtin.cpu.arch;
9const native_os = builtin.os.tag;
10const linux = std.os.linux;12const linux = std.os.linux;
11const emscripten = std.os.emscripten;13const emscripten = std.os.emscripten;
12const wasi = std.os.wasi;14const wasi = std.os.wasi;
...@@ -2587,25 +2589,24 @@ pub const SHUT = switch (native_os) {...@@ -2587,25 +2589,24 @@ pub const SHUT = switch (native_os) {
25872589
2588/// Signal types2590/// Signal types
2589pub const SIG = switch (native_os) {2591pub const SIG = switch (native_os) {
2590 .linux => linux.SIG,2592 .linux, .emscripten => linux.SIG,
2591 .emscripten => emscripten.SIG,2593 .windows => enum(u32) {
2592 .windows => struct {
2593 /// interrupt2594 /// interrupt
2594 pub const INT = 2;2595 INT = 2,
2595 /// illegal instruction - invalid function image2596 /// illegal instruction - invalid function image
2596 pub const ILL = 4;2597 ILL = 4,
2597 /// floating point exception2598 /// floating point exception
2598 pub const FPE = 8;2599 FPE = 8,
2599 /// segment violation2600 /// segment violation
2600 pub const SEGV = 11;2601 SEGV = 11,
2601 /// Software termination signal from kill2602 /// Software termination signal from kill
2602 pub const TERM = 15;2603 TERM = 15,
2603 /// Ctrl-Break sequence2604 /// Ctrl-Break sequence
2604 pub const BREAK = 21;2605 BREAK = 21,
2605 /// abnormal termination triggered by abort call2606 /// abnormal termination triggered by abort call
2606 pub const ABRT = 22;2607 ABRT = 22,
2607 /// SIGABRT compatible with other platforms, same as SIGABRT2608 /// SIGABRT compatible with other platforms, same as SIGABRT
2608 pub const ABRT_COMPAT = 6;2609 ABRT_COMPAT = 6,
26092610
2610 // Signal action codes2611 // Signal action codes
2611 /// default signal action2612 /// default signal action
...@@ -2621,7 +2622,7 @@ pub const SIG = switch (native_os) {...@@ -2621,7 +2622,7 @@ pub const SIG = switch (native_os) {
2621 /// Signal error value (returned by signal call on error)2622 /// Signal error value (returned by signal call on error)
2622 pub const ERR = -1;2623 pub const ERR = -1;
2623 },2624 },
2624 .macos, .ios, .tvos, .watchos, .visionos => struct {2625 .macos, .ios, .tvos, .watchos, .visionos => enum(u32) {
2625 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));2626 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
2626 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);2627 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
2627 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);2628 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);
...@@ -2633,113 +2634,74 @@ pub const SIG = switch (native_os) {...@@ -2633,113 +2634,74 @@ pub const SIG = switch (native_os) {
2633 pub const UNBLOCK = 2;2634 pub const UNBLOCK = 2;
2634 /// set specified signal set2635 /// set specified signal set
2635 pub const SETMASK = 3;2636 pub const SETMASK = 3;
2637
2638 pub const IOT: SIG = .ABRT;
2639 pub const POLL: SIG = .EMT;
2640
2636 /// hangup2641 /// hangup
2637 pub const HUP = 1;2642 HUP = 1,
2638 /// interrupt2643 /// interrupt
2639 pub const INT = 2;2644 INT = 2,
2640 /// quit2645 /// quit
2641 pub const QUIT = 3;2646 QUIT = 3,
2642 /// illegal instruction (not reset when caught)2647 /// illegal instruction (not reset when caught)
2643 pub const ILL = 4;2648 ILL = 4,
2644 /// trace trap (not reset when caught)2649 /// trace trap (not reset when caught)
2645 pub const TRAP = 5;2650 TRAP = 5,
2646 /// abort()2651 /// abort()
2647 pub const ABRT = 6;2652 ABRT = 6,
2648 /// pollable event ([XSR] generated, not supported)
2649 pub const POLL = 7;
2650 /// compatibility
2651 pub const IOT = ABRT;
2652 /// EMT instruction2653 /// EMT instruction
2653 pub const EMT = 7;2654 EMT = 7,
2654 /// floating point exception2655 /// floating point exception
2655 pub const FPE = 8;2656 FPE = 8,
2656 /// kill (cannot be caught or ignored)2657 /// kill (cannot be caught or ignored)
2657 pub const KILL = 9;2658 KILL = 9,
2658 /// bus error2659 /// bus error
2659 pub const BUS = 10;2660 BUS = 10,
2660 /// segmentation violation2661 /// segmentation violation
2661 pub const SEGV = 11;2662 SEGV = 11,
2662 /// bad argument to system call2663 /// bad argument to system call
2663 pub const SYS = 12;2664 SYS = 12,
2664 /// write on a pipe with no one to read it2665 /// write on a pipe with no one to read it
2665 pub const PIPE = 13;2666 PIPE = 13,
2666 /// alarm clock2667 /// alarm clock
2667 pub const ALRM = 14;2668 ALRM = 14,
2668 /// software termination signal from kill2669 /// software termination signal from kill
2669 pub const TERM = 15;2670 TERM = 15,
2670 /// urgent condition on IO channel2671 /// urgent condition on IO channel
2671 pub const URG = 16;2672 URG = 16,
2672 /// sendable stop signal not from tty2673 /// sendable stop signal not from tty
2673 pub const STOP = 17;2674 STOP = 17,
2674 /// stop signal from tty2675 /// stop signal from tty
2675 pub const TSTP = 18;2676 TSTP = 18,
2676 /// continue a stopped process2677 /// continue a stopped process
2677 pub const CONT = 19;2678 CONT = 19,
2678 /// to parent on child stop or exit2679 /// to parent on child stop or exit
2679 pub const CHLD = 20;2680 CHLD = 20,
2680 /// to readers pgrp upon background tty read2681 /// to readers pgrp upon background tty read
2681 pub const TTIN = 21;2682 TTIN = 21,
2682 /// like TTIN for output if (tp->t_local&LTOSTOP)2683 /// like TTIN for output if (tp->t_local&LTOSTOP)
2683 pub const TTOU = 22;2684 TTOU = 22,
2684 /// input/output possible signal2685 /// input/output possible signal
2685 pub const IO = 23;2686 IO = 23,
2686 /// exceeded CPU time limit2687 /// exceeded CPU time limit
2687 pub const XCPU = 24;2688 XCPU = 24,
2688 /// exceeded file size limit2689 /// exceeded file size limit
2689 pub const XFSZ = 25;2690 XFSZ = 25,
2690 /// virtual time alarm2691 /// virtual time alarm
2691 pub const VTALRM = 26;2692 VTALRM = 26,
2692 /// profiling time alarm2693 /// profiling time alarm
2693 pub const PROF = 27;2694 PROF = 27,
2694 /// window size changes2695 /// window size changes
2695 pub const WINCH = 28;2696 WINCH = 28,
2696 /// information request2697 /// information request
2697 pub const INFO = 29;2698 INFO = 29,
2698 /// user defined signal 12699 /// user defined signal 1
2699 pub const USR1 = 30;2700 USR1 = 30,
2700 /// user defined signal 22701 /// user defined signal 2
2701 pub const USR2 = 31;2702 USR2 = 31,
2702 },2703 },
2703 .freebsd => struct {2704 .freebsd => enum(u32) {
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
2743 pub const BLOCK = 1;2705 pub const BLOCK = 1;
2744 pub const UNBLOCK = 2;2706 pub const UNBLOCK = 2;
2745 pub const SETMASK = 3;2707 pub const SETMASK = 3;
...@@ -2763,8 +2725,48 @@ pub const SIG = switch (native_os) {...@@ -2763,8 +2725,48 @@ pub const SIG = switch (native_os) {
2763 pub inline fn VALID(sig: usize) usize {2725 pub inline fn VALID(sig: usize) usize {
2764 return sig <= MAXSIG and sig > 0;2726 return sig <= MAXSIG and sig > 0;
2765 }2727 }
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,
2766 },2768 },
2767 .illumos => struct {2769 .illumos => enum(u32) {
2768 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);2770 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
2769 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));2771 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
2770 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);2772 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);
...@@ -2773,54 +2775,9 @@ pub const SIG = switch (native_os) {...@@ -2773,54 +2775,9 @@ pub const SIG = switch (native_os) {
2773 pub const WORDS = 4;2775 pub const WORDS = 4;
2774 pub const MAXSIG = 75;2776 pub const MAXSIG = 75;
27752777
2776 pub const SIG_BLOCK = 1;2778 pub const BLOCK = 1;
2777 pub const SIG_UNBLOCK = 2;2779 pub const UNBLOCK = 2;
2778 pub const SIG_SETMASK = 3;2780 pub const 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;
28242781
2825 pub const RTMIN = 42;2782 pub const RTMIN = 42;
2826 pub const RTMAX = 74;2783 pub const RTMAX = 74;
...@@ -2837,8 +2794,54 @@ pub const SIG = switch (native_os) {...@@ -2837,8 +2794,54 @@ pub const SIG = switch (native_os) {
2837 pub inline fn VALID(sig: usize) usize {2794 pub inline fn VALID(sig: usize) usize {
2838 return sig <= MAXSIG and sig > 0;2795 return sig <= MAXSIG and sig > 0;
2839 }2796 }
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,
2840 },2843 },
2841 .netbsd => struct {2844 .netbsd => enum(u32) {
2842 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);2845 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
2843 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);2846 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);
2844 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));2847 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
...@@ -2850,40 +2853,6 @@ pub const SIG = switch (native_os) {...@@ -2850,40 +2853,6 @@ pub const SIG = switch (native_os) {
2850 pub const UNBLOCK = 2;2853 pub const UNBLOCK = 2;
2851 pub const SETMASK = 3;2854 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
2887 pub const RTMIN = 33;2856 pub const RTMIN = 33;
2888 pub const RTMAX = 63;2857 pub const RTMAX = 63;
28892858
...@@ -2899,8 +2868,43 @@ pub const SIG = switch (native_os) {...@@ -2899,8 +2868,43 @@ pub const SIG = switch (native_os) {
2899 pub inline fn VALID(sig: usize) usize {2868 pub inline fn VALID(sig: usize) usize {
2900 return sig <= MAXSIG and sig > 0;2869 return sig <= MAXSIG and sig > 0;
2901 }2870 }
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,
2902 },2906 },
2903 .dragonfly => struct {2907 .dragonfly => enum(u32) {
2904 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);2908 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
2905 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);2909 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);
2906 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));2910 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
...@@ -2909,137 +2913,140 @@ pub const SIG = switch (native_os) {...@@ -2909,137 +2913,140 @@ pub const SIG = switch (native_os) {
2909 pub const UNBLOCK = 2;2913 pub const UNBLOCK = 2;
2910 pub const SETMASK = 3;2914 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
2948 pub const WORDS = 4;2916 pub const WORDS = 4;
2949 },2917
2950 .haiku => struct {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) {
2951 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);2956 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
2952 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);2957 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);
2953 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));2958 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
29542959
2955 pub const HOLD: ?Sigaction.handler_fn = @ptrFromInt(3);2960 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
2991 pub const BLOCK = 1;2962 pub const BLOCK = 1;
2992 pub const UNBLOCK = 2;2963 pub const UNBLOCK = 2;
2993 pub const SETMASK = 3;2964 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,
2994 },3000 },
2995 .openbsd => struct {3001 .openbsd => enum(u32) {
2996 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);3002 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
2997 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);3003 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);
2998 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));3004 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
2999 pub const CATCH: ?Sigaction.handler_fn = @ptrFromInt(2);3005 pub const CATCH: ?Sigaction.handler_fn = @ptrFromInt(2);
3000 pub const HOLD: ?Sigaction.handler_fn = @ptrFromInt(3);3006 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
3036 pub const BLOCK = 1;3008 pub const BLOCK = 1;
3037 pub const UNBLOCK = 2;3009 pub const UNBLOCK = 2;
3038 pub const SETMASK = 3;3010 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,
3039 },3046 },
3040 // https://github.com/SerenityOS/serenity/blob/046c23f567a17758d762a33bdf04bacbfd088f9f/Kernel/API/POSIX/signal.h3047 // https://github.com/SerenityOS/serenity/blob/046c23f567a17758d762a33bdf04bacbfd088f9f/Kernel/API/POSIX/signal.h
3041 // https://github.com/SerenityOS/serenity/blob/046c23f567a17758d762a33bdf04bacbfd088f9f/Kernel/API/POSIX/signal_numbers.h3048 // https://github.com/SerenityOS/serenity/blob/046c23f567a17758d762a33bdf04bacbfd088f9f/Kernel/API/POSIX/signal_numbers.h
3042 .serenity => struct {3049 .serenity => enum(u32) {
3043 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);3050 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
3044 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));3051 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
3045 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);3052 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);
...@@ -3048,39 +3055,39 @@ pub const SIG = switch (native_os) {...@@ -3048,39 +3055,39 @@ pub const SIG = switch (native_os) {
3048 pub const UNBLOCK = 2;3055 pub const UNBLOCK = 2;
3049 pub const SETMASK = 3;3056 pub const SETMASK = 3;
30503057
3051 pub const INVAL = 0;3058 INVAL = 0,
3052 pub const HUP = 1;3059 HUP = 1,
3053 pub const INT = 2;3060 INT = 2,
3054 pub const QUIT = 3;3061 QUIT = 3,
3055 pub const ILL = 4;3062 ILL = 4,
3056 pub const TRAP = 5;3063 TRAP = 5,
3057 pub const ABRT = 6;3064 ABRT = 6,
3058 pub const BUS = 7;3065 BUS = 7,
3059 pub const FPE = 8;3066 FPE = 8,
3060 pub const KILL = 9;3067 KILL = 9,
3061 pub const USR1 = 10;3068 USR1 = 10,
3062 pub const SEGV = 11;3069 SEGV = 11,
3063 pub const USR2 = 12;3070 USR2 = 12,
3064 pub const PIPE = 13;3071 PIPE = 13,
3065 pub const ALRM = 14;3072 ALRM = 14,
3066 pub const TERM = 15;3073 TERM = 15,
3067 pub const STKFLT = 16;3074 STKFLT = 16,
3068 pub const CHLD = 17;3075 CHLD = 17,
3069 pub const CONT = 18;3076 CONT = 18,
3070 pub const STOP = 19;3077 STOP = 19,
3071 pub const TSTP = 20;3078 TSTP = 20,
3072 pub const TTIN = 21;3079 TTIN = 21,
3073 pub const TTOU = 22;3080 TTOU = 22,
3074 pub const URG = 23;3081 URG = 23,
3075 pub const XCPU = 24;3082 XCPU = 24,
3076 pub const XFSZ = 25;3083 XFSZ = 25,
3077 pub const VTALRM = 26;3084 VTALRM = 26,
3078 pub const PROF = 27;3085 PROF = 27,
3079 pub const WINCH = 28;3086 WINCH = 28,
3080 pub const IO = 29;3087 IO = 29,
3081 pub const INFO = 30;3088 INFO = 30,
3082 pub const SYS = 31;3089 SYS = 31,
3083 pub const CANCEL = 32;3090 CANCEL = 32,
3084 },3091 },
3085 else => void,3092 else => void,
3086};3093};
...@@ -3117,8 +3124,8 @@ pub const SYS = switch (native_os) {...@@ -3117,8 +3124,8 @@ pub const SYS = switch (native_os) {
31173124
3118/// A common format for the Sigaction struct across a variety of Linux flavors.3125/// A common format for the Sigaction struct across a variety of Linux flavors.
3119const common_linux_Sigaction = extern struct {3126const common_linux_Sigaction = extern struct {
3120 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;3127 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3121 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;3128 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
31223129
3123 handler: extern union {3130 handler: extern union {
3124 handler: ?handler_fn,3131 handler: ?handler_fn,
...@@ -3139,8 +3146,8 @@ pub const Sigaction = switch (native_os) {...@@ -3139,8 +3146,8 @@ pub const Sigaction = switch (native_os) {
3139 => if (builtin.target.abi.isMusl())3146 => if (builtin.target.abi.isMusl())
3140 common_linux_Sigaction3147 common_linux_Sigaction
3141 else if (builtin.target.ptrBitWidth() == 64) extern struct {3148 else if (builtin.target.ptrBitWidth() == 64) extern struct {
3142 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;3149 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3143 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;3150 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
31443151
3145 flags: c_uint,3152 flags: c_uint,
3146 handler: extern union {3153 handler: extern union {
...@@ -3150,8 +3157,8 @@ pub const Sigaction = switch (native_os) {...@@ -3150,8 +3157,8 @@ pub const Sigaction = switch (native_os) {
3150 mask: sigset_t,3157 mask: sigset_t,
3151 restorer: ?*const fn () callconv(.c) void = null,3158 restorer: ?*const fn () callconv(.c) void = null,
3152 } else extern struct {3159 } else extern struct {
3153 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;3160 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3154 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;3161 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
31553162
3156 flags: c_uint,3163 flags: c_uint,
3157 handler: extern union {3164 handler: extern union {
...@@ -3163,8 +3170,8 @@ pub const Sigaction = switch (native_os) {...@@ -3163,8 +3170,8 @@ pub const Sigaction = switch (native_os) {
3163 __resv: [1]c_int = .{0},3170 __resv: [1]c_int = .{0},
3164 },3171 },
3165 .s390x => if (builtin.abi == .gnu) extern struct {3172 .s390x => if (builtin.abi == .gnu) extern struct {
3166 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;3173 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3167 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;3174 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
31683175
3169 handler: extern union {3176 handler: extern union {
3170 handler: ?handler_fn,3177 handler: ?handler_fn,
...@@ -3179,8 +3186,8 @@ pub const Sigaction = switch (native_os) {...@@ -3179,8 +3186,8 @@ pub const Sigaction = switch (native_os) {
3179 },3186 },
3180 .emscripten => emscripten.Sigaction,3187 .emscripten => emscripten.Sigaction,
3181 .netbsd, .macos, .ios, .tvos, .watchos, .visionos => extern struct {3188 .netbsd, .macos, .ios, .tvos, .watchos, .visionos => extern struct {
3182 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;3189 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3183 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;3190 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
31843191
3185 handler: extern union {3192 handler: extern union {
3186 handler: ?handler_fn,3193 handler: ?handler_fn,
...@@ -3190,8 +3197,8 @@ pub const Sigaction = switch (native_os) {...@@ -3190,8 +3197,8 @@ pub const Sigaction = switch (native_os) {
3190 flags: c_uint,3197 flags: c_uint,
3191 },3198 },
3192 .dragonfly, .freebsd => extern struct {3199 .dragonfly, .freebsd => extern struct {
3193 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;3200 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3194 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;3201 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
31953202
3196 /// signal handler3203 /// signal handler
3197 handler: extern union {3204 handler: extern union {
...@@ -3204,8 +3211,8 @@ pub const Sigaction = switch (native_os) {...@@ -3204,8 +3211,8 @@ pub const Sigaction = switch (native_os) {
3204 mask: sigset_t,3211 mask: sigset_t,
3205 },3212 },
3206 .illumos => extern struct {3213 .illumos => extern struct {
3207 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;3214 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3208 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;3215 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
32093216
3210 /// signal options3217 /// signal options
3211 flags: c_uint,3218 flags: c_uint,
...@@ -3218,8 +3225,8 @@ pub const Sigaction = switch (native_os) {...@@ -3218,8 +3225,8 @@ pub const Sigaction = switch (native_os) {
3218 mask: sigset_t,3225 mask: sigset_t,
3219 },3226 },
3220 .haiku => extern struct {3227 .haiku => extern struct {
3221 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;3228 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3222 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;3229 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
32233230
3224 /// signal handler3231 /// signal handler
3225 handler: extern union {3232 handler: extern union {
...@@ -3237,8 +3244,8 @@ pub const Sigaction = switch (native_os) {...@@ -3237,8 +3244,8 @@ pub const Sigaction = switch (native_os) {
3237 userdata: *allowzero anyopaque = undefined,3244 userdata: *allowzero anyopaque = undefined,
3238 },3245 },
3239 .openbsd => extern struct {3246 .openbsd => extern struct {
3240 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;3247 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3241 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;3248 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
32423249
3243 /// signal handler3250 /// signal handler
3244 handler: extern union {3251 handler: extern union {
...@@ -3252,8 +3259,8 @@ pub const Sigaction = switch (native_os) {...@@ -3252,8 +3259,8 @@ pub const Sigaction = switch (native_os) {
3252 },3259 },
3253 // https://github.com/SerenityOS/serenity/blob/ec492a1a0819e6239ea44156825c4ee7234ca3db/Kernel/API/POSIX/signal.h#L39-L463260 // https://github.com/SerenityOS/serenity/blob/ec492a1a0819e6239ea44156825c4ee7234ca3db/Kernel/API/POSIX/signal.h#L39-L46
3254 .serenity => extern struct {3261 .serenity => extern struct {
3255 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;3262 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
3256 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;3263 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
32573264
3258 handler: extern union {3265 handler: extern union {
3259 handler: ?handler_fn,3266 handler: ?handler_fn,
...@@ -4087,8 +4094,9 @@ pub const linger = switch (native_os) {...@@ -4087,8 +4094,9 @@ pub const linger = switch (native_os) {
4087 },4094 },
4088 else => void,4095 else => void,
4089};4096};
4097
4090pub const msghdr = switch (native_os) {4098pub 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,
4092 .openbsd,4100 .openbsd,
4093 .emscripten,4101 .emscripten,
4094 .dragonfly,4102 .dragonfly,
...@@ -4102,36 +4110,28 @@ pub const msghdr = switch (native_os) {...@@ -4102,36 +4110,28 @@ pub const msghdr = switch (native_os) {
4102 .tvos,4110 .tvos,
4103 .visionos,4111 .visionos,
4104 .watchos,4112 .watchos,
4105 => extern struct {4113 .serenity, // https://github.com/SerenityOS/serenity/blob/ac44ec5ebc707f9dd0c3d4759a1e17e91db5d74f/Kernel/API/POSIX/sys/socket.h#L74-L82
4106 /// optional address4114 => posix_msghdr,
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 },
4131 else => void,4115 else => void,
4132};4116};
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
4133pub const msghdr_const = switch (native_os) {4133pub 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,
4135 .openbsd,4135 .openbsd,
4136 .emscripten,4136 .emscripten,
4137 .dragonfly,4137 .dragonfly,
...@@ -4145,36 +4145,37 @@ pub const msghdr_const = switch (native_os) {...@@ -4145,36 +4145,37 @@ pub const msghdr_const = switch (native_os) {
4145 .tvos,4145 .tvos,
4146 .visionos,4146 .visionos,
4147 .watchos,4147 .watchos,
4148 => extern struct {4148 .serenity,
4149 /// optional address4149 => posix_msghdr_const,
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 },
4173 else => void,4150 else => void,
4174};4151};
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
4175pub const cmsghdr = switch (native_os) {4175pub const cmsghdr = switch (native_os) {
4176 .linux => if (@bitSizeOf(usize) > @bitSizeOf(i32) and builtin.abi.isMusl()) posix_cmsghdr else linux.cmsghdr,
4176 // https://github.com/emscripten-core/emscripten/blob/96371ed7888fc78c040179f4d4faa82a6a07a116/system/lib/libc/musl/include/sys/socket.h#L444177 // 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,
4178 // https://github.com/freebsd/freebsd-src/blob/b197d2abcb6895d78bc9df8404e374397aa44748/sys/sys/socket.h#L4924179 // https://github.com/freebsd/freebsd-src/blob/b197d2abcb6895d78bc9df8404e374397aa44748/sys/sys/socket.h#L492
4179 .freebsd,4180 .freebsd,
4180 // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/107c0518337ba90e7fa49e74845d8d44320c9a6d/sys/sys/socket.h#L4524181 // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/107c0518337ba90e7fa49e74845d8d44320c9a6d/sys/sys/socket.h#L452
...@@ -4196,13 +4197,19 @@ pub const cmsghdr = switch (native_os) {...@@ -4196,13 +4197,19 @@ pub const cmsghdr = switch (native_os) {
4196 .tvos,4197 .tvos,
4197 .visionos,4198 .visionos,
4198 .watchos,4199 .watchos,
4199 => extern struct {4200 => posix_cmsghdr,
4200 len: socklen_t,4201
4201 level: c_int,
4202 type: c_int,
4203 },
4204 else => void,4202 else => void,
4205};4203};
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
4206pub const nfds_t = switch (native_os) {4213pub const nfds_t = switch (native_os) {
4207 .linux => linux.nfds_t,4214 .linux => linux.nfds_t,
4208 .emscripten => emscripten.nfds_t,4215 .emscripten => emscripten.nfds_t,
...@@ -4443,7 +4450,7 @@ pub const siginfo_t = switch (native_os) {...@@ -4443,7 +4450,7 @@ pub const siginfo_t = switch (native_os) {
4443 .linux => linux.siginfo_t,4450 .linux => linux.siginfo_t,
4444 .emscripten => emscripten.siginfo_t,4451 .emscripten => emscripten.siginfo_t,
4445 .driverkit, .macos, .ios, .tvos, .watchos, .visionos => extern struct {4452 .driverkit, .macos, .ios, .tvos, .watchos, .visionos => extern struct {
4446 signo: c_int,4453 signo: SIG,
4447 errno: c_int,4454 errno: c_int,
4448 code: c_int,4455 code: c_int,
4449 pid: pid_t,4456 pid: pid_t,
...@@ -4459,7 +4466,7 @@ pub const siginfo_t = switch (native_os) {...@@ -4459,7 +4466,7 @@ pub const siginfo_t = switch (native_os) {
4459 },4466 },
4460 .freebsd => extern struct {4467 .freebsd => extern struct {
4461 // Signal number.4468 // Signal number.
4462 signo: c_int,4469 signo: SIG,
4463 // Errno association.4470 // Errno association.
4464 errno: c_int,4471 errno: c_int,
4465 /// Signal code.4472 /// Signal code.
...@@ -4502,7 +4509,7 @@ pub const siginfo_t = switch (native_os) {...@@ -4502,7 +4509,7 @@ pub const siginfo_t = switch (native_os) {
4502 },4509 },
4503 },4510 },
4504 .illumos => extern struct {4511 .illumos => extern struct {
4505 signo: c_int,4512 signo: SIG,
4506 code: c_int,4513 code: c_int,
4507 errno: c_int,4514 errno: c_int,
4508 // 64bit architectures insert 4bytes of padding here, this is done by4515 // 64bit architectures insert 4bytes of padding here, this is done by
...@@ -4559,7 +4566,7 @@ pub const siginfo_t = switch (native_os) {...@@ -4559,7 +4566,7 @@ pub const siginfo_t = switch (native_os) {
4559 info: netbsd._ksiginfo,4566 info: netbsd._ksiginfo,
4560 },4567 },
4561 .dragonfly => extern struct {4568 .dragonfly => extern struct {
4562 signo: c_int,4569 signo: SIG,
4563 errno: c_int,4570 errno: c_int,
4564 code: c_int,4571 code: c_int,
4565 pid: c_int,4572 pid: c_int,
...@@ -4571,7 +4578,7 @@ pub const siginfo_t = switch (native_os) {...@@ -4571,7 +4578,7 @@ pub const siginfo_t = switch (native_os) {
4571 __spare__: [7]c_int,4578 __spare__: [7]c_int,
4572 },4579 },
4573 .haiku => extern struct {4580 .haiku => extern struct {
4574 signo: i32,4581 signo: SIG,
4575 code: i32,4582 code: i32,
4576 errno: i32,4583 errno: i32,
45774584
...@@ -4580,7 +4587,7 @@ pub const siginfo_t = switch (native_os) {...@@ -4580,7 +4587,7 @@ pub const siginfo_t = switch (native_os) {
4580 addr: *allowzero anyopaque,4587 addr: *allowzero anyopaque,
4581 },4588 },
4582 .openbsd => extern struct {4589 .openbsd => extern struct {
4583 signo: c_int,4590 signo: SIG,
4584 code: c_int,4591 code: c_int,
4585 errno: c_int,4592 errno: c_int,
4586 data: extern union {4593 data: extern union {
...@@ -4615,7 +4622,7 @@ pub const siginfo_t = switch (native_os) {...@@ -4615,7 +4622,7 @@ pub const siginfo_t = switch (native_os) {
4615 },4622 },
4616 // https://github.com/SerenityOS/serenity/blob/ec492a1a0819e6239ea44156825c4ee7234ca3db/Kernel/API/POSIX/signal.h#L27-L374623 // https://github.com/SerenityOS/serenity/blob/ec492a1a0819e6239ea44156825c4ee7234ca3db/Kernel/API/POSIX/signal.h#L27-L37
4617 .serenity => extern struct {4624 .serenity => extern struct {
4618 signo: c_int,4625 signo: SIG,
4619 code: c_int,4626 code: c_int,
4620 errno: c_int,4627 errno: c_int,
4621 pid: pid_t,4628 pid: pid_t,
...@@ -6865,7 +6872,7 @@ pub const IFNAMESIZE = switch (native_os) {...@@ -6865,7 +6872,7 @@ pub const IFNAMESIZE = switch (native_os) {
6865 // https://github.com/SerenityOS/serenity/blob/9882848e0bf783dfc8e8a6d887a848d70d9c58f4/Kernel/API/POSIX/net/if.h#L506872 // https://github.com/SerenityOS/serenity/blob/9882848e0bf783dfc8e8a6d887a848d70d9c58f4/Kernel/API/POSIX/net/if.h#L50
6866 .openbsd, .dragonfly, .netbsd, .freebsd, .macos, .ios, .tvos, .watchos, .visionos, .serenity => 16,6873 .openbsd, .dragonfly, .netbsd, .freebsd, .macos, .ios, .tvos, .watchos, .visionos, .serenity => 16,
6867 .illumos => 32,6874 .illumos => 32,
6868 else => void,6875 else => {},
6869};6876};
68706877
6871pub const stack_t = switch (native_os) {6878pub 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;...@@ -10591,7 +10598,7 @@ pub extern "c" fn lseek(fd: fd_t, offset: off_t, whence: whence_t) off_t;
10591pub extern "c" fn open(path: [*:0]const u8, oflag: O, ...) c_int;10598pub extern "c" fn open(path: [*:0]const u8, oflag: O, ...) c_int;
10592pub extern "c" fn openat(fd: c_int, path: [*:0]const u8, oflag: O, ...) c_int;10599pub extern "c" fn openat(fd: c_int, path: [*:0]const u8, oflag: O, ...) c_int;
10593pub extern "c" fn ftruncate(fd: c_int, length: off_t) c_int;10600pub 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;
10595pub extern "c" fn read(fd: fd_t, buf: [*]u8, nbyte: usize) isize;10602pub extern "c" fn read(fd: fd_t, buf: [*]u8, nbyte: usize) isize;
10596pub extern "c" fn readv(fd: c_int, iov: [*]const iovec, iovcnt: c_uint) isize;10603pub extern "c" fn readv(fd: c_int, iov: [*]const iovec, iovcnt: c_uint) isize;
10597pub extern "c" fn pread(fd: fd_t, buf: [*]u8, nbyte: usize, offset: off_t) isize;10604pub extern "c" fn pread(fd: fd_t, buf: [*]u8, nbyte: usize, offset: off_t) isize;
...@@ -10683,6 +10690,7 @@ pub extern "c" fn sendto(...@@ -10683,6 +10690,7 @@ pub extern "c" fn sendto(
10683 addrlen: socklen_t,10690 addrlen: socklen_t,
10684) isize;10691) isize;
10685pub extern "c" fn sendmsg(sockfd: fd_t, msg: *const msghdr_const, flags: u32) isize;10692pub 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
10687pub extern "c" fn recv(10695pub extern "c" fn recv(
10688 sockfd: fd_t,10696 sockfd: fd_t,
...@@ -10708,7 +10716,7 @@ pub const recvmsg = switch (native_os) {...@@ -10708,7 +10716,7 @@ pub const recvmsg = switch (native_os) {
10708 else => private.recvmsg,10716 else => private.recvmsg,
10709};10717};
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
10713pub extern "c" fn setuid(uid: uid_t) c_int;10721pub extern "c" fn setuid(uid: uid_t) c_int;
10714pub extern "c" fn setgid(gid: gid_t) c_int;10722pub extern "c" fn setgid(gid: gid_t) c_int;
...@@ -10772,6 +10780,8 @@ pub const pthread_setname_np = switch (native_os) {...@@ -10772,6 +10780,8 @@ pub const pthread_setname_np = switch (native_os) {
10772};10780};
1077310781
10774pub extern "c" fn pthread_getname_np(thread: pthread_t, name: [*:0]u8, len: usize) c_int;10782pub 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
10775pub const pthread_threadid_np = switch (native_os) {10785pub const pthread_threadid_np = switch (native_os) {
10776 .macos, .ios, .tvos, .watchos, .visionos => private.pthread_threadid_np,10786 .macos, .ios, .tvos, .watchos, .visionos => private.pthread_threadid_np,
10777 else => {},10787 else => {},
...@@ -10876,13 +10886,13 @@ pub extern "c" fn dn_expand(...@@ -10876,13 +10886,13 @@ pub extern "c" fn dn_expand(
10876 length: c_int,10886 length: c_int,
10877) c_int;10887) c_int;
1087810888
10879pub const PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{};10889pub const PTHREAD_MUTEX_INITIALIZER: pthread_mutex_t = .{};
10880pub extern "c" fn pthread_mutex_lock(mutex: *pthread_mutex_t) E;10890pub extern "c" fn pthread_mutex_lock(mutex: *pthread_mutex_t) E;
10881pub extern "c" fn pthread_mutex_unlock(mutex: *pthread_mutex_t) E;10891pub extern "c" fn pthread_mutex_unlock(mutex: *pthread_mutex_t) E;
10882pub extern "c" fn pthread_mutex_trylock(mutex: *pthread_mutex_t) E;10892pub extern "c" fn pthread_mutex_trylock(mutex: *pthread_mutex_t) E;
10883pub extern "c" fn pthread_mutex_destroy(mutex: *pthread_mutex_t) E;10893pub 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 = .{};
10886pub extern "c" fn pthread_cond_wait(noalias cond: *pthread_cond_t, noalias mutex: *pthread_mutex_t) E;10896pub extern "c" fn pthread_cond_wait(noalias cond: *pthread_cond_t, noalias mutex: *pthread_mutex_t) E;
10887pub extern "c" fn pthread_cond_timedwait(noalias cond: *pthread_cond_t, noalias mutex: *pthread_mutex_t, noalias abstime: *const timespec) E;10897pub extern "c" fn pthread_cond_timedwait(noalias cond: *pthread_cond_t, noalias mutex: *pthread_mutex_t, noalias abstime: *const timespec) E;
10888pub extern "c" fn pthread_cond_signal(cond: *pthread_cond_t) E;10898pub extern "c" fn pthread_cond_signal(cond: *pthread_cond_t) E;
...@@ -11363,12 +11373,12 @@ const private = struct {...@@ -11363,12 +11373,12 @@ const private = struct {
11363 extern "c" fn recvmsg(sockfd: fd_t, msg: *msghdr, flags: u32) isize;11373 extern "c" fn recvmsg(sockfd: fd_t, msg: *msghdr, flags: u32) isize;
11364 extern "c" fn sched_yield() c_int;11374 extern "c" fn sched_yield() c_int;
11365 extern "c" fn sendfile(out_fd: fd_t, in_fd: fd_t, offset: ?*off_t, count: usize) isize;11375 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;11376 extern "c" fn sigaction(sig: SIG, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) c_int;
11367 extern "c" fn sigdelset(set: ?*sigset_t, signo: c_int) c_int;11377 extern "c" fn sigdelset(set: ?*sigset_t, signo: SIG) c_int;
11368 extern "c" fn sigaddset(set: ?*sigset_t, signo: c_int) c_int;11378 extern "c" fn sigaddset(set: ?*sigset_t, signo: SIG) c_int;
11369 extern "c" fn sigfillset(set: ?*sigset_t) c_int;11379 extern "c" fn sigfillset(set: ?*sigset_t) c_int;
11370 extern "c" fn sigemptyset(set: ?*sigset_t) c_int;11380 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;
11372 extern "c" fn sigprocmask(how: c_int, noalias set: ?*const sigset_t, noalias oset: ?*sigset_t) c_int;11382 extern "c" fn sigprocmask(how: c_int, noalias set: ?*const sigset_t, noalias oset: ?*sigset_t) c_int;
11373 extern "c" fn socket(domain: c_uint, sock_type: c_uint, protocol: c_uint) c_int;11383 extern "c" fn socket(domain: c_uint, sock_type: c_uint, protocol: c_uint) c_int;
11374 extern "c" fn socketpair(domain: c_uint, sock_type: c_uint, protocol: c_uint, sv: *[2]fd_t) c_int;11384 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 {...@@ -11420,7 +11430,7 @@ const private = struct {
11420 extern "c" fn __libc_thr_yield() c_int;11430 extern "c" fn __libc_thr_yield() c_int;
11421 extern "c" fn __msync13(addr: *align(page_size) const anyopaque, len: usize, flags: c_int) c_int;11431 extern "c" fn __msync13(addr: *align(page_size) const anyopaque, len: usize, flags: c_int) c_int;
11422 extern "c" fn __nanosleep50(rqtp: *const timespec, rmtp: ?*timespec) c_int;11432 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;
11424 extern "c" fn __sigemptyset14(set: ?*sigset_t) c_int;11434 extern "c" fn __sigemptyset14(set: ?*sigset_t) c_int;
11425 extern "c" fn __sigfillset14(set: ?*sigset_t) c_int;11435 extern "c" fn __sigfillset14(set: ?*sigset_t) c_int;
11426 extern "c" fn __sigprocmask14(how: c_int, noalias set: ?*const sigset_t, noalias oset: ?*sigset_t) c_int;11436 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 @@...@@ -4,6 +4,20 @@
4//! concatenated together in the `bytes` array. The `map` field contains an4//! concatenated together in the `bytes` array. The `map` field contains an
5//! index from the DER-encoded subject name to the index of the containing5//! index from the DER-encoded subject name to the index of the containing
6//! certificate within `bytes`.6//! 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
8/// The key is the contents slice of the subject.22/// The key is the contents slice of the subject.
9map: std.HashMapUnmanaged(der.Element.Slice, u32, MapContext, std.hash_map.default_max_load_percentage) = .empty,23map: 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...@@ -56,18 +70,18 @@ pub const RescanError = RescanLinuxError || RescanMacError || RescanWithPathErro
56/// file system standard locations for certificates.70/// file system standard locations for certificates.
57/// For operating systems that do not have standard CA installations to be71/// For operating systems that do not have standard CA installations to be
58/// found, this function clears the set of certificates.72/// 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 {
60 switch (builtin.os.tag) {74 switch (builtin.os.tag) {
61 .linux => return rescanLinux(cb, gpa),75 .linux => return rescanLinux(cb, gpa, io, now),
62 .macos => return rescanMac(cb, gpa),76 .macos => return rescanMac(cb, gpa, io, now),
63 .freebsd, .openbsd => return rescanWithPath(cb, gpa, "/etc/ssl/cert.pem"),77 .freebsd, .openbsd => return rescanWithPath(cb, gpa, io, now, "/etc/ssl/cert.pem"),
64 .netbsd => return rescanWithPath(cb, gpa, "/etc/openssl/certs/ca-certificates.crt"),78 .netbsd => return rescanWithPath(cb, gpa, io, now, "/etc/openssl/certs/ca-certificates.crt"),
65 .dragonfly => return rescanWithPath(cb, gpa, "/usr/local/etc/ssl/cert.pem"),79 .dragonfly => return rescanWithPath(cb, gpa, io, now, "/usr/local/etc/ssl/cert.pem"),
66 .illumos => return rescanWithPath(cb, gpa, "/etc/ssl/cacert.pem"),80 .illumos => return rescanWithPath(cb, gpa, io, now, "/etc/ssl/cacert.pem"),
67 .haiku => return rescanWithPath(cb, gpa, "/boot/system/data/ssl/CARootCertificates.pem"),81 .haiku => return rescanWithPath(cb, gpa, io, now, "/boot/system/data/ssl/CARootCertificates.pem"),
68 // https://github.com/SerenityOS/serenity/blob/222acc9d389bc6b490d4c39539761b043a4bfcb0/Ports/ca-certificates/package.sh#L1982 // 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"),83 .serenity => return rescanWithPath(cb, gpa, io, now, "/etc/ssl/certs/ca-certificates.crt"),
70 .windows => return rescanWindows(cb, gpa),84 .windows => return rescanWindows(cb, gpa, io, now),
71 else => {},85 else => {},
72 }86 }
73}87}
...@@ -77,7 +91,7 @@ const RescanMacError = @import("Bundle/macos.zig").RescanMacError;...@@ -77,7 +91,7 @@ const RescanMacError = @import("Bundle/macos.zig").RescanMacError;
7791
78const RescanLinuxError = AddCertsFromFilePathError || AddCertsFromDirPathError;92const 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 {
81 // Possible certificate files; stop after finding one.95 // Possible certificate files; stop after finding one.
82 const cert_file_paths = [_][]const u8{96 const cert_file_paths = [_][]const u8{
83 "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.97 "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
...@@ -100,7 +114,7 @@ fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {...@@ -100,7 +114,7 @@ fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {
100114
101 scan: {115 scan: {
102 for (cert_file_paths) |cert_file_path| {116 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)) |_| {
104 break :scan;118 break :scan;
105 } else |err| switch (err) {119 } else |err| switch (err) {
106 error.FileNotFound => continue,120 error.FileNotFound => continue,
...@@ -109,7 +123,7 @@ fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {...@@ -109,7 +123,7 @@ fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {
109 }123 }
110124
111 for (cert_dir_paths) |cert_dir_path| {125 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) {
113 error.FileNotFound => continue,127 error.FileNotFound => continue,
114 else => |e| return e,128 else => |e| return e,
115 };129 };
...@@ -121,19 +135,21 @@ fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {...@@ -121,19 +135,21 @@ fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {
121135
122const RescanWithPathError = AddCertsFromFilePathError;136const 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 {
125 cb.bytes.clearRetainingCapacity();139 cb.bytes.clearRetainingCapacity();
126 cb.map.clearRetainingCapacity();140 cb.map.clearRetainingCapacity();
127 try addCertsFromFilePathAbsolute(cb, gpa, cert_file_path);141 try addCertsFromFilePathAbsolute(cb, gpa, io, now, cert_file_path);
128 cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);142 cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
129}143}
130144
131const RescanWindowsError = Allocator.Error || ParseCertError || std.posix.UnexpectedError || error{FileNotFound};145const 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 {
134 cb.bytes.clearRetainingCapacity();148 cb.bytes.clearRetainingCapacity();
135 cb.map.clearRetainingCapacity();149 cb.map.clearRetainingCapacity();
136150
151 _ = io;
152
137 const w = std.os.windows;153 const w = std.os.windows;
138 const GetLastError = w.GetLastError;154 const GetLastError = w.GetLastError;
139 const root = [4:0]u16{ 'R', 'O', 'O', 'T' };155 const root = [4:0]u16{ 'R', 'O', 'O', 'T' };
...@@ -143,7 +159,7 @@ fn rescanWindows(cb: *Bundle, gpa: Allocator) RescanWindowsError!void {...@@ -143,7 +159,7 @@ fn rescanWindows(cb: *Bundle, gpa: Allocator) RescanWindowsError!void {
143 };159 };
144 defer _ = w.crypt32.CertCloseStore(store, 0);160 defer _ = w.crypt32.CertCloseStore(store, 0);
145161
146 const now_sec = std.time.timestamp();162 const now_sec = now.toSeconds();
147163
148 var ctx = w.crypt32.CertEnumCertificatesInStore(store, null);164 var ctx = w.crypt32.CertEnumCertificatesInStore(store, null);
149 while (ctx) |context| : (ctx = w.crypt32.CertEnumCertificatesInStore(store, ctx)) {165 while (ctx) |context| : (ctx = w.crypt32.CertEnumCertificatesInStore(store, ctx)) {
...@@ -160,28 +176,31 @@ pub const AddCertsFromDirPathError = fs.File.OpenError || AddCertsFromDirError;...@@ -160,28 +176,31 @@ pub const AddCertsFromDirPathError = fs.File.OpenError || AddCertsFromDirError;
160pub fn addCertsFromDirPath(176pub fn addCertsFromDirPath(
161 cb: *Bundle,177 cb: *Bundle,
162 gpa: Allocator,178 gpa: Allocator,
179 io: Io,
163 dir: fs.Dir,180 dir: fs.Dir,
164 sub_dir_path: []const u8,181 sub_dir_path: []const u8,
165) AddCertsFromDirPathError!void {182) AddCertsFromDirPathError!void {
166 var iterable_dir = try dir.openDir(sub_dir_path, .{ .iterate = true });183 var iterable_dir = try dir.openDir(sub_dir_path, .{ .iterate = true });
167 defer iterable_dir.close();184 defer iterable_dir.close();
168 return addCertsFromDir(cb, gpa, iterable_dir);185 return addCertsFromDir(cb, gpa, io, iterable_dir);
169}186}
170187
171pub fn addCertsFromDirPathAbsolute(188pub fn addCertsFromDirPathAbsolute(
172 cb: *Bundle,189 cb: *Bundle,
173 gpa: Allocator,190 gpa: Allocator,
191 io: Io,
192 now: Io.Timestamp,
174 abs_dir_path: []const u8,193 abs_dir_path: []const u8,
175) AddCertsFromDirPathError!void {194) AddCertsFromDirPathError!void {
176 assert(fs.path.isAbsolute(abs_dir_path));195 assert(fs.path.isAbsolute(abs_dir_path));
177 var iterable_dir = try fs.openDirAbsolute(abs_dir_path, .{ .iterate = true });196 var iterable_dir = try fs.openDirAbsolute(abs_dir_path, .{ .iterate = true });
178 defer iterable_dir.close();197 defer iterable_dir.close();
179 return addCertsFromDir(cb, gpa, iterable_dir);198 return addCertsFromDir(cb, gpa, io, now, iterable_dir);
180}199}
181200
182pub const AddCertsFromDirError = AddCertsFromFilePathError;201pub 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 {
185 var it = iterable_dir.iterate();204 var it = iterable_dir.iterate();
186 while (try it.next()) |entry| {205 while (try it.next()) |entry| {
187 switch (entry.kind) {206 switch (entry.kind) {
...@@ -189,32 +208,37 @@ pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.Dir) AddCer...@@ -189,32 +208,37 @@ pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.Dir) AddCer
189 else => continue,208 else => continue,
190 }209 }
191210
192 try addCertsFromFilePath(cb, gpa, iterable_dir, entry.name);211 try addCertsFromFilePath(cb, gpa, io, now, iterable_dir.adaptToNewApi(), entry.name);
193 }212 }
194}213}
195214
196pub const AddCertsFromFilePathError = fs.File.OpenError || AddCertsFromFileError;215pub const AddCertsFromFilePathError = fs.File.OpenError || AddCertsFromFileError || Io.Clock.Error;
197216
198pub fn addCertsFromFilePathAbsolute(217pub fn addCertsFromFilePathAbsolute(
199 cb: *Bundle,218 cb: *Bundle,
200 gpa: Allocator,219 gpa: Allocator,
220 io: Io,
221 now: Io.Timestamp,
201 abs_file_path: []const u8,222 abs_file_path: []const u8,
202) AddCertsFromFilePathError!void {223) AddCertsFromFilePathError!void {
203 assert(fs.path.isAbsolute(abs_file_path));
204 var file = try fs.openFileAbsolute(abs_file_path, .{});224 var file = try fs.openFileAbsolute(abs_file_path, .{});
205 defer file.close();225 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());
207}228}
208229
209pub fn addCertsFromFilePath(230pub fn addCertsFromFilePath(
210 cb: *Bundle,231 cb: *Bundle,
211 gpa: Allocator,232 gpa: Allocator,
212 dir: fs.Dir,233 io: Io,
234 now: Io.Timestamp,
235 dir: Io.Dir,
213 sub_file_path: []const u8,236 sub_file_path: []const u8,
214) AddCertsFromFilePathError!void {237) AddCertsFromFilePathError!void {
215 var file = try dir.openFile(sub_file_path, .{});238 var file = try dir.openFile(io, sub_file_path, .{});
216 defer file.close();239 defer file.close(io);
217 return addCertsFromFile(cb, gpa, file);240 var file_reader = file.reader(io, &.{});
241 return addCertsFromFile(cb, gpa, &file_reader, now.toSeconds());
218}242}
219243
220pub const AddCertsFromFileError = Allocator.Error ||244pub const AddCertsFromFileError = Allocator.Error ||
...@@ -222,10 +246,10 @@ pub const AddCertsFromFileError = Allocator.Error ||...@@ -222,10 +246,10 @@ pub const AddCertsFromFileError = Allocator.Error ||
222 fs.File.ReadError ||246 fs.File.ReadError ||
223 ParseCertError ||247 ParseCertError ||
224 std.base64.Error ||248 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 {251pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file_reader: *Io.File.Reader, now_sec: i64) AddCertsFromFileError!void {
228 const size = try file.getEndPos();252 const size = try file_reader.getSize();
229253
230 // We borrow `bytes` as a temporary buffer for the base64-encoded data.254 // We borrow `bytes` as a temporary buffer for the base64-encoded data.
231 // This is possible by computing the decoded length and reserving the space255 // 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...@@ -236,14 +260,14 @@ pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) AddCertsFrom
236 try cb.bytes.ensureUnusedCapacity(gpa, needed_capacity);260 try cb.bytes.ensureUnusedCapacity(gpa, needed_capacity);
237 const end_reserved: u32 = @intCast(cb.bytes.items.len + decoded_size_upper_bound);261 const end_reserved: u32 = @intCast(cb.bytes.items.len + decoded_size_upper_bound);
238 const buffer = cb.bytes.allocatedSlice()[end_reserved..];262 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 };
240 const encoded_bytes = buffer[0..end_index];266 const encoded_bytes = buffer[0..end_index];
241267
242 const begin_marker = "-----BEGIN CERTIFICATE-----";268 const begin_marker = "-----BEGIN CERTIFICATE-----";
243 const end_marker = "-----END CERTIFICATE-----";269 const end_marker = "-----END CERTIFICATE-----";
244270
245 const now_sec = std.time.timestamp();
246
247 var start_index: usize = 0;271 var start_index: usize = 0;
248 while (mem.indexOfPos(u8, encoded_bytes, start_index, begin_marker)) |begin_marker_start| {272 while (mem.indexOfPos(u8, encoded_bytes, start_index, begin_marker)) |begin_marker_start| {
249 const cert_start = begin_marker_start + begin_marker.len;273 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)...@@ -288,19 +312,6 @@ pub fn parseCert(cb: *Bundle, gpa: Allocator, decoded_start: u32, now_sec: i64)
288 }312 }
289}313}
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
304const MapContext = struct {315const MapContext = struct {
305 cb: *const Bundle,316 cb: *const Bundle,
306317
...@@ -321,8 +332,13 @@ const MapContext = struct {...@@ -321,8 +332,13 @@ const MapContext = struct {
321test "scan for OS-provided certificates" {332test "scan for OS-provided certificates" {
322 if (builtin.os.tag == .wasi) return error.SkipZigTest;333 if (builtin.os.tag == .wasi) return error.SkipZigTest;
323334
335 const io = std.testing.io;
336 const gpa = std.testing.allocator;
337
324 var bundle: Bundle = .{};338 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);
328}344}
lib/std/crypto/Certificate/Bundle/macos.zig+6-6
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const assert = std.debug.assert;3const assert = std.debug.assert;
3const fs = std.fs;4const fs = std.fs;
4const mem = std.mem;5const mem = std.mem;
...@@ -7,7 +8,7 @@ const Bundle = @import("../Bundle.zig");...@@ -7,7 +8,7 @@ const Bundle = @import("../Bundle.zig");
78
8pub const RescanMacError = Allocator.Error || fs.File.OpenError || fs.File.ReadError || fs.File.SeekError || Bundle.ParseCertError || error{EndOfStream};9pub 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 {
11 cb.bytes.clearRetainingCapacity();12 cb.bytes.clearRetainingCapacity();
12 cb.map.clearRetainingCapacity();13 cb.map.clearRetainingCapacity();
1314
...@@ -16,6 +17,7 @@ pub fn rescanMac(cb: *Bundle, gpa: Allocator) RescanMacError!void {...@@ -16,6 +17,7 @@ pub fn rescanMac(cb: *Bundle, gpa: Allocator) RescanMacError!void {
16 "/Library/Keychains/System.keychain",17 "/Library/Keychains/System.keychain",
17 };18 };
1819
20 _ = io; // TODO migrate file system to use std.Io
19 for (keychain_paths) |keychain_path| {21 for (keychain_paths) |keychain_path| {
20 const bytes = std.fs.cwd().readFileAlloc(keychain_path, gpa, .limited(std.math.maxInt(u32))) catch |err| switch (err) {22 const bytes = std.fs.cwd().readFileAlloc(keychain_path, gpa, .limited(std.math.maxInt(u32))) catch |err| switch (err) {
21 error.StreamTooLong => return error.FileTooBig,23 error.StreamTooLong => return error.FileTooBig,
...@@ -23,8 +25,8 @@ pub fn rescanMac(cb: *Bundle, gpa: Allocator) RescanMacError!void {...@@ -23,8 +25,8 @@ pub fn rescanMac(cb: *Bundle, gpa: Allocator) RescanMacError!void {
23 };25 };
24 defer gpa.free(bytes);26 defer gpa.free(bytes);
2527
26 var reader: std.Io.Reader = .fixed(bytes);28 var reader: Io.Reader = .fixed(bytes);
27 scanReader(cb, gpa, &reader) catch |err| switch (err) {29 scanReader(cb, gpa, &reader, now.toSeconds()) catch |err| switch (err) {
28 error.ReadFailed => unreachable, // prebuffered30 error.ReadFailed => unreachable, // prebuffered
29 else => |e| return e,31 else => |e| return e,
30 };32 };
...@@ -33,7 +35,7 @@ pub fn rescanMac(cb: *Bundle, gpa: Allocator) RescanMacError!void {...@@ -33,7 +35,7 @@ pub fn rescanMac(cb: *Bundle, gpa: Allocator) RescanMacError!void {
33 cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);35 cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
34}36}
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 {
37 const db_header = try reader.takeStruct(ApplDbHeader, .big);39 const db_header = try reader.takeStruct(ApplDbHeader, .big);
38 assert(mem.eql(u8, &db_header.signature, "kych"));40 assert(mem.eql(u8, &db_header.signature, "kych"));
3941
...@@ -49,8 +51,6 @@ fn scanReader(cb: *Bundle, gpa: Allocator, reader: *std.Io.Reader) !void {...@@ -49,8 +51,6 @@ fn scanReader(cb: *Bundle, gpa: Allocator, reader: *std.Io.Reader) !void {
49 table_list[table_idx] = try reader.takeInt(u32, .big);51 table_list[table_idx] = try reader.takeInt(u32, .big);
50 }52 }
5153
52 const now_sec = std.time.timestamp();
53
54 for (table_list) |table_offset| {54 for (table_list) |table_offset| {
55 reader.seek = db_header.schema_offset + table_offset;55 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 {...@@ -105,6 +105,14 @@ pub const Options = struct {
105 /// Verify that the server certificate is authorized by a given ca bundle.105 /// Verify that the server certificate is authorized by a given ca bundle.
106 bundle: Certificate.Bundle,106 bundle: Certificate.Bundle,
107 },107 },
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
108 /// If non-null, ssl secrets are logged to this stream. Creating such a log file allows116 /// If non-null, ssl secrets are logged to this stream. Creating such a log file allows
109 /// other programs with access to that file to decrypt all traffic over this connection.117 /// other programs with access to that file to decrypt all traffic over this connection.
110 ///118 ///
...@@ -120,8 +128,6 @@ pub const Options = struct {...@@ -120,8 +128,6 @@ pub const Options = struct {
120 /// application layer itself verifies that the amount of data received equals128 /// application layer itself verifies that the amount of data received equals
121 /// the amount of data expected, such as HTTP with the Content-Length header.129 /// the amount of data expected, such as HTTP with the Content-Length header.
122 allow_truncation_attacks: bool = false,130 allow_truncation_attacks: bool = false,
123 write_buffer: []u8,
124 read_buffer: []u8,
125 /// Populated when `error.TlsAlert` is returned from `init`.131 /// Populated when `error.TlsAlert` is returned from `init`.
126 alert: ?*tls.Alert = null,132 alert: ?*tls.Alert = null,
127};133};
...@@ -189,14 +195,12 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -189,14 +195,12 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
189 };195 };
190 const host_len: u16 = @intCast(host.len);196 const host_len: u16 = @intCast(host.len);
191197
192 var random_buffer: [176]u8 = undefined;198 const client_hello_rand = options.entropy[0..32].*;
193 crypto.random.bytes(&random_buffer);
194 const client_hello_rand = random_buffer[0..32].*;
195 var key_seq: u64 = 0;199 var key_seq: u64 = 0;
196 var server_hello_rand: [32]u8 = undefined;200 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) {
200 // Only possible to happen if the seed is all zeroes.204 // Only possible to happen if the seed is all zeroes.
201 error.IdentityElement => return error.InsufficientEntropy,205 error.IdentityElement => return error.InsufficientEntropy,
202 };206 };
...@@ -321,7 +325,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -321,7 +325,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
321 var handshake_cipher: tls.HandshakeCipher = undefined;325 var handshake_cipher: tls.HandshakeCipher = undefined;
322 var main_cert_pub_key: CertificatePublicKey = undefined;326 var main_cert_pub_key: CertificatePublicKey = undefined;
323 var tls12_negotiated_group: ?tls.NamedGroup = null;327 var tls12_negotiated_group: ?tls.NamedGroup = null;
324 const now_sec = std.time.timestamp();328 const now_sec = options.realtime_now_seconds;
325329
326 var cleartext_fragment_start: usize = 0;330 var cleartext_fragment_start: usize = 0;
327 var cleartext_fragment_end: usize = 0;331 var cleartext_fragment_end: usize = 0;
lib/std/debug.zig+49-24
...@@ -1,4 +1,7 @@...@@ -1,4 +1,7 @@
1const std = @import("std.zig");1const std = @import("std.zig");
2const Io = std.Io;
3const Writer = std.Io.Writer;
4const tty = std.Io.tty;
2const math = std.math;5const math = std.math;
3const mem = std.mem;6const mem = std.mem;
4const posix = std.posix;7const posix = std.posix;
...@@ -7,12 +10,11 @@ const testing = std.testing;...@@ -7,12 +10,11 @@ const testing = std.testing;
7const Allocator = mem.Allocator;10const Allocator = mem.Allocator;
8const File = std.fs.File;11const File = std.fs.File;
9const windows = std.os.windows;12const windows = std.os.windows;
10const Writer = std.Io.Writer;
11const tty = std.Io.tty;
1213
13const builtin = @import("builtin");14const builtin = @import("builtin");
14const native_arch = builtin.cpu.arch;15const native_arch = builtin.cpu.arch;
15const native_os = builtin.os.tag;16const native_os = builtin.os.tag;
17const StackTrace = std.builtin.StackTrace;
1618
17const root = @import("root");19const root = @import("root");
1820
...@@ -82,6 +84,7 @@ pub const SelfInfoError = error{...@@ -82,6 +84,7 @@ pub const SelfInfoError = error{
82 /// The required debug info could not be read from disk due to some IO error.84 /// The required debug info could not be read from disk due to some IO error.
83 ReadFailed,85 ReadFailed,
84 OutOfMemory,86 OutOfMemory,
87 Canceled,
85 Unexpected,88 Unexpected,
86};89};
8790
...@@ -544,7 +547,7 @@ pub fn defaultPanic(...@@ -544,7 +547,7 @@ pub fn defaultPanic(
544 stderr.print("panic: ", .{}) catch break :trace;547 stderr.print("panic: ", .{}) catch break :trace;
545 } else {548 } else {
546 const current_thread_id = std.Thread.getCurrentId();549 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;
548 }551 }
549 stderr.print("{s}\n", .{msg}) catch break :trace;552 stderr.print("{s}\n", .{msg}) catch break :trace;
550553
...@@ -606,8 +609,8 @@ pub const StackUnwindOptions = struct {...@@ -606,8 +609,8 @@ pub const StackUnwindOptions = struct {
606/// the given buffer, so `addr_buf` must have a lifetime at least equal to the `StackTrace`.609/// the given buffer, so `addr_buf` must have a lifetime at least equal to the `StackTrace`.
607///610///
608/// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it.611/// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it.
609pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) std.builtin.StackTrace {612pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) StackTrace {
610 const empty_trace: std.builtin.StackTrace = .{ .index = 0, .instruction_addresses = &.{} };613 const empty_trace: StackTrace = .{ .index = 0, .instruction_addresses = &.{} };
611 if (!std.options.allow_stack_tracing) return empty_trace;614 if (!std.options.allow_stack_tracing) return empty_trace;
612 var it = StackIterator.init(options.context) catch return empty_trace;615 var it = StackIterator.init(options.context) catch return empty_trace;
613 defer it.deinit();616 defer it.deinit();
...@@ -645,6 +648,9 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:...@@ -645,6 +648,9 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
645///648///
646/// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing.649/// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing.
647pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_config: tty.Config) Writer.Error!void {650pub 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
648 if (!std.options.allow_stack_tracing) {654 if (!std.options.allow_stack_tracing) {
649 tty_config.setColor(writer, .dim) catch {};655 tty_config.setColor(writer, .dim) catch {};
650 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});656 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
...@@ -691,6 +697,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri...@@ -691,6 +697,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
691 error.UnsupportedDebugInfo => "unwind info unsupported",697 error.UnsupportedDebugInfo => "unwind info unsupported",
692 error.ReadFailed => "filesystem error",698 error.ReadFailed => "filesystem error",
693 error.OutOfMemory => "out of memory",699 error.OutOfMemory => "out of memory",
700 error.Canceled => "operation canceled",
694 error.Unexpected => "unexpected error",701 error.Unexpected => "unexpected error",
695 };702 };
696 if (it.stratOk(options.allow_unsafe_unwind)) {703 if (it.stratOk(options.allow_unsafe_unwind)) {
...@@ -728,7 +735,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri...@@ -728,7 +735,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
728 }735 }
729 // `ret_addr` is the return address, which is *after* the function call.736 // `ret_addr` is the return address, which is *after* the function call.
730 // Subtract 1 to get an address *in* the function call for a better source location.737 // 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);
732 printed_any_frame = true;739 printed_any_frame = true;
733 },740 },
734 };741 };
...@@ -752,14 +759,29 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {...@@ -752,14 +759,29 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
752 };759 };
753}760}
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
755/// Write a previously captured stack trace to `writer`, annotated with source locations.772/// 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 {
757 if (!std.options.allow_stack_tracing) {774 if (!std.options.allow_stack_tracing) {
758 tty_config.setColor(writer, .dim) catch {};775 tty_config.setColor(writer, .dim) catch {};
759 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});776 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
760 tty_config.setColor(writer, .reset) catch {};777 tty_config.setColor(writer, .reset) catch {};
761 return;778 return;
762 }779 }
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
763 // Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if785 // Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if
764 // `st` is `@errorReturnTrace()` and errors are encountered while writing the stack trace.786 // `st` is `@errorReturnTrace()` and errors are encountered while writing the stack trace.
765 const n_frames = st.index;787 const n_frames = st.index;
...@@ -777,7 +799,7 @@ pub fn writeStackTrace(st: *const std.builtin.StackTrace, writer: *Writer, tty_c...@@ -777,7 +799,7 @@ pub fn writeStackTrace(st: *const std.builtin.StackTrace, writer: *Writer, tty_c
777 for (st.instruction_addresses[0..captured_frames]) |ret_addr| {799 for (st.instruction_addresses[0..captured_frames]) |ret_addr| {
778 // `ret_addr` is the return address, which is *after* the function call.800 // `ret_addr` is the return address, which is *after* the function call.
779 // Subtract 1 to get an address *in* the function call for a better source location.801 // 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);
781 }803 }
782 if (n_frames > captured_frames) {804 if (n_frames > captured_frames) {
783 tty_config.setColor(writer, .bold) catch {};805 tty_config.setColor(writer, .bold) catch {};
...@@ -786,7 +808,7 @@ pub fn writeStackTrace(st: *const std.builtin.StackTrace, writer: *Writer, tty_c...@@ -786,7 +808,7 @@ pub fn writeStackTrace(st: *const std.builtin.StackTrace, writer: *Writer, tty_c
786 }808 }
787}809}
788/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors.810/// 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 {
790 const tty_config = tty.detectConfig(.stderr());812 const tty_config = tty.detectConfig(.stderr());
791 const stderr = lockStderrWriter(&.{});813 const stderr = lockStderrWriter(&.{});
792 defer unlockStderrWriter();814 defer unlockStderrWriter();
...@@ -1073,13 +1095,13 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {...@@ -1073,13 +1095,13 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
1073 return ptr;1095 return ptr;
1074}1096}
10751097
1076fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void {1098fn printSourceAtAddress(gpa: Allocator, io: Io, 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) {1099 const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) {
1078 error.MissingDebugInfo,1100 error.MissingDebugInfo,
1079 error.UnsupportedDebugInfo,1101 error.UnsupportedDebugInfo,
1080 error.InvalidDebugInfo,1102 error.InvalidDebugInfo,
1081 => .unknown,1103 => .unknown,
1082 error.ReadFailed, error.Unexpected => s: {1104 error.ReadFailed, error.Unexpected, error.Canceled => s: {
1083 tty_config.setColor(writer, .dim) catch {};1105 tty_config.setColor(writer, .dim) catch {};
1084 try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});1106 try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
1085 tty_config.setColor(writer, .reset) catch {};1107 tty_config.setColor(writer, .reset) catch {};
...@@ -1387,10 +1409,10 @@ pub fn maybeEnableSegfaultHandler() void {...@@ -1387,10 +1409,10 @@ pub fn maybeEnableSegfaultHandler() void {
1387var windows_segfault_handle: ?windows.HANDLE = null;1409var windows_segfault_handle: ?windows.HANDLE = null;
13881410
1389pub fn updateSegfaultHandler(act: ?*const posix.Sigaction) void {1411pub fn updateSegfaultHandler(act: ?*const posix.Sigaction) void {
1390 posix.sigaction(posix.SIG.SEGV, act, null);1412 posix.sigaction(.SEGV, act, null);
1391 posix.sigaction(posix.SIG.ILL, act, null);1413 posix.sigaction(.ILL, act, null);
1392 posix.sigaction(posix.SIG.BUS, act, null);1414 posix.sigaction(.BUS, act, null);
1393 posix.sigaction(posix.SIG.FPE, act, null);1415 posix.sigaction(.FPE, act, null);
1394}1416}
13951417
1396/// Attaches a global handler for several signals which, when triggered, prints output to stderr1418/// Attaches a global handler for several signals which, when triggered, prints output to stderr
...@@ -1435,7 +1457,7 @@ fn resetSegfaultHandler() void {...@@ -1435,7 +1457,7 @@ fn resetSegfaultHandler() void {
1435 updateSegfaultHandler(&act);1457 updateSegfaultHandler(&act);
1436}1458}
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 {
1439 if (use_trap_panic) @trap();1461 if (use_trap_panic) @trap();
1440 const addr: ?usize, const name: []const u8 = info: {1462 const addr: ?usize, const name: []const u8 = info: {
1441 if (native_os == .linux and native_arch == .x86_64) {1463 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...@@ -1447,7 +1469,7 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa
1447 // for example when reading/writing model-specific registers1469 // for example when reading/writing model-specific registers
1448 // by executing `rdmsr` or `wrmsr` in user-space (unprivileged mode).1470 // by executing `rdmsr` or `wrmsr` in user-space (unprivileged mode).
1449 const SI_KERNEL = 0x80;1471 const SI_KERNEL = 0x80;
1450 if (sig == posix.SIG.SEGV and info.code == SI_KERNEL) {1472 if (sig == .SEGV and info.code == SI_KERNEL) {
1451 break :info .{ null, "General protection exception" };1473 break :info .{ null, "General protection exception" };
1452 }1474 }
1453 }1475 }
...@@ -1474,10 +1496,10 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa...@@ -1474,10 +1496,10 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa
1474 else => comptime unreachable,1496 else => comptime unreachable,
1475 };1497 };
1476 const name = switch (sig) {1498 const name = switch (sig) {
1477 posix.SIG.SEGV => "Segmentation fault",1499 .SEGV => "Segmentation fault",
1478 posix.SIG.ILL => "Illegal instruction",1500 .ILL => "Illegal instruction",
1479 posix.SIG.BUS => "Bus error",1501 .BUS => "Bus error",
1480 posix.SIG.FPE => "Arithmetic exception",1502 .FPE => "Arithmetic exception",
1481 else => unreachable,1503 else => unreachable,
1482 };1504 };
1483 break :info .{ addr, name };1505 break :info .{ addr, name };
...@@ -1579,11 +1601,14 @@ test "manage resources correctly" {...@@ -1579,11 +1601,14 @@ test "manage resources correctly" {
1579 }1601 }
1580 };1602 };
1581 const gpa = std.testing.allocator;1603 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(&.{});
1583 var di: SelfInfo = .init;1607 var di: SelfInfo = .init;
1584 defer di.deinit(gpa);1608 defer di.deinit(gpa);
1585 try printSourceAtAddress(1609 try printSourceAtAddress(
1586 gpa,1610 gpa,
1611 io,
1587 &di,1612 &di,
1588 &discarding.writer,1613 &discarding.writer,
1589 S.showMyTrace(),1614 S.showMyTrace(),
...@@ -1657,7 +1682,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize...@@ -1657,7 +1682,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
1657 stderr.print("{s}:\n", .{t.notes[i]}) catch return;1682 stderr.print("{s}:\n", .{t.notes[i]}) catch return;
1658 var frames_array_mutable = frames_array;1683 var frames_array_mutable = frames_array;
1659 const frames = mem.sliceTo(frames_array_mutable[0..], 0);1684 const frames = mem.sliceTo(frames_array_mutable[0..], 0);
1660 const stack_trace: std.builtin.StackTrace = .{1685 const stack_trace: StackTrace = .{
1661 .index = frames.len,1686 .index = frames.len,
1662 .instruction_addresses = frames,1687 .instruction_addresses = frames,
1663 };1688 };
lib/std/debug/ElfFile.zig+3-1
...@@ -108,6 +108,8 @@ pub const LoadError = error{...@@ -108,6 +108,8 @@ pub const LoadError = error{
108 LockedMemoryLimitExceeded,108 LockedMemoryLimitExceeded,
109 ProcessFdQuotaExceeded,109 ProcessFdQuotaExceeded,
110 SystemFdQuotaExceeded,110 SystemFdQuotaExceeded,
111 Streaming,
112 Canceled,
111 Unexpected,113 Unexpected,
112};114};
113115
...@@ -408,7 +410,7 @@ fn loadInner(...@@ -408,7 +410,7 @@ fn loadInner(
408 arena: Allocator,410 arena: Allocator,
409 elf_file: std.fs.File,411 elf_file: std.fs.File,
410 opt_crc: ?u32,412 opt_crc: ?u32,
411) (LoadError || error{CrcMismatch})!LoadInnerResult {413) (LoadError || error{ CrcMismatch, Streaming, Canceled })!LoadInnerResult {
412 const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: {414 const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: {
413 const file_len = std.math.cast(415 const file_len = std.math.cast(
414 usize,416 usize,
lib/std/debug/SelfInfo/Elf.zig+5-1
...@@ -28,7 +28,8 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void {...@@ -28,7 +28,8 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
28 if (si.unwind_cache) |cache| gpa.free(cache);28 if (si.unwind_cache) |cache| gpa.free(cache);
29}29}
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;
32 const module = try si.findModule(gpa, address, .exclusive);33 const module = try si.findModule(gpa, address, .exclusive);
33 defer si.rwlock.unlock();34 defer si.rwlock.unlock();
3435
...@@ -336,6 +337,7 @@ const Module = struct {...@@ -336,6 +337,7 @@ const Module = struct {
336 var elf_file = load_result catch |err| switch (err) {337 var elf_file = load_result catch |err| switch (err) {
337 error.OutOfMemory,338 error.OutOfMemory,
338 error.Unexpected,339 error.Unexpected,
340 error.Canceled,
339 => |e| return e,341 => |e| return e,
340342
341 error.Overflow,343 error.Overflow,
...@@ -353,6 +355,7 @@ const Module = struct {...@@ -353,6 +355,7 @@ const Module = struct {
353 error.LockedMemoryLimitExceeded,355 error.LockedMemoryLimitExceeded,
354 error.ProcessFdQuotaExceeded,356 error.ProcessFdQuotaExceeded,
355 error.SystemFdQuotaExceeded,357 error.SystemFdQuotaExceeded,
358 error.Streaming,
356 => return error.ReadFailed,359 => return error.ReadFailed,
357 };360 };
358 errdefer elf_file.deinit(gpa);361 errdefer elf_file.deinit(gpa);
...@@ -487,6 +490,7 @@ const DlIterContext = struct {...@@ -487,6 +490,7 @@ const DlIterContext = struct {
487};490};
488491
489const std = @import("std");492const std = @import("std");
493const Io = std.Io;
490const Allocator = std.mem.Allocator;494const Allocator = std.mem.Allocator;
491const Dwarf = std.debug.Dwarf;495const Dwarf = std.debug.Dwarf;
492const Error = std.debug.SelfInfoError;496const Error = std.debug.SelfInfoError;
lib/std/debug/SelfInfo/MachO.zig+6-1
...@@ -30,7 +30,8 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void {...@@ -30,7 +30,8 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
30 si.ofiles.deinit(gpa);30 si.ofiles.deinit(gpa);
31}31}
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;
34 const module = try si.findModule(gpa, address);35 const module = try si.findModule(gpa, address);
35 defer si.mutex.unlock();36 defer si.mutex.unlock();
3637
...@@ -117,11 +118,14 @@ pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error...@@ -117,11 +118,14 @@ pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error
117 error.ReadFailed,118 error.ReadFailed,
118 error.OutOfMemory,119 error.OutOfMemory,
119 error.Unexpected,120 error.Unexpected,
121 error.Canceled,
120 => |e| return e,122 => |e| return e,
123
121 error.UnsupportedRegister,124 error.UnsupportedRegister,
122 error.UnsupportedAddrSize,125 error.UnsupportedAddrSize,
123 error.UnimplementedUserOpcode,126 error.UnimplementedUserOpcode,
124 => return error.UnsupportedDebugInfo,127 => return error.UnsupportedDebugInfo,
128
125 error.Overflow,129 error.Overflow,
126 error.EndOfStream,130 error.EndOfStream,
127 error.StreamTooLong,131 error.StreamTooLong,
...@@ -967,6 +971,7 @@ fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile {...@@ -967,6 +971,7 @@ fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile {
967}971}
968972
969const std = @import("std");973const std = @import("std");
974const Io = std.Io;
970const Allocator = std.mem.Allocator;975const Allocator = std.mem.Allocator;
971const Dwarf = std.debug.Dwarf;976const Dwarf = std.debug.Dwarf;
972const Error = std.debug.SelfInfoError;977const Error = std.debug.SelfInfoError;
lib/std/debug/SelfInfo/Windows.zig+19-14
...@@ -20,11 +20,11 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void {...@@ -20,11 +20,11 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
20 module_name_arena.deinit();20 module_name_arena.deinit();
21}21}
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 {
24 si.mutex.lock();24 si.mutex.lock();
25 defer si.mutex.unlock();25 defer si.mutex.unlock();
26 const module = try si.findModule(gpa, address);26 const module = try si.findModule(gpa, address);
27 const di = try module.getDebugInfo(gpa);27 const di = try module.getDebugInfo(gpa, io);
28 return di.getSymbol(gpa, address - module.base_address);28 return di.getSymbol(gpa, address - module.base_address);
29}29}
30pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 {30pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 {
...@@ -190,6 +190,7 @@ const Module = struct {...@@ -190,6 +190,7 @@ const Module = struct {
190190
191 const DebugInfo = struct {191 const DebugInfo = struct {
192 arena: std.heap.ArenaAllocator.State,192 arena: std.heap.ArenaAllocator.State,
193 io: Io,
193 coff_image_base: u64,194 coff_image_base: u64,
194 mapped_file: ?MappedFile,195 mapped_file: ?MappedFile,
195 dwarf: ?Dwarf,196 dwarf: ?Dwarf,
...@@ -209,9 +210,10 @@ const Module = struct {...@@ -209,9 +210,10 @@ const Module = struct {
209 };210 };
210211
211 fn deinit(di: *DebugInfo, gpa: Allocator) void {212 fn deinit(di: *DebugInfo, gpa: Allocator) void {
213 const io = di.io;
212 if (di.dwarf) |*dwarf| dwarf.deinit(gpa);214 if (di.dwarf) |*dwarf| dwarf.deinit(gpa);
213 if (di.pdb) |*pdb| {215 if (di.pdb) |*pdb| {
214 pdb.file_reader.file.close();216 pdb.file_reader.file.close(io);
215 pdb.deinit();217 pdb.deinit();
216 }218 }
217 if (di.mapped_file) |*mf| mf.deinit();219 if (di.mapped_file) |*mf| mf.deinit();
...@@ -277,11 +279,11 @@ const Module = struct {...@@ -277,11 +279,11 @@ const Module = struct {
277 }279 }
278 };280 };
279281
280 fn getDebugInfo(module: *Module, gpa: Allocator) Error!*DebugInfo {282 fn getDebugInfo(module: *Module, gpa: Allocator, io: Io) Error!*DebugInfo {
281 if (module.di == null) module.di = loadDebugInfo(module, gpa);283 if (module.di == null) module.di = loadDebugInfo(module, gpa, io);
282 return if (module.di.?) |*di| di else |err| err;284 return if (module.di.?) |*di| di else |err| err;
283 }285 }
284 fn loadDebugInfo(module: *const Module, gpa: Allocator) Error!DebugInfo {286 fn loadDebugInfo(module: *const Module, gpa: Allocator, io: Io) Error!DebugInfo {
285 const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address);287 const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address);
286 const mapped = mapped_ptr[0..module.size];288 const mapped = mapped_ptr[0..module.size];
287 var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo;289 var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo;
...@@ -305,7 +307,10 @@ const Module = struct {...@@ -305,7 +307,10 @@ const Module = struct {
305 windows.PATH_MAX_WIDE,307 windows.PATH_MAX_WIDE,
306 );308 );
307 if (len == 0) return error.MissingDebugInfo;309 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,
309 error.Unexpected => |e| return e,314 error.Unexpected => |e| return e,
310 error.FileNotFound => return error.MissingDebugInfo,315 error.FileNotFound => return error.MissingDebugInfo,
311316
...@@ -314,8 +319,6 @@ const Module = struct {...@@ -314,8 +319,6 @@ const Module = struct {
314 error.NotDir,319 error.NotDir,
315 error.SymLinkLoop,320 error.SymLinkLoop,
316 error.NameTooLong,321 error.NameTooLong,
317 error.InvalidUtf8,
318 error.InvalidWtf8,
319 error.BadPathName,322 error.BadPathName,
320 => return error.InvalidDebugInfo,323 => return error.InvalidDebugInfo,
321324
...@@ -338,7 +341,7 @@ const Module = struct {...@@ -338,7 +341,7 @@ const Module = struct {
338 error.FileBusy,341 error.FileBusy,
339 => return error.ReadFailed,342 => return error.ReadFailed,
340 };343 };
341 errdefer coff_file.close();344 errdefer coff_file.close(io);
342 var section_handle: windows.HANDLE = undefined;345 var section_handle: windows.HANDLE = undefined;
343 const create_section_rc = windows.ntdll.NtCreateSection(346 const create_section_rc = windows.ntdll.NtCreateSection(
344 &section_handle,347 &section_handle,
...@@ -372,7 +375,7 @@ const Module = struct {...@@ -372,7 +375,7 @@ const Module = struct {
372 const section_view = section_view_ptr.?[0..coff_len];375 const section_view = section_view_ptr.?[0..coff_len];
373 coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;376 coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
374 break :mapped .{377 break :mapped .{
375 .file = coff_file,378 .file = .adaptFromNewApi(coff_file),
376 .section_handle = section_handle,379 .section_handle = section_handle,
377 .section_view = section_view,380 .section_view = section_view,
378 };381 };
...@@ -434,8 +437,8 @@ const Module = struct {...@@ -434,8 +437,8 @@ const Module = struct {
434 };437 };
435 errdefer pdb_file.close();438 errdefer pdb_file.close();
436439
437 const pdb_reader = try arena.create(std.fs.File.Reader);440 const pdb_reader = try arena.create(Io.File.Reader);
438 pdb_reader.* = pdb_file.reader(try arena.alloc(u8, 4096));441 pdb_reader.* = pdb_file.reader(io, try arena.alloc(u8, 4096));
439442
440 var pdb = Pdb.init(gpa, pdb_reader) catch |err| switch (err) {443 var pdb = Pdb.init(gpa, pdb_reader) catch |err| switch (err) {
441 error.OutOfMemory, error.ReadFailed, error.Unexpected => |e| return e,444 error.OutOfMemory, error.ReadFailed, error.Unexpected => |e| return e,
...@@ -473,7 +476,7 @@ const Module = struct {...@@ -473,7 +476,7 @@ const Module = struct {
473 break :pdb pdb;476 break :pdb pdb;
474 };477 };
475 errdefer if (opt_pdb) |*pdb| {478 errdefer if (opt_pdb) |*pdb| {
476 pdb.file_reader.file.close();479 pdb.file_reader.file.close(io);
477 pdb.deinit();480 pdb.deinit();
478 };481 };
479482
...@@ -483,6 +486,7 @@ const Module = struct {...@@ -483,6 +486,7 @@ const Module = struct {
483486
484 return .{487 return .{
485 .arena = arena_instance.state,488 .arena = arena_instance.state,
489 .io = io,
486 .coff_image_base = coff_image_base,490 .coff_image_base = coff_image_base,
487 .mapped_file = mapped_file,491 .mapped_file = mapped_file,
488 .dwarf = opt_dwarf,492 .dwarf = opt_dwarf,
...@@ -544,6 +548,7 @@ fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) error{ MissingDebug...@@ -544,6 +548,7 @@ fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) error{ MissingDebug
544}548}
545549
546const std = @import("std");550const std = @import("std");
551const Io = std.Io;
547const Allocator = std.mem.Allocator;552const Allocator = std.mem.Allocator;
548const Dwarf = std.debug.Dwarf;553const Dwarf = std.debug.Dwarf;
549const Pdb = std.debug.Pdb;554const Pdb = std.debug.Pdb;
lib/std/dynamic_library.zig+2
...@@ -137,6 +137,8 @@ const ElfDynLibError = error{...@@ -137,6 +137,8 @@ const ElfDynLibError = error{
137 ElfStringSectionNotFound,137 ElfStringSectionNotFound,
138 ElfSymSectionNotFound,138 ElfSymSectionNotFound,
139 ElfHashTableNotFound,139 ElfHashTableNotFound,
140 Canceled,
141 Streaming,
140} || posix.OpenError || posix.MMapError;142} || posix.OpenError || posix.MMapError;
141143
142pub const ElfDynLib = struct {144pub const ElfDynLib = struct {
lib/std/elf.zig+121-45
...@@ -1,9 +1,11 @@...@@ -1,9 +1,11 @@
1//! Executable and Linkable Format.1//! Executable and Linkable Format.
22
3const std = @import("std.zig");3const std = @import("std.zig");
4const Io = std.Io;
4const math = std.math;5const math = std.math;
5const mem = std.mem;6const mem = std.mem;
6const assert = std.debug.assert;7const assert = std.debug.assert;
8const Endian = std.builtin.Endian;
7const native_endian = @import("builtin").target.cpu.arch.endian();9const native_endian = @import("builtin").target.cpu.arch.endian();
810
9pub const AT_NULL = 0;11pub const AT_NULL = 0;
...@@ -568,7 +570,7 @@ pub const ET = enum(u16) {...@@ -568,7 +570,7 @@ pub const ET = enum(u16) {
568/// All integers are native endian.570/// All integers are native endian.
569pub const Header = struct {571pub const Header = struct {
570 is_64: bool,572 is_64: bool,
571 endian: std.builtin.Endian,573 endian: Endian,
572 os_abi: OSABI,574 os_abi: OSABI,
573 /// The meaning of this value depends on `os_abi`.575 /// The meaning of this value depends on `os_abi`.
574 abi_version: u8,576 abi_version: u8,
...@@ -583,48 +585,76 @@ pub const Header = struct {...@@ -583,48 +585,76 @@ pub const Header = struct {
583 shnum: u16,585 shnum: u16,
584 shstrndx: u16,586 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 {
587 return .{589 return .{
588 .elf_header = h,590 .is_64 = h.is_64,
591 .endian = h.endian,
592 .phnum = h.phnum,
593 .phoff = h.phoff,
589 .file_reader = file_reader,594 .file_reader = file_reader,
590 };595 };
591 }596 }
592597
593 pub fn iterateProgramHeadersBuffer(h: Header, buf: []const u8) ProgramHeaderBufferIterator {598 pub fn iterateProgramHeadersBuffer(h: *const Header, buf: []const u8) ProgramHeaderBufferIterator {
594 return .{599 return .{
595 .elf_header = h,600 .is_64 = h.is_64,
601 .endian = h.endian,
602 .phnum = h.phnum,
603 .phoff = h.phoff,
596 .buf = buf,604 .buf = buf,
597 };605 };
598 }606 }
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 {
601 return .{609 return .{
602 .elf_header = h,610 .is_64 = h.is_64,
611 .endian = h.endian,
612 .shnum = h.shnum,
613 .shoff = h.shoff,
603 .file_reader = file_reader,614 .file_reader = file_reader,
604 };615 };
605 }616 }
606617
607 pub fn iterateSectionHeadersBuffer(h: Header, buf: []const u8) SectionHeaderBufferIterator {618 pub fn iterateSectionHeadersBuffer(h: *const Header, buf: []const u8) SectionHeaderBufferIterator {
608 return .{619 return .{
609 .elf_header = h,620 .is_64 = h.is_64,
621 .endian = h.endian,
622 .shnum = h.shnum,
623 .shoff = h.shoff,
610 .buf = buf,624 .buf = buf,
611 };625 };
612 }626 }
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{
615 InvalidElfMagic,644 InvalidElfMagic,
616 InvalidElfVersion,645 InvalidElfVersion,
617 InvalidElfClass,646 InvalidElfClass,
618 InvalidElfEndian,647 InvalidElfEndian,
619 };648 };
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 {
622 const buf = try r.peek(@sizeOf(Elf64_Ehdr));652 const buf = try r.peek(@sizeOf(Elf64_Ehdr));
623653
624 if (!mem.eql(u8, buf[0..4], MAGIC)) return error.InvalidElfMagic;654 if (!mem.eql(u8, buf[0..4], MAGIC)) return error.InvalidElfMagic;
625 if (buf[EI.VERSION] != 1) return error.InvalidElfVersion;655 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]) {
628 ELFDATA2LSB => .little,658 ELFDATA2LSB => .little,
629 ELFDATA2MSB => .big,659 ELFDATA2MSB => .big,
630 else => return error.InvalidElfEndian,660 else => return error.InvalidElfEndian,
...@@ -637,7 +667,7 @@ pub const Header = struct {...@@ -637,7 +667,7 @@ pub const Header = struct {
637 };667 };
638 }668 }
639669
640 pub fn init(hdr: anytype, endian: std.builtin.Endian) Header {670 pub fn init(hdr: anytype, endian: Endian) Header {
641 // Converting integers to exhaustive enums using `@enumFromInt` could cause a panic.671 // Converting integers to exhaustive enums using `@enumFromInt` could cause a panic.
642 comptime assert(!@typeInfo(OSABI).@"enum".is_exhaustive);672 comptime assert(!@typeInfo(OSABI).@"enum".is_exhaustive);
643 return .{673 return .{
...@@ -664,46 +694,54 @@ pub const Header = struct {...@@ -664,46 +694,54 @@ pub const Header = struct {
664};694};
665695
666pub const ProgramHeaderIterator = struct {696pub const ProgramHeaderIterator = struct {
667 elf_header: Header,697 is_64: bool,
668 file_reader: *std.fs.File.Reader,698 endian: Endian,
699 phnum: u16,
700 phoff: u64,
701
702 file_reader: *Io.File.Reader,
669 index: usize = 0,703 index: usize = 0,
670704
671 pub fn next(it: *ProgramHeaderIterator) !?Elf64_Phdr {705 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;
673 defer it.index += 1;707 defer it.index += 1;
674708
675 const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Phdr) else @sizeOf(Elf32_Phdr);709 const size: u64 = if (it.is_64) @sizeOf(Elf64_Phdr) else @sizeOf(Elf32_Phdr);
676 const offset = it.elf_header.phoff + size * it.index;710 const offset = it.phoff + size * it.index;
677 try it.file_reader.seekTo(offset);711 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);
680 }714 }
681};715};
682716
683pub const ProgramHeaderBufferIterator = struct {717pub const ProgramHeaderBufferIterator = struct {
684 elf_header: Header,718 is_64: bool,
719 endian: Endian,
720 phnum: u16,
721 phoff: u64,
722
685 buf: []const u8,723 buf: []const u8,
686 index: usize = 0,724 index: usize = 0,
687725
688 pub fn next(it: *ProgramHeaderBufferIterator) !?Elf64_Phdr {726 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;
690 defer it.index += 1;728 defer it.index += 1;
691729
692 const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Phdr) else @sizeOf(Elf32_Phdr);730 const size: u64 = if (it.is_64) @sizeOf(Elf64_Phdr) else @sizeOf(Elf32_Phdr);
693 const offset = it.elf_header.phoff + size * it.index;731 const offset = it.phoff + size * it.index;
694 var reader = std.Io.Reader.fixed(it.buf[offset..]);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);
697 }735 }
698};736};
699737
700fn takePhdr(reader: *std.Io.Reader, elf_header: Header) !?Elf64_Phdr {738pub fn takeProgramHeader(reader: *Io.Reader, is_64: bool, endian: Endian) !Elf64_Phdr {
701 if (elf_header.is_64) {739 if (is_64) {
702 const phdr = try reader.takeStruct(Elf64_Phdr, elf_header.endian);740 const phdr = try reader.takeStruct(Elf64_Phdr, endian);
703 return phdr;741 return phdr;
704 }742 }
705743
706 const phdr = try reader.takeStruct(Elf32_Phdr, elf_header.endian);744 const phdr = try reader.takeStruct(Elf32_Phdr, endian);
707 return .{745 return .{
708 .p_type = phdr.p_type,746 .p_type = phdr.p_type,
709 .p_offset = phdr.p_offset,747 .p_offset = phdr.p_offset,
...@@ -717,47 +755,55 @@ fn takePhdr(reader: *std.Io.Reader, elf_header: Header) !?Elf64_Phdr {...@@ -717,47 +755,55 @@ fn takePhdr(reader: *std.Io.Reader, elf_header: Header) !?Elf64_Phdr {
717}755}
718756
719pub const SectionHeaderIterator = struct {757pub const SectionHeaderIterator = struct {
720 elf_header: Header,758 is_64: bool,
721 file_reader: *std.fs.File.Reader,759 endian: Endian,
760 shnum: u16,
761 shoff: u64,
762
763 file_reader: *Io.File.Reader,
722 index: usize = 0,764 index: usize = 0,
723765
724 pub fn next(it: *SectionHeaderIterator) !?Elf64_Shdr {766 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;
726 defer it.index += 1;768 defer it.index += 1;
727769
728 const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr);770 const size: u64 = if (it.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr);
729 const offset = it.elf_header.shoff + size * it.index;771 const offset = it.shoff + size * it.index;
730 try it.file_reader.seekTo(offset);772 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);
733 }775 }
734};776};
735777
736pub const SectionHeaderBufferIterator = struct {778pub const SectionHeaderBufferIterator = struct {
737 elf_header: Header,779 is_64: bool,
780 endian: Endian,
781 shnum: u16,
782 shoff: u64,
783
738 buf: []const u8,784 buf: []const u8,
739 index: usize = 0,785 index: usize = 0,
740786
741 pub fn next(it: *SectionHeaderBufferIterator) !?Elf64_Shdr {787 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;
743 defer it.index += 1;789 defer it.index += 1;
744790
745 const size: u64 = if (it.elf_header.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr);791 const size: u64 = if (it.is_64) @sizeOf(Elf64_Shdr) else @sizeOf(Elf32_Shdr);
746 const offset = it.elf_header.shoff + size * it.index;792 const offset = it.shoff + size * it.index;
747 if (offset > it.buf.len) return error.EndOfStream;793 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);
751 }797 }
752};798};
753799
754fn takeShdr(reader: *std.Io.Reader, elf_header: Header) !?Elf64_Shdr {800pub fn takeSectionHeader(reader: *Io.Reader, is_64: bool, endian: Endian) !Elf64_Shdr {
755 if (elf_header.is_64) {801 if (is_64) {
756 const shdr = try reader.takeStruct(Elf64_Shdr, elf_header.endian);802 const shdr = try reader.takeStruct(Elf64_Shdr, endian);
757 return shdr;803 return shdr;
758 }804 }
759805
760 const shdr = try reader.takeStruct(Elf32_Shdr, elf_header.endian);806 const shdr = try reader.takeStruct(Elf32_Shdr, endian);
761 return .{807 return .{
762 .sh_name = shdr.sh_name,808 .sh_name = shdr.sh_name,
763 .sh_type = shdr.sh_type,809 .sh_type = shdr.sh_type,
...@@ -772,6 +818,36 @@ fn takeShdr(reader: *std.Io.Reader, elf_header: Header) !?Elf64_Shdr {...@@ -772,6 +818,36 @@ fn takeShdr(reader: *std.Io.Reader, elf_header: Header) !?Elf64_Shdr {
772 };818 };
773}819}
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
775pub const EI = struct {851pub const EI = struct {
776 pub const CLASS = 4;852 pub const CLASS = 4;
777 pub const DATA = 5;853 pub const DATA = 5;
lib/std/fs.zig+24-163
...@@ -1,14 +1,15 @@...@@ -1,14 +1,15 @@
1//! File System.1//! File System.
2const builtin = @import("builtin");
3const native_os = builtin.os.tag;
24
3const std = @import("std.zig");5const std = @import("std.zig");
4const builtin = @import("builtin");6const Io = std.Io;
5const root = @import("root");7const root = @import("root");
6const mem = std.mem;8const mem = std.mem;
7const base64 = std.base64;9const base64 = std.base64;
8const crypto = std.crypto;10const crypto = std.crypto;
9const Allocator = std.mem.Allocator;11const Allocator = std.mem.Allocator;
10const assert = std.debug.assert;12const assert = std.debug.assert;
11const native_os = builtin.os.tag;
12const posix = std.posix;13const posix = std.posix;
13const windows = std.os.windows;14const windows = std.os.windows;
1415
...@@ -97,23 +98,6 @@ pub const base64_encoder = base64.Base64Encoder.init(base64_alphabet, null);...@@ -97,23 +98,6 @@ pub const base64_encoder = base64.Base64Encoder.init(base64_alphabet, null);
97/// Base64 decoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem.98/// Base64 decoder, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem.
98pub const base64_decoder = base64.Base64Decoder.init(base64_alphabet, null);99pub 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
117/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`101/// Same as `Dir.copyFile`, except asserts that both `source_path` and `dest_path`
118/// are absolute. See `Dir.copyFile` for a function that operates on both102/// are absolute. See `Dir.copyFile` for a function that operates on both
119/// absolute and relative paths.103/// absolute and relative paths.
...@@ -131,6 +115,8 @@ pub fn copyFileAbsolute(...@@ -131,6 +115,8 @@ pub fn copyFileAbsolute(
131 return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args);115 return Dir.copyFile(my_cwd, source_path, my_cwd, dest_path, args);
132}116}
133117
118test copyFileAbsolute {}
119
134/// Create a new directory, based on an absolute path.120/// Create a new directory, based on an absolute path.
135/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates121/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
136/// on both absolute and relative paths.122/// on both absolute and relative paths.
...@@ -142,17 +128,15 @@ pub fn makeDirAbsolute(absolute_path: []const u8) !void {...@@ -142,17 +128,15 @@ pub fn makeDirAbsolute(absolute_path: []const u8) !void {
142 return posix.mkdir(absolute_path, Dir.default_mode);128 return posix.mkdir(absolute_path, Dir.default_mode);
143}129}
144130
131test makeDirAbsolute {}
132
145/// Same as `makeDirAbsolute` except the parameter is null-terminated.133/// Same as `makeDirAbsolute` except the parameter is null-terminated.
146pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {134pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
147 assert(path.isAbsoluteZ(absolute_path_z));135 assert(path.isAbsoluteZ(absolute_path_z));
148 return posix.mkdirZ(absolute_path_z, Dir.default_mode);136 return posix.mkdirZ(absolute_path_z, Dir.default_mode);
149}137}
150138
151/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 LE-encoded string.139test makeDirAbsoluteZ {}
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}
156140
157/// Same as `Dir.deleteDir` except the path is absolute.141/// Same as `Dir.deleteDir` except the path is absolute.
158/// On Windows, `dir_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).142/// 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 {...@@ -169,12 +153,6 @@ pub fn deleteDirAbsoluteZ(dir_path: [*:0]const u8) !void {
169 return posix.rmdirZ(dir_path);153 return posix.rmdirZ(dir_path);
170}154}
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
178/// Same as `Dir.rename` except the paths are absolute.156/// Same as `Dir.rename` except the paths are absolute.
179/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).157/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
180/// On WASI, both paths should be encoded as valid UTF-8.158/// 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 {...@@ -192,13 +170,6 @@ pub fn renameAbsoluteZ(old_path: [*:0]const u8, new_path: [*:0]const u8) !void {
192 return posix.renameZ(old_path, new_path);170 return posix.renameZ(old_path, new_path);
193}171}
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
202/// Same as `Dir.rename`, except `new_sub_path` is relative to `new_dir`173/// Same as `Dir.rename`, except `new_sub_path` is relative to `new_dir`
203pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void {174pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void {
204 return posix.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path);175 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...@@ -209,15 +180,7 @@ pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_su
209 return posix.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);180 return posix.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);
210}181}
211182
212/// Same as `rename` except the parameters are WTF16LE, NT prefixed.183/// Deprecated in favor of `Io.Dir.cwd`.
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.
221pub fn cwd() Dir {184pub fn cwd() Dir {
222 if (native_os == .windows) {185 if (native_os == .windows) {
223 return .{ .fd = windows.peb().ProcessParameters.CurrentDirectory.Handle };186 return .{ .fd = windows.peb().ProcessParameters.CurrentDirectory.Handle };
...@@ -251,12 +214,6 @@ pub fn openDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenOptions)...@@ -251,12 +214,6 @@ pub fn openDirAbsoluteZ(absolute_path_c: [*:0]const u8, flags: Dir.OpenOptions)
251 assert(path.isAbsoluteZ(absolute_path_c));214 assert(path.isAbsoluteZ(absolute_path_c));
252 return cwd().openDirZ(absolute_path_c, flags);215 return cwd().openDirZ(absolute_path_c, flags);
253}216}
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
260/// Opens a file for reading or writing, without attempting to create a new file, based on an absolute path.217/// Opens a file for reading or writing, without attempting to create a new file, based on an absolute path.
261/// Call `File.close` to release the resource.218/// Call `File.close` to release the resource.
262/// Asserts that the path is absolute. See `Dir.openFile` for a function that219/// 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...@@ -271,18 +228,6 @@ pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.O
271 return cwd().openFile(absolute_path, flags);228 return cwd().openFile(absolute_path, flags);
272}229}
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
286/// Test accessing `path`.231/// Test accessing `path`.
287/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.232/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.
288/// For example, instead of testing if a file exists and then opening it, just233/// 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...@@ -291,21 +236,10 @@ pub fn openFileAbsoluteW(absolute_path_w: []const u16, flags: File.OpenFlags) Fi
291/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).236/// On Windows, `absolute_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
292/// On WASI, `absolute_path` should be encoded as valid UTF-8.237/// On WASI, `absolute_path` should be encoded as valid UTF-8.
293/// On other platforms, `absolute_path` is an opaque sequence of bytes with no particular encoding.238/// 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 {
295 assert(path.isAbsolute(absolute_path));240 assert(path.isAbsolute(absolute_path));
296 try cwd().access(absolute_path, flags);241 try cwd().access(absolute_path, flags);
297}242}
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
309/// Creates, opens, or overwrites a file with write access, based on an absolute path.243/// Creates, opens, or overwrites a file with write access, based on an absolute path.
310/// Call `File.close` to release the resource.244/// Call `File.close` to release the resource.
311/// Asserts that the path is absolute. See `Dir.createFile` for a function that245/// 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...@@ -320,18 +254,6 @@ pub fn createFileAbsolute(absolute_path: []const u8, flags: File.CreateFlags) Fi
320 return cwd().createFile(absolute_path, flags);254 return cwd().createFile(absolute_path, flags);
321}255}
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
335/// Delete a file name and possibly the file it refers to, based on an absolute path.257/// Delete a file name and possibly the file it refers to, based on an absolute path.
336/// Asserts that the path is absolute. See `Dir.deleteFile` for a function that258/// Asserts that the path is absolute. See `Dir.deleteFile` for a function that
337/// operates on both absolute and relative paths.259/// operates on both absolute and relative paths.
...@@ -344,18 +266,6 @@ pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void {...@@ -344,18 +266,6 @@ pub fn deleteFileAbsolute(absolute_path: []const u8) Dir.DeleteFileError!void {
344 return cwd().deleteFile(absolute_path);266 return cwd().deleteFile(absolute_path);
345}267}
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
359/// Removes a symlink, file, or directory.269/// Removes a symlink, file, or directory.
360/// This is equivalent to `Dir.deleteTree` with the base directory.270/// This is equivalent to `Dir.deleteTree` with the base directory.
361/// Asserts that the path is absolute. See `Dir.deleteTree` for a function that271/// 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...@@ -387,19 +297,6 @@ pub fn readLinkAbsolute(pathname: []const u8, buffer: *[max_path_bytes]u8) ![]u8
387 return posix.readlink(pathname, buffer);297 return posix.readlink(pathname, buffer);
388}298}
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
403/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.300/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
404/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent301/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
405/// one; the latter case is known as a dangling link.302/// one; the latter case is known as a dangling link.
...@@ -437,44 +334,21 @@ pub fn symLinkAbsoluteW(...@@ -437,44 +334,21 @@ pub fn symLinkAbsoluteW(
437 return windows.CreateSymbolicLink(null, mem.span(sym_link_path_w), mem.span(target_path_w), flags.is_directory);334 return windows.CreateSymbolicLink(null, mem.span(sym_link_path_w), mem.span(target_path_w), flags.is_directory);
438}335}
439336
440/// Same as `symLinkAbsolute` except the parameters are null-terminated pointers.337pub const OpenSelfExeError = Io.File.OpenSelfExeError;
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;
458338
339/// Deprecated in favor of `Io.File.openSelfExe`.
459pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {340pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
460 if (native_os == .linux or native_os == .serenity) {341 if (native_os == .linux or native_os == .serenity or native_os == .windows) {
461 return openFileAbsoluteZ("/proc/self/exe", flags);342 var threaded: Io.Threaded = .init_single_threaded;
462 }343 const io = threaded.ioBasic();
463 if (native_os == .windows) {344 return .adaptFromNewApi(try Io.File.openSelfExe(io, flags));
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);
471 }345 }
472 // Use of max_path_bytes here is valid as the resulting path is immediately346 // Use of max_path_bytes here is valid as the resulting path is immediately
473 // opened with no modification.347 // opened with no modification.
474 var buf: [max_path_bytes]u8 = undefined;348 var buf: [max_path_bytes]u8 = undefined;
475 const self_exe_path = try selfExePath(&buf);349 const self_exe_path = try selfExePath(&buf);
476 buf[self_exe_path.len] = 0;350 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);
478}352}
479353
480// This is `posix.ReadLinkError || posix.RealPathError` with impossible errors excluded354// This is `posix.ReadLinkError || posix.RealPathError` with impossible errors excluded
...@@ -515,6 +389,8 @@ pub const SelfExePathError = error{...@@ -515,6 +389,8 @@ pub const SelfExePathError = error{
515 /// On Windows, the volume does not contain a recognized file system. File389 /// On Windows, the volume does not contain a recognized file system. File
516 /// system drivers might not be loaded, or the volume may be corrupt.390 /// system drivers might not be loaded, or the volume may be corrupt.
517 UnrecognizedVolume,391 UnrecognizedVolume,
392
393 Canceled,
518} || posix.SysCtlError;394} || posix.SysCtlError;
519395
520/// `selfExePath` except allocates the result on the heap.396/// `selfExePath` except allocates the result on the heap.
...@@ -554,7 +430,6 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {...@@ -554,7 +430,6 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
554430
555 var real_path_buf: [max_path_bytes]u8 = undefined;431 var real_path_buf: [max_path_bytes]u8 = undefined;
556 const real_path = std.posix.realpathZ(&symlink_path_buf, &real_path_buf) catch |err| switch (err) {432 const real_path = std.posix.realpathZ(&symlink_path_buf, &real_path_buf) catch |err| switch (err) {
557 error.InvalidWtf8 => unreachable, // Windows-only
558 error.NetworkNotFound => unreachable, // Windows-only433 error.NetworkNotFound => unreachable, // Windows-only
559 else => |e| return e,434 else => |e| return e,
560 };435 };
...@@ -565,15 +440,11 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {...@@ -565,15 +440,11 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
565 }440 }
566 switch (native_os) {441 switch (native_os) {
567 .linux, .serenity => return posix.readlinkZ("/proc/self/exe", out_buffer) catch |err| switch (err) {442 .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
570 error.UnsupportedReparsePointType => unreachable, // Windows-only443 error.UnsupportedReparsePointType => unreachable, // Windows-only
571 error.NetworkNotFound => unreachable, // Windows-only444 error.NetworkNotFound => unreachable, // Windows-only
572 else => |e| return e,445 else => |e| return e,
573 },446 },
574 .illumos => return posix.readlinkZ("/proc/self/path/a.out", out_buffer) catch |err| switch (err) {447 .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
577 error.UnsupportedReparsePointType => unreachable, // Windows-only448 error.UnsupportedReparsePointType => unreachable, // Windows-only
578 error.NetworkNotFound => unreachable, // Windows-only449 error.NetworkNotFound => unreachable, // Windows-only
579 else => |e| return e,450 else => |e| return e,
...@@ -602,7 +473,6 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {...@@ -602,7 +473,6 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
602 // argv[0] is a path (relative or absolute): use realpath(3) directly473 // argv[0] is a path (relative or absolute): use realpath(3) directly
603 var real_path_buf: [max_path_bytes]u8 = undefined;474 var real_path_buf: [max_path_bytes]u8 = undefined;
604 const real_path = posix.realpathZ(std.os.argv[0], &real_path_buf) catch |err| switch (err) {475 const real_path = posix.realpathZ(std.os.argv[0], &real_path_buf) catch |err| switch (err) {
605 error.InvalidWtf8 => unreachable, // Windows-only
606 error.NetworkNotFound => unreachable, // Windows-only476 error.NetworkNotFound => unreachable, // Windows-only
607 else => |e| return e,477 else => |e| return e,
608 };478 };
...@@ -645,10 +515,7 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {...@@ -645,10 +515,7 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
645 // that the symlink points to, though, so we need to get the realpath.515 // that the symlink points to, though, so we need to get the realpath.
646 var pathname_w = try windows.wToPrefixedFileW(null, image_path_name);516 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) {518 const wide_slice = try std.fs.cwd().realpathW2(pathname_w.span(), &pathname_w.data);
649 error.InvalidWtf8 => unreachable,
650 else => |e| return e,
651 };
652519
653 const len = std.unicode.calcWtf8Len(wide_slice);520 const len = std.unicode.calcWtf8Len(wide_slice);
654 if (len > out_buffer.len)521 if (len > out_buffer.len)
...@@ -702,16 +569,10 @@ pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {...@@ -702,16 +569,10 @@ pub fn realpathAlloc(allocator: Allocator, pathname: []const u8) ![]u8 {
702}569}
703570
704test {571test {
705 if (native_os != .wasi) {572 _ = AtomicFile;
706 _ = &makeDirAbsolute;573 _ = Dir;
707 _ = &makeDirAbsoluteZ;574 _ = File;
708 _ = &copyFileAbsolute;575 _ = path;
709 _ = &updateFileAbsolute;
710 }
711 _ = &AtomicFile;
712 _ = &Dir;
713 _ = &File;
714 _ = &path;
715 _ = @import("fs/test.zig");576 _ = @import("fs/test.zig");
716 _ = @import("fs/get_app_data_dir.zig");577 _ = @import("fs/get_app_data_dir.zig");
717}578}
lib/std/fs/Dir.zig+103-928
...@@ -1,6 +1,11 @@...@@ -1,6 +1,11 @@
1//! Deprecated in favor of `Io.Dir`.
1const Dir = @This();2const Dir = @This();
3
2const builtin = @import("builtin");4const builtin = @import("builtin");
5const native_os = builtin.os.tag;
6
3const std = @import("../std.zig");7const std = @import("../std.zig");
8const Io = std.Io;
4const File = std.fs.File;9const File = std.fs.File;
5const AtomicFile = std.fs.AtomicFile;10const AtomicFile = std.fs.AtomicFile;
6const base64_encoder = fs.base64_encoder;11const base64_encoder = fs.base64_encoder;
...@@ -12,7 +17,6 @@ const Allocator = std.mem.Allocator;...@@ -12,7 +17,6 @@ const Allocator = std.mem.Allocator;
12const assert = std.debug.assert;17const assert = std.debug.assert;
13const linux = std.os.linux;18const linux = std.os.linux;
14const windows = std.os.windows;19const windows = std.os.windows;
15const native_os = builtin.os.tag;
16const have_flock = @TypeOf(posix.system.flock) != void;20const have_flock = @TypeOf(posix.system.flock) != void;
1721
18fd: Handle,22fd: Handle,
...@@ -32,10 +36,6 @@ const IteratorError = error{...@@ -32,10 +36,6 @@ const IteratorError = error{
32 AccessDenied,36 AccessDenied,
33 PermissionDenied,37 PermissionDenied,
34 SystemResources,38 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,
39} || posix.UnexpectedError;39} || posix.UnexpectedError;
4040
41pub const Iterator = switch (native_os) {41pub const Iterator = switch (native_os) {
...@@ -549,7 +549,6 @@ pub const Iterator = switch (native_os) {...@@ -549,7 +549,6 @@ pub const Iterator = switch (native_os) {
549 .INVAL => unreachable,549 .INVAL => unreachable,
550 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.550 .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
551 .NOTCAPABLE => return error.AccessDenied,551 .NOTCAPABLE => return error.AccessDenied,
552 .ILSEQ => return error.InvalidUtf8, // An entry's name cannot be encoded as UTF-8.
553 else => |err| return posix.unexpectedErrno(err),552 else => |err| return posix.unexpectedErrno(err),
554 }553 }
555 if (bufused == 0) return null;554 if (bufused == 0) return null;
...@@ -840,517 +839,73 @@ pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker {...@@ -840,517 +839,73 @@ pub fn walk(self: Dir, allocator: Allocator) Allocator.Error!Walker {
840 };839 };
841}840}
842841
843pub const OpenError = error{842pub const OpenError = Io.Dir.OpenError;
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;
865843
866pub fn close(self: *Dir) void {844pub fn close(self: *Dir) void {
867 posix.close(self.fd);845 posix.close(self.fd);
868 self.* = undefined;846 self.* = undefined;
869}847}
870848
871/// Opens a file for reading or writing, without attempting to create a new file.849/// Deprecated in favor of `Io.Dir.openFile`.
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.
878pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {850pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
879 if (native_os == .windows) {851 var threaded: Io.Threaded = .init_single_threaded;
880 const path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);852 const io = threaded.ioBasic();
881 return self.openFileW(path_w.span(), flags);853 return .adaptFromNewApi(try Io.Dir.openFile(self.adaptToNewApi(), io, sub_path, 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 };
998}854}
999855
1000/// Same as `openFile` but Windows-only and the path parameter is856/// Deprecated in favor of `Io.Dir.createFile`.
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.
1043pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {857pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
1044 if (native_os == .windows) {858 var threaded: Io.Threaded = .init_single_threaded;
1045 const path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);859 const io = threaded.ioBasic();
1046 return self.createFileW(path_w.span(), flags);860 const new_file = try Io.Dir.createFile(self.adaptToNewApi(), io, sub_path, flags);
1047 }861 return .adaptFromNewApi(new_file);
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 };
1149}862}
1150863
1151/// Same as `createFile` but Windows-only and the path parameter is864/// Deprecated in favor of `Io.Dir.MakeError`.
1152/// [WTF-16](https://wtf-8.codeberg.page/#potentially-ill-formed-utf-16) encoded.865pub const MakeError = Io.Dir.MakeError;
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;
1193866
1194/// Creates a single directory with a relative or absolute path.867/// Deprecated in favor of `Io.Dir.makeDir`.
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.
1200pub fn makeDir(self: Dir, sub_path: []const u8) MakeError!void {868pub 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);
1202}872}
1203873
1204/// Same as `makeDir`, but `sub_path` is null-terminated.874/// Deprecated in favor of `Io.Dir.makeDir`.
1205/// To create multiple directories to make an entire path, see `makePath`.
1206/// To operate on only absolute paths, see `makeDirAbsoluteZ`.
1207pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) MakeError!void {875pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) MakeError!void {
1208 try posix.mkdiratZ(self.fd, sub_path, default_mode);876 try posix.mkdiratZ(self.fd, sub_path, default_mode);
1209}877}
1210878
1211/// Creates a single directory with a relative or absolute null-terminated WTF-16 LE-encoded path.879/// Deprecated in favor of `Io.Dir.makeDir`.
1212/// To create multiple directories to make an entire path, see `makePath`.
1213/// To operate on only absolute paths, see `makeDirAbsoluteW`.
1214pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) MakeError!void {880pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) MakeError!void {
1215 try posix.mkdiratW(self.fd, mem.span(sub_path), default_mode);881 try posix.mkdiratW(self.fd, mem.span(sub_path), default_mode);
1216}882}
1217883
1218/// Calls makeDir iteratively to make an entire path884/// Deprecated in favor of `Io.Dir.makePath`.
1219/// (i.e. creating any parent directories that do not exist).885pub fn makePath(self: Dir, sub_path: []const u8) MakePathError!void {
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 {
1236 _ = try self.makePathStatus(sub_path);886 _ = try self.makePathStatus(sub_path);
1237}887}
1238888
1239pub const MakePathStatus = enum { existed, created };889/// Deprecated in favor of `Io.Dir.MakePathStatus`.
1240/// Same as `makePath` except returns whether the path already existed or was successfully created.890pub const MakePathStatus = Io.Dir.MakePathStatus;
1241pub fn makePathStatus(self: Dir, sub_path: []const u8) (MakeError || StatFileError)!MakePathStatus {891/// Deprecated in favor of `Io.Dir.MakePathError`.
1242 var it = try fs.path.componentIterator(sub_path);892pub const MakePathError = Io.Dir.MakePathError;
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 };
1286893
1287 while (true) {894/// Deprecated in favor of `Io.Dir.makePathStatus`.
1288 const sub_path_w = try w.sliceToPrefixedFileW(self.fd, component.path);895pub fn makePathStatus(self: Dir, sub_path: []const u8) MakePathError!MakePathStatus {
1289 const is_last = it.peekNext() == null;896 var threaded: Io.Threaded = .init_single_threaded;
1290 var result = self.makeOpenDirAccessMaskW(sub_path_w.span().ptr, access_mask, .{897 const io = threaded.ioBasic();
1291 .no_follow = no_follow,898 return Io.Dir.makePathStatus(.{ .handle = self.fd }, io, sub_path);
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 }
1323}899}
1324900
1325/// This function performs `makePath`, followed by `openDir`.901/// Deprecated in favor of `Io.Dir.makeOpenPath`.
1326/// If supported by the OS, this operation is atomic. It is not atomic on902pub fn makeOpenPath(dir: Dir, sub_path: []const u8, options: OpenOptions) Io.Dir.MakeOpenPathError!Dir {
1327/// all operating systems.903 var threaded: Io.Threaded = .init_single_threaded;
1328/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).904 const io = threaded.ioBasic();
1329/// On WASI, `sub_path` should be encoded as valid UTF-8.905 return .adaptFromNewApi(try Io.Dir.makeOpenPath(dir.adaptToNewApi(), io, sub_path, options));
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 };
1351}906}
1352907
1353pub const RealPathError = posix.RealPathError;908pub const RealPathError = posix.RealPathError || error{Canceled};
1354909
1355/// This function returns the canonicalized absolute pathname of910/// This function returns the canonicalized absolute pathname of
1356/// `pathname` relative to this `Dir`. If `pathname` is absolute, ignores this911/// `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...@@ -1408,7 +963,6 @@ pub fn realpathZ(self: Dir, pathname: [*:0]const u8, out_buffer: []u8) RealPathE
1408 error.FileLocksNotSupported => return error.Unexpected,963 error.FileLocksNotSupported => return error.Unexpected,
1409 error.FileBusy => return error.Unexpected,964 error.FileBusy => return error.Unexpected,
1410 error.WouldBlock => return error.Unexpected,965 error.WouldBlock => return error.Unexpected,
1411 error.InvalidUtf8 => unreachable, // WASI-only
1412 else => |e| return e,966 else => |e| return e,
1413 };967 };
1414 defer posix.close(fd);968 defer posix.close(fd);
...@@ -1510,234 +1064,14 @@ pub fn setAsCwd(self: Dir) !void {...@@ -1510,234 +1064,14 @@ pub fn setAsCwd(self: Dir) !void {
1510 try posix.fchdir(self.fd);1064 try posix.fchdir(self.fd);
1511}1065}
15121066
1513pub const OpenOptions = struct {1067/// Deprecated in favor of `Io.Dir.OpenOptions`.
1514 /// `true` means the opened directory can be used as the `Dir` parameter1068pub const OpenOptions = Io.Dir.OpenOptions;
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,
15221069
1523 /// `true` means it won't dereference the symlinks.1070/// Deprecated in favor of `Io.Dir.openDir`.
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.
1535pub fn openDir(self: Dir, sub_path: []const u8, args: OpenOptions) OpenError!Dir {1071pub fn openDir(self: Dir, sub_path: []const u8, args: OpenOptions) OpenError!Dir {
1536 switch (native_os) {1072 var threaded: Io.Threaded = .init_single_threaded;
1537 .windows => {1073 const io = threaded.ioBasic();
1538 const sub_path_w = try windows.sliceToPrefixedFileW(self.fd, sub_path);1074 return .adaptFromNewApi(try Io.Dir.openDir(.{ .handle = self.fd }, io, sub_path, args));
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 }
1741}1075}
17421076
1743pub const DeleteFileError = posix.UnlinkError;1077pub const DeleteFileError = posix.UnlinkError;
...@@ -1801,11 +1135,9 @@ pub const DeleteDirError = error{...@@ -1801,11 +1135,9 @@ pub const DeleteDirError = error{
1801 NotDir,1135 NotDir,
1802 SystemResources,1136 SystemResources,
1803 ReadOnlyFileSystem,1137 ReadOnlyFileSystem,
1804 /// WASI-only; file paths must be valid UTF-8.1138 /// WASI: file paths must be valid UTF-8.
1805 InvalidUtf8,1139 /// Windows: file paths provided by the user must be valid WTF-8.
1806 /// Windows-only; file paths provided by the user must be valid WTF-8.
1807 /// https://wtf-8.codeberg.page/1140 /// https://wtf-8.codeberg.page/
1808 InvalidWtf8,
1809 BadPathName,1141 BadPathName,
1810 /// On Windows, `\\server` or `\\server\share` was not found.1142 /// On Windows, `\\server` or `\\server\share` was not found.
1811 NetworkNotFound,1143 NetworkNotFound,
...@@ -1906,10 +1238,7 @@ pub fn symLink(...@@ -1906,10 +1238,7 @@ pub fn symLink(
1906 // when converting to an NT namespaced path. CreateSymbolicLink in1238 // when converting to an NT namespaced path. CreateSymbolicLink in
1907 // symLinkW will handle the necessary conversion.1239 // symLinkW will handle the necessary conversion.
1908 var target_path_w: windows.PathSpace = undefined;1240 var target_path_w: windows.PathSpace = undefined;
1909 if (try std.unicode.checkWtf8ToWtf16LeOverflow(target_path, &target_path_w.data)) {1241 target_path_w.len = try windows.wtf8ToWtf16Le(&target_path_w.data, target_path);
1910 return error.NameTooLong;
1911 }
1912 target_path_w.len = try std.unicode.wtf8ToWtf16Le(&target_path_w.data, target_path);
1913 target_path_w.data[target_path_w.len] = 0;1242 target_path_w.data[target_path_w.len] = 0;
1914 // However, we need to canonicalize any path separators to `\`, since if1243 // However, we need to canonicalize any path separators to `\`, since if
1915 // the target path is relative, then it must use `\` as the path separator.1244 // 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 {...@@ -2052,20 +1381,11 @@ pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
2052 return windows.ReadLink(self.fd, sub_path_w, buffer);1381 return windows.ReadLink(self.fd, sub_path_w, buffer);
2053}1382}
20541383
2055/// Read all of file contents using a preallocated buffer.1384/// Deprecated in favor of `Io.Dir.readFile`.
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.
2063pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {1385pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
2064 var file = try self.openFile(file_path, .{});1386 var threaded: Io.Threaded = .init_single_threaded;
2065 defer file.close();1387 const io = threaded.ioBasic();
20661388 return Io.Dir.readFile(.{ .handle = self.fd }, io, file_path, buffer);
2067 const end_index = try file.readAll(buffer);
2068 return buffer[0..end_index];
2069}1389}
20701390
2071pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Error || error{1391pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Error || error{
...@@ -2091,7 +1411,7 @@ pub fn readFileAlloc(...@@ -2091,7 +1411,7 @@ pub fn readFileAlloc(
2091 /// Used to allocate the result.1411 /// Used to allocate the result.
2092 gpa: Allocator,1412 gpa: Allocator,
2093 /// If reached or exceeded, `error.StreamTooLong` is returned instead.1413 /// If reached or exceeded, `error.StreamTooLong` is returned instead.
2094 limit: std.Io.Limit,1414 limit: Io.Limit,
2095) ReadFileAllocError![]u8 {1415) ReadFileAllocError![]u8 {
2096 return readFileAllocOptions(dir, sub_path, gpa, limit, .of(u8), null);1416 return readFileAllocOptions(dir, sub_path, gpa, limit, .of(u8), null);
2097}1417}
...@@ -2101,6 +1421,8 @@ pub fn readFileAlloc(...@@ -2101,6 +1421,8 @@ pub fn readFileAlloc(
2101///1421///
2102/// If the file size is already known, a better alternative is to initialize a1422/// If the file size is already known, a better alternative is to initialize a
2103/// `File.Reader`.1423/// `File.Reader`.
1424///
1425/// TODO move this function to Io.Dir
2104pub fn readFileAllocOptions(1426pub fn readFileAllocOptions(
2105 dir: Dir,1427 dir: Dir,
2106 /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).1428 /// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
...@@ -2110,13 +1432,16 @@ pub fn readFileAllocOptions(...@@ -2110,13 +1432,16 @@ pub fn readFileAllocOptions(
2110 /// Used to allocate the result.1432 /// Used to allocate the result.
2111 gpa: Allocator,1433 gpa: Allocator,
2112 /// If reached or exceeded, `error.StreamTooLong` is returned instead.1434 /// If reached or exceeded, `error.StreamTooLong` is returned instead.
2113 limit: std.Io.Limit,1435 limit: Io.Limit,
2114 comptime alignment: std.mem.Alignment,1436 comptime alignment: std.mem.Alignment,
2115 comptime sentinel: ?u8,1437 comptime sentinel: ?u8,
2116) ReadFileAllocError!(if (sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) {1438) 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
2117 var file = try dir.openFile(sub_path, .{});1442 var file = try dir.openFile(sub_path, .{});
2118 defer file.close();1443 defer file.close();
2119 var file_reader = file.reader(&.{});1444 var file_reader = file.reader(io, &.{});
2120 return file_reader.interface.allocRemainingAlignedSentinel(gpa, limit, alignment, sentinel) catch |err| switch (err) {1445 return file_reader.interface.allocRemainingAlignedSentinel(gpa, limit, alignment, sentinel) catch |err| switch (err) {
2121 error.ReadFailed => return file_reader.err.?,1446 error.ReadFailed => return file_reader.err.?,
2122 error.OutOfMemory, error.StreamTooLong => |e| return e,1447 error.OutOfMemory, error.StreamTooLong => |e| return e,
...@@ -2138,24 +1463,19 @@ pub const DeleteTreeError = error{...@@ -2138,24 +1463,19 @@ pub const DeleteTreeError = error{
2138 FileBusy,1463 FileBusy,
2139 DeviceBusy,1464 DeviceBusy,
2140 ProcessNotFound,1465 ProcessNotFound,
2141
2142 /// One of the path components was not a directory.1466 /// One of the path components was not a directory.
2143 /// This error is unreachable if `sub_path` does not contain a path separator.1467 /// This error is unreachable if `sub_path` does not contain a path separator.
2144 NotDir,1468 NotDir,
21451469 /// WASI: file paths must be valid UTF-8.
2146 /// WASI-only; file paths must be valid UTF-8.1470 /// Windows: file paths provided by the user must be valid WTF-8.
2147 InvalidUtf8,
2148
2149 /// Windows-only; file paths provided by the user must be valid WTF-8.
2150 /// https://wtf-8.codeberg.page/1471 /// https://wtf-8.codeberg.page/
2151 InvalidWtf8,
2152
2153 /// On Windows, file paths cannot contain these characters:1472 /// On Windows, file paths cannot contain these characters:
2154 /// '/', '*', '?', '"', '<', '>', '|'1473 /// '/', '*', '?', '"', '<', '>', '|'
2155 BadPathName,1474 BadPathName,
2156
2157 /// On Windows, `\\server` or `\\server\share` was not found.1475 /// On Windows, `\\server` or `\\server\share` was not found.
2158 NetworkNotFound,1476 NetworkNotFound,
1477
1478 Canceled,
2159} || posix.UnexpectedError;1479} || posix.UnexpectedError;
21601480
2161/// Whether `sub_path` describes a symlink, file, or directory, this function1481/// 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 {...@@ -2196,7 +1516,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
2196 if (treat_as_dir) {1516 if (treat_as_dir) {
2197 if (stack.unusedCapacitySlice().len >= 1) {1517 if (stack.unusedCapacitySlice().len >= 1) {
2198 var iterable_dir = top.iter.dir.openDir(entry.name, .{1518 var iterable_dir = top.iter.dir.openDir(entry.name, .{
2199 .no_follow = true,1519 .follow_symlinks = false,
2200 .iterate = true,1520 .iterate = true,
2201 }) catch |err| switch (err) {1521 }) catch |err| switch (err) {
2202 error.NotDir => {1522 error.NotDir => {
...@@ -2212,17 +1532,15 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {...@@ -2212,17 +1532,15 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
2212 error.PermissionDenied,1532 error.PermissionDenied,
2213 error.SymLinkLoop,1533 error.SymLinkLoop,
2214 error.ProcessFdQuotaExceeded,1534 error.ProcessFdQuotaExceeded,
2215 error.ProcessNotFound,
2216 error.NameTooLong,1535 error.NameTooLong,
2217 error.SystemFdQuotaExceeded,1536 error.SystemFdQuotaExceeded,
2218 error.NoDevice,1537 error.NoDevice,
2219 error.SystemResources,1538 error.SystemResources,
2220 error.Unexpected,1539 error.Unexpected,
2221 error.InvalidUtf8,
2222 error.InvalidWtf8,
2223 error.BadPathName,1540 error.BadPathName,
2224 error.NetworkNotFound,1541 error.NetworkNotFound,
2225 error.DeviceBusy,1542 error.DeviceBusy,
1543 error.Canceled,
2226 => |e| return e,1544 => |e| return e,
2227 };1545 };
2228 stack.appendAssumeCapacity(.{1546 stack.appendAssumeCapacity(.{
...@@ -2251,8 +1569,6 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {...@@ -2251,8 +1569,6 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
22511569
2252 error.AccessDenied,1570 error.AccessDenied,
2253 error.PermissionDenied,1571 error.PermissionDenied,
2254 error.InvalidUtf8,
2255 error.InvalidWtf8,
2256 error.SymLinkLoop,1572 error.SymLinkLoop,
2257 error.NameTooLong,1573 error.NameTooLong,
2258 error.SystemResources,1574 error.SystemResources,
...@@ -2294,7 +1610,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {...@@ -2294,7 +1610,7 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
2294 handle_entry: while (true) {1610 handle_entry: while (true) {
2295 if (treat_as_dir) {1611 if (treat_as_dir) {
2296 break :iterable_dir parent_dir.openDir(name, .{1612 break :iterable_dir parent_dir.openDir(name, .{
2297 .no_follow = true,1613 .follow_symlinks = false,
2298 .iterate = true,1614 .iterate = true,
2299 }) catch |err| switch (err) {1615 }) catch |err| switch (err) {
2300 error.NotDir => {1616 error.NotDir => {
...@@ -2309,18 +1625,16 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {...@@ -2309,18 +1625,16 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
2309 error.AccessDenied,1625 error.AccessDenied,
2310 error.PermissionDenied,1626 error.PermissionDenied,
2311 error.SymLinkLoop,1627 error.SymLinkLoop,
2312 error.ProcessNotFound,
2313 error.ProcessFdQuotaExceeded,1628 error.ProcessFdQuotaExceeded,
2314 error.NameTooLong,1629 error.NameTooLong,
2315 error.SystemFdQuotaExceeded,1630 error.SystemFdQuotaExceeded,
2316 error.NoDevice,1631 error.NoDevice,
2317 error.SystemResources,1632 error.SystemResources,
2318 error.Unexpected,1633 error.Unexpected,
2319 error.InvalidUtf8,
2320 error.InvalidWtf8,
2321 error.BadPathName,1634 error.BadPathName,
2322 error.NetworkNotFound,1635 error.NetworkNotFound,
2323 error.DeviceBusy,1636 error.DeviceBusy,
1637 error.Canceled,
2324 => |e| return e,1638 => |e| return e,
2325 };1639 };
2326 } else {1640 } else {
...@@ -2339,8 +1653,6 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {...@@ -2339,8 +1653,6 @@ pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
23391653
2340 error.AccessDenied,1654 error.AccessDenied,
2341 error.PermissionDenied,1655 error.PermissionDenied,
2342 error.InvalidUtf8,
2343 error.InvalidWtf8,
2344 error.SymLinkLoop,1656 error.SymLinkLoop,
2345 error.NameTooLong,1657 error.NameTooLong,
2346 error.SystemResources,1658 error.SystemResources,
...@@ -2402,7 +1714,7 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint...@@ -2402,7 +1714,7 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
2402 handle_entry: while (true) {1714 handle_entry: while (true) {
2403 if (treat_as_dir) {1715 if (treat_as_dir) {
2404 const new_dir = dir.openDir(entry.name, .{1716 const new_dir = dir.openDir(entry.name, .{
2405 .no_follow = true,1717 .follow_symlinks = false,
2406 .iterate = true,1718 .iterate = true,
2407 }) catch |err| switch (err) {1719 }) catch |err| switch (err) {
2408 error.NotDir => {1720 error.NotDir => {
...@@ -2417,18 +1729,16 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint...@@ -2417,18 +1729,16 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
2417 error.AccessDenied,1729 error.AccessDenied,
2418 error.PermissionDenied,1730 error.PermissionDenied,
2419 error.SymLinkLoop,1731 error.SymLinkLoop,
2420 error.ProcessNotFound,
2421 error.ProcessFdQuotaExceeded,1732 error.ProcessFdQuotaExceeded,
2422 error.NameTooLong,1733 error.NameTooLong,
2423 error.SystemFdQuotaExceeded,1734 error.SystemFdQuotaExceeded,
2424 error.NoDevice,1735 error.NoDevice,
2425 error.SystemResources,1736 error.SystemResources,
2426 error.Unexpected,1737 error.Unexpected,
2427 error.InvalidUtf8,
2428 error.InvalidWtf8,
2429 error.BadPathName,1738 error.BadPathName,
2430 error.NetworkNotFound,1739 error.NetworkNotFound,
2431 error.DeviceBusy,1740 error.DeviceBusy,
1741 error.Canceled,
2432 => |e| return e,1742 => |e| return e,
2433 };1743 };
2434 if (cleanup_dir_parent) |*d| d.close();1744 if (cleanup_dir_parent) |*d| d.close();
...@@ -2454,8 +1764,6 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint...@@ -2454,8 +1764,6 @@ fn deleteTreeMinStackSizeWithKindHint(self: Dir, sub_path: []const u8, kind_hint
24541764
2455 error.AccessDenied,1765 error.AccessDenied,
2456 error.PermissionDenied,1766 error.PermissionDenied,
2457 error.InvalidUtf8,
2458 error.InvalidWtf8,
2459 error.SymLinkLoop,1767 error.SymLinkLoop,
2460 error.NameTooLong,1768 error.NameTooLong,
2461 error.SystemResources,1769 error.SystemResources,
...@@ -2503,7 +1811,7 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File...@@ -2503,7 +1811,7 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
2503 handle_entry: while (true) {1811 handle_entry: while (true) {
2504 if (treat_as_dir) {1812 if (treat_as_dir) {
2505 break :iterable_dir self.openDir(sub_path, .{1813 break :iterable_dir self.openDir(sub_path, .{
2506 .no_follow = true,1814 .follow_symlinks = false,
2507 .iterate = true,1815 .iterate = true,
2508 }) catch |err| switch (err) {1816 }) catch |err| switch (err) {
2509 error.NotDir => {1817 error.NotDir => {
...@@ -2519,17 +1827,15 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File...@@ -2519,17 +1827,15 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
2519 error.PermissionDenied,1827 error.PermissionDenied,
2520 error.SymLinkLoop,1828 error.SymLinkLoop,
2521 error.ProcessFdQuotaExceeded,1829 error.ProcessFdQuotaExceeded,
2522 error.ProcessNotFound,
2523 error.NameTooLong,1830 error.NameTooLong,
2524 error.SystemFdQuotaExceeded,1831 error.SystemFdQuotaExceeded,
2525 error.NoDevice,1832 error.NoDevice,
2526 error.SystemResources,1833 error.SystemResources,
2527 error.Unexpected,1834 error.Unexpected,
2528 error.InvalidUtf8,
2529 error.InvalidWtf8,
2530 error.BadPathName,1835 error.BadPathName,
2531 error.DeviceBusy,1836 error.DeviceBusy,
2532 error.NetworkNotFound,1837 error.NetworkNotFound,
1838 error.Canceled,
2533 => |e| return e,1839 => |e| return e,
2534 };1840 };
2535 } else {1841 } else {
...@@ -2545,8 +1851,6 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File...@@ -2545,8 +1851,6 @@ fn deleteTreeOpenInitialSubpath(self: Dir, sub_path: []const u8, kind_hint: File
25451851
2546 error.AccessDenied,1852 error.AccessDenied,
2547 error.PermissionDenied,1853 error.PermissionDenied,
2548 error.InvalidUtf8,
2549 error.InvalidWtf8,
2550 error.SymLinkLoop,1854 error.SymLinkLoop,
2551 error.NameTooLong,1855 error.NameTooLong,
2552 error.SystemResources,1856 error.SystemResources,
...@@ -2582,47 +1886,14 @@ pub fn writeFile(self: Dir, options: WriteFileOptions) WriteFileError!void {...@@ -2582,47 +1886,14 @@ pub fn writeFile(self: Dir, options: WriteFileOptions) WriteFileError!void {
2582 try file.writeAll(options.data);1886 try file.writeAll(options.data);
2583}1887}
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`.1892/// Deprecated in favor of `Io.Dir.access`.
2588/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).1893pub fn access(self: Dir, sub_path: []const u8, options: Io.Dir.AccessOptions) AccessError!void {
2589/// On WASI, `sub_path` should be encoded as valid UTF-8.1894 var threaded: Io.Threaded = .init_single_threaded;
2590/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.1895 const io = threaded.ioBasic();
2591/// Be careful of Time-Of-Check-Time-Of-Use race conditions when using this function.1896 return Io.Dir.access(self.adaptToNewApi(), io, sub_path, options);
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);
2626}1897}
26271898
2628pub const CopyFileOptions = struct {1899pub const CopyFileOptions = struct {
...@@ -2630,77 +1901,9 @@ pub const CopyFileOptions = struct {...@@ -2630,77 +1901,9 @@ pub const CopyFileOptions = struct {
2630 override_mode: ?File.Mode = null,1901 override_mode: ?File.Mode = null,
2631};1902};
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
2701pub const CopyFileError = File.OpenError || File.StatError ||1904pub const CopyFileError = File.OpenError || File.StatError ||
2702 AtomicFile.InitError || AtomicFile.FinishError ||1905 AtomicFile.InitError || AtomicFile.FinishError ||
2703 File.ReadError || File.WriteError;1906 File.ReadError || File.WriteError || error{InvalidFileName};
27041907
2705/// Atomically creates a new file at `dest_path` within `dest_dir` with the1908/// Atomically creates a new file at `dest_path` within `dest_dir` with the
2706/// same contents as `source_path` within `source_dir`, overwriting any already1909/// same contents as `source_path` within `source_dir`, overwriting any already
...@@ -2715,6 +1918,8 @@ pub const CopyFileError = File.OpenError || File.StatError ||...@@ -2715,6 +1918,8 @@ pub const CopyFileError = File.OpenError || File.StatError ||
2715/// [WTF-8](https://wtf-8.codeberg.page/). On WASI, both paths should be1918/// [WTF-8](https://wtf-8.codeberg.page/). On WASI, both paths should be
2716/// encoded as valid UTF-8. On other platforms, both paths are an opaque1919/// encoded as valid UTF-8. On other platforms, both paths are an opaque
2717/// sequence of bytes with no particular encoding.1920/// sequence of bytes with no particular encoding.
1921///
1922/// TODO move this function to Io.Dir
2718pub fn copyFile(1923pub fn copyFile(
2719 source_dir: Dir,1924 source_dir: Dir,
2720 source_path: []const u8,1925 source_path: []const u8,
...@@ -2722,11 +1927,15 @@ pub fn copyFile(...@@ -2722,11 +1927,15 @@ pub fn copyFile(
2722 dest_path: []const u8,1927 dest_path: []const u8,
2723 options: CopyFileOptions,1928 options: CopyFileOptions,
2724) CopyFileError!void {1929) CopyFileError!void {
2725 var file_reader: File.Reader = .init(try source_dir.openFile(source_path, .{}), &.{});1930 var threaded: Io.Threaded = .init_single_threaded;
2726 defer file_reader.file.close();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
2728 const mode = options.override_mode orelse blk: {1937 const mode = options.override_mode orelse blk: {
2729 const st = try file_reader.file.stat();1938 const st = try file_reader.file.stat(io);
2730 file_reader.size = st.size;1939 file_reader.size = st.size;
2731 break :blk st.mode;1940 break :blk st.mode;
2732 };1941 };
...@@ -2776,6 +1985,7 @@ pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions)...@@ -2776,6 +1985,7 @@ pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions)
2776pub const Stat = File.Stat;1985pub const Stat = File.Stat;
2777pub const StatError = File.StatError;1986pub const StatError = File.StatError;
27781987
1988/// Deprecated in favor of `Io.Dir.stat`.
2779pub fn stat(self: Dir) StatError!Stat {1989pub fn stat(self: Dir) StatError!Stat {
2780 const file: File = .{ .handle = self.fd };1990 const file: File = .{ .handle = self.fd };
2781 return file.stat();1991 return file.stat();
...@@ -2783,54 +1993,11 @@ pub fn stat(self: Dir) StatError!Stat {...@@ -2783,54 +1993,11 @@ pub fn stat(self: Dir) StatError!Stat {
27831993
2784pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError;1994pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError;
27851995
2786/// Returns metadata for a file inside the directory.1996/// Deprecated in favor of `Io.Dir.statPath`.
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.
2797pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {1997pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
2798 if (native_os == .windows) {1998 var threaded: Io.Threaded = .init_single_threaded;
2799 var file = try self.openFile(sub_path, .{});1999 const io = threaded.ioBasic();
2800 defer file.close();2000 return Io.Dir.statPath(.{ .handle = self.fd }, io, sub_path, .{});
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);
2834}2001}
28352002
2836pub const ChmodError = File.ChmodError;2003pub const ChmodError = File.ChmodError;
...@@ -2867,3 +2034,11 @@ pub fn setPermissions(self: Dir, permissions: Permissions) SetPermissionsError!v...@@ -2867,3 +2034,11 @@ pub fn setPermissions(self: Dir, permissions: Permissions) SetPermissionsError!v
2867 const file: File = .{ .handle = self.fd };2034 const file: File = .{ .handle = self.fd };
2868 try file.setPermissions(permissions);2035 try file.setPermissions(permissions);
2869}2036}
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 @@...@@ -1,10 +1,12 @@
1const File = @This();
2
1const builtin = @import("builtin");3const builtin = @import("builtin");
2const Os = std.builtin.Os;
3const native_os = builtin.os.tag;4const native_os = builtin.os.tag;
4const is_windows = native_os == .windows;5const is_windows = native_os == .windows;
56
6const File = @This();
7const std = @import("../std.zig");7const std = @import("../std.zig");
8const Io = std.Io;
9const Os = std.builtin.Os;
8const Allocator = std.mem.Allocator;10const Allocator = std.mem.Allocator;
9const posix = std.posix;11const posix = std.posix;
10const math = std.math;12const math = std.math;
...@@ -17,25 +19,12 @@ const Alignment = std.mem.Alignment;...@@ -17,25 +19,12 @@ const Alignment = std.mem.Alignment;
17/// The OS-specific file descriptor or file handle.19/// The OS-specific file descriptor or file handle.
18handle: Handle,20handle: Handle,
1921
20pub const Handle = posix.fd_t;22pub const Handle = Io.File.Handle;
21pub const Mode = posix.mode_t;23pub const Mode = Io.File.Mode;
22pub const INode = posix.ino_t;24pub const INode = Io.File.INode;
23pub const Uid = posix.uid_t;25pub const Uid = posix.uid_t;
24pub const Gid = posix.gid_t;26pub const Gid = posix.gid_t;
2527pub const Kind = Io.File.Kind;
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};
3928
40/// This is the default mode given to POSIX operating systems for creating29/// This is the default mode given to POSIX operating systems for creating
41/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,30/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,
...@@ -43,98 +32,16 @@ pub const Kind = enum {...@@ -43,98 +32,16 @@ pub const Kind = enum {
43/// the `touch` command, which would correspond to `0o644`. However, POSIX32/// the `touch` command, which would correspond to `0o644`. However, POSIX
44/// libc implementations use `0o666` inside `fopen` and then rely on the33/// libc implementations use `0o666` inside `fopen` and then rely on the
45/// process-scoped "umask" setting to adjust this number for file creation.34/// process-scoped "umask" setting to adjust this number for file creation.
46pub const default_mode = switch (builtin.os.tag) {35pub const default_mode: Mode = if (Mode == u0) 0 else 0o666;
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};
8536
86pub const Lock = enum {37/// Deprecated in favor of `Io.File.OpenError`.
87 none,38pub const OpenError = Io.File.OpenError || error{WouldBlock};
88 shared,39/// Deprecated in favor of `Io.File.OpenMode`.
89 exclusive,40pub const OpenMode = Io.File.OpenMode;
90};41/// Deprecated in favor of `Io.File.Lock`.
9142pub const Lock = Io.File.Lock;
92pub const OpenFlags = struct {43/// Deprecated in favor of `Io.File.OpenFlags`.
93 mode: OpenMode = .read_only,44pub const OpenFlags = Io.File.OpenFlags;
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};
13845
139pub const CreateFlags = struct {46pub const CreateFlags = struct {
140 /// Whether the file will be created with read access.47 /// Whether the file will be created with read access.
...@@ -399,193 +306,15 @@ pub fn mode(self: File) ModeError!Mode {...@@ -399,193 +306,15 @@ pub fn mode(self: File) ModeError!Mode {
399 return (try self.stat()).mode;306 return (try self.stat()).mode;
400}307}
401308
402pub const Stat = struct {309pub const Stat = Io.File.Stat;
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};
506310
507pub const StatError = posix.FStatError;311pub const StatError = posix.FStatError;
508312
509/// Returns `Stat` containing basic information about the `File`.313/// Returns `Stat` containing basic information about the `File`.
510/// TODO: integrate with async I/O
511pub fn stat(self: File) StatError!Stat {314pub fn stat(self: File) StatError!Stat {
512 if (builtin.os.tag == .windows) {315 var threaded: Io.Threaded = .init_single_threaded;
513 var io_status_block: windows.IO_STATUS_BLOCK = undefined;316 const io = threaded.ioBasic();
514 var info: windows.FILE_ALL_INFORMATION = undefined;317 return Io.File.stat(.{ .handle = self.handle }, io);
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);
589}318}
590319
591pub const ChmodError = posix.FChmodError;320pub const ChmodError = posix.FChmodError;
...@@ -782,9 +511,9 @@ pub const UpdateTimesError = posix.FutimensError || windows.SetFileTimeError;...@@ -782,9 +511,9 @@ pub const UpdateTimesError = posix.FutimensError || windows.SetFileTimeError;
782pub fn updateTimes(511pub fn updateTimes(
783 self: File,512 self: File,
784 /// access timestamp in nanoseconds513 /// access timestamp in nanoseconds
785 atime: i128,514 atime: Io.Timestamp,
786 /// last modification timestamp in nanoseconds515 /// last modification timestamp in nanoseconds
787 mtime: i128,516 mtime: Io.Timestamp,
788) UpdateTimesError!void {517) UpdateTimesError!void {
789 if (builtin.os.tag == .windows) {518 if (builtin.os.tag == .windows) {
790 const atime_ft = windows.nanoSecondsToFileTime(atime);519 const atime_ft = windows.nanoSecondsToFileTime(atime);
...@@ -793,12 +522,12 @@ pub fn updateTimes(...@@ -793,12 +522,12 @@ pub fn updateTimes(
793 }522 }
794 const times = [2]posix.timespec{523 const times = [2]posix.timespec{
795 posix.timespec{524 posix.timespec{
796 .sec = math.cast(isize, @divFloor(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),
797 .nsec = math.cast(isize, @mod(atime, std.time.ns_per_s)) orelse maxInt(isize),526 .nsec = math.cast(isize, @mod(atime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize),
798 },527 },
799 posix.timespec{528 posix.timespec{
800 .sec = math.cast(isize, @divFloor(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),
801 .nsec = math.cast(isize, @mod(mtime, std.time.ns_per_s)) orelse maxInt(isize),530 .nsec = math.cast(isize, @mod(mtime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize),
802 },531 },
803 };532 };
804 try posix.futimens(self.handle, &times);533 try posix.futimens(self.handle, &times);
...@@ -815,17 +544,6 @@ pub fn read(self: File, buffer: []u8) ReadError!usize {...@@ -815,17 +544,6 @@ pub fn read(self: File, buffer: []u8) ReadError!usize {
815 return posix.read(self.handle, buffer);544 return posix.read(self.handle, buffer);
816}545}
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
829/// On Windows, this function currently does alter the file pointer.547/// On Windows, this function currently does alter the file pointer.
830/// https://github.com/ziglang/zig/issues/12783548/// https://github.com/ziglang/zig/issues/12783
831pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize {549pub 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 {...@@ -858,36 +576,6 @@ pub fn readv(self: File, iovecs: []const posix.iovec) ReadError!usize {
858 return posix.readv(self.handle, iovecs);576 return posix.readv(self.handle, iovecs);
859}577}
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
891/// See https://github.com/ziglang/zig/issues/7699579/// See https://github.com/ziglang/zig/issues/7699
892/// On Windows, this function currently does alter the file pointer.580/// On Windows, this function currently does alter the file pointer.
893/// https://github.com/ziglang/zig/issues/12783581/// https://github.com/ziglang/zig/issues/12783
...@@ -901,28 +589,6 @@ pub fn preadv(self: File, iovecs: []const posix.iovec, offset: u64) PReadError!u...@@ -901,28 +589,6 @@ pub fn preadv(self: File, iovecs: []const posix.iovec, offset: u64) PReadError!u
901 return posix.preadv(self.handle, iovecs, offset);589 return posix.preadv(self.handle, iovecs, offset);
902}590}
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
926pub const WriteError = posix.WriteError;592pub const WriteError = posix.WriteError;
927pub const PWriteError = posix.PWriteError;593pub const PWriteError = posix.PWriteError;
928594
...@@ -934,7 +600,6 @@ pub fn write(self: File, bytes: []const u8) WriteError!usize {...@@ -934,7 +600,6 @@ pub fn write(self: File, bytes: []const u8) WriteError!usize {
934 return posix.write(self.handle, bytes);600 return posix.write(self.handle, bytes);
935}601}
936602
937/// Deprecated in favor of `Writer`.
938pub fn writeAll(self: File, bytes: []const u8) WriteError!void {603pub fn writeAll(self: File, bytes: []const u8) WriteError!void {
939 var index: usize = 0;604 var index: usize = 0;
940 while (index < bytes.len) {605 while (index < bytes.len) {
...@@ -942,6 +607,14 @@ pub fn writeAll(self: File, bytes: []const u8) WriteError!void {...@@ -942,6 +607,14 @@ pub fn writeAll(self: File, bytes: []const u8) WriteError!void {
942 }607 }
943}608}
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
945/// On Windows, this function currently does alter the file pointer.618/// On Windows, this function currently does alter the file pointer.
946/// https://github.com/ziglang/zig/issues/12783619/// https://github.com/ziglang/zig/issues/12783
947pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize {620pub 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 {...@@ -952,14 +625,6 @@ pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize {
952 return posix.pwrite(self.handle, bytes, offset);625 return posix.pwrite(self.handle, bytes, offset);
953}626}
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
963/// See https://github.com/ziglang/zig/issues/7699628/// See https://github.com/ziglang/zig/issues/7699
964pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize {629pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize {
965 if (is_windows) {630 if (is_windows) {
...@@ -972,31 +637,6 @@ pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize {...@@ -972,31 +637,6 @@ pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize {
972 return posix.writev(self.handle, iovecs);637 return posix.writev(self.handle, iovecs);
973}638}
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
1000/// See https://github.com/ziglang/zig/issues/7699640/// See https://github.com/ziglang/zig/issues/7699
1001/// On Windows, this function currently does alter the file pointer.641/// On Windows, this function currently does alter the file pointer.
1002/// https://github.com/ziglang/zig/issues/12783642/// https://github.com/ziglang/zig/issues/12783
...@@ -1011,23 +651,6 @@ pub fn pwritev(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError...@@ -1011,23 +651,6 @@ pub fn pwritev(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError
1011}651}
1012652
1013/// Deprecated in favor of `Writer`.653/// 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
1031pub const CopyRangeError = posix.CopyFileRangeError;654pub const CopyRangeError = posix.CopyFileRangeError;
1032655
1033/// Deprecated in favor of `Writer`.656/// Deprecated in favor of `Writer`.
...@@ -1052,449 +675,8 @@ pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: u...@@ -1052,449 +675,8 @@ pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: u
1052 return total_bytes_copied;675 return total_bytes_copied;
1053}676}
1054677
1055/// Memoizes key information about a file handle such as:678/// Deprecated in favor of `Io.File.Reader`.
1056/// * The size from calling stat, or the error that occurred therein.679pub const Reader = Io.File.Reader;
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};
1498680
1499pub const Writer = struct {681pub const Writer = struct {
1500 file: File,682 file: File,
...@@ -1507,7 +689,7 @@ pub const Writer = struct {...@@ -1507,7 +689,7 @@ pub const Writer = struct {
1507 copy_file_range_err: ?CopyFileRangeError = null,689 copy_file_range_err: ?CopyFileRangeError = null,
1508 fcopyfile_err: ?FcopyfileError = null,690 fcopyfile_err: ?FcopyfileError = null,
1509 seek_err: ?Writer.SeekError = null,691 seek_err: ?Writer.SeekError = null,
1510 interface: std.Io.Writer,692 interface: Io.Writer,
1511693
1512 pub const Mode = Reader.Mode;694 pub const Mode = Reader.Mode;
1513695
...@@ -1553,23 +735,25 @@ pub const Writer = struct {...@@ -1553,23 +735,25 @@ pub const Writer = struct {
1553 };735 };
1554 }736 }
1555737
1556 pub fn initInterface(buffer: []u8) std.Io.Writer {738 pub fn initInterface(buffer: []u8) Io.Writer {
1557 return .{739 return .{
1558 .vtable = &.{740 .vtable = &.{
1559 .drain = drain,741 .drain = drain,
1560 .sendFile = switch (builtin.zig_backend) {742 .sendFile = switch (builtin.zig_backend) {
1561 else => sendFile,743 else => sendFile,
1562 .stage2_aarch64 => std.Io.Writer.unimplementedSendFile,744 .stage2_aarch64 => Io.Writer.unimplementedSendFile,
1563 },745 },
1564 },746 },
1565 .buffer = buffer,747 .buffer = buffer,
1566 };748 };
1567 }749 }
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 {
1570 defer w.* = undefined;753 defer w.* = undefined;
1571 return .{754 return .{
1572 .file = w.file,755 .io = io,
756 .file = .{ .handle = w.file.handle },
1573 .mode = w.mode,757 .mode = w.mode,
1574 .pos = w.pos,758 .pos = w.pos,
1575 .interface = Reader.initInterface(w.interface.buffer),759 .interface = Reader.initInterface(w.interface.buffer),
...@@ -1577,7 +761,7 @@ pub const Writer = struct {...@@ -1577,7 +761,7 @@ pub const Writer = struct {
1577 };761 };
1578 }762 }
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 {
1581 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));765 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
1582 const handle = w.file.handle;766 const handle = w.file.handle;
1583 const buffered = io_w.buffered();767 const buffered = io_w.buffered();
...@@ -1727,10 +911,10 @@ pub const Writer = struct {...@@ -1727,10 +911,10 @@ pub const Writer = struct {
1727 }911 }
1728912
1729 pub fn sendFile(913 pub fn sendFile(
1730 io_w: *std.Io.Writer,914 io_w: *Io.Writer,
1731 file_reader: *Reader,915 file_reader: *Io.File.Reader,
1732 limit: std.Io.Limit,916 limit: Io.Limit,
1733 ) std.Io.Writer.FileError!usize {917 ) Io.Writer.FileError!usize {
1734 const reader_buffered = file_reader.interface.buffered();918 const reader_buffered = file_reader.interface.buffered();
1735 if (reader_buffered.len >= @intFromEnum(limit))919 if (reader_buffered.len >= @intFromEnum(limit))
1736 return sendFileBuffered(io_w, file_reader, limit.slice(reader_buffered));920 return sendFileBuffered(io_w, file_reader, limit.slice(reader_buffered));
...@@ -1994,16 +1178,16 @@ pub const Writer = struct {...@@ -1994,16 +1178,16 @@ pub const Writer = struct {
1994 }1178 }
19951179
1996 fn sendFileBuffered(1180 fn sendFileBuffered(
1997 io_w: *std.Io.Writer,1181 io_w: *Io.Writer,
1998 file_reader: *Reader,1182 file_reader: *Io.File.Reader,
1999 reader_buffered: []const u8,1183 reader_buffered: []const u8,
2000 ) std.Io.Writer.FileError!usize {1184 ) Io.Writer.FileError!usize {
2001 const n = try drain(io_w, &.{reader_buffered}, 1);1185 const n = try drain(io_w, &.{reader_buffered}, 1);
2002 file_reader.seekBy(@intCast(n)) catch return error.ReadFailed;1186 file_reader.seekBy(@intCast(n)) catch return error.ReadFailed;
2003 return n;1187 return n;
2004 }1188 }
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 {
2007 try w.interface.flush();1191 try w.interface.flush();
2008 try seekToUnbuffered(w, offset);1192 try seekToUnbuffered(w, offset);
2009 }1193 }
...@@ -2027,7 +1211,7 @@ pub const Writer = struct {...@@ -2027,7 +1211,7 @@ pub const Writer = struct {
2027 }1211 }
2028 }1212 }
20291213
2030 pub const EndError = SetEndPosError || std.Io.Writer.Error;1214 pub const EndError = SetEndPosError || Io.Writer.Error;
20311215
2032 /// Flushes any buffered data and sets the end position of the file.1216 /// Flushes any buffered data and sets the end position of the file.
2033 ///1217 ///
...@@ -2058,15 +1242,15 @@ pub const Writer = struct {...@@ -2058,15 +1242,15 @@ pub const Writer = struct {
2058///1242///
2059/// Positional is more threadsafe, since the global seek position is not1243/// Positional is more threadsafe, since the global seek position is not
2060/// affected.1244/// affected.
2061pub fn reader(file: File, buffer: []u8) Reader {1245pub fn reader(file: File, io: Io, buffer: []u8) Reader {
2062 return .init(file, buffer);1246 return .init(.{ .handle = file.handle }, io, buffer);
2063}1247}
20641248
2065/// Positional is more threadsafe, since the global seek position is not1249/// Positional is more threadsafe, since the global seek position is not
2066/// affected, but when such syscalls are not available, preemptively1250/// affected, but when such syscalls are not available, preemptively
2067/// initializing in streaming mode skips a failed syscall.1251/// initializing in streaming mode skips a failed syscall.
2068pub fn readerStreaming(file: File, buffer: []u8) Reader {1252pub fn readerStreaming(file: File, io: Io, buffer: []u8) Reader {
2069 return .initStreaming(file, buffer);1253 return .initStreaming(.{ .handle = file.handle }, io, buffer);
2070}1254}
20711255
2072/// Defaults to positional reading; falls back to streaming.1256/// Defaults to positional reading; falls back to streaming.
...@@ -2246,3 +1430,11 @@ pub fn downgradeLock(file: File) LockError!void {...@@ -2246,3 +1430,11 @@ pub fn downgradeLock(file: File) LockError!void {
2246 };1430 };
2247 }1431 }
2248}1432}
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 {...@@ -313,7 +313,7 @@ pub fn isAbsoluteWindowsW(path_w: [*:0]const u16) bool {
313 return isAbsoluteWindowsImpl(u16, mem.sliceTo(path_w, 0));313 return isAbsoluteWindowsImpl(u16, mem.sliceTo(path_w, 0));
314}314}
315315
316pub fn isAbsoluteWindowsWTF16(path: []const u16) bool {316pub fn isAbsoluteWindowsWtf16(path: []const u16) bool {
317 return isAbsoluteWindowsImpl(u16, path);317 return isAbsoluteWindowsImpl(u16, path);
318}318}
319319
lib/std/fs/test.zig+91-155
...@@ -1,10 +1,12 @@...@@ -1,10 +1,12 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");1const builtin = @import("builtin");
2const native_os = builtin.os.tag;
3
4const std = @import("../std.zig");
5const Io = std.Io;
3const testing = std.testing;6const testing = std.testing;
4const fs = std.fs;7const fs = std.fs;
5const mem = std.mem;8const mem = std.mem;
6const wasi = std.os.wasi;9const wasi = std.os.wasi;
7const native_os = builtin.os.tag;
8const windows = std.os.windows;10const windows = std.os.windows;
9const posix = std.posix;11const posix = std.posix;
1012
...@@ -73,6 +75,7 @@ const PathType = enum {...@@ -73,6 +75,7 @@ const PathType = enum {
73};75};
7476
75const TestContext = struct {77const TestContext = struct {
78 io: Io,
76 path_type: PathType,79 path_type: PathType,
77 path_sep: u8,80 path_sep: u8,
78 arena: ArenaAllocator,81 arena: ArenaAllocator,
...@@ -83,6 +86,7 @@ const TestContext = struct {...@@ -83,6 +86,7 @@ const TestContext = struct {
83 pub fn init(path_type: PathType, path_sep: u8, allocator: mem.Allocator, transform_fn: *const PathType.TransformFn) TestContext {86 pub fn init(path_type: PathType, path_sep: u8, allocator: mem.Allocator, transform_fn: *const PathType.TransformFn) TestContext {
84 const tmp = tmpDir(.{ .iterate = true });87 const tmp = tmpDir(.{ .iterate = true });
85 return .{88 return .{
89 .io = testing.io,
86 .path_type = path_type,90 .path_type = path_type,
87 .path_sep = path_sep,91 .path_sep = path_sep,
88 .arena = ArenaAllocator.init(allocator),92 .arena = ArenaAllocator.init(allocator),
...@@ -1319,6 +1323,8 @@ test "max file name component lengths" {...@@ -1319,6 +1323,8 @@ test "max file name component lengths" {
1319}1323}
13201324
1321test "writev, readv" {1325test "writev, readv" {
1326 const io = testing.io;
1327
1322 var tmp = tmpDir(.{});1328 var tmp = tmpDir(.{});
1323 defer tmp.cleanup();1329 defer tmp.cleanup();
13241330
...@@ -1327,78 +1333,55 @@ test "writev, readv" {...@@ -1327,78 +1333,55 @@ test "writev, readv" {
13271333
1328 var buf1: [line1.len]u8 = undefined;1334 var buf1: [line1.len]u8 = undefined;
1329 var buf2: [line2.len]u8 = undefined;1335 var buf2: [line2.len]u8 = undefined;
1330 var write_vecs = [_]posix.iovec_const{1336 var write_vecs: [2][]const u8 = .{ line1, line2 };
1331 .{1337 var read_vecs: [2][]u8 = .{ &buf2, &buf1 };
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 };
13501338
1351 var src_file = try tmp.dir.createFile("test.txt", .{ .read = true });1339 var src_file = try tmp.dir.createFile("test.txt", .{ .read = true });
1352 defer src_file.close();1340 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();
1355 try testing.expectEqual(@as(u64, line1.len + line2.len), try src_file.getEndPos());1346 try testing.expectEqual(@as(u64, line1.len + line2.len), try src_file.getEndPos());
1356 try src_file.seekTo(0);1347
1357 const read = try src_file.readvAll(&read_vecs);1348 var reader = writer.moveToReader(io);
1358 try testing.expectEqual(@as(usize, line1.len + line2.len), read);1349 try reader.seekTo(0);
1350 try reader.interface.readVecAll(&read_vecs);
1359 try testing.expectEqualStrings(&buf1, "line2\n");1351 try testing.expectEqualStrings(&buf1, "line2\n");
1360 try testing.expectEqualStrings(&buf2, "line1\n");1352 try testing.expectEqualStrings(&buf2, "line1\n");
1353 try testing.expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1));
1361}1354}
13621355
1363test "pwritev, preadv" {1356test "pwritev, preadv" {
1357 const io = testing.io;
1358
1364 var tmp = tmpDir(.{});1359 var tmp = tmpDir(.{});
1365 defer tmp.cleanup();1360 defer tmp.cleanup();
13661361
1367 const line1 = "line1\n";1362 const line1 = "line1\n";
1368 const line2 = "line2\n";1363 const line2 = "line2\n";
13691364 var lines: [2][]const u8 = .{ line1, line2 };
1370 var buf1: [line1.len]u8 = undefined;1365 var buf1: [line1.len]u8 = undefined;
1371 var buf2: [line2.len]u8 = undefined;1366 var buf2: [line2.len]u8 = undefined;
1372 var write_vecs = [_]posix.iovec_const{1367 var read_vecs: [2][]u8 = .{ &buf2, &buf1 };
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 };
13921368
1393 var src_file = try tmp.dir.createFile("test.txt", .{ .read = true });1369 var src_file = try tmp.dir.createFile("test.txt", .{ .read = true });
1394 defer src_file.close();1370 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();
1397 try testing.expectEqual(@as(u64, 16 + line1.len + line2.len), try src_file.getEndPos());1377 try testing.expectEqual(@as(u64, 16 + line1.len + line2.len), try src_file.getEndPos());
1398 const read = try src_file.preadvAll(&read_vecs, 16);1378
1399 try testing.expectEqual(@as(usize, line1.len + line2.len), read);1379 var reader = writer.moveToReader(io);
1380 try reader.seekTo(16);
1381 try reader.interface.readVecAll(&read_vecs);
1400 try testing.expectEqualStrings(&buf1, "line2\n");1382 try testing.expectEqualStrings(&buf1, "line2\n");
1401 try testing.expectEqualStrings(&buf2, "line1\n");1383 try testing.expectEqualStrings(&buf2, "line1\n");
1384 try testing.expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1));
1402}1385}
14031386
1404test "setEndPos" {1387test "setEndPos" {
...@@ -1406,6 +1389,8 @@ test "setEndPos" {...@@ -1406,6 +1389,8 @@ test "setEndPos" {
1406 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;1389 if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
1407 if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/238061390 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
1409 var tmp = tmpDir(.{});1394 var tmp = tmpDir(.{});
1410 defer tmp.cleanup();1395 defer tmp.cleanup();
14111396
...@@ -1416,11 +1401,13 @@ test "setEndPos" {...@@ -1416,11 +1401,13 @@ test "setEndPos" {
14161401
1417 const initial_size = try f.getEndPos();1402 const initial_size = try f.getEndPos();
1418 var buffer: [32]u8 = undefined;1403 var buffer: [32]u8 = undefined;
1404 var reader = f.reader(io, &.{});
14191405
1420 {1406 {
1421 try f.setEndPos(initial_size);1407 try f.setEndPos(initial_size);
1422 try testing.expectEqual(initial_size, try f.getEndPos());1408 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));
1424 try testing.expectEqualStrings("ninebytes", buffer[0..@intCast(initial_size)]);1411 try testing.expectEqualStrings("ninebytes", buffer[0..@intCast(initial_size)]);
1425 }1412 }
14261413
...@@ -1428,7 +1415,8 @@ test "setEndPos" {...@@ -1428,7 +1415,8 @@ test "setEndPos" {
1428 const larger = initial_size + 4;1415 const larger = initial_size + 4;
1429 try f.setEndPos(larger);1416 try f.setEndPos(larger);
1430 try testing.expectEqual(larger, try f.getEndPos());1417 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));
1432 try testing.expectEqualStrings("ninebytes\x00\x00\x00\x00", buffer[0..@intCast(larger)]);1420 try testing.expectEqualStrings("ninebytes\x00\x00\x00\x00", buffer[0..@intCast(larger)]);
1433 }1421 }
14341422
...@@ -1436,27 +1424,15 @@ test "setEndPos" {...@@ -1436,27 +1424,15 @@ test "setEndPos" {
1436 const smaller = initial_size - 5;1424 const smaller = initial_size - 5;
1437 try f.setEndPos(smaller);1425 try f.setEndPos(smaller);
1438 try testing.expectEqual(smaller, try f.getEndPos());1426 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));
1440 try testing.expectEqualStrings("nine", buffer[0..@intCast(smaller)]);1429 try testing.expectEqualStrings("nine", buffer[0..@intCast(smaller)]);
1441 }1430 }
14421431
1443 try f.setEndPos(0);1432 try f.setEndPos(0);
1444 try testing.expectEqual(0, try f.getEndPos());1433 try testing.expectEqual(0, try f.getEndPos());
1445 try testing.expectEqual(0, try f.preadAll(&buffer, 0));1434 try reader.seekTo(0);
14461435 try testing.expectEqual(0, try reader.interface.readSliceShort(&buffer));
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)));
1460}1436}
14611437
1462test "access file" {1438test "access file" {
...@@ -1476,6 +1452,8 @@ test "access file" {...@@ -1476,6 +1452,8 @@ test "access file" {
1476}1452}
14771453
1478test "sendfile" {1454test "sendfile" {
1455 const io = testing.io;
1456
1479 var tmp = tmpDir(.{});1457 var tmp = tmpDir(.{});
1480 defer tmp.cleanup();1458 defer tmp.cleanup();
14811459
...@@ -1486,21 +1464,14 @@ test "sendfile" {...@@ -1486,21 +1464,14 @@ test "sendfile" {
14861464
1487 const line1 = "line1\n";1465 const line1 = "line1\n";
1488 const line2 = "second line\n";1466 const line2 = "second line\n";
1489 var vecs = [_]posix.iovec_const{1467 var vecs = [_][]const u8{ line1, line2 };
1490 .{
1491 .base = line1,
1492 .len = line1.len,
1493 },
1494 .{
1495 .base = line2,
1496 .len = line2.len,
1497 },
1498 };
14991468
1500 var src_file = try dir.createFile("sendfile1.txt", .{ .read = true });1469 var src_file = try dir.createFile("sendfile1.txt", .{ .read = true });
1501 defer src_file.close();1470 defer src_file.close();
15021471 {
1503 try src_file.writevAll(&vecs);1472 var fw = src_file.writer(&.{});
1473 try fw.interface.writeVecAll(&vecs);
1474 }
15041475
1505 var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true });1476 var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true });
1506 defer dest_file.close();1477 defer dest_file.close();
...@@ -1513,7 +1484,7 @@ test "sendfile" {...@@ -1513,7 +1484,7 @@ test "sendfile" {
1513 var trailers: [2][]const u8 = .{ trailer1, trailer2 };1484 var trailers: [2][]const u8 = .{ trailer1, trailer2 };
15141485
1515 var written_buf: [100]u8 = undefined;1486 var written_buf: [100]u8 = undefined;
1516 var file_reader = src_file.reader(&.{});1487 var file_reader = src_file.reader(io, &.{});
1517 var fallback_buffer: [50]u8 = undefined;1488 var fallback_buffer: [50]u8 = undefined;
1518 var file_writer = dest_file.writer(&fallback_buffer);1489 var file_writer = dest_file.writer(&fallback_buffer);
1519 try file_writer.interface.writeVecAll(&headers);1490 try file_writer.interface.writeVecAll(&headers);
...@@ -1521,11 +1492,15 @@ test "sendfile" {...@@ -1521,11 +1492,15 @@ test "sendfile" {
1521 try testing.expectEqual(10, try file_writer.interface.sendFileAll(&file_reader, .limited(10)));1492 try testing.expectEqual(10, try file_writer.interface.sendFileAll(&file_reader, .limited(10)));
1522 try file_writer.interface.writeVecAll(&trailers);1493 try file_writer.interface.writeVecAll(&trailers);
1523 try file_writer.interface.flush();1494 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);
1525 try testing.expectEqualStrings("header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n", written_buf[0..amt]);1498 try testing.expectEqualStrings("header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n", written_buf[0..amt]);
1526}1499}
15271500
1528test "sendfile with buffered data" {1501test "sendfile with buffered data" {
1502 const io = testing.io;
1503
1529 var tmp = tmpDir(.{});1504 var tmp = tmpDir(.{});
1530 defer tmp.cleanup();1505 defer tmp.cleanup();
15311506
...@@ -1543,7 +1518,7 @@ test "sendfile with buffered data" {...@@ -1543,7 +1518,7 @@ test "sendfile with buffered data" {
1543 defer dest_file.close();1518 defer dest_file.close();
15441519
1545 var src_buffer: [32]u8 = undefined;1520 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
1548 try file_reader.seekTo(0);1523 try file_reader.seekTo(0);
1549 try file_reader.interface.fill(8);1524 try file_reader.interface.fill(8);
...@@ -1554,37 +1529,14 @@ test "sendfile with buffered data" {...@@ -1554,37 +1529,14 @@ test "sendfile with buffered data" {
1554 try std.testing.expectEqual(4, try file_writer.interface.sendFileAll(&file_reader, .limited(4)));1529 try std.testing.expectEqual(4, try file_writer.interface.sendFileAll(&file_reader, .limited(4)));
15551530
1556 var written_buf: [8]u8 = undefined;1531 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
1559 try std.testing.expectEqual(4, amt);1536 try std.testing.expectEqual(4, amt);
1560 try std.testing.expectEqualSlices(u8, "AAAA", written_buf[0..amt]);1537 try std.testing.expectEqualSlices(u8, "AAAA", written_buf[0..amt]);
1561}1538}
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
1588test "copyFile" {1540test "copyFile" {
1589 try testWithAllSupportedPathTypes(struct {1541 try testWithAllSupportedPathTypes(struct {
1590 fn impl(ctx: *TestContext) !void {1542 fn impl(ctx: *TestContext) !void {
...@@ -1708,8 +1660,8 @@ test "open file with exclusive lock twice, make sure second lock waits" {...@@ -1708,8 +1660,8 @@ test "open file with exclusive lock twice, make sure second lock waits" {
1708 }1660 }
1709 };1661 };
17101662
1711 var started = std.Thread.ResetEvent{};1663 var started: std.Thread.ResetEvent = .unset;
1712 var locked = std.Thread.ResetEvent{};1664 var locked: std.Thread.ResetEvent = .unset;
17131665
1714 const t = try std.Thread.spawn(.{}, S.checkFn, .{1666 const t = try std.Thread.spawn(.{}, S.checkFn, .{
1715 &ctx.dir,1667 &ctx.dir,
...@@ -1773,7 +1725,7 @@ test "read from locked file" {...@@ -1773,7 +1725,7 @@ test "read from locked file" {
1773 const f = try ctx.dir.createFile(filename, .{ .read = true });1725 const f = try ctx.dir.createFile(filename, .{ .read = true });
1774 defer f.close();1726 defer f.close();
1775 var buffer: [1]u8 = undefined;1727 var buffer: [1]u8 = undefined;
1776 _ = try f.readAll(&buffer);1728 _ = try f.read(&buffer);
1777 }1729 }
1778 {1730 {
1779 const f = try ctx.dir.createFile(filename, .{1731 const f = try ctx.dir.createFile(filename, .{
...@@ -1785,9 +1737,9 @@ test "read from locked file" {...@@ -1785,9 +1737,9 @@ test "read from locked file" {
1785 defer f2.close();1737 defer f2.close();
1786 var buffer: [1]u8 = undefined;1738 var buffer: [1]u8 = undefined;
1787 if (builtin.os.tag == .windows) {1739 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));
1789 } else {1741 } else {
1790 try std.testing.expectEqual(0, f2.readAll(&buffer));1742 try std.testing.expectEqual(0, f2.read(&buffer));
1791 }1743 }
1792 }1744 }
1793 }1745 }
...@@ -1944,6 +1896,7 @@ test "'.' and '..' in fs.Dir functions" {...@@ -1944,6 +1896,7 @@ test "'.' and '..' in fs.Dir functions" {
19441896
1945 try testWithAllSupportedPathTypes(struct {1897 try testWithAllSupportedPathTypes(struct {
1946 fn impl(ctx: *TestContext) !void {1898 fn impl(ctx: *TestContext) !void {
1899 const io = ctx.io;
1947 const subdir_path = try ctx.transformPath("./subdir");1900 const subdir_path = try ctx.transformPath("./subdir");
1948 const file_path = try ctx.transformPath("./subdir/../file");1901 const file_path = try ctx.transformPath("./subdir/../file");
1949 const copy_path = try ctx.transformPath("./subdir/../copy");1902 const copy_path = try ctx.transformPath("./subdir/../copy");
...@@ -1966,8 +1919,9 @@ test "'.' and '..' in fs.Dir functions" {...@@ -1966,8 +1919,9 @@ test "'.' and '..' in fs.Dir functions" {
1966 try ctx.dir.deleteFile(rename_path);1919 try ctx.dir.deleteFile(rename_path);
19671920
1968 try ctx.dir.writeFile(.{ .sub_path = update_path, .data = "something" });1921 try ctx.dir.writeFile(.{ .sub_path = update_path, .data = "something" });
1969 const prev_status = try ctx.dir.updateFile(file_path, ctx.dir, update_path, .{});1922 var dir = ctx.dir.adaptToNewApi();
1970 try testing.expectEqual(fs.Dir.PrevStatus.stale, prev_status);1923 const prev_status = try dir.updateFile(io, file_path, dir, update_path, .{});
1924 try testing.expectEqual(Io.Dir.PrevStatus.stale, prev_status);
19711925
1972 try ctx.dir.deleteDir(subdir_path);1926 try ctx.dir.deleteDir(subdir_path);
1973 }1927 }
...@@ -2005,13 +1959,6 @@ test "'.' and '..' in absolute functions" {...@@ -2005,13 +1959,6 @@ test "'.' and '..' in absolute functions" {
2005 renamed_file.close();1959 renamed_file.close();
2006 try fs.deleteFileAbsolute(renamed_file_path);1960 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
2015 try fs.deleteDirAbsolute(subdir_path);1962 try fs.deleteDirAbsolute(subdir_path);
2016}1963}
20171964
...@@ -2072,48 +2019,40 @@ test "delete a setAsCwd directory on Windows" {...@@ -2072,48 +2019,40 @@ test "delete a setAsCwd directory on Windows" {
20722019
2073test "invalid UTF-8/WTF-8 paths" {2020test "invalid UTF-8/WTF-8 paths" {
2074 const expected_err = switch (native_os) {2021 const expected_err = switch (native_os) {
2075 .wasi => error.InvalidUtf8,2022 .wasi => error.BadPathName,
2076 .windows => error.InvalidWtf8,2023 .windows => error.BadPathName,
2077 else => return error.SkipZigTest,2024 else => return error.SkipZigTest,
2078 };2025 };
20792026
2080 try testWithAllSupportedPathTypes(struct {2027 try testWithAllSupportedPathTypes(struct {
2081 fn impl(ctx: *TestContext) !void {2028 fn impl(ctx: *TestContext) !void {
2029 const io = ctx.io;
2082 // This is both invalid UTF-8 and WTF-8, since \xFF is an invalid start byte2030 // This is both invalid UTF-8 and WTF-8, since \xFF is an invalid start byte
2083 const invalid_path = try ctx.transformPath("\xFF");2031 const invalid_path = try ctx.transformPath("\xFF");
20842032
2085 try testing.expectError(expected_err, ctx.dir.openFile(invalid_path, .{}));2033 try testing.expectError(expected_err, ctx.dir.openFile(invalid_path, .{}));
2086 try testing.expectError(expected_err, ctx.dir.openFileZ(invalid_path, .{}));
20872034
2088 try testing.expectError(expected_err, ctx.dir.createFile(invalid_path, .{}));2035 try testing.expectError(expected_err, ctx.dir.createFile(invalid_path, .{}));
2089 try testing.expectError(expected_err, ctx.dir.createFileZ(invalid_path, .{}));
20902036
2091 try testing.expectError(expected_err, ctx.dir.makeDir(invalid_path));2037 try testing.expectError(expected_err, ctx.dir.makeDir(invalid_path));
2092 try testing.expectError(expected_err, ctx.dir.makeDirZ(invalid_path));
20932038
2094 try testing.expectError(expected_err, ctx.dir.makePath(invalid_path));2039 try testing.expectError(expected_err, ctx.dir.makePath(invalid_path));
2095 try testing.expectError(expected_err, ctx.dir.makeOpenPath(invalid_path, .{}));2040 try testing.expectError(expected_err, ctx.dir.makeOpenPath(invalid_path, .{}));
20962041
2097 try testing.expectError(expected_err, ctx.dir.openDir(invalid_path, .{}));2042 try testing.expectError(expected_err, ctx.dir.openDir(invalid_path, .{}));
2098 try testing.expectError(expected_err, ctx.dir.openDirZ(invalid_path, .{}));
20992043
2100 try testing.expectError(expected_err, ctx.dir.deleteFile(invalid_path));2044 try testing.expectError(expected_err, ctx.dir.deleteFile(invalid_path));
2101 try testing.expectError(expected_err, ctx.dir.deleteFileZ(invalid_path));
21022045
2103 try testing.expectError(expected_err, ctx.dir.deleteDir(invalid_path));2046 try testing.expectError(expected_err, ctx.dir.deleteDir(invalid_path));
2104 try testing.expectError(expected_err, ctx.dir.deleteDirZ(invalid_path));
21052047
2106 try testing.expectError(expected_err, ctx.dir.rename(invalid_path, invalid_path));2048 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
2109 try testing.expectError(expected_err, ctx.dir.symLink(invalid_path, invalid_path, .{}));2050 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, .{}));
2111 if (native_os == .wasi) {2051 if (native_os == .wasi) {
2112 try testing.expectError(expected_err, ctx.dir.symLinkWasi(invalid_path, invalid_path, .{}));2052 try testing.expectError(expected_err, ctx.dir.symLinkWasi(invalid_path, invalid_path, .{}));
2113 }2053 }
21142054
2115 try testing.expectError(expected_err, ctx.dir.readLink(invalid_path, &[_]u8{}));2055 try testing.expectError(expected_err, ctx.dir.readLink(invalid_path, &[_]u8{}));
2116 try testing.expectError(expected_err, ctx.dir.readLinkZ(invalid_path, &[_]u8{}));
2117 if (native_os == .wasi) {2056 if (native_os == .wasi) {
2118 try testing.expectError(expected_err, ctx.dir.readLinkWasi(invalid_path, &[_]u8{}));2057 try testing.expectError(expected_err, ctx.dir.readLinkWasi(invalid_path, &[_]u8{}));
2119 }2058 }
...@@ -2127,47 +2066,34 @@ test "invalid UTF-8/WTF-8 paths" {...@@ -2127,47 +2066,34 @@ test "invalid UTF-8/WTF-8 paths" {
2127 try testing.expectError(expected_err, ctx.dir.writeFile(.{ .sub_path = invalid_path, .data = "" }));2066 try testing.expectError(expected_err, ctx.dir.writeFile(.{ .sub_path = invalid_path, .data = "" }));
21282067
2129 try testing.expectError(expected_err, ctx.dir.access(invalid_path, .{}));2068 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, .{}));
2133 try testing.expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, .{}));2072 try testing.expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, .{}));
21342073
2135 try testing.expectError(expected_err, ctx.dir.statFile(invalid_path));2074 try testing.expectError(expected_err, ctx.dir.statFile(invalid_path));
21362075
2137 if (native_os != .wasi) {2076 if (native_os != .wasi) {
2138 try testing.expectError(expected_err, ctx.dir.realpath(invalid_path, &[_]u8{}));2077 try testing.expectError(expected_err, ctx.dir.realpath(invalid_path, &[_]u8{}));
2139 try testing.expectError(expected_err, ctx.dir.realpathZ(invalid_path, &[_]u8{}));
2140 try testing.expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path));2078 try testing.expectError(expected_err, ctx.dir.realpathAlloc(testing.allocator, invalid_path));
2141 }2079 }
21422080
2143 try testing.expectError(expected_err, fs.rename(ctx.dir, invalid_path, ctx.dir, invalid_path));2081 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
2146 if (native_os != .wasi and ctx.path_type != .relative) {2083 if (native_os != .wasi and ctx.path_type != .relative) {
2147 try testing.expectError(expected_err, fs.updateFileAbsolute(invalid_path, invalid_path, .{}));
2148 try testing.expectError(expected_err, fs.copyFileAbsolute(invalid_path, invalid_path, .{}));2084 try testing.expectError(expected_err, fs.copyFileAbsolute(invalid_path, invalid_path, .{}));
2149 try testing.expectError(expected_err, fs.makeDirAbsolute(invalid_path));2085 try testing.expectError(expected_err, fs.makeDirAbsolute(invalid_path));
2150 try testing.expectError(expected_err, fs.makeDirAbsoluteZ(invalid_path));
2151 try testing.expectError(expected_err, fs.deleteDirAbsolute(invalid_path));2086 try testing.expectError(expected_err, fs.deleteDirAbsolute(invalid_path));
2152 try testing.expectError(expected_err, fs.deleteDirAbsoluteZ(invalid_path));
2153 try testing.expectError(expected_err, fs.renameAbsolute(invalid_path, invalid_path));2087 try testing.expectError(expected_err, fs.renameAbsolute(invalid_path, invalid_path));
2154 try testing.expectError(expected_err, fs.renameAbsoluteZ(invalid_path, invalid_path));
2155 try testing.expectError(expected_err, fs.openDirAbsolute(invalid_path, .{}));2088 try testing.expectError(expected_err, fs.openDirAbsolute(invalid_path, .{}));
2156 try testing.expectError(expected_err, fs.openDirAbsoluteZ(invalid_path, .{}));
2157 try testing.expectError(expected_err, fs.openFileAbsolute(invalid_path, .{}));2089 try testing.expectError(expected_err, fs.openFileAbsolute(invalid_path, .{}));
2158 try testing.expectError(expected_err, fs.openFileAbsoluteZ(invalid_path, .{}));
2159 try testing.expectError(expected_err, fs.accessAbsolute(invalid_path, .{}));2090 try testing.expectError(expected_err, fs.accessAbsolute(invalid_path, .{}));
2160 try testing.expectError(expected_err, fs.accessAbsoluteZ(invalid_path, .{}));
2161 try testing.expectError(expected_err, fs.createFileAbsolute(invalid_path, .{}));2091 try testing.expectError(expected_err, fs.createFileAbsolute(invalid_path, .{}));
2162 try testing.expectError(expected_err, fs.createFileAbsoluteZ(invalid_path, .{}));
2163 try testing.expectError(expected_err, fs.deleteFileAbsolute(invalid_path));2092 try testing.expectError(expected_err, fs.deleteFileAbsolute(invalid_path));
2164 try testing.expectError(expected_err, fs.deleteFileAbsoluteZ(invalid_path));
2165 try testing.expectError(expected_err, fs.deleteTreeAbsolute(invalid_path));2093 try testing.expectError(expected_err, fs.deleteTreeAbsolute(invalid_path));
2166 var readlink_buf: [fs.max_path_bytes]u8 = undefined;2094 var readlink_buf: [fs.max_path_bytes]u8 = undefined;
2167 try testing.expectError(expected_err, fs.readLinkAbsolute(invalid_path, &readlink_buf));2095 try testing.expectError(expected_err, fs.readLinkAbsolute(invalid_path, &readlink_buf));
2168 try testing.expectError(expected_err, fs.readLinkAbsoluteZ(invalid_path, &readlink_buf));
2169 try testing.expectError(expected_err, fs.symLinkAbsolute(invalid_path, invalid_path, .{}));2096 try testing.expectError(expected_err, fs.symLinkAbsolute(invalid_path, invalid_path, .{}));
2170 try testing.expectError(expected_err, fs.symLinkAbsoluteZ(invalid_path, invalid_path, .{}));
2171 try testing.expectError(expected_err, fs.realpathAlloc(testing.allocator, invalid_path));2097 try testing.expectError(expected_err, fs.realpathAlloc(testing.allocator, invalid_path));
2172 }2098 }
2173 }2099 }
...@@ -2175,6 +2101,8 @@ test "invalid UTF-8/WTF-8 paths" {...@@ -2175,6 +2101,8 @@ test "invalid UTF-8/WTF-8 paths" {
2175}2101}
21762102
2177test "read file non vectored" {2103test "read file non vectored" {
2104 const io = std.testing.io;
2105
2178 var tmp_dir = testing.tmpDir(.{});2106 var tmp_dir = testing.tmpDir(.{});
2179 defer tmp_dir.cleanup();2107 defer tmp_dir.cleanup();
21802108
...@@ -2188,7 +2116,7 @@ test "read file non vectored" {...@@ -2188,7 +2116,7 @@ test "read file non vectored" {
2188 try file_writer.interface.flush();2116 try file_writer.interface.flush();
2189 }2117 }
21902118
2191 var file_reader: std.fs.File.Reader = .init(file, &.{});2119 var file_reader: std.Io.File.Reader = .initAdapted(file, io, &.{});
21922120
2193 var write_buffer: [100]u8 = undefined;2121 var write_buffer: [100]u8 = undefined;
2194 var w: std.Io.Writer = .fixed(&write_buffer);2122 var w: std.Io.Writer = .fixed(&write_buffer);
...@@ -2205,6 +2133,8 @@ test "read file non vectored" {...@@ -2205,6 +2133,8 @@ test "read file non vectored" {
2205}2133}
22062134
2207test "seek keeping partial buffer" {2135test "seek keeping partial buffer" {
2136 const io = std.testing.io;
2137
2208 var tmp_dir = testing.tmpDir(.{});2138 var tmp_dir = testing.tmpDir(.{});
2209 defer tmp_dir.cleanup();2139 defer tmp_dir.cleanup();
22102140
...@@ -2219,7 +2149,7 @@ test "seek keeping partial buffer" {...@@ -2219,7 +2149,7 @@ test "seek keeping partial buffer" {
2219 }2149 }
22202150
2221 var read_buffer: [3]u8 = undefined;2151 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
2224 try testing.expectEqual(0, file_reader.logicalPos());2154 try testing.expectEqual(0, file_reader.logicalPos());
22252155
...@@ -2246,13 +2176,15 @@ test "seek keeping partial buffer" {...@@ -2246,13 +2176,15 @@ test "seek keeping partial buffer" {
2246}2176}
22472177
2248test "seekBy" {2178test "seekBy" {
2179 const io = testing.io;
2180
2249 var tmp_dir = testing.tmpDir(.{});2181 var tmp_dir = testing.tmpDir(.{});
2250 defer tmp_dir.cleanup();2182 defer tmp_dir.cleanup();
22512183
2252 try tmp_dir.dir.writeFile(.{ .sub_path = "blah.txt", .data = "let's test seekBy" });2184 try tmp_dir.dir.writeFile(.{ .sub_path = "blah.txt", .data = "let's test seekBy" });
2253 const f = try tmp_dir.dir.openFile("blah.txt", .{ .mode = .read_only });2185 const f = try tmp_dir.dir.openFile("blah.txt", .{ .mode = .read_only });
2254 defer f.close();2186 defer f.close();
2255 var reader = f.readerStreaming(&.{});2187 var reader = f.readerStreaming(io, &.{});
2256 try reader.seekBy(2);2188 try reader.seekBy(2);
22572189
2258 var buffer: [20]u8 = undefined;2190 var buffer: [20]u8 = undefined;
...@@ -2265,6 +2197,8 @@ test "seekTo flushes buffered data" {...@@ -2265,6 +2197,8 @@ test "seekTo flushes buffered data" {
2265 var tmp = std.testing.tmpDir(.{});2197 var tmp = std.testing.tmpDir(.{});
2266 defer tmp.cleanup();2198 defer tmp.cleanup();
22672199
2200 const io = std.testing.io;
2201
2268 const contents = "data";2202 const contents = "data";
22692203
2270 const file = try tmp.dir.createFile("seek.bin", .{ .read = true });2204 const file = try tmp.dir.createFile("seek.bin", .{ .read = true });
...@@ -2279,7 +2213,7 @@ test "seekTo flushes buffered data" {...@@ -2279,7 +2213,7 @@ test "seekTo flushes buffered data" {
2279 }2213 }
22802214
2281 var read_buffer: [16]u8 = undefined;2215 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
2284 var buf: [4]u8 = undefined;2218 var buf: [4]u8 = undefined;
2285 try file_reader.interface.readSliceAll(&buf);2219 try file_reader.interface.readSliceAll(&buf);
...@@ -2287,6 +2221,8 @@ test "seekTo flushes buffered data" {...@@ -2287,6 +2221,8 @@ test "seekTo flushes buffered data" {
2287}2221}
22882222
2289test "File.Writer sendfile with buffered contents" {2223test "File.Writer sendfile with buffered contents" {
2224 const io = testing.io;
2225
2290 var tmp_dir = testing.tmpDir(.{});2226 var tmp_dir = testing.tmpDir(.{});
2291 defer tmp_dir.cleanup();2227 defer tmp_dir.cleanup();
22922228
...@@ -2298,7 +2234,7 @@ test "File.Writer sendfile with buffered contents" {...@@ -2298,7 +2234,7 @@ test "File.Writer sendfile with buffered contents" {
2298 defer out.close();2234 defer out.close();
22992235
2300 var in_buf: [2]u8 = undefined;2236 var in_buf: [2]u8 = undefined;
2301 var in_r = in.reader(&in_buf);2237 var in_r = in.reader(io, &in_buf);
2302 _ = try in_r.getSize(); // Catch seeks past end by populating size2238 _ = try in_r.getSize(); // Catch seeks past end by populating size
2303 try in_r.interface.fill(2);2239 try in_r.interface.fill(2);
23042240
...@@ -2312,7 +2248,7 @@ test "File.Writer sendfile with buffered contents" {...@@ -2312,7 +2248,7 @@ test "File.Writer sendfile with buffered contents" {
2312 var check = try tmp_dir.dir.openFile("b", .{});2248 var check = try tmp_dir.dir.openFile("b", .{});
2313 defer check.close();2249 defer check.close();
2314 var check_buf: [4]u8 = undefined;2250 var check_buf: [4]u8 = undefined;
2315 var check_r = check.reader(&check_buf);2251 var check_r = check.reader(io, &check_buf);
2316 try testing.expectEqualStrings("abcd", try check_r.interface.take(4));2252 try testing.expectEqualStrings("abcd", try check_r.interface.take(4));
2317 try testing.expectError(error.EndOfStream, check_r.interface.takeByte());2253 try testing.expectError(error.EndOfStream, check_r.interface.takeByte());
2318}2254}
lib/std/hash_map.zig+4-4
...@@ -1827,9 +1827,9 @@ test "put and remove loop in random order" {...@@ -1827,9 +1827,9 @@ test "put and remove loop in random order" {
1827 }1827 }
1828}1828}
18291829
1830test "remove one million elements in random order" {1830test "remove many elements in random order" {
1831 const Map = AutoHashMap(u32, u32);1831 const Map = AutoHashMap(u32, u32);
1832 const n = 1000 * 1000;1832 const n = 1000 * 100;
1833 var map = Map.init(std.heap.page_allocator);1833 var map = Map.init(std.heap.page_allocator);
1834 defer map.deinit();1834 defer map.deinit();
18351835
...@@ -2147,14 +2147,14 @@ test "getOrPut allocation failure" {...@@ -2147,14 +2147,14 @@ test "getOrPut allocation failure" {
2147 try testing.expectError(error.OutOfMemory, map.getOrPut(std.testing.failing_allocator, "hello"));2147 try testing.expectError(error.OutOfMemory, map.getOrPut(std.testing.failing_allocator, "hello"));
2148}2148}
21492149
2150test "std.hash_map rehash" {2150test "rehash" {
2151 var map = AutoHashMap(usize, usize).init(std.testing.allocator);2151 var map = AutoHashMap(usize, usize).init(std.testing.allocator);
2152 defer map.deinit();2152 defer map.deinit();
21532153
2154 var prng = std.Random.DefaultPrng.init(0);2154 var prng = std.Random.DefaultPrng.init(0);
2155 const random = prng.random();2155 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
2159 for (0..count) |i| {2159 for (0..count) |i| {
2160 try map.put(i, i);2160 try map.put(i, i);
lib/std/heap/debug_allocator.zig+90-19
...@@ -80,15 +80,15 @@...@@ -80,15 +80,15 @@
80//!80//!
81//! Resizing and remapping are forwarded directly to the backing allocator,81//! Resizing and remapping are forwarded directly to the backing allocator,
82//! except where such operations would change the category from large to small.82//! except where such operations would change the category from large to small.
83const builtin = @import("builtin");
84const StackTrace = std.builtin.StackTrace;
8385
84const std = @import("std");86const std = @import("std");
85const builtin = @import("builtin");
86const log = std.log.scoped(.gpa);87const log = std.log.scoped(.gpa);
87const math = std.math;88const math = std.math;
88const assert = std.debug.assert;89const assert = std.debug.assert;
89const mem = std.mem;90const mem = std.mem;
90const Allocator = std.mem.Allocator;91const Allocator = std.mem.Allocator;
91const StackTrace = std.builtin.StackTrace;
9292
93const default_page_size: usize = switch (builtin.os.tag) {93const default_page_size: usize = switch (builtin.os.tag) {
94 // Makes `std.heap.PageAllocator` take the happy path.94 // Makes `std.heap.PageAllocator` take the happy path.
...@@ -421,7 +421,12 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -421,7 +421,12 @@ pub fn DebugAllocator(comptime config: Config) type {
421 return usedBitsCount(slot_count) * @sizeOf(usize);421 return usedBitsCount(slot_count) * @sizeOf(usize);
422 }422 }
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 {
425 const size_class = @as(usize, 1) << @as(Log2USize, @intCast(size_class_index));430 const size_class = @as(usize, 1) << @as(Log2USize, @intCast(size_class_index));
426 const slot_count = slot_counts[size_class_index];431 const slot_count = slot_counts[size_class_index];
427 var leaks: usize = 0;432 var leaks: usize = 0;
...@@ -436,7 +441,13 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -436,7 +441,13 @@ pub fn DebugAllocator(comptime config: Config) type {
436 const stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc);441 const stack_trace = bucketStackTrace(bucket, slot_count, slot_index, .alloc);
437 const page_addr = @intFromPtr(bucket) & ~(page_size - 1);442 const page_addr = @intFromPtr(bucket) & ~(page_size - 1);
438 const addr = page_addr + slot_index * size_class;443 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 });
440 leaks += 1;451 leaks += 1;
441 }452 }
442 }453 }
...@@ -449,12 +460,14 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -449,12 +460,14 @@ pub fn DebugAllocator(comptime config: Config) type {
449 pub fn detectLeaks(self: *Self) usize {460 pub fn detectLeaks(self: *Self) usize {
450 var leaks: usize = 0;461 var leaks: usize = 0;
451462
463 const tty_config = std.Io.tty.detectConfig(.stderr());
464
452 for (self.buckets, 0..) |init_optional_bucket, size_class_index| {465 for (self.buckets, 0..) |init_optional_bucket, size_class_index| {
453 var optional_bucket = init_optional_bucket;466 var optional_bucket = init_optional_bucket;
454 const slot_count = slot_counts[size_class_index];467 const slot_count = slot_counts[size_class_index];
455 const used_bits_count = usedBitsCount(slot_count);468 const used_bits_count = usedBitsCount(slot_count);
456 while (optional_bucket) |bucket| {469 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);
458 optional_bucket = bucket.prev;471 optional_bucket = bucket.prev;
459 }472 }
460 }473 }
...@@ -464,7 +477,11 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -464,7 +477,11 @@ pub fn DebugAllocator(comptime config: Config) type {
464 if (config.retain_metadata and large_alloc.freed) continue;477 if (config.retain_metadata and large_alloc.freed) continue;
465 const stack_trace = large_alloc.getStackTrace(.alloc);478 const stack_trace = large_alloc.getStackTrace(.alloc);
466 log.err("memory address 0x{x} leaked: {f}", .{479 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 },
468 });485 });
469 leaks += 1;486 leaks += 1;
470 }487 }
...@@ -519,8 +536,20 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -519,8 +536,20 @@ pub fn DebugAllocator(comptime config: Config) type {
519 fn reportDoubleFree(ret_addr: usize, alloc_stack_trace: StackTrace, free_stack_trace: StackTrace) void {536 fn reportDoubleFree(ret_addr: usize, alloc_stack_trace: StackTrace, free_stack_trace: StackTrace) void {
520 var addr_buf: [stack_n]usize = undefined;537 var addr_buf: [stack_n]usize = undefined;
521 const second_free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);538 const second_free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
539 const tty_config = std.Io.tty.detectConfig(.stderr());
522 log.err("Double free detected. Allocation: {f} First free: {f} Second free: {f}", .{540 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 },
524 });553 });
525 }554 }
526555
...@@ -561,11 +590,18 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -561,11 +590,18 @@ pub fn DebugAllocator(comptime config: Config) type {
561 if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {590 if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {
562 var addr_buf: [stack_n]usize = undefined;591 var addr_buf: [stack_n]usize = undefined;
563 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);592 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
593 const tty_config = std.Io.tty.detectConfig(.stderr());
564 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{594 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
565 entry.value_ptr.bytes.len,595 entry.value_ptr.bytes.len,
566 old_mem.len,596 old_mem.len,
567 entry.value_ptr.getStackTrace(.alloc),597 std.debug.FormatStackTrace{
568 free_stack_trace,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 },
569 });605 });
570 }606 }
571607
...@@ -667,11 +703,18 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -667,11 +703,18 @@ pub fn DebugAllocator(comptime config: Config) type {
667 if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {703 if (config.safety and old_mem.len != entry.value_ptr.bytes.len) {
668 var addr_buf: [stack_n]usize = undefined;704 var addr_buf: [stack_n]usize = undefined;
669 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);705 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = ret_addr }, &addr_buf);
706 const tty_config = std.Io.tty.detectConfig(.stderr());
670 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{707 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
671 entry.value_ptr.bytes.len,708 entry.value_ptr.bytes.len,
672 old_mem.len,709 old_mem.len,
673 entry.value_ptr.getStackTrace(.alloc),710 std.debug.FormatStackTrace{
674 free_stack_trace,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 },
675 });718 });
676 }719 }
677720
...@@ -892,19 +935,33 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -892,19 +935,33 @@ pub fn DebugAllocator(comptime config: Config) type {
892 var addr_buf: [stack_n]usize = undefined;935 var addr_buf: [stack_n]usize = undefined;
893 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);936 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);
894 if (old_memory.len != requested_size) {937 if (old_memory.len != requested_size) {
938 const tty_config = std.Io.tty.detectConfig(.stderr());
895 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{939 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
896 requested_size,940 requested_size,
897 old_memory.len,941 old_memory.len,
898 bucketStackTrace(bucket, slot_count, slot_index, .alloc),942 std.debug.FormatStackTrace{
899 free_stack_trace,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 },
900 });950 });
901 }951 }
902 if (alignment != slot_alignment) {952 if (alignment != slot_alignment) {
953 const tty_config = std.Io.tty.detectConfig(.stderr());
903 log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{954 log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{
904 slot_alignment.toByteUnits(),955 slot_alignment.toByteUnits(),
905 alignment.toByteUnits(),956 alignment.toByteUnits(),
906 bucketStackTrace(bucket, slot_count, slot_index, .alloc),957 std.debug.FormatStackTrace{
907 free_stack_trace,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 },
908 });965 });
909 }966 }
910 }967 }
...@@ -987,19 +1044,33 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -987,19 +1044,33 @@ pub fn DebugAllocator(comptime config: Config) type {
987 var addr_buf: [stack_n]usize = undefined;1044 var addr_buf: [stack_n]usize = undefined;
988 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);1045 const free_stack_trace = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &addr_buf);
989 if (memory.len != requested_size) {1046 if (memory.len != requested_size) {
1047 const tty_config = std.Io.tty.detectConfig(.stderr());
990 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{1048 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {f} Free: {f}", .{
991 requested_size,1049 requested_size,
992 memory.len,1050 memory.len,
993 bucketStackTrace(bucket, slot_count, slot_index, .alloc),1051 std.debug.FormatStackTrace{
994 free_stack_trace,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 },
995 });1059 });
996 }1060 }
997 if (alignment != slot_alignment) {1061 if (alignment != slot_alignment) {
1062 const tty_config = std.Io.tty.detectConfig(.stderr());
998 log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{1063 log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {f} Free: {f}", .{
999 slot_alignment.toByteUnits(),1064 slot_alignment.toByteUnits(),
1000 alignment.toByteUnits(),1065 alignment.toByteUnits(),
1001 bucketStackTrace(bucket, slot_count, slot_index, .alloc),1066 std.debug.FormatStackTrace{
1002 free_stack_trace,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 },
1003 });1074 });
1004 }1075 }
1005 }1076 }
lib/std/http/Client.zig+104-109
...@@ -9,12 +9,13 @@ const builtin = @import("builtin");...@@ -9,12 +9,13 @@ const builtin = @import("builtin");
9const testing = std.testing;9const testing = std.testing;
10const http = std.http;10const http = std.http;
11const mem = std.mem;11const mem = std.mem;
12const net = std.net;
13const Uri = std.Uri;12const Uri = std.Uri;
14const Allocator = mem.Allocator;13const Allocator = mem.Allocator;
15const assert = std.debug.assert;14const assert = std.debug.assert;
15const Io = std.Io;
16const Writer = std.Io.Writer;16const Writer = std.Io.Writer;
17const Reader = std.Io.Reader;17const Reader = std.Io.Reader;
18const HostName = std.Io.net.HostName;
1819
19const Client = @This();20const Client = @This();
2021
...@@ -22,6 +23,8 @@ pub const disable_tls = std.options.http_disable_tls;...@@ -22,6 +23,8 @@ pub const disable_tls = std.options.http_disable_tls;
2223
23/// Used for all client allocations. Must be thread-safe.24/// Used for all client allocations. Must be thread-safe.
24allocator: Allocator,25allocator: Allocator,
26/// Used for opening TCP connections.
27io: Io,
2528
26ca_bundle: if (disable_tls) void else std.crypto.Certificate.Bundle = if (disable_tls) {} else .{},29ca_bundle: if (disable_tls) void else std.crypto.Certificate.Bundle = if (disable_tls) {} else .{},
27ca_bundle_mutex: std.Thread.Mutex = .{},30ca_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...@@ -32,9 +35,11 @@ tls_buffer_size: if (disable_tls) u0 else usize = if (disable_tls) 0 else std.cr
32/// traffic over connections created with this `Client`.35/// traffic over connections created with this `Client`.
33ssl_key_log: ?*std.crypto.tls.Client.SslKeyLog = null,36ssl_key_log: ?*std.crypto.tls.Client.SslKeyLog = null,
3437
35/// When this is `true`, the next time this client performs an HTTPS request,38/// The time used to decide whether certificates are expired.
36/// it will first rescan the system for root certificates.39///
37next_https_rescan_certs: bool = true,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
39/// The pool of connections that can be reused (and currently in use).44/// The pool of connections that can be reused (and currently in use).
40connection_pool: ConnectionPool = .{},45connection_pool: ConnectionPool = .{},
...@@ -67,7 +72,7 @@ pub const ConnectionPool = struct {...@@ -67,7 +72,7 @@ pub const ConnectionPool = struct {
6772
68 /// The criteria for a connection to be considered a match.73 /// The criteria for a connection to be considered a match.
69 pub const Criteria = struct {74 pub const Criteria = struct {
70 host: []const u8,75 host: HostName,
71 port: u16,76 port: u16,
72 protocol: Protocol,77 protocol: Protocol,
73 };78 };
...@@ -87,7 +92,7 @@ pub const ConnectionPool = struct {...@@ -87,7 +92,7 @@ pub const ConnectionPool = struct {
87 if (connection.port != criteria.port) continue;92 if (connection.port != criteria.port) continue;
8893
89 // Domain names are case-insensitive (RFC 5890, Section 2.3.2.4)94 // 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
92 pool.acquireUnsafe(connection);97 pool.acquireUnsafe(connection);
93 return connection;98 return connection;
...@@ -116,19 +121,19 @@ pub const ConnectionPool = struct {...@@ -116,19 +121,19 @@ pub const ConnectionPool = struct {
116 /// If the connection is marked as closing, it will be closed instead.121 /// If the connection is marked as closing, it will be closed instead.
117 ///122 ///
118 /// Threadsafe.123 /// Threadsafe.
119 pub fn release(pool: *ConnectionPool, connection: *Connection) void {124 pub fn release(pool: *ConnectionPool, connection: *Connection, io: Io) void {
120 pool.mutex.lock();125 pool.mutex.lock();
121 defer pool.mutex.unlock();126 defer pool.mutex.unlock();
122127
123 pool.used.remove(&connection.pool_node);128 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
127 if (pool.free_len >= pool.free_size) {132 if (pool.free_len >= pool.free_size) {
128 const popped: *Connection = @alignCast(@fieldParentPtr("pool_node", pool.free.popFirst().?));133 const popped: *Connection = @alignCast(@fieldParentPtr("pool_node", pool.free.popFirst().?));
129 pool.free_len -= 1;134 pool.free_len -= 1;
130135
131 popped.destroy();136 popped.destroy(io);
132 }137 }
133138
134 if (connection.proxied) {139 if (connection.proxied) {
...@@ -176,21 +181,21 @@ pub const ConnectionPool = struct {...@@ -176,21 +181,21 @@ pub const ConnectionPool = struct {
176 /// All future operations on the connection pool will deadlock.181 /// All future operations on the connection pool will deadlock.
177 ///182 ///
178 /// Threadsafe.183 /// Threadsafe.
179 pub fn deinit(pool: *ConnectionPool) void {184 pub fn deinit(pool: *ConnectionPool, io: Io) void {
180 pool.mutex.lock();185 pool.mutex.lock();
181186
182 var next = pool.free.first;187 var next = pool.free.first;
183 while (next) |node| {188 while (next) |node| {
184 const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node));189 const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node));
185 next = node.next;190 next = node.next;
186 connection.destroy();191 connection.destroy(io);
187 }192 }
188193
189 next = pool.used.first;194 next = pool.used.first;
190 while (next) |node| {195 while (next) |node| {
191 const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node));196 const connection: *Connection = @alignCast(@fieldParentPtr("pool_node", node));
192 next = node.next;197 next = node.next;
193 connection.destroy();198 connection.destroy(io);
194 }199 }
195200
196 pool.* = undefined;201 pool.* = undefined;
...@@ -225,8 +230,8 @@ pub const Protocol = enum {...@@ -225,8 +230,8 @@ pub const Protocol = enum {
225230
226pub const Connection = struct {231pub const Connection = struct {
227 client: *Client,232 client: *Client,
228 stream_writer: net.Stream.Writer,233 stream_writer: Io.net.Stream.Writer,
229 stream_reader: net.Stream.Reader,234 stream_reader: Io.net.Stream.Reader,
230 /// Entry in `ConnectionPool.used` or `ConnectionPool.free`.235 /// Entry in `ConnectionPool.used` or `ConnectionPool.free`.
231 pool_node: std.DoublyLinkedList.Node,236 pool_node: std.DoublyLinkedList.Node,
232 port: u16,237 port: u16,
...@@ -240,28 +245,29 @@ pub const Connection = struct {...@@ -240,28 +245,29 @@ pub const Connection = struct {
240245
241 fn create(246 fn create(
242 client: *Client,247 client: *Client,
243 remote_host: []const u8,248 remote_host: HostName,
244 port: u16,249 port: u16,
245 stream: net.Stream,250 stream: Io.net.Stream,
246 ) error{OutOfMemory}!*Plain {251 ) error{OutOfMemory}!*Plain {
252 const io = client.io;
247 const gpa = client.allocator;253 const gpa = client.allocator;
248 const alloc_len = allocLen(client, remote_host.len);254 const alloc_len = allocLen(client, remote_host.bytes.len);
249 const base = try gpa.alignedAlloc(u8, .of(Plain), alloc_len);255 const base = try gpa.alignedAlloc(u8, .of(Plain), alloc_len);
250 errdefer gpa.free(base);256 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];
252 const socket_read_buffer = host_buffer.ptr[host_buffer.len..][0..client.read_buffer_size];258 const socket_read_buffer = host_buffer.ptr[host_buffer.len..][0..client.read_buffer_size];
253 const socket_write_buffer = socket_read_buffer.ptr[socket_read_buffer.len..][0..client.write_buffer_size];259 const socket_write_buffer = socket_read_buffer.ptr[socket_read_buffer.len..][0..client.write_buffer_size];
254 assert(base.ptr + alloc_len == socket_write_buffer.ptr + socket_write_buffer.len);260 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);
256 const plain: *Plain = @ptrCast(base);262 const plain: *Plain = @ptrCast(base);
257 plain.* = .{263 plain.* = .{
258 .connection = .{264 .connection = .{
259 .client = client,265 .client = client,
260 .stream_writer = stream.writer(socket_write_buffer),266 .stream_writer = stream.writer(io, socket_write_buffer),
261 .stream_reader = stream.reader(socket_read_buffer),267 .stream_reader = stream.reader(io, socket_read_buffer),
262 .pool_node = .{},268 .pool_node = .{},
263 .port = port,269 .port = port,
264 .host_len = @intCast(remote_host.len),270 .host_len = @intCast(remote_host.bytes.len),
265 .proxied = false,271 .proxied = false,
266 .closing = false,272 .closing = false,
267 .protocol = .plain,273 .protocol = .plain,
...@@ -281,9 +287,9 @@ pub const Connection = struct {...@@ -281,9 +287,9 @@ pub const Connection = struct {
281 return @sizeOf(Plain) + host_len + client.read_buffer_size + client.write_buffer_size;287 return @sizeOf(Plain) + host_len + client.read_buffer_size + client.write_buffer_size;
282 }288 }
283289
284 fn host(plain: *Plain) []u8 {290 fn host(plain: *Plain) HostName {
285 const base: [*]u8 = @ptrCast(plain);291 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] };
287 }293 }
288 };294 };
289295
...@@ -291,17 +297,19 @@ pub const Connection = struct {...@@ -291,17 +297,19 @@ pub const Connection = struct {
291 client: std.crypto.tls.Client,297 client: std.crypto.tls.Client,
292 connection: Connection,298 connection: Connection,
293299
300 /// Asserts that `client.now` is non-null.
294 fn create(301 fn create(
295 client: *Client,302 client: *Client,
296 remote_host: []const u8,303 remote_host: HostName,
297 port: u16,304 port: u16,
298 stream: net.Stream,305 stream: Io.net.Stream,
299 ) error{ OutOfMemory, TlsInitializationFailed }!*Tls {306 ) !*Tls {
307 const io = client.io;
300 const gpa = client.allocator;308 const gpa = client.allocator;
301 const alloc_len = allocLen(client, remote_host.len);309 const alloc_len = allocLen(client, remote_host.bytes.len);
302 const base = try gpa.alignedAlloc(u8, .of(Tls), alloc_len);310 const base = try gpa.alignedAlloc(u8, .of(Tls), alloc_len);
303 errdefer gpa.free(base);311 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];
305 // The TLS client wants enough buffer for the max encrypted frame313 // The TLS client wants enough buffer for the max encrypted frame
306 // size, and the HTTP body reader wants enough buffer for the314 // size, and the HTTP body reader wants enough buffer for the
307 // entire HTTP header. This means we need a combined upper bound.315 // entire HTTP header. This means we need a combined upper bound.
...@@ -311,35 +319,43 @@ pub const Connection = struct {...@@ -311,35 +319,43 @@ pub const Connection = struct {
311 const socket_write_buffer = tls_write_buffer.ptr[tls_write_buffer.len..][0..client.write_buffer_size];319 const socket_write_buffer = tls_write_buffer.ptr[tls_write_buffer.len..][0..client.write_buffer_size];
312 const socket_read_buffer = socket_write_buffer.ptr[socket_write_buffer.len..][0..client.tls_buffer_size];320 const socket_read_buffer = socket_write_buffer.ptr[socket_write_buffer.len..][0..client.tls_buffer_size];
313 assert(base.ptr + alloc_len == socket_read_buffer.ptr + socket_read_buffer.len);321 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);
315 const tls: *Tls = @ptrCast(base);323 const tls: *Tls = @ptrCast(base);
324 var random_buffer: [176]u8 = undefined;
325 std.crypto.random.bytes(&random_buffer);
316 tls.* = .{326 tls.* = .{
317 .connection = .{327 .connection = .{
318 .client = client,328 .client = client,
319 .stream_writer = stream.writer(tls_write_buffer),329 .stream_writer = stream.writer(io, tls_write_buffer),
320 .stream_reader = stream.reader(socket_read_buffer),330 .stream_reader = stream.reader(io, socket_read_buffer),
321 .pool_node = .{},331 .pool_node = .{},
322 .port = port,332 .port = port,
323 .host_len = @intCast(remote_host.len),333 .host_len = @intCast(remote_host.bytes.len),
324 .proxied = false,334 .proxied = false,
325 .closing = false,335 .closing = false,
326 .protocol = .tls,336 .protocol = .tls,
327 },337 },
328 // TODO data race here on ca_bundle if the user sets next_https_rescan_certs to true338 // TODO data race here on ca_bundle if the user sets `now` to null
329 .client = std.crypto.tls.Client.init(339 .client = std.crypto.tls.Client.init(
330 tls.connection.stream_reader.interface(),340 &tls.connection.stream_reader.interface,
331 &tls.connection.stream_writer.interface,341 &tls.connection.stream_writer.interface,
332 .{342 .{
333 .host = .{ .explicit = remote_host },343 .host = .{ .explicit = remote_host.bytes },
334 .ca = .{ .bundle = client.ca_bundle },344 .ca = .{ .bundle = client.ca_bundle },
335 .ssl_key_log = client.ssl_key_log,345 .ssl_key_log = client.ssl_key_log,
336 .read_buffer = tls_read_buffer,346 .read_buffer = tls_read_buffer,
337 .write_buffer = socket_write_buffer,347 .write_buffer = socket_write_buffer,
348 .entropy = &random_buffer,
349 .realtime_now_seconds = client.now.?.toSeconds(),
338 // This is appropriate for HTTPS because the HTTP headers contain350 // This is appropriate for HTTPS because the HTTP headers contain
339 // the content length which is used to detect truncation attacks.351 // the content length which is used to detect truncation attacks.
340 .allow_truncation_attacks = true,352 .allow_truncation_attacks = true,
341 },353 },
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 },
343 };359 };
344 return tls;360 return tls;
345 }361 }
...@@ -357,32 +373,32 @@ pub const Connection = struct {...@@ -357,32 +373,32 @@ pub const Connection = struct {
357 client.write_buffer_size + client.tls_buffer_size;373 client.write_buffer_size + client.tls_buffer_size;
358 }374 }
359375
360 fn host(tls: *Tls) []u8 {376 fn host(tls: *Tls) HostName {
361 const base: [*]u8 = @ptrCast(tls);377 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] };
363 }379 }
364 };380 };
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
368 pub fn getReadError(c: *const Connection) ?ReadError {384 pub fn getReadError(c: *const Connection) ?ReadError {
369 return switch (c.protocol) {385 return switch (c.protocol) {
370 .tls => {386 .tls => {
371 if (disable_tls) unreachable;387 if (disable_tls) unreachable;
372 const tls: *const Tls = @alignCast(@fieldParentPtr("connection", c));388 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.?;
374 },390 },
375 .plain => {391 .plain => {
376 return c.stream_reader.getError();392 return c.stream_reader.err.?;
377 },393 },
378 };394 };
379 }395 }
380396
381 fn getStream(c: *Connection) net.Stream {397 fn getStream(c: *Connection) Io.net.Stream {
382 return c.stream_reader.getStream();398 return c.stream_reader.stream;
383 }399 }
384400
385 pub fn host(c: *Connection) []u8 {401 pub fn host(c: *Connection) HostName {
386 return switch (c.protocol) {402 return switch (c.protocol) {
387 .tls => {403 .tls => {
388 if (disable_tls) unreachable;404 if (disable_tls) unreachable;
...@@ -398,8 +414,8 @@ pub const Connection = struct {...@@ -398,8 +414,8 @@ pub const Connection = struct {
398414
399 /// If this is called without calling `flush` or `end`, data will be415 /// If this is called without calling `flush` or `end`, data will be
400 /// dropped unsent.416 /// dropped unsent.
401 pub fn destroy(c: *Connection) void {417 pub fn destroy(c: *Connection, io: Io) void {
402 c.getStream().close();418 c.stream_reader.stream.close(io);
403 switch (c.protocol) {419 switch (c.protocol) {
404 .tls => {420 .tls => {
405 if (disable_tls) unreachable;421 if (disable_tls) unreachable;
...@@ -435,7 +451,7 @@ pub const Connection = struct {...@@ -435,7 +451,7 @@ pub const Connection = struct {
435 const tls: *Tls = @alignCast(@fieldParentPtr("connection", c));451 const tls: *Tls = @alignCast(@fieldParentPtr("connection", c));
436 return &tls.client.reader;452 return &tls.client.reader;
437 },453 },
438 .plain => c.stream_reader.interface(),454 .plain => &c.stream_reader.interface,
439 };455 };
440 }456 }
441457
...@@ -864,6 +880,7 @@ pub const Request = struct {...@@ -864,6 +880,7 @@ pub const Request = struct {
864880
865 /// Returns the request's `Connection` back to the pool of the `Client`.881 /// Returns the request's `Connection` back to the pool of the `Client`.
866 pub fn deinit(r: *Request) void {882 pub fn deinit(r: *Request) void {
883 const io = r.client.io;
867 if (r.connection) |connection| {884 if (r.connection) |connection| {
868 connection.closing = connection.closing or switch (r.reader.state) {885 connection.closing = connection.closing or switch (r.reader.state) {
869 .ready => false,886 .ready => false,
...@@ -878,7 +895,7 @@ pub const Request = struct {...@@ -878,7 +895,7 @@ pub const Request = struct {
878 },895 },
879 else => true,896 else => true,
880 };897 };
881 r.client.connection_pool.release(connection);898 r.client.connection_pool.release(connection, io);
882 }899 }
883 r.* = undefined;900 r.* = undefined;
884 }901 }
...@@ -1180,6 +1197,7 @@ pub const Request = struct {...@@ -1180,6 +1197,7 @@ pub const Request = struct {
1180 ///1197 ///
1181 /// `aux_buf` must outlive accesses to `Request.uri`.1198 /// `aux_buf` must outlive accesses to `Request.uri`.
1182 fn redirect(r: *Request, head: *const Response.Head, aux_buf: *[]u8) !void {1199 fn redirect(r: *Request, head: *const Response.Head, aux_buf: *[]u8) !void {
1200 const io = r.client.io;
1183 const new_location = head.location orelse return error.HttpRedirectLocationMissing;1201 const new_location = head.location orelse return error.HttpRedirectLocationMissing;
1184 if (new_location.len > aux_buf.*.len) return error.HttpRedirectLocationOversize;1202 if (new_location.len > aux_buf.*.len) return error.HttpRedirectLocationOversize;
1185 const location = aux_buf.*[0..new_location.len];1203 const location = aux_buf.*[0..new_location.len];
...@@ -1196,19 +1214,20 @@ pub const Request = struct {...@@ -1196,19 +1214,20 @@ pub const Request = struct {
1196 error.UnexpectedCharacter => return error.HttpRedirectLocationInvalid,1214 error.UnexpectedCharacter => return error.HttpRedirectLocationInvalid,
1197 error.InvalidFormat => return error.HttpRedirectLocationInvalid,1215 error.InvalidFormat => return error.HttpRedirectLocationInvalid,
1198 error.InvalidPort => return error.HttpRedirectLocationInvalid,1216 error.InvalidPort => return error.HttpRedirectLocationInvalid,
1217 error.InvalidHostName => return error.HttpRedirectLocationInvalid,
1199 error.NoSpaceLeft => return error.HttpRedirectLocationOversize,1218 error.NoSpaceLeft => return error.HttpRedirectLocationOversize,
1200 };1219 };
12011220
1202 const protocol = Protocol.fromUri(new_uri) orelse return error.UnsupportedUriScheme;1221 const protocol = Protocol.fromUri(new_uri) orelse return error.UnsupportedUriScheme;
1203 const old_connection = r.connection.?;1222 const old_connection = r.connection.?;
1204 const old_host = old_connection.host();1223 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;
1206 const new_host = try new_uri.getHost(&new_host_name_buffer);1225 const new_host = try new_uri.getHost(&new_host_name_buffer);
1207 const keep_privileged_headers =1226 const keep_privileged_headers =
1208 std.ascii.eqlIgnoreCase(r.uri.scheme, new_uri.scheme) and1227 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);
1212 r.connection = null;1231 r.connection = null;
12131232
1214 if (!keep_privileged_headers) {1233 if (!keep_privileged_headers) {
...@@ -1264,7 +1283,7 @@ pub const Request = struct {...@@ -1264,7 +1283,7 @@ pub const Request = struct {
12641283
1265pub const Proxy = struct {1284pub const Proxy = struct {
1266 protocol: Protocol,1285 protocol: Protocol,
1267 host: []const u8,1286 host: HostName,
1268 authorization: ?[]const u8,1287 authorization: ?[]const u8,
1269 port: u16,1288 port: u16,
1270 supports_connect: bool,1289 supports_connect: bool,
...@@ -1275,9 +1294,10 @@ pub const Proxy = struct {...@@ -1275,9 +1294,10 @@ pub const Proxy = struct {
1275/// All pending requests must be de-initialized and all active connections released1294/// All pending requests must be de-initialized and all active connections released
1276/// before calling this function.1295/// before calling this function.
1277pub fn deinit(client: *Client) void {1296pub fn deinit(client: *Client) void {
1297 const io = client.io;
1278 assert(client.connection_pool.used.first == null); // There are still active requests.1298 assert(client.connection_pool.used.first == null); // There are still active requests.
12791299
1280 client.connection_pool.deinit();1300 client.connection_pool.deinit(io);
1281 if (!disable_tls) client.ca_bundle.deinit(client.allocator);1301 if (!disable_tls) client.ca_bundle.deinit(client.allocator);
12821302
1283 client.* = undefined;1303 client.* = undefined;
...@@ -1383,25 +1403,16 @@ pub const basic_authorization = struct {...@@ -1383,25 +1403,16 @@ pub const basic_authorization = struct {
1383 }1403 }
1384};1404};
13851405
1386pub const ConnectTcpError = Allocator.Error || error{1406pub const ConnectTcpError = error{
1387 ConnectionRefused,
1388 NetworkUnreachable,
1389 ConnectionTimedOut,
1390 ConnectionResetByPeer,
1391 TemporaryNameServerFailure,
1392 NameServerFailure,
1393 UnknownHostName,
1394 HostLacksNetworkAddresses,
1395 UnexpectedConnectFailure,
1396 TlsInitializationFailed,1407 TlsInitializationFailed,
1397};1408} || Allocator.Error || HostName.ConnectError;
13981409
1399/// Reuses a `Connection` if one matching `host` and `port` is already open.1410/// Reuses a `Connection` if one matching `host` and `port` is already open.
1400///1411///
1401/// Threadsafe.1412/// Threadsafe.
1402pub fn connectTcp(1413pub fn connectTcp(
1403 client: *Client,1414 client: *Client,
1404 host: []const u8,1415 host: HostName,
1405 port: u16,1416 port: u16,
1406 protocol: Protocol,1417 protocol: Protocol,
1407) ConnectTcpError!*Connection {1418) ConnectTcpError!*Connection {
...@@ -1409,15 +1420,17 @@ pub fn connectTcp(...@@ -1409,15 +1420,17 @@ pub fn connectTcp(
1409}1420}
14101421
1411pub const ConnectTcpOptions = struct {1422pub const ConnectTcpOptions = struct {
1412 host: []const u8,1423 host: HostName,
1413 port: u16,1424 port: u16,
1414 protocol: Protocol,1425 protocol: Protocol,
14151426
1416 proxied_host: ?[]const u8 = null,1427 proxied_host: ?HostName = null,
1417 proxied_port: ?u16 = null,1428 proxied_port: ?u16 = null,
1429 timeout: Io.Timeout = .none,
1418};1430};
14191431
1420pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcpError!*Connection {1432pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcpError!*Connection {
1433 const io = client.io;
1421 const host = options.host;1434 const host = options.host;
1422 const port = options.port;1435 const port = options.port;
1423 const protocol = options.protocol;1436 const protocol = options.protocol;
...@@ -1431,23 +1444,18 @@ pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcp...@@ -1431,23 +1444,18 @@ pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcp
1431 .protocol = protocol,1444 .protocol = protocol,
1432 })) |conn| return conn;1445 })) |conn| return conn;
14331446
1434 const stream = net.tcpConnectToHost(client.allocator, host, port) catch |err| switch (err) {1447 var stream = try host.connect(io, port, .{ .mode = .stream });
1435 error.ConnectionRefused => return error.ConnectionRefused,1448 errdefer stream.close(io);
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();
14461449
1447 switch (protocol) {1450 switch (protocol) {
1448 .tls => {1451 .tls => {
1449 if (disable_tls) return error.TlsInitializationFailed;1452 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 };
1451 client.connection_pool.addUsed(&tc.connection);1459 client.connection_pool.addUsed(&tc.connection);
1452 return &tc.connection;1460 return &tc.connection;
1453 },1461 },
...@@ -1476,7 +1484,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti...@@ -1476,7 +1484,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti
1476 errdefer client.allocator.destroy(conn);1484 errdefer client.allocator.destroy(conn);
1477 conn.* = .{ .data = undefined };1485 conn.* = .{ .data = undefined };
14781486
1479 const stream = try std.net.connectUnixSocket(path);1487 const stream = try Io.net.connectUnixSocket(path);
1480 errdefer stream.close();1488 errdefer stream.close();
14811489
1482 conn.data = .{1490 conn.data = .{
...@@ -1501,9 +1509,10 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti...@@ -1501,9 +1509,10 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti
1501pub fn connectProxied(1509pub fn connectProxied(
1502 client: *Client,1510 client: *Client,
1503 proxy: *Proxy,1511 proxy: *Proxy,
1504 proxied_host: []const u8,1512 proxied_host: HostName,
1505 proxied_port: u16,1513 proxied_port: u16,
1506) !*Connection {1514) !*Connection {
1515 const io = client.io;
1507 if (!proxy.supports_connect) return error.TunnelNotSupported;1516 if (!proxy.supports_connect) return error.TunnelNotSupported;
15081517
1509 if (client.connection_pool.findConnection(.{1518 if (client.connection_pool.findConnection(.{
...@@ -1523,12 +1532,12 @@ pub fn connectProxied(...@@ -1523,12 +1532,12 @@ pub fn connectProxied(
1523 });1532 });
1524 errdefer {1533 errdefer {
1525 connection.closing = true;1534 connection.closing = true;
1526 client.connection_pool.release(connection);1535 client.connection_pool.release(connection, io);
1527 }1536 }
15281537
1529 var req = client.request(.CONNECT, .{1538 var req = client.request(.CONNECT, .{
1530 .scheme = "http",1539 .scheme = "http",
1531 .host = .{ .raw = proxied_host },1540 .host = .{ .raw = proxied_host.bytes },
1532 .port = proxied_port,1541 .port = proxied_port,
1533 }, .{1542 }, .{
1534 .redirect_behavior = .unhandled,1543 .redirect_behavior = .unhandled,
...@@ -1573,7 +1582,7 @@ pub const ConnectError = ConnectTcpError || RequestError;...@@ -1573,7 +1582,7 @@ pub const ConnectError = ConnectTcpError || RequestError;
1573/// This function is threadsafe.1582/// This function is threadsafe.
1574pub fn connect(1583pub fn connect(
1575 client: *Client,1584 client: *Client,
1576 host: []const u8,1585 host: HostName,
1577 port: u16,1586 port: u16,
1578 protocol: Protocol,1587 protocol: Protocol,
1579) ConnectError!*Connection {1588) ConnectError!*Connection {
...@@ -1583,9 +1592,7 @@ pub fn connect(...@@ -1583,9 +1592,7 @@ pub fn connect(
1583 } orelse return client.connectTcp(host, port, protocol);1592 } orelse return client.connectTcp(host, port, protocol);
15841593
1585 // Prevent proxying through itself.1594 // Prevent proxying through itself.
1586 if (std.ascii.eqlIgnoreCase(proxy.host, host) and1595 if (proxy.host.eql(host) and proxy.port == port and proxy.protocol == protocol) {
1587 proxy.port == port and proxy.protocol == protocol)
1588 {
1589 return client.connectTcp(host, port, protocol);1596 return client.connectTcp(host, port, protocol);
1590 }1597 }
15911598
...@@ -1605,7 +1612,6 @@ pub fn connect(...@@ -1605,7 +1612,6 @@ pub fn connect(
1605pub const RequestError = ConnectTcpError || error{1612pub const RequestError = ConnectTcpError || error{
1606 UnsupportedUriScheme,1613 UnsupportedUriScheme,
1607 UriMissingHost,1614 UriMissingHost,
1608 UriHostTooLong,
1609 CertificateBundleLoadFailure,1615 CertificateBundleLoadFailure,
1610};1616};
16111617
...@@ -1663,6 +1669,8 @@ pub fn request(...@@ -1663,6 +1669,8 @@ pub fn request(
1663 uri: Uri,1669 uri: Uri,
1664 options: RequestOptions,1670 options: RequestOptions,
1665) RequestError!Request {1671) RequestError!Request {
1672 const io = client.io;
1673
1666 if (std.debug.runtime_safety) {1674 if (std.debug.runtime_safety) {
1667 for (options.extra_headers) |header| {1675 for (options.extra_headers) |header| {
1668 assert(header.name.len != 0);1676 assert(header.name.len != 0);
...@@ -1681,20 +1689,21 @@ pub fn request(...@@ -1681,20 +1689,21 @@ pub fn request(
16811689
1682 if (protocol == .tls) {1690 if (protocol == .tls) {
1683 if (disable_tls) unreachable;1691 if (disable_tls) unreachable;
1684 if (@atomicLoad(bool, &client.next_https_rescan_certs, .acquire)) {1692 {
1685 client.ca_bundle_mutex.lock();1693 client.ca_bundle_mutex.lock();
1686 defer client.ca_bundle_mutex.unlock();1694 defer client.ca_bundle_mutex.unlock();
16871695
1688 if (client.next_https_rescan_certs) {1696 if (client.now == null) {
1689 client.ca_bundle.rescan(client.allocator) catch1697 const now = try Io.Clock.real.now(io);
1698 client.now = now;
1699 client.ca_bundle.rescan(client.allocator, io, now) catch
1690 return error.CertificateBundleLoadFailure;1700 return error.CertificateBundleLoadFailure;
1691 @atomicStore(bool, &client.next_https_rescan_certs, false, .release);
1692 }1701 }
1693 }1702 }
1694 }1703 }
16951704
1696 const connection = options.connection orelse c: {1705 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;
1698 const host_name = try uri.getHost(&host_name_buffer);1707 const host_name = try uri.getHost(&host_name_buffer);
1699 break :c try client.connect(host_name, uriPort(uri, protocol), protocol);1708 break :c try client.connect(host_name, uriPort(uri, protocol), protocol);
1700 };1709 };
...@@ -1832,20 +1841,6 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {...@@ -1832,20 +1841,6 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {
1832 return .{ .status = response.head.status };1841 return .{ .status = response.head.status };
1833}1842}
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
1849test {1844test {
1850 _ = Response;1845 _ = Response;
1851}1846}
lib/std/http/Server.zig+4-4
...@@ -688,7 +688,7 @@ pub const WebSocket = struct {...@@ -688,7 +688,7 @@ pub const WebSocket = struct {
688 pub const ReadSmallTextMessageError = error{688 pub const ReadSmallTextMessageError = error{
689 ConnectionClose,689 ConnectionClose,
690 UnexpectedOpCode,690 UnexpectedOpCode,
691 MessageTooBig,691 MessageOversize,
692 MissingMaskBit,692 MissingMaskBit,
693 ReadFailed,693 ReadFailed,
694 EndOfStream,694 EndOfStream,
...@@ -717,15 +717,15 @@ pub const WebSocket = struct {...@@ -717,15 +717,15 @@ pub const WebSocket = struct {
717 _ => return error.UnexpectedOpCode,717 _ => return error.UnexpectedOpCode,
718 }718 }
719719
720 if (!h0.fin) return error.MessageTooBig;720 if (!h0.fin) return error.MessageOversize;
721 if (!h1.mask) return error.MissingMaskBit;721 if (!h1.mask) return error.MissingMaskBit;
722722
723 const len: usize = switch (h1.payload_len) {723 const len: usize = switch (h1.payload_len) {
724 .len16 => try in.takeInt(u16, .big),724 .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,
726 else => @intFromEnum(h1.payload_len),726 else => @intFromEnum(h1.payload_len),
727 };727 };
728 if (len > in.buffer.len) return error.MessageTooBig;728 if (len > in.buffer.len) return error.MessageOversize;
729 const mask: u32 = @bitCast((try in.takeArray(4)).*);729 const mask: u32 = @bitCast((try in.takeArray(4)).*);
730 const payload = try in.take(len);730 const payload = try in.take(len);
731731
lib/std/http/test.zig+124-91
...@@ -1,27 +1,36 @@...@@ -1,27 +1,36 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2const native_endian = builtin.cpu.arch.endian();
3
2const std = @import("std");4const std = @import("std");
3const http = std.http;5const http = std.http;
4const mem = std.mem;6const mem = std.mem;
5const native_endian = builtin.cpu.arch.endian();7const net = std.Io.net;
8const Io = std.Io;
6const expect = std.testing.expect;9const expect = std.testing.expect;
7const expectEqual = std.testing.expectEqual;10const expectEqual = std.testing.expectEqual;
8const expectEqualStrings = std.testing.expectEqualStrings;11const expectEqualStrings = std.testing.expectEqualStrings;
9const expectError = std.testing.expectError;12const expectError = std.testing.expectError;
1013
11test "trailers" {14test "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 {
13 fn run(test_server: *TestServer) anyerror!void {22 fn run(test_server: *TestServer) anyerror!void {
14 const net_server = &test_server.net_server;23 const net_server = &test_server.net_server;
15 var recv_buffer: [1024]u8 = undefined;24 var recv_buffer: [1024]u8 = undefined;
16 var send_buffer: [1024]u8 = undefined;25 var send_buffer: [1024]u8 = undefined;
17 var remaining: usize = 1;26 var remaining: usize = 1;
18 while (remaining != 0) : (remaining -= 1) {27 while (remaining != 0) : (remaining -= 1) {
19 const connection = try net_server.accept();28 var stream = try net_server.accept(io);
20 defer connection.stream.close();29 defer stream.close(io);
2130
22 var connection_br = connection.stream.reader(&recv_buffer);31 var connection_br = stream.reader(io, &recv_buffer);
23 var connection_bw = connection.stream.writer(&send_buffer);32 var connection_bw = stream.writer(io, &send_buffer);
24 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);33 var server = http.Server.init(&connection_br.interface, &connection_bw.interface);
2534
26 try expectEqual(.ready, server.reader.state);35 try expectEqual(.ready, server.reader.state);
27 var request = try server.receiveHead();36 var request = try server.receiveHead();
...@@ -49,7 +58,7 @@ test "trailers" {...@@ -49,7 +58,7 @@ test "trailers" {
4958
50 const gpa = std.testing.allocator;59 const gpa = std.testing.allocator;
5160
52 var client: http.Client = .{ .allocator = gpa };61 var client: http.Client = .{ .allocator = gpa, .io = io };
53 defer client.deinit();62 defer client.deinit();
5463
55 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/trailer", .{64 const location = try std.fmt.allocPrint(gpa, "http://127.0.0.1:{d}/trailer", .{
...@@ -92,17 +101,18 @@ test "trailers" {...@@ -92,17 +101,18 @@ test "trailers" {
92}101}
93102
94test "HTTP server handles a chunked transfer coding request" {103test "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 {
96 fn run(test_server: *TestServer) anyerror!void {106 fn run(test_server: *TestServer) anyerror!void {
97 const net_server = &test_server.net_server;107 const net_server = &test_server.net_server;
98 var recv_buffer: [8192]u8 = undefined;108 var recv_buffer: [8192]u8 = undefined;
99 var send_buffer: [500]u8 = undefined;109 var send_buffer: [500]u8 = undefined;
100 const connection = try net_server.accept();110 var stream = try net_server.accept(io);
101 defer connection.stream.close();111 defer stream.close(io);
102112
103 var connection_br = connection.stream.reader(&recv_buffer);113 var connection_br = stream.reader(io, &recv_buffer);
104 var connection_bw = connection.stream.writer(&send_buffer);114 var connection_bw = stream.writer(io, &send_buffer);
105 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);115 var server = http.Server.init(&connection_br.interface, &connection_bw.interface);
106 var request = try server.receiveHead();116 var request = try server.receiveHead();
107117
108 try expect(request.head.transfer_encoding == .chunked);118 try expect(request.head.transfer_encoding == .chunked);
...@@ -136,12 +146,13 @@ test "HTTP server handles a chunked transfer coding request" {...@@ -136,12 +146,13 @@ test "HTTP server handles a chunked transfer coding request" {
136 "0\r\n" ++146 "0\r\n" ++
137 "\r\n";147 "\r\n";
138148
139 const gpa = std.testing.allocator;149 const host_name: net.HostName = try .init("127.0.0.1");
140 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());150 var stream = try host_name.connect(io, test_server.port(), .{ .mode = .stream });
141 defer stream.close();151 defer stream.close(io);
142 var stream_writer = stream.writer(&.{});152 var stream_writer = stream.writer(io, &.{});
143 try stream_writer.interface.writeAll(request_bytes);153 try stream_writer.interface.writeAll(request_bytes);
144154
155 const gpa = std.testing.allocator;
145 const expected_response =156 const expected_response =
146 "HTTP/1.1 200 OK\r\n" ++157 "HTTP/1.1 200 OK\r\n" ++
147 "connection: close\r\n" ++158 "connection: close\r\n" ++
...@@ -149,26 +160,27 @@ test "HTTP server handles a chunked transfer coding request" {...@@ -149,26 +160,27 @@ test "HTTP server handles a chunked transfer coding request" {
149 "content-type: text/plain\r\n" ++160 "content-type: text/plain\r\n" ++
150 "\r\n" ++161 "\r\n" ++
151 "message from server!\n";162 "message from server!\n";
152 var stream_reader = stream.reader(&.{});163 var stream_reader = stream.reader(io, &.{});
153 const response = try stream_reader.interface().allocRemaining(gpa, .limited(expected_response.len + 1));164 const response = try stream_reader.interface.allocRemaining(gpa, .limited(expected_response.len + 1));
154 defer gpa.free(response);165 defer gpa.free(response);
155 try expectEqualStrings(expected_response, response);166 try expectEqualStrings(expected_response, response);
156}167}
157168
158test "echo content server" {169test "echo content server" {
159 const test_server = try createTestServer(struct {170 const io = std.testing.io;
171 const test_server = try createTestServer(io, struct {
160 fn run(test_server: *TestServer) anyerror!void {172 fn run(test_server: *TestServer) anyerror!void {
161 const net_server = &test_server.net_server;173 const net_server = &test_server.net_server;
162 var recv_buffer: [1024]u8 = undefined;174 var recv_buffer: [1024]u8 = undefined;
163 var send_buffer: [100]u8 = undefined;175 var send_buffer: [100]u8 = undefined;
164176
165 accept: while (!test_server.shutting_down) {177 accept: while (!test_server.shutting_down) {
166 const connection = try net_server.accept();178 var stream = try net_server.accept(io);
167 defer connection.stream.close();179 defer stream.close(io);
168180
169 var connection_br = connection.stream.reader(&recv_buffer);181 var connection_br = stream.reader(io, &recv_buffer);
170 var connection_bw = connection.stream.writer(&send_buffer);182 var connection_bw = stream.writer(io, &send_buffer);
171 var http_server = http.Server.init(connection_br.interface(), &connection_bw.interface);183 var http_server = http.Server.init(&connection_br.interface, &connection_bw.interface);
172184
173 while (http_server.reader.state == .ready) {185 while (http_server.reader.state == .ready) {
174 var request = http_server.receiveHead() catch |err| switch (err) {186 var request = http_server.receiveHead() catch |err| switch (err) {
...@@ -235,7 +247,7 @@ test "echo content server" {...@@ -235,7 +247,7 @@ test "echo content server" {
235 defer test_server.destroy();247 defer test_server.destroy();
236248
237 {249 {
238 var client: http.Client = .{ .allocator = std.testing.allocator };250 var client: http.Client = .{ .allocator = std.testing.allocator, .io = io };
239 defer client.deinit();251 defer client.deinit();
240252
241 try echoTests(&client, test_server.port());253 try echoTests(&client, test_server.port());
...@@ -243,6 +255,8 @@ test "echo content server" {...@@ -243,6 +255,8 @@ test "echo content server" {
243}255}
244256
245test "Server.Request.respondStreaming non-chunked, unknown content-length" {257test "Server.Request.respondStreaming non-chunked, unknown content-length" {
258 const io = std.testing.io;
259
246 if (builtin.os.tag == .windows) {260 if (builtin.os.tag == .windows) {
247 // https://github.com/ziglang/zig/issues/21457261 // https://github.com/ziglang/zig/issues/21457
248 return error.SkipZigTest;262 return error.SkipZigTest;
...@@ -250,19 +264,19 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {...@@ -250,19 +264,19 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
250264
251 // In this case, the response is expected to stream until the connection is265 // In this case, the response is expected to stream until the connection is
252 // closed, indicating the end of the body.266 // closed, indicating the end of the body.
253 const test_server = try createTestServer(struct {267 const test_server = try createTestServer(io, struct {
254 fn run(test_server: *TestServer) anyerror!void {268 fn run(test_server: *TestServer) anyerror!void {
255 const net_server = &test_server.net_server;269 const net_server = &test_server.net_server;
256 var recv_buffer: [1000]u8 = undefined;270 var recv_buffer: [1000]u8 = undefined;
257 var send_buffer: [500]u8 = undefined;271 var send_buffer: [500]u8 = undefined;
258 var remaining: usize = 1;272 var remaining: usize = 1;
259 while (remaining != 0) : (remaining -= 1) {273 while (remaining != 0) : (remaining -= 1) {
260 const connection = try net_server.accept();274 var stream = try net_server.accept(io);
261 defer connection.stream.close();275 defer stream.close(io);
262276
263 var connection_br = connection.stream.reader(&recv_buffer);277 var connection_br = stream.reader(io, &recv_buffer);
264 var connection_bw = connection.stream.writer(&send_buffer);278 var connection_bw = stream.writer(io, &send_buffer);
265 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);279 var server = http.Server.init(&connection_br.interface, &connection_bw.interface);
266280
267 try expectEqual(.ready, server.reader.state);281 try expectEqual(.ready, server.reader.state);
268 var request = try server.receiveHead();282 var request = try server.receiveHead();
...@@ -286,14 +300,15 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {...@@ -286,14 +300,15 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
286 defer test_server.destroy();300 defer test_server.destroy();
287301
288 const request_bytes = "GET /foo HTTP/1.1\r\n\r\n";302 const request_bytes = "GET /foo HTTP/1.1\r\n\r\n";
289 const gpa = std.testing.allocator;303 const host_name: net.HostName = try .init("127.0.0.1");
290 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());304 var stream = try host_name.connect(io, test_server.port(), .{ .mode = .stream });
291 defer stream.close();305 defer stream.close(io);
292 var stream_writer = stream.writer(&.{});306 var stream_writer = stream.writer(io, &.{});
293 try stream_writer.interface.writeAll(request_bytes);307 try stream_writer.interface.writeAll(request_bytes);
294308
295 var stream_reader = stream.reader(&.{});309 var stream_reader = stream.reader(io, &.{});
296 const response = try stream_reader.interface().allocRemaining(gpa, .unlimited);310 const gpa = std.testing.allocator;
311 const response = try stream_reader.interface.allocRemaining(gpa, .unlimited);
297 defer gpa.free(response);312 defer gpa.free(response);
298313
299 var expected_response = std.array_list.Managed(u8).init(gpa);314 var expected_response = std.array_list.Managed(u8).init(gpa);
...@@ -316,19 +331,21 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {...@@ -316,19 +331,21 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" {
316}331}
317332
318test "receiving arbitrary http headers from the client" {333test "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 {
320 fn run(test_server: *TestServer) anyerror!void {337 fn run(test_server: *TestServer) anyerror!void {
321 const net_server = &test_server.net_server;338 const net_server = &test_server.net_server;
322 var recv_buffer: [666]u8 = undefined;339 var recv_buffer: [666]u8 = undefined;
323 var send_buffer: [777]u8 = undefined;340 var send_buffer: [777]u8 = undefined;
324 var remaining: usize = 1;341 var remaining: usize = 1;
325 while (remaining != 0) : (remaining -= 1) {342 while (remaining != 0) : (remaining -= 1) {
326 const connection = try net_server.accept();343 var stream = try net_server.accept(io);
327 defer connection.stream.close();344 defer stream.close(io);
328345
329 var connection_br = connection.stream.reader(&recv_buffer);346 var connection_br = stream.reader(io, &recv_buffer);
330 var connection_bw = connection.stream.writer(&send_buffer);347 var connection_bw = stream.writer(io, &send_buffer);
331 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);348 var server = http.Server.init(&connection_br.interface, &connection_bw.interface);
332349
333 try expectEqual(.ready, server.reader.state);350 try expectEqual(.ready, server.reader.state);
334 var request = try server.receiveHead();351 var request = try server.receiveHead();
...@@ -356,14 +373,15 @@ test "receiving arbitrary http headers from the client" {...@@ -356,14 +373,15 @@ test "receiving arbitrary http headers from the client" {
356 "CoNneCtIoN:close\r\n" ++373 "CoNneCtIoN:close\r\n" ++
357 "aoeu: asdf \r\n" ++374 "aoeu: asdf \r\n" ++
358 "\r\n";375 "\r\n";
359 const gpa = std.testing.allocator;376 const host_name: net.HostName = try .init("127.0.0.1");
360 const stream = try std.net.tcpConnectToHost(gpa, "127.0.0.1", test_server.port());377 var stream = try host_name.connect(io, test_server.port(), .{ .mode = .stream });
361 defer stream.close();378 defer stream.close(io);
362 var stream_writer = stream.writer(&.{});379 var stream_writer = stream.writer(io, &.{});
363 try stream_writer.interface.writeAll(request_bytes);380 try stream_writer.interface.writeAll(request_bytes);
364381
365 var stream_reader = stream.reader(&.{});382 var stream_reader = stream.reader(io, &.{});
366 const response = try stream_reader.interface().allocRemaining(gpa, .unlimited);383 const gpa = std.testing.allocator;
384 const response = try stream_reader.interface.allocRemaining(gpa, .unlimited);
367 defer gpa.free(response);385 defer gpa.free(response);
368386
369 var expected_response = std.array_list.Managed(u8).init(gpa);387 var expected_response = std.array_list.Managed(u8).init(gpa);
...@@ -376,24 +394,26 @@ test "receiving arbitrary http headers from the client" {...@@ -376,24 +394,26 @@ test "receiving arbitrary http headers from the client" {
376}394}
377395
378test "general client/server API coverage" {396test "general client/server API coverage" {
397 const io = std.testing.io;
398
379 if (builtin.os.tag == .windows) {399 if (builtin.os.tag == .windows) {
380 // This test was never passing on Windows.400 // This test was never passing on Windows.
381 return error.SkipZigTest;401 return error.SkipZigTest;
382 }402 }
383403
384 const test_server = try createTestServer(struct {404 const test_server = try createTestServer(io, struct {
385 fn run(test_server: *TestServer) anyerror!void {405 fn run(test_server: *TestServer) anyerror!void {
386 const net_server = &test_server.net_server;406 const net_server = &test_server.net_server;
387 var recv_buffer: [1024]u8 = undefined;407 var recv_buffer: [1024]u8 = undefined;
388 var send_buffer: [100]u8 = undefined;408 var send_buffer: [100]u8 = undefined;
389409
390 outer: while (!test_server.shutting_down) {410 outer: while (!test_server.shutting_down) {
391 var connection = try net_server.accept();411 var stream = try net_server.accept(io);
392 defer connection.stream.close();412 defer stream.close(io);
393413
394 var connection_br = connection.stream.reader(&recv_buffer);414 var connection_br = stream.reader(io, &recv_buffer);
395 var connection_bw = connection.stream.writer(&send_buffer);415 var connection_bw = stream.writer(io, &send_buffer);
396 var http_server = http.Server.init(connection_br.interface(), &connection_bw.interface);416 var http_server = http.Server.init(&connection_br.interface, &connection_bw.interface);
397417
398 while (http_server.reader.state == .ready) {418 while (http_server.reader.state == .ready) {
399 var request = http_server.receiveHead() catch |err| switch (err) {419 var request = http_server.receiveHead() catch |err| switch (err) {
...@@ -401,7 +421,7 @@ test "general client/server API coverage" {...@@ -401,7 +421,7 @@ test "general client/server API coverage" {
401 else => |e| return e,421 else => |e| return e,
402 };422 };
403423
404 try handleRequest(&request, net_server.listen_address.getPort());424 try handleRequest(&request, net_server.socket.address.getPort());
405 }425 }
406 }426 }
407 }427 }
...@@ -530,10 +550,10 @@ test "general client/server API coverage" {...@@ -530,10 +550,10 @@ test "general client/server API coverage" {
530 }550 }
531551
532 fn getUnusedTcpPort() !u16 {552 fn getUnusedTcpPort() !u16 {
533 const addr = try std.net.Address.parseIp("127.0.0.1", 0);553 const addr = try net.IpAddress.parse("127.0.0.1", 0);
534 var s = try addr.listen(.{});554 var s = try addr.listen(io, .{});
535 defer s.deinit();555 defer s.deinit(io);
536 return s.listen_address.in.getPort();556 return s.socket.address.getPort();
537 }557 }
538 });558 });
539 defer test_server.destroy();559 defer test_server.destroy();
...@@ -541,7 +561,7 @@ test "general client/server API coverage" {...@@ -541,7 +561,7 @@ test "general client/server API coverage" {
541 const log = std.log.scoped(.client);561 const log = std.log.scoped(.client);
542562
543 const gpa = std.testing.allocator;563 const gpa = std.testing.allocator;
544 var client: http.Client = .{ .allocator = gpa };564 var client: http.Client = .{ .allocator = gpa, .io = io };
545 defer client.deinit();565 defer client.deinit();
546566
547 const port = test_server.port();567 const port = test_server.port();
...@@ -867,18 +887,20 @@ test "general client/server API coverage" {...@@ -867,18 +887,20 @@ test "general client/server API coverage" {
867}887}
868888
869test "Server streams both reading and writing" {889test "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 {
871 fn run(test_server: *TestServer) anyerror!void {893 fn run(test_server: *TestServer) anyerror!void {
872 const net_server = &test_server.net_server;894 const net_server = &test_server.net_server;
873 var recv_buffer: [1024]u8 = undefined;895 var recv_buffer: [1024]u8 = undefined;
874 var send_buffer: [777]u8 = undefined;896 var send_buffer: [777]u8 = undefined;
875897
876 const connection = try net_server.accept();898 var stream = try net_server.accept(io);
877 defer connection.stream.close();899 defer stream.close(io);
878900
879 var connection_br = connection.stream.reader(&recv_buffer);901 var connection_br = stream.reader(io, &recv_buffer);
880 var connection_bw = connection.stream.writer(&send_buffer);902 var connection_bw = stream.writer(io, &send_buffer);
881 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);903 var server = http.Server.init(&connection_br.interface, &connection_bw.interface);
882 var request = try server.receiveHead();904 var request = try server.receiveHead();
883 var read_buffer: [100]u8 = undefined;905 var read_buffer: [100]u8 = undefined;
884 var br = try request.readerExpectContinue(&read_buffer);906 var br = try request.readerExpectContinue(&read_buffer);
...@@ -904,7 +926,10 @@ test "Server streams both reading and writing" {...@@ -904,7 +926,10 @@ test "Server streams both reading and writing" {
904 });926 });
905 defer test_server.destroy();927 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 };
908 defer client.deinit();933 defer client.deinit();
909934
910 var redirect_buffer: [555]u8 = undefined;935 var redirect_buffer: [555]u8 = undefined;
...@@ -1075,36 +1100,40 @@ fn echoTests(client: *http.Client, port: u16) !void {...@@ -1075,36 +1100,40 @@ fn echoTests(client: *http.Client, port: u16) !void {
1075}1100}
10761101
1077const TestServer = struct {1102const TestServer = struct {
1103 io: Io,
1078 shutting_down: bool,1104 shutting_down: bool,
1079 server_thread: std.Thread,1105 server_thread: std.Thread,
1080 net_server: std.net.Server,1106 net_server: net.Server,
10811107
1082 fn destroy(self: *@This()) void {1108 fn destroy(self: *@This()) void {
1109 const io = self.io;
1083 self.shutting_down = true;1110 self.shutting_down = true;
1084 const conn = std.net.tcpConnectToAddress(self.net_server.listen_address) catch @panic("shutdown failure");1111 var stream = self.net_server.socket.address.connect(io, .{ .mode = .stream }) catch
1085 conn.close();1112 @panic("shutdown failure");
1113 stream.close(io);
10861114
1087 self.server_thread.join();1115 self.server_thread.join();
1088 self.net_server.deinit();1116 self.net_server.deinit(io);
1089 std.testing.allocator.destroy(self);1117 std.testing.allocator.destroy(self);
1090 }1118 }
10911119
1092 fn port(self: @This()) u16 {1120 fn port(self: @This()) u16 {
1093 return self.net_server.listen_address.in.getPort();1121 return self.net_server.socket.address.getPort();
1094 }1122 }
1095};1123};
10961124
1097fn createTestServer(S: type) !*TestServer {1125fn createTestServer(io: Io, S: type) !*TestServer {
1098 if (builtin.single_threaded) return error.SkipZigTest;1126 if (builtin.single_threaded) return error.SkipZigTest;
1099 if (builtin.zig_backend == .stage2_llvm and native_endian == .big) {1127 if (builtin.zig_backend == .stage2_llvm and native_endian == .big) {
1100 // https://github.com/ziglang/zig/issues/137821128 // https://github.com/ziglang/zig/issues/13782
1101 return error.SkipZigTest;1129 return error.SkipZigTest;
1102 }1130 }
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);
1105 const test_server = try std.testing.allocator.create(TestServer);1133 const test_server = try std.testing.allocator.create(TestServer);
1106 test_server.* = .{1134 test_server.* = .{
1107 .net_server = try address.listen(.{ .reuse_address = true }),1135 .io = io,
1136 .net_server = try address.listen(io, .{ .reuse_address = true }),
1108 .shutting_down = false,1137 .shutting_down = false,
1109 .server_thread = try std.Thread.spawn(.{}, S.run, .{test_server}),1138 .server_thread = try std.Thread.spawn(.{}, S.run, .{test_server}),
1110 };1139 };
...@@ -1112,18 +1141,19 @@ fn createTestServer(S: type) !*TestServer {...@@ -1112,18 +1141,19 @@ fn createTestServer(S: type) !*TestServer {
1112}1141}
11131142
1114test "redirect to different connection" {1143test "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 {
1116 fn run(test_server: *TestServer) anyerror!void {1146 fn run(test_server: *TestServer) anyerror!void {
1117 const net_server = &test_server.net_server;1147 const net_server = &test_server.net_server;
1118 var recv_buffer: [888]u8 = undefined;1148 var recv_buffer: [888]u8 = undefined;
1119 var send_buffer: [777]u8 = undefined;1149 var send_buffer: [777]u8 = undefined;
11201150
1121 const connection = try net_server.accept();1151 var stream = try net_server.accept(io);
1122 defer connection.stream.close();1152 defer stream.close(io);
11231153
1124 var connection_br = connection.stream.reader(&recv_buffer);1154 var connection_br = stream.reader(io, &recv_buffer);
1125 var connection_bw = connection.stream.writer(&send_buffer);1155 var connection_bw = stream.writer(io, &send_buffer);
1126 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);1156 var server = http.Server.init(&connection_br.interface, &connection_bw.interface);
1127 var request = try server.receiveHead();1157 var request = try server.receiveHead();
1128 try expectEqualStrings(request.head.target, "/ok");1158 try expectEqualStrings(request.head.target, "/ok");
1129 try request.respond("good job, you pass", .{});1159 try request.respond("good job, you pass", .{});
...@@ -1136,23 +1166,23 @@ test "redirect to different connection" {...@@ -1136,23 +1166,23 @@ test "redirect to different connection" {
1136 };1166 };
1137 global.other_port = test_server_new.port();1167 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 {
1140 fn run(test_server: *TestServer) anyerror!void {1170 fn run(test_server: *TestServer) anyerror!void {
1141 const net_server = &test_server.net_server;1171 const net_server = &test_server.net_server;
1142 var recv_buffer: [999]u8 = undefined;1172 var recv_buffer: [999]u8 = undefined;
1143 var send_buffer: [100]u8 = undefined;1173 var send_buffer: [100]u8 = undefined;
11441174
1145 const connection = try net_server.accept();1175 var stream = try net_server.accept(io);
1146 defer connection.stream.close();1176 defer stream.close(io);
11471177
1148 var loc_buf: [50]u8 = undefined;1178 var loc_buf: [50]u8 = undefined;
1149 const new_loc = try std.fmt.bufPrint(&loc_buf, "http://127.0.0.1:{d}/ok", .{1179 const new_loc = try std.fmt.bufPrint(&loc_buf, "http://127.0.0.1:{d}/ok", .{
1150 global.other_port.?,1180 global.other_port.?,
1151 });1181 });
11521182
1153 var connection_br = connection.stream.reader(&recv_buffer);1183 var connection_br = stream.reader(io, &recv_buffer);
1154 var connection_bw = connection.stream.writer(&send_buffer);1184 var connection_bw = stream.writer(io, &send_buffer);
1155 var server = http.Server.init(connection_br.interface(), &connection_bw.interface);1185 var server = http.Server.init(&connection_br.interface, &connection_bw.interface);
1156 var request = try server.receiveHead();1186 var request = try server.receiveHead();
1157 try expectEqualStrings(request.head.target, "/help");1187 try expectEqualStrings(request.head.target, "/help");
1158 try request.respond("", .{1188 try request.respond("", .{
...@@ -1167,7 +1197,10 @@ test "redirect to different connection" {...@@ -1167,7 +1197,10 @@ test "redirect to different connection" {
11671197
1168 const gpa = std.testing.allocator;1198 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 };
1171 defer client.deinit();1204 defer client.deinit();
11721205
1173 var loc_buf: [100]u8 = undefined;1206 var loc_buf: [100]u8 = undefined;
lib/std/mem.zig+46-23
...@@ -1678,6 +1678,7 @@ test "indexOfPos empty needle" {...@@ -1678,6 +1678,7 @@ test "indexOfPos empty needle" {
1678/// needle.len must be > 01678/// needle.len must be > 0
1679/// does not count overlapping needles1679/// does not count overlapping needles
1680pub fn count(comptime T: type, haystack: []const T, needle: []const T) usize {1680pub fn count(comptime T: type, haystack: []const T, needle: []const T) usize {
1681 if (needle.len == 1) return countScalar(T, haystack, needle[0]);
1681 assert(needle.len > 0);1682 assert(needle.len > 0);
1682 var i: usize = 0;1683 var i: usize = 0;
1683 var found: usize = 0;1684 var found: usize = 0;
...@@ -1704,9 +1705,9 @@ test count {...@@ -1704,9 +1705,9 @@ test count {
1704 try testing.expect(count(u8, "owowowu", "owowu") == 1);1705 try testing.expect(count(u8, "owowowu", "owowu") == 1);
1705}1706}
17061707
1707/// Returns the number of needles inside the haystack1708/// Returns the number of times `element` appears in a slice of memory.
1708pub fn countScalar(comptime T: type, haystack: []const T, needle: T) usize {1709pub fn countScalar(comptime T: type, list: []const T, element: T) usize {
1709 const n = haystack.len;1710 const n = list.len;
1710 var i: usize = 0;1711 var i: usize = 0;
1711 var found: usize = 0;1712 var found: usize = 0;
17121713
...@@ -1716,16 +1717,16 @@ pub fn countScalar(comptime T: type, haystack: []const T, needle: T) usize {...@@ -1716,16 +1717,16 @@ pub fn countScalar(comptime T: type, haystack: []const T, needle: T) usize {
1716 if (std.simd.suggestVectorLength(T)) |block_size| {1717 if (std.simd.suggestVectorLength(T)) |block_size| {
1717 const Block = @Vector(block_size, T);1718 const Block = @Vector(block_size, T);
17181719
1719 const letter_mask: Block = @splat(needle);1720 const letter_mask: Block = @splat(element);
1720 while (n - i >= block_size) : (i += block_size) {1721 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].*;
1722 found += std.simd.countTrues(letter_mask == haystack_block);1723 found += std.simd.countTrues(letter_mask == haystack_block);
1723 }1724 }
1724 }1725 }
1725 }1726 }
17261727
1727 for (haystack[i..n]) |item| {1728 for (list[i..n]) |item| {
1728 found += @intFromBool(item == needle);1729 found += @intFromBool(item == element);
1729 }1730 }
17301731
1731 return found;1732 return found;
...@@ -1735,6 +1736,7 @@ test countScalar {...@@ -1735,6 +1736,7 @@ test countScalar {
1735 try testing.expectEqual(0, countScalar(u8, "", 'h'));1736 try testing.expectEqual(0, countScalar(u8, "", 'h'));
1736 try testing.expectEqual(1, countScalar(u8, "h", 'h'));1737 try testing.expectEqual(1, countScalar(u8, "h", 'h'));
1737 try testing.expectEqual(2, countScalar(u8, "hh", 'h'));1738 try testing.expectEqual(2, countScalar(u8, "hh", 'h'));
1739 try testing.expectEqual(2, countScalar(u8, "ahhb", 'h'));
1738 try testing.expectEqual(3, countScalar(u8, " abcabc abc", 'b'));1740 try testing.expectEqual(3, countScalar(u8, " abcabc abc", 'b'));
1739}1741}
17401742
...@@ -1744,6 +1746,7 @@ test countScalar {...@@ -1744,6 +1746,7 @@ test countScalar {
1744//1746//
1745/// See also: `containsAtLeastScalar`1747/// See also: `containsAtLeastScalar`
1746pub fn containsAtLeast(comptime T: type, haystack: []const T, expected_count: usize, needle: []const T) bool {1748pub 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]);
1747 assert(needle.len > 0);1750 assert(needle.len > 0);
1748 if (expected_count == 0) return true;1751 if (expected_count == 0) return true;
17491752
...@@ -1774,32 +1777,52 @@ test containsAtLeast {...@@ -1774,32 +1777,52 @@ test containsAtLeast {
1774 try testing.expect(!containsAtLeast(u8, " radar radar ", 3, "radar"));1777 try testing.expect(!containsAtLeast(u8, " radar radar ", 3, "radar"));
1775}1778}
17761779
1777/// Returns true if the haystack contains expected_count or more needles1780/// Deprecated in favor of `containsAtLeastScalar2`.
1778//1781pub fn containsAtLeastScalar(comptime T: type, list: []const T, minimum: usize, element: T) bool {
1779/// See also: `containsAtLeast`1782 return containsAtLeastScalar2(T, list, element, minimum);
1780pub fn containsAtLeastScalar(comptime T: type, haystack: []const T, expected_count: usize, needle: T) bool {1783}
1781 if (expected_count == 0) return true;
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;
1783 var found: usize = 0;1793 var found: usize = 0;
17841794
1785 for (haystack) |item| {1795 if (use_vectors_for_comparison and
1786 if (item == needle) {1796 (@typeInfo(T) == .int or @typeInfo(T) == .float) and std.math.isPowerOfTwo(@bitSizeOf(T)))
1787 found += 1;1797 {
1788 if (found == expected_count) return true;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 }
1789 }1807 }
1790 }1808 }
17911809
1810 for (list[i..n]) |item| {
1811 found += @intFromBool(item == element);
1812 if (found >= minimum) return true;
1813 }
1814
1792 return false;1815 return false;
1793}1816}
17941817
1795test containsAtLeastScalar {1818test containsAtLeastScalar2 {
1796 try testing.expect(containsAtLeastScalar(u8, "aa", 0, 'a'));1819 try testing.expect(containsAtLeastScalar2(u8, "aa", 'a', 0));
1797 try testing.expect(containsAtLeastScalar(u8, "aa", 1, 'a'));1820 try testing.expect(containsAtLeastScalar2(u8, "aa", 'a', 1));
1798 try testing.expect(containsAtLeastScalar(u8, "aa", 2, 'a'));1821 try testing.expect(containsAtLeastScalar2(u8, "aa", 'a', 2));
1799 try testing.expect(!containsAtLeastScalar(u8, "aa", 3, 'a'));1822 try testing.expect(!containsAtLeastScalar2(u8, "aa", 'a', 3));
18001823
1801 try testing.expect(containsAtLeastScalar(u8, "adadda", 3, 'd'));1824 try testing.expect(containsAtLeastScalar2(u8, "adadda", 'd', 3));
1802 try testing.expect(!containsAtLeastScalar(u8, "adadda", 4, 'd'));1825 try testing.expect(!containsAtLeastScalar2(u8, "adadda", 'd', 4));
1803}1826}
18041827
1805/// Reads an integer from memory with size equal to bytes.len.1828/// 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...@@ -57,7 +57,7 @@ pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (native_o
57};57};
5858
59/// Call from Windows-specific code if you already have a WTF-16LE encoded, null terminated string.59/// 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`.
61pub fn accessW(path: [*:0]const u16) windows.GetFileAttributesError!void {61pub fn accessW(path: [*:0]const u16) windows.GetFileAttributesError!void {
62 const ret = try windows.GetFileAttributesW(path);62 const ret = try windows.GetFileAttributesW(path);
63 if (ret != windows.INVALID_FILE_ATTRIBUTES) {63 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....@@ -137,8 +137,6 @@ pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix.
137 switch (err) {137 switch (err) {
138 error.NotLink => unreachable,138 error.NotLink => unreachable,
139 error.BadPathName => unreachable,139 error.BadPathName => unreachable,
140 error.InvalidUtf8 => unreachable, // WASI-only
141 error.InvalidWtf8 => unreachable, // Windows-only
142 error.UnsupportedReparsePointType => unreachable, // Windows-only140 error.UnsupportedReparsePointType => unreachable, // Windows-only
143 error.NetworkNotFound => unreachable, // Windows-only141 error.NetworkNotFound => unreachable, // Windows-only
144 else => |e| return e,142 else => |e| return e,
...@@ -153,7 +151,6 @@ pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix....@@ -153,7 +151,6 @@ pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix.
153 const target = posix.readlinkZ(proc_path, out_buffer) catch |err| switch (err) {151 const target = posix.readlinkZ(proc_path, out_buffer) catch |err| switch (err) {
154 error.UnsupportedReparsePointType => unreachable,152 error.UnsupportedReparsePointType => unreachable,
155 error.NotLink => unreachable,153 error.NotLink => unreachable,
156 error.InvalidUtf8 => unreachable, // WASI-only
157 else => |e| return e,154 else => |e| return e,
158 };155 };
159 return target;156 return target;
...@@ -201,28 +198,13 @@ pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix....@@ -201,28 +198,13 @@ pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix.
201 }198 }
202}199}
203200
204/// WASI-only. Same as `fstatat` but targeting WASI.201pub const FstatError = error{
205/// `pathname` should be encoded as valid UTF-8.202 SystemResources,
206/// See also `fstatat`.203 AccessDenied,
207pub fn fstatat_wasi(dirfd: posix.fd_t, pathname: []const u8, flags: wasi.lookupflags_t) posix.FStatAtError!wasi.filestat_t {204 Unexpected,
208 var stat: wasi.filestat_t = undefined;205};
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}
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 {
226 var stat: wasi.filestat_t = undefined;208 var stat: wasi.filestat_t = undefined;
227 switch (wasi.fd_filestat_get(fd, &stat)) {209 switch (wasi.fd_filestat_get(fd, &stat)) {
228 .SUCCESS => return stat,210 .SUCCESS => return stat,
lib/std/os/emscripten.zig+1-44
...@@ -479,50 +479,7 @@ pub const SHUT = struct {...@@ -479,50 +479,7 @@ pub const SHUT = struct {
479 pub const RDWR = 2;479 pub const RDWR = 2;
480};480};
481481
482pub const SIG = struct {482pub const SIG = linux.SIG;
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};
526483
527pub const Sigaction = extern struct {484pub const Sigaction = extern struct {
528 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;485 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;
lib/std/os/linux.zig+146-191
...@@ -1,10 +1,8 @@...@@ -1,10 +1,8 @@
1//! This file provides the system interface functions for Linux matching those1//! This file provides the system interface functions for Linux matching those
2//! that are provided by libc, whether or not libc is linked. The following2//! that are provided by libc, whether or not libc is linked. The following
3//! abstractions are made:3//! abstractions are made:
4//! * Work around kernel bugs and limitations. For example, see sendmmsg.
5//! * Implement all the syscalls in the same way that libc functions will4//! * Implement all the syscalls in the same way that libc functions will
6//! provide `rename` when only the `renameat` syscall exists.5//! provide `rename` when only the `renameat` syscall exists.
7//! * Does not support POSIX thread cancellation.
8const std = @import("../std.zig");6const std = @import("../std.zig");
9const builtin = @import("builtin");7const builtin = @import("builtin");
10const assert = std.debug.assert;8const assert = std.debug.assert;
...@@ -624,7 +622,7 @@ pub fn fork() usize {...@@ -624,7 +622,7 @@ pub fn fork() usize {
624 } else if (@hasField(SYS, "fork")) {622 } else if (@hasField(SYS, "fork")) {
625 return syscall0(.fork);623 return syscall0(.fork);
626 } else {624 } else {
627 return syscall2(.clone, SIG.CHLD, 0);625 return syscall2(.clone, @intFromEnum(SIG.CHLD), 0);
628 }626 }
629}627}
630628
...@@ -1534,16 +1532,16 @@ pub fn getrandom(buf: [*]u8, count: usize, flags: u32) usize {...@@ -1534,16 +1532,16 @@ pub fn getrandom(buf: [*]u8, count: usize, flags: u32) usize {
1534 return syscall3(.getrandom, @intFromPtr(buf), count, flags);1532 return syscall3(.getrandom, @intFromPtr(buf), count, flags);
1535}1533}
15361534
1537pub fn kill(pid: pid_t, sig: i32) usize {1535pub fn kill(pid: pid_t, sig: SIG) usize {
1538 return syscall2(.kill, @as(usize, @bitCast(@as(isize, pid))), @as(usize, @bitCast(@as(isize, sig))));1536 return syscall2(.kill, @as(usize, @bitCast(@as(isize, pid))), @intFromEnum(sig));
1539}1537}
15401538
1541pub fn tkill(tid: pid_t, sig: i32) usize {1539pub fn tkill(tid: pid_t, sig: SIG) usize {
1542 return syscall2(.tkill, @as(usize, @bitCast(@as(isize, tid))), @as(usize, @bitCast(@as(isize, sig))));1540 return syscall2(.tkill, @as(usize, @bitCast(@as(isize, tid))), @intFromEnum(sig));
1543}1541}
15441542
1545pub fn tgkill(tgid: pid_t, tid: pid_t, sig: i32) usize {1543pub fn tgkill(tgid: pid_t, tid: pid_t, sig: SIG) usize {
1546 return syscall3(.tgkill, @as(usize, @bitCast(@as(isize, tgid))), @as(usize, @bitCast(@as(isize, tid))), @as(usize, @bitCast(@as(isize, sig))));1544 return syscall3(.tgkill, @as(usize, @bitCast(@as(isize, tgid))), @as(usize, @bitCast(@as(isize, tid))), @intFromEnum(sig));
1547}1545}
15481546
1549pub fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8) usize {1547pub fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8) usize {
...@@ -1836,7 +1834,7 @@ pub fn seteuid(euid: uid_t) usize {...@@ -1836,7 +1834,7 @@ pub fn seteuid(euid: uid_t) usize {
1836 // id will not be changed. Since uid_t is unsigned, this wraps around to the1834 // id will not be changed. Since uid_t is unsigned, this wraps around to the
1837 // max value in C.1835 // max value in C.
1838 comptime assert(@typeInfo(uid_t) == .int and @typeInfo(uid_t).int.signedness == .unsigned);1836 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));
1840}1838}
18411839
1842pub fn setegid(egid: gid_t) usize {1840pub fn setegid(egid: gid_t) usize {
...@@ -1847,7 +1845,7 @@ pub fn setegid(egid: gid_t) usize {...@@ -1847,7 +1845,7 @@ pub fn setegid(egid: gid_t) usize {
1847 // id will not be changed. Since gid_t is unsigned, this wraps around to the1845 // id will not be changed. Since gid_t is unsigned, this wraps around to the
1848 // max value in C.1846 // max value in C.
1849 comptime assert(@typeInfo(uid_t) == .int and @typeInfo(uid_t).int.signedness == .unsigned);1847 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));
1851}1849}
18521850
1853pub fn getresuid(ruid: *uid_t, euid: *uid_t, suid: *uid_t) usize {1851pub 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: ?*...@@ -1925,11 +1923,11 @@ pub fn sigprocmask(flags: u32, noalias set: ?*const sigset_t, noalias oldset: ?*
1925 return syscall4(.rt_sigprocmask, flags, @intFromPtr(set), @intFromPtr(oldset), NSIG / 8);1923 return syscall4(.rt_sigprocmask, flags, @intFromPtr(set), @intFromPtr(oldset), NSIG / 8);
1926}1924}
19271925
1928pub fn sigaction(sig: u8, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) usize {1926pub fn sigaction(sig: SIG, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) usize {
1929 assert(sig > 0);1927 assert(@intFromEnum(sig) > 0);
1930 assert(sig < NSIG);1928 assert(@intFromEnum(sig) < NSIG);
1931 assert(sig != SIG.KILL);1929 assert(sig != .KILL);
1932 assert(sig != SIG.STOP);1930 assert(sig != .STOP);
19331931
1934 var ksa: k_sigaction = undefined;1932 var ksa: k_sigaction = undefined;
1935 var oldksa: k_sigaction = undefined;1933 var oldksa: k_sigaction = undefined;
...@@ -1960,8 +1958,8 @@ pub fn sigaction(sig: u8, noalias act: ?*const Sigaction, noalias oact: ?*Sigact...@@ -1960,8 +1958,8 @@ pub fn sigaction(sig: u8, noalias act: ?*const Sigaction, noalias oact: ?*Sigact
19601958
1961 const result = switch (native_arch) {1959 const result = switch (native_arch) {
1962 // The sparc version of rt_sigaction needs the restorer function to be passed as an argument too.1960 // 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),1961 .sparc, .sparc64 => syscall5(.rt_sigaction, @intFromEnum(sig), ksa_arg, oldksa_arg, @intFromPtr(ksa.restorer), mask_size),
1964 else => syscall4(.rt_sigaction, sig, ksa_arg, oldksa_arg, mask_size),1962 else => syscall4(.rt_sigaction, @intFromEnum(sig), ksa_arg, oldksa_arg, mask_size),
1965 };1963 };
1966 if (E.init(result) != .SUCCESS) return result;1964 if (E.init(result) != .SUCCESS) return result;
19671965
...@@ -2011,27 +2009,27 @@ pub fn sigfillset() sigset_t {...@@ -2011,27 +2009,27 @@ pub fn sigfillset() sigset_t {
2011 return [_]SigsetElement{~@as(SigsetElement, 0)} ** sigset_len;2009 return [_]SigsetElement{~@as(SigsetElement, 0)} ** sigset_len;
2012}2010}
20132011
2014fn sigset_bit_index(sig: usize) struct { word: usize, mask: SigsetElement } {2012fn sigset_bit_index(sig: SIG) struct { word: usize, mask: SigsetElement } {
2015 assert(sig > 0);2013 assert(@intFromEnum(sig) > 0);
2016 assert(sig < NSIG);2014 assert(@intFromEnum(sig) < NSIG);
2017 const bit = sig - 1;2015 const bit = @intFromEnum(sig) - 1;
2018 return .{2016 return .{
2019 .word = bit / @bitSizeOf(SigsetElement),2017 .word = bit / @bitSizeOf(SigsetElement),
2020 .mask = @as(SigsetElement, 1) << @truncate(bit % @bitSizeOf(SigsetElement)),2018 .mask = @as(SigsetElement, 1) << @truncate(bit % @bitSizeOf(SigsetElement)),
2021 };2019 };
2022}2020}
20232021
2024pub fn sigaddset(set: *sigset_t, sig: usize) void {2022pub fn sigaddset(set: *sigset_t, sig: SIG) void {
2025 const index = sigset_bit_index(sig);2023 const index = sigset_bit_index(sig);
2026 (set.*)[index.word] |= index.mask;2024 (set.*)[index.word] |= index.mask;
2027}2025}
20282026
2029pub fn sigdelset(set: *sigset_t, sig: usize) void {2027pub fn sigdelset(set: *sigset_t, sig: SIG) void {
2030 const index = sigset_bit_index(sig);2028 const index = sigset_bit_index(sig);
2031 (set.*)[index.word] ^= index.mask;2029 (set.*)[index.word] ^= index.mask;
2032}2030}
20332031
2034pub fn sigismember(set: *const sigset_t, sig: usize) bool {2032pub fn sigismember(set: *const sigset_t, sig: SIG) bool {
2035 const index = sigset_bit_index(sig);2033 const index = sigset_bit_index(sig);
2036 return ((set.*)[index.word] & index.mask) != 0;2034 return ((set.*)[index.word] & index.mask) != 0;
2037}2035}
...@@ -2081,44 +2079,7 @@ pub fn sendmsg(fd: i32, msg: *const msghdr_const, flags: u32) usize {...@@ -2081,44 +2079,7 @@ pub fn sendmsg(fd: i32, msg: *const msghdr_const, flags: u32) usize {
2081 }2079 }
2082}2080}
20832081
2084pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize {2082pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr, 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 }
2122 return syscall4(.sendmmsg, @as(usize, @bitCast(@as(isize, fd))), @intFromPtr(msgvec), vlen, flags);2083 return syscall4(.sendmmsg, @as(usize, @bitCast(@as(isize, fd))), @intFromPtr(msgvec), vlen, flags);
2123}2084}
21242085
...@@ -2674,11 +2635,11 @@ pub fn pidfd_getfd(pidfd: fd_t, targetfd: fd_t, flags: u32) usize {...@@ -2674,11 +2635,11 @@ pub fn pidfd_getfd(pidfd: fd_t, targetfd: fd_t, flags: u32) usize {
2674 );2635 );
2675}2636}
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 {
2678 return syscall4(2639 return syscall4(
2679 .pidfd_send_signal,2640 .pidfd_send_signal,
2680 @as(usize, @bitCast(@as(isize, pidfd))),2641 @as(usize, @bitCast(@as(isize, pidfd))),
2681 @as(usize, @bitCast(@as(isize, sig))),2642 @intFromEnum(sig),
2682 @intFromPtr(info),2643 @intFromPtr(info),
2683 flags,2644 flags,
2684 );2645 );
...@@ -3775,136 +3736,138 @@ pub const SA = if (is_mips) struct {...@@ -3775,136 +3736,138 @@ pub const SA = if (is_mips) struct {
3775 pub const RESTORER = 0x04000000;3736 pub const RESTORER = 0x04000000;
3776};3737};
37773738
3778pub const SIG = if (is_mips) struct {3739pub const SIG = if (is_mips) enum(u32) {
3779 pub const BLOCK = 1;3740 pub const BLOCK = 1;
3780 pub const UNBLOCK = 2;3741 pub const UNBLOCK = 2;
3781 pub const SETMASK = 3;3742 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
3818 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));3744 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
3819 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);3745 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
3820 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);3746 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) {
3822 pub const BLOCK = 1;3784 pub const BLOCK = 1;
3823 pub const UNBLOCK = 2;3785 pub const UNBLOCK = 2;
3824 pub const SETMASK = 4;3786 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
3862 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));3788 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
3863 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);3789 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
3864 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);3790 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) {
3866 pub const BLOCK = 0;3829 pub const BLOCK = 0;
3867 pub const UNBLOCK = 1;3830 pub const UNBLOCK = 1;
3868 pub const SETMASK = 2;3831 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
3905 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));3833 pub const ERR: ?Sigaction.handler_fn = @ptrFromInt(maxInt(usize));
3906 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);3834 pub const DFL: ?Sigaction.handler_fn = @ptrFromInt(0);
3907 pub const IGN: ?Sigaction.handler_fn = @ptrFromInt(1);3835 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,
3908};3871};
39093872
3910pub const kernel_rwf = u32;3873pub const kernel_rwf = u32;
...@@ -5825,7 +5788,7 @@ pub const TFD = switch (native_arch) {...@@ -5825,7 +5788,7 @@ pub const TFD = switch (native_arch) {
5825};5788};
58265789
5827const k_sigaction_funcs = struct {5790const 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;
5829 const restorer = *const fn () callconv(.c) void;5792 const restorer = *const fn () callconv(.c) void;
5830};5793};
58315794
...@@ -5856,8 +5819,8 @@ pub const k_sigaction = switch (native_arch) {...@@ -5856,8 +5819,8 @@ pub const k_sigaction = switch (native_arch) {
5856///5819///
5857/// Renamed from `sigaction` to `Sigaction` to avoid conflict with the syscall.5820/// Renamed from `sigaction` to `Sigaction` to avoid conflict with the syscall.
5858pub const Sigaction = struct {5821pub const Sigaction = struct {
5859 pub const handler_fn = *align(1) const fn (i32) callconv(.c) void;5822 pub const handler_fn = *align(1) const fn (SIG) callconv(.c) void;
5860 pub const sigaction_fn = *const fn (i32, *const siginfo_t, ?*anyopaque) callconv(.c) void;5823 pub const sigaction_fn = *const fn (SIG, *const siginfo_t, ?*anyopaque) callconv(.c) void;
58615824
5862 handler: extern union {5825 handler: extern union {
5863 handler: ?handler_fn,5826 handler: ?handler_fn,
...@@ -5994,11 +5957,6 @@ pub const mmsghdr = extern struct {...@@ -5994,11 +5957,6 @@ pub const mmsghdr = extern struct {
5994 len: u32,5957 len: u32,
5995};5958};
59965959
5997pub const mmsghdr_const = extern struct {
5998 hdr: msghdr_const,
5999 len: u32,
6000};
6001
6002pub const epoll_data = extern union {5960pub const epoll_data = extern union {
6003 ptr: usize,5961 ptr: usize,
6004 fd: i32,5962 fd: i32,
...@@ -6304,14 +6262,14 @@ const siginfo_fields_union = extern union {...@@ -6304,14 +6262,14 @@ const siginfo_fields_union = extern union {
63046262
6305pub const siginfo_t = if (is_mips)6263pub const siginfo_t = if (is_mips)
6306 extern struct {6264 extern struct {
6307 signo: i32,6265 signo: SIG,
6308 code: i32,6266 code: i32,
6309 errno: i32,6267 errno: i32,
6310 fields: siginfo_fields_union,6268 fields: siginfo_fields_union,
6311 }6269 }
6312else6270else
6313 extern struct {6271 extern struct {
6314 signo: i32,6272 signo: SIG,
6315 errno: i32,6273 errno: i32,
6316 code: i32,6274 code: i32,
6317 fields: siginfo_fields_union,6275 fields: siginfo_fields_union,
...@@ -7140,12 +7098,6 @@ pub const IPPROTO = struct {...@@ -7140,12 +7098,6 @@ pub const IPPROTO = struct {
7140 pub const MAX = 256;7098 pub const MAX = 256;
7141};7099};
71427100
7143pub const RR = struct {
7144 pub const A = 1;
7145 pub const CNAME = 5;
7146 pub const AAAA = 28;
7147};
7148
7149pub const tcp_repair_opt = extern struct {7101pub const tcp_repair_opt = extern struct {
7150 opt_code: u32,7102 opt_code: u32,
7151 opt_val: u32,7103 opt_val: u32,
...@@ -8700,7 +8652,7 @@ pub const PR = enum(i32) {...@@ -8700,7 +8652,7 @@ pub const PR = enum(i32) {
8700 pub const SET_MM_MAP = 14;8652 pub const SET_MM_MAP = 14;
8701 pub const SET_MM_MAP_SIZE = 15;8653 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
8705 pub const FP_MODE_FR = 1 << 0;8657 pub const FP_MODE_FR = 1 << 0;
8706 pub const FP_MODE_FRE = 1 << 1;8658 pub const FP_MODE_FRE = 1 << 1;
...@@ -9884,8 +9836,10 @@ pub const msghdr = extern struct {...@@ -9884,8 +9836,10 @@ pub const msghdr = extern struct {
9884 name: ?*sockaddr,9836 name: ?*sockaddr,
9885 namelen: socklen_t,9837 namelen: socklen_t,
9886 iov: [*]iovec,9838 iov: [*]iovec,
9839 /// The kernel and glibc use `usize` for this field; POSIX and musl use `c_int`.
9887 iovlen: usize,9840 iovlen: usize,
9888 control: ?*anyopaque,9841 control: ?*anyopaque,
9842 /// The kernel and glibc use `usize` for this field; POSIX and musl use `socklen_t`.
9889 controllen: usize,9843 controllen: usize,
9890 flags: u32,9844 flags: u32,
9891};9845};
...@@ -9902,6 +9856,7 @@ pub const msghdr_const = extern struct {...@@ -9902,6 +9856,7 @@ pub const msghdr_const = extern struct {
99029856
9903// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/socket.h?id=b320789d6883cc00ac78ce83bccbfe7ed58afcf0#n1059857// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/socket.h?id=b320789d6883cc00ac78ce83bccbfe7ed58afcf0#n105
9904pub const cmsghdr = extern struct {9858pub const cmsghdr = extern struct {
9859 /// The kernel and glibc use `usize` for this field; musl uses `socklen_t`.
9905 len: usize,9860 len: usize,
9906 level: i32,9861 level: i32,
9907 type: i32,9862 type: i32,
lib/std/os/linux/IoUring.zig+161-138
...@@ -3,14 +3,14 @@ const std = @import("std");...@@ -3,14 +3,14 @@ const std = @import("std");
3const builtin = @import("builtin");3const builtin = @import("builtin");
4const assert = std.debug.assert;4const assert = std.debug.assert;
5const mem = std.mem;5const mem = std.mem;
6const net = std.net;6const net = std.Io.net;
7const posix = std.posix;7const posix = std.posix;
8const linux = std.os.linux;8const linux = std.os.linux;
9const testing = std.testing;9const testing = std.testing;
10const is_linux = builtin.os.tag == .linux;10const is_linux = builtin.os.tag == .linux;
11const page_size_min = std.heap.page_size_min;11const page_size_min = std.heap.page_size_min;
1212
13fd: posix.fd_t = -1,13fd: linux.fd_t = -1,
14sq: SubmissionQueue,14sq: SubmissionQueue,
15cq: CompletionQueue,15cq: CompletionQueue,
16flags: u32,16flags: u32,
...@@ -62,7 +62,7 @@ pub fn init_params(entries: u16, p: *linux.io_uring_params) !IoUring {...@@ -62,7 +62,7 @@ pub fn init_params(entries: u16, p: *linux.io_uring_params) !IoUring {
62 .NOSYS => return error.SystemOutdated,62 .NOSYS => return error.SystemOutdated,
63 else => |errno| return posix.unexpectedErrno(errno),63 else => |errno| return posix.unexpectedErrno(errno),
64 }64 }
65 const fd = @as(posix.fd_t, @intCast(res));65 const fd = @as(linux.fd_t, @intCast(res));
66 assert(fd >= 0);66 assert(fd >= 0);
67 errdefer posix.close(fd);67 errdefer posix.close(fd);
6868
...@@ -341,7 +341,7 @@ pub fn cq_advance(self: *IoUring, count: u32) void {...@@ -341,7 +341,7 @@ pub fn cq_advance(self: *IoUring, count: u32) void {
341/// apply to the write, since the fsync may complete before the write is issued to the disk.341/// apply to the write, since the fsync may complete before the write is issued to the disk.
342/// You should preferably use `link_with_next_sqe()` on a write's SQE to link it with an fsync,342/// You should preferably use `link_with_next_sqe()` on a write's SQE to link it with an fsync,
343/// or else insert a full write barrier using `drain_previous_sqes()` when queueing an fsync.343/// 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 {
345 const sqe = try self.get_sqe();345 const sqe = try self.get_sqe();
346 sqe.prep_fsync(fd, flags);346 sqe.prep_fsync(fd, flags);
347 sqe.user_data = user_data;347 sqe.user_data = user_data;
...@@ -386,7 +386,7 @@ pub const ReadBuffer = union(enum) {...@@ -386,7 +386,7 @@ pub const ReadBuffer = union(enum) {
386pub fn read(386pub fn read(
387 self: *IoUring,387 self: *IoUring,
388 user_data: u64,388 user_data: u64,
389 fd: posix.fd_t,389 fd: linux.fd_t,
390 buffer: ReadBuffer,390 buffer: ReadBuffer,
391 offset: u64,391 offset: u64,
392) !*linux.io_uring_sqe {392) !*linux.io_uring_sqe {
...@@ -409,7 +409,7 @@ pub fn read(...@@ -409,7 +409,7 @@ pub fn read(
409pub fn write(409pub fn write(
410 self: *IoUring,410 self: *IoUring,
411 user_data: u64,411 user_data: u64,
412 fd: posix.fd_t,412 fd: linux.fd_t,
413 buffer: []const u8,413 buffer: []const u8,
414 offset: u64,414 offset: u64,
415) !*linux.io_uring_sqe {415) !*linux.io_uring_sqe {
...@@ -433,7 +433,7 @@ pub fn write(...@@ -433,7 +433,7 @@ pub fn write(
433/// See https://github.com/axboe/liburing/issues/291433/// See https://github.com/axboe/liburing/issues/291
434///434///
435/// Returns a pointer to the SQE so that you can further modify the SQE for advanced use cases.435/// 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 {
437 const sqe = try self.get_sqe();437 const sqe = try self.get_sqe();
438 sqe.prep_splice(fd_in, off_in, fd_out, off_out, len);438 sqe.prep_splice(fd_in, off_in, fd_out, off_out, len);
439 sqe.user_data = user_data;439 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...@@ -448,7 +448,7 @@ pub fn splice(self: *IoUring, user_data: u64, fd_in: posix.fd_t, off_in: u64, fd
448pub fn read_fixed(448pub fn read_fixed(
449 self: *IoUring,449 self: *IoUring,
450 user_data: u64,450 user_data: u64,
451 fd: posix.fd_t,451 fd: linux.fd_t,
452 buffer: *posix.iovec,452 buffer: *posix.iovec,
453 offset: u64,453 offset: u64,
454 buffer_index: u16,454 buffer_index: u16,
...@@ -466,7 +466,7 @@ pub fn read_fixed(...@@ -466,7 +466,7 @@ pub fn read_fixed(
466pub fn writev(466pub fn writev(
467 self: *IoUring,467 self: *IoUring,
468 user_data: u64,468 user_data: u64,
469 fd: posix.fd_t,469 fd: linux.fd_t,
470 iovecs: []const posix.iovec_const,470 iovecs: []const posix.iovec_const,
471 offset: u64,471 offset: u64,
472) !*linux.io_uring_sqe {472) !*linux.io_uring_sqe {
...@@ -484,7 +484,7 @@ pub fn writev(...@@ -484,7 +484,7 @@ pub fn writev(
484pub fn write_fixed(484pub fn write_fixed(
485 self: *IoUring,485 self: *IoUring,
486 user_data: u64,486 user_data: u64,
487 fd: posix.fd_t,487 fd: linux.fd_t,
488 buffer: *posix.iovec,488 buffer: *posix.iovec,
489 offset: u64,489 offset: u64,
490 buffer_index: u16,490 buffer_index: u16,
...@@ -501,7 +501,7 @@ pub fn write_fixed(...@@ -501,7 +501,7 @@ pub fn write_fixed(
501pub fn accept(501pub fn accept(
502 self: *IoUring,502 self: *IoUring,
503 user_data: u64,503 user_data: u64,
504 fd: posix.fd_t,504 fd: linux.fd_t,
505 addr: ?*posix.sockaddr,505 addr: ?*posix.sockaddr,
506 addrlen: ?*posix.socklen_t,506 addrlen: ?*posix.socklen_t,
507 flags: u32,507 flags: u32,
...@@ -523,7 +523,7 @@ pub fn accept(...@@ -523,7 +523,7 @@ pub fn accept(
523pub fn accept_multishot(523pub fn accept_multishot(
524 self: *IoUring,524 self: *IoUring,
525 user_data: u64,525 user_data: u64,
526 fd: posix.fd_t,526 fd: linux.fd_t,
527 addr: ?*posix.sockaddr,527 addr: ?*posix.sockaddr,
528 addrlen: ?*posix.socklen_t,528 addrlen: ?*posix.socklen_t,
529 flags: u32,529 flags: u32,
...@@ -548,7 +548,7 @@ pub fn accept_multishot(...@@ -548,7 +548,7 @@ pub fn accept_multishot(
548pub fn accept_direct(548pub fn accept_direct(
549 self: *IoUring,549 self: *IoUring,
550 user_data: u64,550 user_data: u64,
551 fd: posix.fd_t,551 fd: linux.fd_t,
552 addr: ?*posix.sockaddr,552 addr: ?*posix.sockaddr,
553 addrlen: ?*posix.socklen_t,553 addrlen: ?*posix.socklen_t,
554 flags: u32,554 flags: u32,
...@@ -564,7 +564,7 @@ pub fn accept_direct(...@@ -564,7 +564,7 @@ pub fn accept_direct(
564pub fn accept_multishot_direct(564pub fn accept_multishot_direct(
565 self: *IoUring,565 self: *IoUring,
566 user_data: u64,566 user_data: u64,
567 fd: posix.fd_t,567 fd: linux.fd_t,
568 addr: ?*posix.sockaddr,568 addr: ?*posix.sockaddr,
569 addrlen: ?*posix.socklen_t,569 addrlen: ?*posix.socklen_t,
570 flags: u32,570 flags: u32,
...@@ -580,7 +580,7 @@ pub fn accept_multishot_direct(...@@ -580,7 +580,7 @@ pub fn accept_multishot_direct(
580pub fn connect(580pub fn connect(
581 self: *IoUring,581 self: *IoUring,
582 user_data: u64,582 user_data: u64,
583 fd: posix.fd_t,583 fd: linux.fd_t,
584 addr: *const posix.sockaddr,584 addr: *const posix.sockaddr,
585 addrlen: posix.socklen_t,585 addrlen: posix.socklen_t,
586) !*linux.io_uring_sqe {586) !*linux.io_uring_sqe {
...@@ -595,8 +595,8 @@ pub fn connect(...@@ -595,8 +595,8 @@ pub fn connect(
595pub fn epoll_ctl(595pub fn epoll_ctl(
596 self: *IoUring,596 self: *IoUring,
597 user_data: u64,597 user_data: u64,
598 epfd: posix.fd_t,598 epfd: linux.fd_t,
599 fd: posix.fd_t,599 fd: linux.fd_t,
600 op: u32,600 op: u32,
601 ev: ?*linux.epoll_event,601 ev: ?*linux.epoll_event,
602) !*linux.io_uring_sqe {602) !*linux.io_uring_sqe {
...@@ -626,7 +626,7 @@ pub const RecvBuffer = union(enum) {...@@ -626,7 +626,7 @@ pub const RecvBuffer = union(enum) {
626pub fn recv(626pub fn recv(
627 self: *IoUring,627 self: *IoUring,
628 user_data: u64,628 user_data: u64,
629 fd: posix.fd_t,629 fd: linux.fd_t,
630 buffer: RecvBuffer,630 buffer: RecvBuffer,
631 flags: u32,631 flags: u32,
632) !*linux.io_uring_sqe {632) !*linux.io_uring_sqe {
...@@ -650,7 +650,7 @@ pub fn recv(...@@ -650,7 +650,7 @@ pub fn recv(
650pub fn send(650pub fn send(
651 self: *IoUring,651 self: *IoUring,
652 user_data: u64,652 user_data: u64,
653 fd: posix.fd_t,653 fd: linux.fd_t,
654 buffer: []const u8,654 buffer: []const u8,
655 flags: u32,655 flags: u32,
656) !*linux.io_uring_sqe {656) !*linux.io_uring_sqe {
...@@ -678,7 +678,7 @@ pub fn send(...@@ -678,7 +678,7 @@ pub fn send(
678pub fn send_zc(678pub fn send_zc(
679 self: *IoUring,679 self: *IoUring,
680 user_data: u64,680 user_data: u64,
681 fd: posix.fd_t,681 fd: linux.fd_t,
682 buffer: []const u8,682 buffer: []const u8,
683 send_flags: u32,683 send_flags: u32,
684 zc_flags: u16,684 zc_flags: u16,
...@@ -695,7 +695,7 @@ pub fn send_zc(...@@ -695,7 +695,7 @@ pub fn send_zc(
695pub fn send_zc_fixed(695pub fn send_zc_fixed(
696 self: *IoUring,696 self: *IoUring,
697 user_data: u64,697 user_data: u64,
698 fd: posix.fd_t,698 fd: linux.fd_t,
699 buffer: []const u8,699 buffer: []const u8,
700 send_flags: u32,700 send_flags: u32,
701 zc_flags: u16,701 zc_flags: u16,
...@@ -713,8 +713,8 @@ pub fn send_zc_fixed(...@@ -713,8 +713,8 @@ pub fn send_zc_fixed(
713pub fn recvmsg(713pub fn recvmsg(
714 self: *IoUring,714 self: *IoUring,
715 user_data: u64,715 user_data: u64,
716 fd: posix.fd_t,716 fd: linux.fd_t,
717 msg: *posix.msghdr,717 msg: *linux.msghdr,
718 flags: u32,718 flags: u32,
719) !*linux.io_uring_sqe {719) !*linux.io_uring_sqe {
720 const sqe = try self.get_sqe();720 const sqe = try self.get_sqe();
...@@ -729,8 +729,8 @@ pub fn recvmsg(...@@ -729,8 +729,8 @@ pub fn recvmsg(
729pub fn sendmsg(729pub fn sendmsg(
730 self: *IoUring,730 self: *IoUring,
731 user_data: u64,731 user_data: u64,
732 fd: posix.fd_t,732 fd: linux.fd_t,
733 msg: *const posix.msghdr_const,733 msg: *const linux.msghdr_const,
734 flags: u32,734 flags: u32,
735) !*linux.io_uring_sqe {735) !*linux.io_uring_sqe {
736 const sqe = try self.get_sqe();736 const sqe = try self.get_sqe();
...@@ -745,8 +745,8 @@ pub fn sendmsg(...@@ -745,8 +745,8 @@ pub fn sendmsg(
745pub fn sendmsg_zc(745pub fn sendmsg_zc(
746 self: *IoUring,746 self: *IoUring,
747 user_data: u64,747 user_data: u64,
748 fd: posix.fd_t,748 fd: linux.fd_t,
749 msg: *const posix.msghdr_const,749 msg: *const linux.msghdr_const,
750 flags: u32,750 flags: u32,
751) !*linux.io_uring_sqe {751) !*linux.io_uring_sqe {
752 const sqe = try self.get_sqe();752 const sqe = try self.get_sqe();
...@@ -761,7 +761,7 @@ pub fn sendmsg_zc(...@@ -761,7 +761,7 @@ pub fn sendmsg_zc(
761pub fn openat(761pub fn openat(
762 self: *IoUring,762 self: *IoUring,
763 user_data: u64,763 user_data: u64,
764 fd: posix.fd_t,764 fd: linux.fd_t,
765 path: [*:0]const u8,765 path: [*:0]const u8,
766 flags: linux.O,766 flags: linux.O,
767 mode: posix.mode_t,767 mode: posix.mode_t,
...@@ -786,7 +786,7 @@ pub fn openat(...@@ -786,7 +786,7 @@ pub fn openat(
786pub fn openat_direct(786pub fn openat_direct(
787 self: *IoUring,787 self: *IoUring,
788 user_data: u64,788 user_data: u64,
789 fd: posix.fd_t,789 fd: linux.fd_t,
790 path: [*:0]const u8,790 path: [*:0]const u8,
791 flags: linux.O,791 flags: linux.O,
792 mode: posix.mode_t,792 mode: posix.mode_t,
...@@ -801,7 +801,7 @@ pub fn openat_direct(...@@ -801,7 +801,7 @@ pub fn openat_direct(
801/// Queues (but does not submit) an SQE to perform a `close(2)`.801/// Queues (but does not submit) an SQE to perform a `close(2)`.
802/// Returns a pointer to the SQE.802/// Returns a pointer to the SQE.
803/// Available since 5.6.803/// 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 {
805 const sqe = try self.get_sqe();805 const sqe = try self.get_sqe();
806 sqe.prep_close(fd);806 sqe.prep_close(fd);
807 sqe.user_data = user_data;807 sqe.user_data = user_data;
...@@ -896,7 +896,7 @@ pub fn link_timeout(...@@ -896,7 +896,7 @@ pub fn link_timeout(
896pub fn poll_add(896pub fn poll_add(
897 self: *IoUring,897 self: *IoUring,
898 user_data: u64,898 user_data: u64,
899 fd: posix.fd_t,899 fd: linux.fd_t,
900 poll_mask: u32,900 poll_mask: u32,
901) !*linux.io_uring_sqe {901) !*linux.io_uring_sqe {
902 const sqe = try self.get_sqe();902 const sqe = try self.get_sqe();
...@@ -939,7 +939,7 @@ pub fn poll_update(...@@ -939,7 +939,7 @@ pub fn poll_update(
939pub fn fallocate(939pub fn fallocate(
940 self: *IoUring,940 self: *IoUring,
941 user_data: u64,941 user_data: u64,
942 fd: posix.fd_t,942 fd: linux.fd_t,
943 mode: i32,943 mode: i32,
944 offset: u64,944 offset: u64,
945 len: u64,945 len: u64,
...@@ -955,7 +955,7 @@ pub fn fallocate(...@@ -955,7 +955,7 @@ pub fn fallocate(
955pub fn statx(955pub fn statx(
956 self: *IoUring,956 self: *IoUring,
957 user_data: u64,957 user_data: u64,
958 fd: posix.fd_t,958 fd: linux.fd_t,
959 path: [:0]const u8,959 path: [:0]const u8,
960 flags: u32,960 flags: u32,
961 mask: u32,961 mask: u32,
...@@ -1008,9 +1008,9 @@ pub fn shutdown(...@@ -1008,9 +1008,9 @@ pub fn shutdown(
1008pub fn renameat(1008pub fn renameat(
1009 self: *IoUring,1009 self: *IoUring,
1010 user_data: u64,1010 user_data: u64,
1011 old_dir_fd: posix.fd_t,1011 old_dir_fd: linux.fd_t,
1012 old_path: [*:0]const u8,1012 old_path: [*:0]const u8,
1013 new_dir_fd: posix.fd_t,1013 new_dir_fd: linux.fd_t,
1014 new_path: [*:0]const u8,1014 new_path: [*:0]const u8,
1015 flags: u32,1015 flags: u32,
1016) !*linux.io_uring_sqe {1016) !*linux.io_uring_sqe {
...@@ -1025,7 +1025,7 @@ pub fn renameat(...@@ -1025,7 +1025,7 @@ pub fn renameat(
1025pub fn unlinkat(1025pub fn unlinkat(
1026 self: *IoUring,1026 self: *IoUring,
1027 user_data: u64,1027 user_data: u64,
1028 dir_fd: posix.fd_t,1028 dir_fd: linux.fd_t,
1029 path: [*:0]const u8,1029 path: [*:0]const u8,
1030 flags: u32,1030 flags: u32,
1031) !*linux.io_uring_sqe {1031) !*linux.io_uring_sqe {
...@@ -1040,7 +1040,7 @@ pub fn unlinkat(...@@ -1040,7 +1040,7 @@ pub fn unlinkat(
1040pub fn mkdirat(1040pub fn mkdirat(
1041 self: *IoUring,1041 self: *IoUring,
1042 user_data: u64,1042 user_data: u64,
1043 dir_fd: posix.fd_t,1043 dir_fd: linux.fd_t,
1044 path: [*:0]const u8,1044 path: [*:0]const u8,
1045 mode: posix.mode_t,1045 mode: posix.mode_t,
1046) !*linux.io_uring_sqe {1046) !*linux.io_uring_sqe {
...@@ -1056,7 +1056,7 @@ pub fn symlinkat(...@@ -1056,7 +1056,7 @@ pub fn symlinkat(
1056 self: *IoUring,1056 self: *IoUring,
1057 user_data: u64,1057 user_data: u64,
1058 target: [*:0]const u8,1058 target: [*:0]const u8,
1059 new_dir_fd: posix.fd_t,1059 new_dir_fd: linux.fd_t,
1060 link_path: [*:0]const u8,1060 link_path: [*:0]const u8,
1061) !*linux.io_uring_sqe {1061) !*linux.io_uring_sqe {
1062 const sqe = try self.get_sqe();1062 const sqe = try self.get_sqe();
...@@ -1070,9 +1070,9 @@ pub fn symlinkat(...@@ -1070,9 +1070,9 @@ pub fn symlinkat(
1070pub fn linkat(1070pub fn linkat(
1071 self: *IoUring,1071 self: *IoUring,
1072 user_data: u64,1072 user_data: u64,
1073 old_dir_fd: posix.fd_t,1073 old_dir_fd: linux.fd_t,
1074 old_path: [*:0]const u8,1074 old_path: [*:0]const u8,
1075 new_dir_fd: posix.fd_t,1075 new_dir_fd: linux.fd_t,
1076 new_path: [*:0]const u8,1076 new_path: [*:0]const u8,
1077 flags: u32,1077 flags: u32,
1078) !*linux.io_uring_sqe {1078) !*linux.io_uring_sqe {
...@@ -1144,7 +1144,7 @@ pub fn waitid(...@@ -1144,7 +1144,7 @@ pub fn waitid(
1144/// Registering file descriptors will wait for the ring to idle.1144/// Registering file descriptors will wait for the ring to idle.
1145/// Files are automatically unregistered by the kernel when the ring is torn down.1145/// Files are automatically unregistered by the kernel when the ring is torn down.
1146/// An application need unregister only if it wants to register a new array of file descriptors.1146/// 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 {
1148 assert(self.fd >= 0);1148 assert(self.fd >= 0);
1149 const res = linux.io_uring_register(1149 const res = linux.io_uring_register(
1150 self.fd,1150 self.fd,
...@@ -1163,7 +1163,7 @@ pub fn register_files(self: *IoUring, fds: []const posix.fd_t) !void {...@@ -1163,7 +1163,7 @@ pub fn register_files(self: *IoUring, fds: []const posix.fd_t) !void {
1163/// * removing an existing entry (set the fd to -1)1163/// * removing an existing entry (set the fd to -1)
1164/// * replacing an existing entry with a new fd1164/// * replacing an existing entry with a new fd
1165/// Adding new file descriptors must be done with `register_files`.1165/// 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 {
1167 assert(self.fd >= 0);1167 assert(self.fd >= 0);
11681168
1169 const FilesUpdate = extern struct {1169 const FilesUpdate = extern struct {
...@@ -1232,7 +1232,7 @@ pub fn register_file_alloc_range(self: *IoUring, offset: u32, len: u32) !void {...@@ -1232,7 +1232,7 @@ pub fn register_file_alloc_range(self: *IoUring, offset: u32, len: u32) !void {
1232/// Registers the file descriptor for an eventfd that will be notified of completion events on1232/// Registers the file descriptor for an eventfd that will be notified of completion events on
1233/// an io_uring instance.1233/// an io_uring instance.
1234/// Only a single a eventfd can be registered at any given point in time.1234/// 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 {
1236 assert(self.fd >= 0);1236 assert(self.fd >= 0);
1237 const res = linux.io_uring_register(1237 const res = linux.io_uring_register(
1238 self.fd,1238 self.fd,
...@@ -1247,7 +1247,7 @@ pub fn register_eventfd(self: *IoUring, fd: posix.fd_t) !void {...@@ -1247,7 +1247,7 @@ pub fn register_eventfd(self: *IoUring, fd: posix.fd_t) !void {
1247/// an io_uring instance. Notifications are only posted for events that complete in an async manner.1247/// an io_uring instance. Notifications are only posted for events that complete in an async manner.
1248/// This means that events that complete inline while being submitted do not trigger a notification event.1248/// This means that events that complete inline while being submitted do not trigger a notification event.
1249/// Only a single eventfd can be registered at any given point in time.1249/// 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 {
1251 assert(self.fd >= 0);1251 assert(self.fd >= 0);
1252 const res = linux.io_uring_register(1252 const res = linux.io_uring_register(
1253 self.fd,1253 self.fd,
...@@ -1405,7 +1405,7 @@ pub fn socket_direct_alloc(...@@ -1405,7 +1405,7 @@ pub fn socket_direct_alloc(
1405pub fn bind(1405pub fn bind(
1406 self: *IoUring,1406 self: *IoUring,
1407 user_data: u64,1407 user_data: u64,
1408 fd: posix.fd_t,1408 fd: linux.fd_t,
1409 addr: *const posix.sockaddr,1409 addr: *const posix.sockaddr,
1410 addrlen: posix.socklen_t,1410 addrlen: posix.socklen_t,
1411 flags: u32,1411 flags: u32,
...@@ -1422,7 +1422,7 @@ pub fn bind(...@@ -1422,7 +1422,7 @@ pub fn bind(
1422pub fn listen(1422pub fn listen(
1423 self: *IoUring,1423 self: *IoUring,
1424 user_data: u64,1424 user_data: u64,
1425 fd: posix.fd_t,1425 fd: linux.fd_t,
1426 backlog: usize,1426 backlog: usize,
1427 flags: u32,1427 flags: u32,
1428) !*linux.io_uring_sqe {1428) !*linux.io_uring_sqe {
...@@ -1513,7 +1513,7 @@ pub const SubmissionQueue = struct {...@@ -1513,7 +1513,7 @@ pub const SubmissionQueue = struct {
1513 sqe_head: u32 = 0,1513 sqe_head: u32 = 0,
1514 sqe_tail: u32 = 0,1514 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 {
1517 assert(fd >= 0);1517 assert(fd >= 0);
1518 assert((p.features & linux.IORING_FEAT_SINGLE_MMAP) != 0);1518 assert((p.features & linux.IORING_FEAT_SINGLE_MMAP) != 0);
1519 const size = @max(1519 const size = @max(
...@@ -1576,7 +1576,7 @@ pub const CompletionQueue = struct {...@@ -1576,7 +1576,7 @@ pub const CompletionQueue = struct {
1576 overflow: *u32,1576 overflow: *u32,
1577 cqes: []linux.io_uring_cqe,1577 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 {
1580 assert(fd >= 0);1580 assert(fd >= 0);
1581 assert((p.features & linux.IORING_FEAT_SINGLE_MMAP) != 0);1581 assert((p.features & linux.IORING_FEAT_SINGLE_MMAP) != 0);
1582 const mmap = sq.mmap;1582 const mmap = sq.mmap;
...@@ -1677,7 +1677,7 @@ pub const BufferGroup = struct {...@@ -1677,7 +1677,7 @@ pub const BufferGroup = struct {
1677 }1677 }
16781678
1679 // Prepare recv operation which will select buffer from this group.1679 // 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 {
1681 var sqe = try self.ring.get_sqe();1681 var sqe = try self.ring.get_sqe();
1682 sqe.prep_rw(.RECV, fd, 0, 0, 0);1682 sqe.prep_rw(.RECV, fd, 0, 0, 0);
1683 sqe.rw_flags = flags;1683 sqe.rw_flags = flags;
...@@ -1688,7 +1688,7 @@ pub const BufferGroup = struct {...@@ -1688,7 +1688,7 @@ pub const BufferGroup = struct {
1688 }1688 }
16891689
1690 // Prepare multishot recv operation which will select buffer from this group.1690 // 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 {
1692 var sqe = try self.recv(user_data, fd, flags);1692 var sqe = try self.recv(user_data, fd, flags);
1693 sqe.ioprio |= linux.IORING_RECV_MULTISHOT;1693 sqe.ioprio |= linux.IORING_RECV_MULTISHOT;
1694 return sqe;1694 return sqe;
...@@ -1732,7 +1732,7 @@ pub const BufferGroup = struct {...@@ -1732,7 +1732,7 @@ pub const BufferGroup = struct {
1732/// `entries` is the number of entries requested in the buffer ring, must be power of 2.1732/// `entries` is the number of entries requested in the buffer ring, must be power of 2.
1733/// `group_id` is the chosen buffer group ID, unique in IO_Uring.1733/// `group_id` is the chosen buffer group ID, unique in IO_Uring.
1734pub fn setup_buf_ring(1734pub fn setup_buf_ring(
1735 fd: posix.fd_t,1735 fd: linux.fd_t,
1736 entries: u16,1736 entries: u16,
1737 group_id: u16,1737 group_id: u16,
1738 flags: linux.io_uring_buf_reg.Flags,1738 flags: linux.io_uring_buf_reg.Flags,
...@@ -1758,7 +1758,7 @@ pub fn setup_buf_ring(...@@ -1758,7 +1758,7 @@ pub fn setup_buf_ring(
1758}1758}
17591759
1760fn register_buf_ring(1760fn register_buf_ring(
1761 fd: posix.fd_t,1761 fd: linux.fd_t,
1762 addr: u64,1762 addr: u64,
1763 entries: u32,1763 entries: u32,
1764 group_id: u16,1764 group_id: u16,
...@@ -1780,7 +1780,7 @@ fn register_buf_ring(...@@ -1780,7 +1780,7 @@ fn register_buf_ring(
1780 try handle_register_buf_ring_result(res);1780 try handle_register_buf_ring_result(res);
1781}1781}
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 {
1784 var reg = mem.zeroInit(linux.io_uring_buf_reg, .{1784 var reg = mem.zeroInit(linux.io_uring_buf_reg, .{
1785 .bgid = group_id,1785 .bgid = group_id,
1786 });1786 });
...@@ -1802,7 +1802,7 @@ fn handle_register_buf_ring_result(res: usize) !void {...@@ -1802,7 +1802,7 @@ fn handle_register_buf_ring_result(res: usize) !void {
1802}1802}
18031803
1804// Unregisters a previously registered shared buffer ring, returned from io_uring_setup_buf_ring.1804// 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 {
1806 unregister_buf_ring(fd, group_id) catch {};1806 unregister_buf_ring(fd, group_id) catch {};
1807 var mmap: []align(page_size_min) u8 = undefined;1807 var mmap: []align(page_size_min) u8 = undefined;
1808 mmap.ptr = @ptrCast(br);1808 mmap.ptr = @ptrCast(br);
...@@ -1873,7 +1873,7 @@ test "nop" {...@@ -1873,7 +1873,7 @@ test "nop" {
1873 };1873 };
1874 defer {1874 defer {
1875 ring.deinit();1875 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");
1877 }1877 }
18781878
1879 const sqe = try ring.nop(0xaaaaaaaa);1879 const sqe = try ring.nop(0xaaaaaaaa);
...@@ -1949,7 +1949,7 @@ test "readv" {...@@ -1949,7 +1949,7 @@ test "readv" {
1949 // https://github.com/torvalds/linux/blob/v5.4/fs/io_uring.c#L3119-L3124 vs1949 // https://github.com/torvalds/linux/blob/v5.4/fs/io_uring.c#L3119-L3124 vs
1950 // https://github.com/torvalds/linux/blob/v5.8/fs/io_uring.c#L6687-L66911950 // https://github.com/torvalds/linux/blob/v5.8/fs/io_uring.c#L6687-L6691
1951 // We therefore avoid stressing sparse fd sets here:1951 // 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;
1953 const fd_index = 0;1953 const fd_index = 0;
1954 registered_fds[fd_index] = fd;1954 registered_fds[fd_index] = fd;
1955 try ring.register_files(registered_fds[0..]);1955 try ring.register_files(registered_fds[0..]);
...@@ -2361,28 +2361,31 @@ test "sendmsg/recvmsg" {...@@ -2361,28 +2361,31 @@ test "sendmsg/recvmsg" {
2361 };2361 };
2362 defer ring.deinit();2362 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);
2367 defer posix.close(server);2370 defer posix.close(server);
2368 try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEPORT, &mem.toBytes(@as(c_int, 1)));2371 try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEPORT, &mem.toBytes(@as(c_int, 1)));
2369 try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));2372 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
2372 // set address_server to the OS-chosen IP/port.2375 // set address_server to the OS-chosen IP/port.
2373 var slen: posix.socklen_t = address_server.getOsSockLen();2376 var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in);
2374 try posix.getsockname(server, &address_server.any, &slen);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);
2377 defer posix.close(client);2380 defer posix.close(client);
23782381
2379 const buffer_send = [_]u8{42} ** 128;2382 const buffer_send = [_]u8{42} ** 128;
2380 const iovecs_send = [_]posix.iovec_const{2383 const iovecs_send = [_]posix.iovec_const{
2381 posix.iovec_const{ .base = &buffer_send, .len = buffer_send.len },2384 posix.iovec_const{ .base = &buffer_send, .len = buffer_send.len },
2382 };2385 };
2383 const msg_send: posix.msghdr_const = .{2386 const msg_send: linux.msghdr_const = .{
2384 .name = &address_server.any,2387 .name = addrAny(&address_server),
2385 .namelen = address_server.getOsSockLen(),2388 .namelen = @sizeOf(linux.sockaddr.in),
2386 .iov = &iovecs_send,2389 .iov = &iovecs_send,
2387 .iovlen = 1,2390 .iovlen = 1,
2388 .control = null,2391 .control = null,
...@@ -2398,11 +2401,13 @@ test "sendmsg/recvmsg" {...@@ -2398,11 +2401,13 @@ test "sendmsg/recvmsg" {
2398 var iovecs_recv = [_]posix.iovec{2401 var iovecs_recv = [_]posix.iovec{
2399 posix.iovec{ .base = &buffer_recv, .len = buffer_recv.len },2402 posix.iovec{ .base = &buffer_recv, .len = buffer_recv.len },
2400 };2403 };
2401 const addr = [_]u8{0} ** 4;2404 var address_recv: linux.sockaddr.in = .{
2402 var address_recv = net.Address.initIp4(addr, 0);2405 .port = 0,
2403 var msg_recv: posix.msghdr = .{2406 .addr = 0,
2404 .name = &address_recv.any,2407 };
2405 .namelen = address_recv.getOsSockLen(),2408 var msg_recv: linux.msghdr = .{
2409 .name = addrAny(&address_recv),
2410 .namelen = @sizeOf(linux.sockaddr.in),
2406 .iov = &iovecs_recv,2411 .iov = &iovecs_recv,
2407 .iovlen = 1,2412 .iovlen = 1,
2408 .control = null,2413 .control = null,
...@@ -2441,6 +2446,8 @@ test "sendmsg/recvmsg" {...@@ -2441,6 +2446,8 @@ test "sendmsg/recvmsg" {
2441test "timeout (after a relative time)" {2446test "timeout (after a relative time)" {
2442 if (!is_linux) return error.SkipZigTest;2447 if (!is_linux) return error.SkipZigTest;
24432448
2449 const io = testing.io;
2450
2444 var ring = IoUring.init(1, 0) catch |err| switch (err) {2451 var ring = IoUring.init(1, 0) catch |err| switch (err) {
2445 error.SystemOutdated => return error.SkipZigTest,2452 error.SystemOutdated => return error.SkipZigTest,
2446 error.PermissionDenied => return error.SkipZigTest,2453 error.PermissionDenied => return error.SkipZigTest,
...@@ -2452,12 +2459,12 @@ test "timeout (after a relative time)" {...@@ -2452,12 +2459,12 @@ test "timeout (after a relative time)" {
2452 const margin = 5;2459 const margin = 5;
2453 const ts: linux.kernel_timespec = .{ .sec = 0, .nsec = ms * 1000000 };2460 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);
2456 const sqe = try ring.timeout(0x55555555, &ts, 0, 0);2463 const sqe = try ring.timeout(0x55555555, &ts, 0, 0);
2457 try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe.opcode);2464 try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe.opcode);
2458 try testing.expectEqual(@as(u32, 1), try ring.submit());2465 try testing.expectEqual(@as(u32, 1), try ring.submit());
2459 const cqe = try ring.copy_cqe();2466 const cqe = try ring.copy_cqe();
2460 const stopped = std.time.milliTimestamp();2467 const stopped = try std.Io.Clock.awake.now(io);
24612468
2462 try testing.expectEqual(linux.io_uring_cqe{2469 try testing.expectEqual(linux.io_uring_cqe{
2463 .user_data = 0x55555555,2470 .user_data = 0x55555555,
...@@ -2466,7 +2473,8 @@ test "timeout (after a relative time)" {...@@ -2466,7 +2473,8 @@ test "timeout (after a relative time)" {
2466 }, cqe);2473 }, cqe);
24672474
2468 // Tests should not depend on timings: skip test if outside margin.2475 // 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;
2470}2478}
24712479
2472test "timeout (after a number of completions)" {2480test "timeout (after a number of completions)" {
...@@ -2777,7 +2785,7 @@ test "register_files_update" {...@@ -2777,7 +2785,7 @@ test "register_files_update" {
2777 const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);2785 const fd = try posix.openZ("/dev/zero", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
2778 defer posix.close(fd);2786 defer posix.close(fd);
27792787
2780 var registered_fds = [_]posix.fd_t{0} ** 2;2788 var registered_fds = [_]linux.fd_t{0} ** 2;
2781 const fd_index = 0;2789 const fd_index = 0;
2782 const fd_index2 = 1;2790 const fd_index2 = 1;
2783 registered_fds[fd_index] = fd;2791 registered_fds[fd_index] = fd;
...@@ -2861,19 +2869,22 @@ test "shutdown" {...@@ -2861,19 +2869,22 @@ test "shutdown" {
2861 };2869 };
2862 defer ring.deinit();2870 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
2866 // Socket bound, expect shutdown to work2877 // Socket bound, expect shutdown to work
2867 {2878 {
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);
2869 defer posix.close(server);2880 defer posix.close(server);
2870 try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));2881 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));
2872 try posix.listen(server, 1);2883 try posix.listen(server, 1);
28732884
2874 // set address to the OS-chosen IP/port.2885 // set address to the OS-chosen IP/port.
2875 var slen: posix.socklen_t = address.getOsSockLen();2886 var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in);
2876 try posix.getsockname(server, &address.any, &slen);2887 try posix.getsockname(server, addrAny(&address), &slen);
28772888
2878 const shutdown_sqe = try ring.shutdown(0x445445445, server, linux.SHUT.RD);2889 const shutdown_sqe = try ring.shutdown(0x445445445, server, linux.SHUT.RD);
2879 try testing.expectEqual(linux.IORING_OP.SHUTDOWN, shutdown_sqe.opcode);2890 try testing.expectEqual(linux.IORING_OP.SHUTDOWN, shutdown_sqe.opcode);
...@@ -2898,7 +2909,7 @@ test "shutdown" {...@@ -2898,7 +2909,7 @@ test "shutdown" {
28982909
2899 // Socket not bound, expect to fail with ENOTCONN2910 // Socket not bound, expect to fail with ENOTCONN
2900 {2911 {
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);
2902 defer posix.close(server);2913 defer posix.close(server);
29032914
2904 const shutdown_sqe = ring.shutdown(0x445445445, server, linux.SHUT.RD) catch |err| switch (err) {2915 const shutdown_sqe = ring.shutdown(0x445445445, server, linux.SHUT.RD) catch |err| switch (err) {
...@@ -2966,22 +2977,11 @@ test "renameat" {...@@ -2966,22 +2977,11 @@ test "renameat" {
2966 }, cqe);2977 }, cqe);
29672978
2968 // Validate that the old file doesn't exist anymore2979 // Validate that the old file doesn't exist anymore
2969 {2980 try testing.expectError(error.FileNotFound, tmp.dir.openFile(old_path, .{}));
2970 _ = tmp.dir.openFile(old_path, .{}) catch |err| switch (err) {
2971 error.FileNotFound => {},
2972 else => std.debug.panic("unexpected error: {}", .{err}),
2973 };
2974 }
29752981
2976 // Validate that the new file exists with the proper content2982 // Validate that the new file exists with the proper content
2977 {2983 var new_file_data: [16]u8 = undefined;
2978 const new_file = try tmp.dir.openFile(new_path, .{});2984 try testing.expectEqualStrings("hello", try tmp.dir.readFile(new_path, &new_file_data));
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 }
2985}2985}
29862986
2987test "unlinkat" {2987test "unlinkat" {
...@@ -3179,12 +3179,8 @@ test "linkat" {...@@ -3179,12 +3179,8 @@ test "linkat" {
3179 }, cqe);3179 }, cqe);
31803180
3181 // Validate the second file3181 // Validate the second file
3182 const second_file = try tmp.dir.openFile(second_path, .{});
3183 defer second_file.close();
3184
3185 var second_file_data: [16]u8 = undefined;3182 var second_file_data: [16]u8 = undefined;
3186 const bytes_read = try second_file.readAll(&second_file_data);3183 try testing.expectEqualStrings("hello", try tmp.dir.readFile(second_path, &second_file_data));
3187 try testing.expectEqualStrings("hello", second_file_data[0..bytes_read]);
3188}3184}
31893185
3190test "provide_buffers: read" {3186test "provide_buffers: read" {
...@@ -3588,7 +3584,10 @@ const SocketTestHarness = struct {...@@ -3588,7 +3584,10 @@ const SocketTestHarness = struct {
35883584
3589fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness {3585fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness {
3590 // Create a TCP server socket3586 // 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 };
3592 const listener_socket = try createListenerSocket(&address);3591 const listener_socket = try createListenerSocket(&address);
3593 errdefer posix.close(listener_socket);3592 errdefer posix.close(listener_socket);
35943593
...@@ -3598,9 +3597,9 @@ fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness {...@@ -3598,9 +3597,9 @@ fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness {
3598 _ = try ring.accept(0xaaaaaaaa, listener_socket, &accept_addr, &accept_addr_len, 0);3597 _ = try ring.accept(0xaaaaaaaa, listener_socket, &accept_addr, &accept_addr_len, 0);
35993598
3600 // Create a TCP client socket3599 // 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);
3602 errdefer posix.close(client);3601 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
3605 try testing.expectEqual(@as(u32, 2), try ring.submit());3604 try testing.expectEqual(@as(u32, 2), try ring.submit());
36063605
...@@ -3636,18 +3635,18 @@ fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness {...@@ -3636,18 +3635,18 @@ fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness {
3636 };3635 };
3637}3636}
36383637
3639fn createListenerSocket(address: *net.Address) !posix.socket_t {3638fn createListenerSocket(address: *linux.sockaddr.in) !posix.socket_t {
3640 const kernel_backlog = 1;3639 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);
3642 errdefer posix.close(listener_socket);3641 errdefer posix.close(listener_socket);
36433642
3644 try posix.setsockopt(listener_socket, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));3643 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));
3646 try posix.listen(listener_socket, kernel_backlog);3645 try posix.listen(listener_socket, kernel_backlog);
36473646
3648 // set address to the OS-chosen IP/port.3647 // set address to the OS-chosen IP/port.
3649 var slen: posix.socklen_t = address.getOsSockLen();3648 var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in);
3650 try posix.getsockname(listener_socket, &address.any, &slen);3649 try posix.getsockname(listener_socket, addrAny(address), &slen);
36513650
3652 return listener_socket;3651 return listener_socket;
3653}3652}
...@@ -3662,7 +3661,10 @@ test "accept multishot" {...@@ -3662,7 +3661,10 @@ test "accept multishot" {
3662 };3661 };
3663 defer ring.deinit();3662 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 };
3666 const listener_socket = try createListenerSocket(&address);3668 const listener_socket = try createListenerSocket(&address);
3667 defer posix.close(listener_socket);3669 defer posix.close(listener_socket);
36683670
...@@ -3676,9 +3678,9 @@ test "accept multishot" {...@@ -3676,9 +3678,9 @@ test "accept multishot" {
3676 var nr: usize = 4; // number of clients to connect3678 var nr: usize = 4; // number of clients to connect
3677 while (nr > 0) : (nr -= 1) {3679 while (nr > 0) : (nr -= 1) {
3678 // connect client3680 // 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);
3680 errdefer posix.close(client);3682 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
3683 // test accept completion3685 // test accept completion
3684 var cqe = try ring.copy_cqe();3686 var cqe = try ring.copy_cqe();
...@@ -3756,10 +3758,13 @@ test "accept_direct" {...@@ -3756,10 +3758,13 @@ test "accept_direct" {
3756 else => return err,3758 else => return err,
3757 };3759 };
3758 defer ring.deinit();3760 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
3761 // register direct file descriptors3766 // register direct file descriptors
3762 var registered_fds = [_]posix.fd_t{-1} ** 2;3767 var registered_fds = [_]linux.fd_t{-1} ** 2;
3763 try ring.register_files(registered_fds[0..]);3768 try ring.register_files(registered_fds[0..]);
37643769
3765 const listener_socket = try createListenerSocket(&address);3770 const listener_socket = try createListenerSocket(&address);
...@@ -3779,8 +3784,8 @@ test "accept_direct" {...@@ -3779,8 +3784,8 @@ test "accept_direct" {
3779 try testing.expectEqual(@as(u32, 1), try ring.submit());3784 try testing.expectEqual(@as(u32, 1), try ring.submit());
37803785
3781 // connect3786 // connect
3782 const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);3787 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3783 try posix.connect(client, &address.any, address.getOsSockLen());3788 try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
3784 defer posix.close(client);3789 defer posix.close(client);
37853790
3786 // accept completion3791 // accept completion
...@@ -3813,8 +3818,8 @@ test "accept_direct" {...@@ -3813,8 +3818,8 @@ test "accept_direct" {
3813 _ = try ring.accept_direct(accept_userdata, listener_socket, null, null, 0);3818 _ = try ring.accept_direct(accept_userdata, listener_socket, null, null, 0);
3814 try testing.expectEqual(@as(u32, 1), try ring.submit());3819 try testing.expectEqual(@as(u32, 1), try ring.submit());
3815 // connect3820 // connect
3816 const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);3821 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3817 try posix.connect(client, &address.any, address.getOsSockLen());3822 try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
3818 defer posix.close(client);3823 defer posix.close(client);
3819 // completion with error3824 // completion with error
3820 const cqe_accept = try ring.copy_cqe();3825 const cqe_accept = try ring.copy_cqe();
...@@ -3830,6 +3835,11 @@ test "accept_direct" {...@@ -3830,6 +3835,11 @@ test "accept_direct" {
3830test "accept_multishot_direct" {3835test "accept_multishot_direct" {
3831 try skipKernelLessThan(.{ .major = 5, .minor = 19, .patch = 0 });3836 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
3833 var ring = IoUring.init(1, 0) catch |err| switch (err) {3843 var ring = IoUring.init(1, 0) catch |err| switch (err) {
3834 error.SystemOutdated => return error.SkipZigTest,3844 error.SystemOutdated => return error.SkipZigTest,
3835 error.PermissionDenied => return error.SkipZigTest,3845 error.PermissionDenied => return error.SkipZigTest,
...@@ -3837,9 +3847,12 @@ test "accept_multishot_direct" {...@@ -3837,9 +3847,12 @@ test "accept_multishot_direct" {
3837 };3847 };
3838 defer ring.deinit();3848 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;
3843 try ring.register_files(registered_fds[0..]);3856 try ring.register_files(registered_fds[0..]);
38443857
3845 const listener_socket = try createListenerSocket(&address);3858 const listener_socket = try createListenerSocket(&address);
...@@ -3855,8 +3868,8 @@ test "accept_multishot_direct" {...@@ -3855,8 +3868,8 @@ test "accept_multishot_direct" {
38553868
3856 for (registered_fds) |_| {3869 for (registered_fds) |_| {
3857 // connect3870 // connect
3858 const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);3871 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3859 try posix.connect(client, &address.any, address.getOsSockLen());3872 try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
3860 defer posix.close(client);3873 defer posix.close(client);
38613874
3862 // accept completion3875 // accept completion
...@@ -3870,8 +3883,8 @@ test "accept_multishot_direct" {...@@ -3870,8 +3883,8 @@ test "accept_multishot_direct" {
3870 // Multishot is terminated (more flag is not set).3883 // Multishot is terminated (more flag is not set).
3871 {3884 {
3872 // connect3885 // connect
3873 const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);3886 const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
3874 try posix.connect(client, &address.any, address.getOsSockLen());3887 try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
3875 defer posix.close(client);3888 defer posix.close(client);
3876 // completion with error3889 // completion with error
3877 const cqe_accept = try ring.copy_cqe();3890 const cqe_accept = try ring.copy_cqe();
...@@ -3902,7 +3915,7 @@ test "socket" {...@@ -3902,7 +3915,7 @@ test "socket" {
3902 // test completion3915 // test completion
3903 var cqe = try ring.copy_cqe();3916 var cqe = try ring.copy_cqe();
3904 try testing.expectEqual(posix.E.SUCCESS, cqe.err());3917 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);
3906 try testing.expect(fd > 2);3919 try testing.expect(fd > 2);
39073920
3908 posix.close(fd);3921 posix.close(fd);
...@@ -3918,7 +3931,7 @@ test "socket_direct/socket_direct_alloc/close_direct" {...@@ -3918,7 +3931,7 @@ test "socket_direct/socket_direct_alloc/close_direct" {
3918 };3931 };
3919 defer ring.deinit();3932 defer ring.deinit();
39203933
3921 var registered_fds = [_]posix.fd_t{-1} ** 3;3934 var registered_fds = [_]linux.fd_t{-1} ** 3;
3922 try ring.register_files(registered_fds[0..]);3935 try ring.register_files(registered_fds[0..]);
39233936
3924 // create socket in registered file descriptor at index 0 (last param)3937 // create socket in registered file descriptor at index 0 (last param)
...@@ -3944,7 +3957,10 @@ test "socket_direct/socket_direct_alloc/close_direct" {...@@ -3944,7 +3957,10 @@ test "socket_direct/socket_direct_alloc/close_direct" {
3944 try testing.expect(cqe_socket.res == 2); // returns registered file index3957 try testing.expect(cqe_socket.res == 2); // returns registered file index
39453958
3946 // use sockets from registered_fds in connect operation3959 // 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 };
3948 const listener_socket = try createListenerSocket(&address);3964 const listener_socket = try createListenerSocket(&address);
3949 defer posix.close(listener_socket);3965 defer posix.close(listener_socket);
3950 const accept_userdata: u64 = 0xaaaaaaaa;3966 const accept_userdata: u64 = 0xaaaaaaaa;
...@@ -3954,7 +3970,7 @@ test "socket_direct/socket_direct_alloc/close_direct" {...@@ -3954,7 +3970,7 @@ test "socket_direct/socket_direct_alloc/close_direct" {
3954 // prepare accept3970 // prepare accept
3955 _ = try ring.accept(accept_userdata, listener_socket, null, null, 0);3971 _ = try ring.accept(accept_userdata, listener_socket, null, null, 0);
3956 // prepare connect with fixed socket3972 // 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));
3958 connect_sqe.flags |= linux.IOSQE_FIXED_FILE; // fd is fixed file index3974 connect_sqe.flags |= linux.IOSQE_FIXED_FILE; // fd is fixed file index
3959 // submit both3975 // submit both
3960 try testing.expectEqual(@as(u32, 2), try ring.submit());3976 try testing.expectEqual(@as(u32, 2), try ring.submit());
...@@ -3996,7 +4012,7 @@ test "openat_direct/close_direct" {...@@ -3996,7 +4012,7 @@ test "openat_direct/close_direct" {
3996 };4012 };
3997 defer ring.deinit();4013 defer ring.deinit();
39984014
3999 var registered_fds = [_]posix.fd_t{-1} ** 3;4015 var registered_fds = [_]linux.fd_t{-1} ** 3;
4000 try ring.register_files(registered_fds[0..]);4016 try ring.register_files(registered_fds[0..]);
40014017
4002 var tmp = std.testing.tmpDir(.{});4018 var tmp = std.testing.tmpDir(.{});
...@@ -4383,7 +4399,7 @@ test "ring mapped buffers multishot recv" {...@@ -4383,7 +4399,7 @@ test "ring mapped buffers multishot recv" {
4383fn buf_grp_recv_submit_get_cqe(4399fn buf_grp_recv_submit_get_cqe(
4384 ring: *IoUring,4400 ring: *IoUring,
4385 buf_grp: *BufferGroup,4401 buf_grp: *BufferGroup,
4386 fd: posix.fd_t,4402 fd: linux.fd_t,
4387 user_data: u64,4403 user_data: u64,
4388) !linux.io_uring_cqe {4404) !linux.io_uring_cqe {
4389 // prepare and submit recv4405 // prepare and submit recv
...@@ -4483,24 +4499,27 @@ test "bind/listen/connect" {...@@ -4483,24 +4499,27 @@ test "bind/listen/connect" {
4483 // LISTEN is higher required operation4499 // LISTEN is higher required operation
4484 if (!probe.is_supported(.LISTEN)) return error.SkipZigTest;4500 if (!probe.is_supported(.LISTEN)) return error.SkipZigTest;
44854501
4486 var addr = net.Address.initIp4([4]u8{ 127, 0, 0, 1 }, 0);4502 var addr: linux.sockaddr.in = .{
4487 const proto: u32 = if (addr.any.family == linux.AF.UNIX) 0 else linux.IPPROTO.TCP;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
4489 const listen_fd = brk: {4508 const listen_fd = brk: {
4490 // Create socket4509 // 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);
4492 try testing.expectEqual(1, try ring.submit());4511 try testing.expectEqual(1, try ring.submit());
4493 var cqe = try ring.copy_cqe();4512 var cqe = try ring.copy_cqe();
4494 try testing.expectEqual(1, cqe.user_data);4513 try testing.expectEqual(1, cqe.user_data);
4495 try testing.expectEqual(posix.E.SUCCESS, cqe.err());4514 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);
4497 try testing.expect(listen_fd > 2);4516 try testing.expect(listen_fd > 2);
44984517
4499 // Prepare: set socket option * 2, bind, listen4518 // Prepare: set socket option * 2, bind, listen
4500 var optval: u32 = 1;4519 var optval: u32 = 1;
4501 (try ring.setsockopt(2, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEADDR, mem.asBytes(&optval))).link_next();4520 (try ring.setsockopt(2, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEADDR, mem.asBytes(&optval))).link_next();
4502 (try ring.setsockopt(3, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEPORT, mem.asBytes(&optval))).link_next();4521 (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();
4504 _ = try ring.listen(5, listen_fd, 1, 0);4523 _ = try ring.listen(5, listen_fd, 1, 0);
4505 // Submit 4 operations4524 // Submit 4 operations
4506 try testing.expectEqual(4, try ring.submit());4525 try testing.expectEqual(4, try ring.submit());
...@@ -4521,28 +4540,28 @@ test "bind/listen/connect" {...@@ -4521,28 +4540,28 @@ test "bind/listen/connect" {
4521 try testing.expectEqual(1, optval);4540 try testing.expectEqual(1, optval);
45224541
4523 // Read system assigned port into addr4542 // Read system assigned port into addr
4524 var addr_len: posix.socklen_t = addr.getOsSockLen();4543 var addr_len: posix.socklen_t = @sizeOf(linux.sockaddr.in);
4525 try posix.getsockname(listen_fd, &addr.any, &addr_len);4544 try posix.getsockname(listen_fd, addrAny(&addr), &addr_len);
45264545
4527 break :brk listen_fd;4546 break :brk listen_fd;
4528 };4547 };
45294548
4530 const connect_fd = brk: {4549 const connect_fd = brk: {
4531 // Create connect socket4550 // 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);
4533 try testing.expectEqual(1, try ring.submit());4552 try testing.expectEqual(1, try ring.submit());
4534 const cqe = try ring.copy_cqe();4553 const cqe = try ring.copy_cqe();
4535 try testing.expectEqual(6, cqe.user_data);4554 try testing.expectEqual(6, cqe.user_data);
4536 try testing.expectEqual(posix.E.SUCCESS, cqe.err());4555 try testing.expectEqual(posix.E.SUCCESS, cqe.err());
4537 // Get connect socket fd4556 // Get connect socket fd
4538 const connect_fd: posix.fd_t = @intCast(cqe.res);4557 const connect_fd: linux.fd_t = @intCast(cqe.res);
4539 try testing.expect(connect_fd > 2 and connect_fd != listen_fd);4558 try testing.expect(connect_fd > 2 and connect_fd != listen_fd);
4540 break :brk connect_fd;4559 break :brk connect_fd;
4541 };4560 };
45424561
4543 // Prepare accept/connect operations4562 // Prepare accept/connect operations
4544 _ = try ring.accept(7, listen_fd, null, null, 0);4563 _ = 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));
4546 try testing.expectEqual(2, try ring.submit());4565 try testing.expectEqual(2, try ring.submit());
4547 // Get listener accepted socket4566 // Get listener accepted socket
4548 var accept_fd: posix.socket_t = 0;4567 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...@@ -4604,3 +4623,7 @@ fn testSendRecv(ring: *IoUring, send_fd: posix.socket_t, recv_fd: posix.socket_t
4604 try testing.expectEqualSlices(u8, buffer_send, buffer_recv[0..buffer_send.len]);4623 try testing.expectEqualSlices(u8, buffer_send, buffer_recv[0..buffer_send.len]);
4605 try testing.expectEqualSlices(u8, buffer_send, buffer_recv[buffer_send.len..]);4624 try testing.expectEqualSlices(u8, buffer_send, buffer_recv[buffer_send.len..]);
4606}4625}
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 {...@@ -136,7 +136,13 @@ pub fn clone() callconv(.naked) u64 {
136 );136 );
137}137}
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
141pub fn restore_rt() callconv(.naked) noreturn {147pub fn restore_rt() callconv(.naked) noreturn {
142 asm volatile (148 asm volatile (
lib/std/os/linux/test.zig+24-46
...@@ -1,5 +1,7 @@...@@ -1,5 +1,7 @@
1const std = @import("../../std.zig");
2const builtin = @import("builtin");1const builtin = @import("builtin");
2
3const std = @import("../../std.zig");
4const assert = std.debug.assert;
3const linux = std.os.linux;5const linux = std.os.linux;
4const mem = std.mem;6const mem = std.mem;
5const elf = std.elf;7const elf = std.elf;
...@@ -128,58 +130,32 @@ test "fadvise" {...@@ -128,58 +130,32 @@ test "fadvise" {
128}130}
129131
130test "sigset_t" {132test "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
133 var sigset = linux.sigemptyset();136 var sigset = linux.sigemptyset();
134137
135 // See that none are set, then set each one, see that they're all set, then138 // See that none are set, then set each one, see that they're all set, then
136 // remove them all, and then see that none are set.139 // remove them all, and then see that none are set.
137 for (1..linux.NSIG) |i| {140 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));
139 }143 }
140 for (1..linux.NSIG) |i| {144 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);
142 }147 }
143 for (1..linux.NSIG) |i| {148 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));
145 }151 }
146 for (1..linux.NSIG) |i| {152 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);
148 }155 }
149 for (1..linux.NSIG) |i| {156 for (1..linux.NSIG) |i| {
150 try expectEqual(linux.sigismember(&sigset, @truncate(i)), false);157 const sig = std.meta.intToEnum(SIG, i) catch continue;
151 }158 try expectEqual(false, linux.sigismember(&sigset, sig));
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);
183 }159 }
184}160}
185161
...@@ -187,14 +163,16 @@ test "sigfillset" {...@@ -187,14 +163,16 @@ test "sigfillset" {
187 // unlike the C library, all the signals are set in the kernel-level fillset163 // unlike the C library, all the signals are set in the kernel-level fillset
188 const sigset = linux.sigfillset();164 const sigset = linux.sigfillset();
189 for (1..linux.NSIG) |i| {165 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));
191 }168 }
192}169}
193170
194test "sigemptyset" {171test "sigemptyset" {
195 const sigset = linux.sigemptyset();172 const sigset = linux.sigemptyset();
196 for (1..linux.NSIG) |i| {173 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));
198 }176 }
199}177}
200178
...@@ -208,14 +186,14 @@ test "sysinfo" {...@@ -208,14 +186,14 @@ test "sysinfo" {
208}186}
209187
210comptime {188comptime {
211 std.debug.assert(128 == @as(u32, @bitCast(linux.FUTEX_OP{ .cmd = @enumFromInt(0), .private = true, .realtime = false })));189 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 })));190 assert(256 == @as(u32, @bitCast(linux.FUTEX_OP{ .cmd = @enumFromInt(0), .private = false, .realtime = true })));
213191
214 // Check futex_param4 union is packed correctly192 // Check futex_param4 union is packed correctly
215 const param_union = linux.futex_param4{193 const param_union = linux.futex_param4{
216 .val2 = 0xaabbcc,194 .val2 = 0xaabbcc,
217 };195 };
218 std.debug.assert(@intFromPtr(param_union.timeout) == 0xaabbcc);196 assert(@intFromPtr(param_union.timeout) == 0xaabbcc);
219}197}
220198
221test "futex v1" {199test "futex v1" {
...@@ -298,8 +276,8 @@ test "futex v1" {...@@ -298,8 +276,8 @@ test "futex v1" {
298}276}
299277
300comptime {278comptime {
301 std.debug.assert(2 == @as(u32, @bitCast(linux.FUTEX2_FLAGS{ .size = .U32, .private = false })));279 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 })));280 assert(128 == @as(u32, @bitCast(linux.FUTEX2_FLAGS{ .size = @enumFromInt(0), .private = true })));
303}281}
304282
305test "futex2_waitv" {283test "futex2_waitv" {
lib/std/os/linux/x86.zig+2
...@@ -159,12 +159,14 @@ pub fn clone() callconv(.naked) u32 {...@@ -159,12 +159,14 @@ pub fn clone() callconv(.naked) u32 {
159pub fn restore() callconv(.naked) noreturn {159pub fn restore() callconv(.naked) noreturn {
160 switch (builtin.zig_backend) {160 switch (builtin.zig_backend) {
161 .stage2_c => asm volatile (161 .stage2_c => asm volatile (
162 \\ addl $4, %%esp
162 \\ movl %[number], %%eax163 \\ movl %[number], %%eax
163 \\ int $0x80164 \\ int $0x80
164 :165 :
165 : [number] "i" (@intFromEnum(SYS.sigreturn)),166 : [number] "i" (@intFromEnum(SYS.sigreturn)),
166 ),167 ),
167 else => asm volatile (168 else => asm volatile (
169 \\ addl $4, %%esp
168 \\ int $0x80170 \\ int $0x80
169 :171 :
170 : [number] "{eax}" (@intFromEnum(SYS.sigreturn)),172 : [number] "{eax}" (@intFromEnum(SYS.sigreturn)),
lib/std/os/windows.zig+51-177
...@@ -5,12 +5,14 @@...@@ -5,12 +5,14 @@
5//! slices as well as APIs which accept null-terminated WTF16LE byte buffers.5//! slices as well as APIs which accept null-terminated WTF16LE byte buffers.
66
7const builtin = @import("builtin");7const builtin = @import("builtin");
8const native_arch = builtin.cpu.arch;
9
8const std = @import("../std.zig");10const std = @import("../std.zig");
11const Io = std.Io;
9const mem = std.mem;12const mem = std.mem;
10const assert = std.debug.assert;13const assert = std.debug.assert;
11const math = std.math;14const math = std.math;
12const maxInt = std.math.maxInt;15const maxInt = std.math.maxInt;
13const native_arch = builtin.cpu.arch;
14const UnexpectedError = std.posix.UnexpectedError;16const UnexpectedError = std.posix.UnexpectedError;
1517
16test {18test {
...@@ -87,7 +89,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN...@@ -87,7 +89,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
87 };89 };
88 var attr = OBJECT_ATTRIBUTES{90 var attr = OBJECT_ATTRIBUTES{
89 .Length = @sizeOf(OBJECT_ATTRIBUTES),91 .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,
91 .Attributes = if (options.sa) |ptr| blk: { // Note we do not use OBJ_CASE_INSENSITIVE here.93 .Attributes = if (options.sa) |ptr| blk: { // Note we do not use OBJ_CASE_INSENSITIVE here.
92 const inherit: ULONG = if (ptr.bInheritHandle == TRUE) OBJ_INHERIT else 0;94 const inherit: ULONG = if (ptr.bInheritHandle == TRUE) OBJ_INHERIT else 0;
93 break :blk inherit;95 break :blk inherit;
...@@ -146,7 +148,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN...@@ -146,7 +148,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
146 // call has failed. There is not really a sane way to handle148 // call has failed. There is not really a sane way to handle
147 // this other than retrying the creation after the OS finishes149 // this other than retrying the creation after the OS finishes
148 // the deletion.150 // the deletion.
149 std.Thread.sleep(std.time.ns_per_ms);151 _ = kernel32.SleepEx(1, TRUE);
150 continue;152 continue;
151 },153 },
152 .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,154 .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
...@@ -604,7 +606,7 @@ pub const ReadFileError = error{...@@ -604,7 +606,7 @@ pub const ReadFileError = error{
604 BrokenPipe,606 BrokenPipe,
605 /// The specified network name is no longer available.607 /// The specified network name is no longer available.
606 ConnectionResetByPeer,608 ConnectionResetByPeer,
607 OperationAborted,609 Canceled,
608 /// Unable to read file due to lock.610 /// Unable to read file due to lock.
609 LockViolation,611 LockViolation,
610 /// Known to be possible when:612 /// Known to be possible when:
...@@ -654,7 +656,7 @@ pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usiz...@@ -654,7 +656,7 @@ pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usiz
654656
655pub const WriteFileError = error{657pub const WriteFileError = error{
656 SystemResources,658 SystemResources,
657 OperationAborted,659 Canceled,
658 BrokenPipe,660 BrokenPipe,
659 NotOpenForWriting,661 NotOpenForWriting,
660 /// The process cannot access the file because another process has locked662 /// The process cannot access the file because another process has locked
...@@ -694,7 +696,7 @@ pub fn WriteFile(...@@ -694,7 +696,7 @@ pub fn WriteFile(
694 switch (GetLastError()) {696 switch (GetLastError()) {
695 .INVALID_USER_BUFFER => return error.SystemResources,697 .INVALID_USER_BUFFER => return error.SystemResources,
696 .NOT_ENOUGH_MEMORY => return error.SystemResources,698 .NOT_ENOUGH_MEMORY => return error.SystemResources,
697 .OPERATION_ABORTED => return error.OperationAborted,699 .OPERATION_ABORTED => return error.Canceled,
698 .NOT_ENOUGH_QUOTA => return error.SystemResources,700 .NOT_ENOUGH_QUOTA => return error.SystemResources,
699 .IO_PENDING => unreachable,701 .IO_PENDING => unreachable,
700 .NO_DATA => return error.BrokenPipe,702 .NO_DATA => return error.BrokenPipe,
...@@ -845,7 +847,7 @@ pub fn CreateSymbolicLink(...@@ -845,7 +847,7 @@ pub fn CreateSymbolicLink(
845 // the C:\ drive.847 // the C:\ drive.
846 .rooted => break :target_path target_path,848 .rooted => break :target_path target_path,
847 // Keep relative paths relative, but anything else needs to get NT-prefixed.849 // 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))
849 break :target_path target_path,851 break :target_path target_path,
850 },852 },
851 // Already an NT path, no need to do anything to it853 // Already an NT path, no need to do anything to it
...@@ -854,7 +856,7 @@ pub fn CreateSymbolicLink(...@@ -854,7 +856,7 @@ pub fn CreateSymbolicLink(
854 }856 }
855 var prefixed_target_path = try wToPrefixedFileW(dir, target_path);857 var prefixed_target_path = try wToPrefixedFileW(dir, target_path);
856 // We do this after prefixing to ensure that drive-relative paths are treated as absolute858 // 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());
858 break :target_path prefixed_target_path.span();860 break :target_path prefixed_target_path.span();
859 };861 };
860862
...@@ -862,7 +864,7 @@ pub fn CreateSymbolicLink(...@@ -862,7 +864,7 @@ pub fn CreateSymbolicLink(
862 var buffer: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;864 var buffer: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
863 const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4;865 const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4;
864 const header_len = @sizeOf(ULONG) + @sizeOf(USHORT) * 2;866 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);
866 const symlink_data = SYMLINK_DATA{868 const symlink_data = SYMLINK_DATA{
867 .ReparseTag = IO_REPARSE_TAG_SYMLINK,869 .ReparseTag = IO_REPARSE_TAG_SYMLINK,
868 .ReparseDataLength = @intCast(buf_len - header_len),870 .ReparseDataLength = @intCast(buf_len - header_len),
...@@ -903,7 +905,7 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin...@@ -903,7 +905,7 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin
903 };905 };
904 var attr = OBJECT_ATTRIBUTES{906 var attr = OBJECT_ATTRIBUTES{
905 .Length = @sizeOf(OBJECT_ATTRIBUTES),907 .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,
907 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.909 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
908 .ObjectName = &nt_name,910 .ObjectName = &nt_name,
909 .SecurityDescriptor = null,911 .SecurityDescriptor = null,
...@@ -1033,7 +1035,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil...@@ -1033,7 +1035,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
10331035
1034 var attr = OBJECT_ATTRIBUTES{1036 var attr = OBJECT_ATTRIBUTES{
1035 .Length = @sizeOf(OBJECT_ATTRIBUTES),1037 .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,
1037 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.1039 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
1038 .ObjectName = &nt_name,1040 .ObjectName = &nt_name,
1039 .SecurityDescriptor = null,1041 .SecurityDescriptor = null,
...@@ -1572,131 +1574,6 @@ pub fn GetFileAttributesW(lpFileName: [*:0]const u16) GetFileAttributesError!DWO...@@ -1572,131 +1574,6 @@ pub fn GetFileAttributesW(lpFileName: [*:0]const u16) GetFileAttributesError!DWO
1572 return rc;1574 return rc;
1573}1575}
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
1700pub fn getpeername(s: ws2_32.SOCKET, name: *ws2_32.sockaddr, namelen: *ws2_32.socklen_t) i32 {1577pub fn getpeername(s: ws2_32.SOCKET, name: *ws2_32.sockaddr, namelen: *ws2_32.socklen_t) i32 {
1701 return ws2_32.getpeername(s, name, @as(*i32, @ptrCast(namelen)));1578 return ws2_32.getpeername(s, name, @as(*i32, @ptrCast(namelen)));
1702}1579}
...@@ -2219,25 +2096,25 @@ pub fn peb() *PEB {...@@ -2219,25 +2096,25 @@ pub fn peb() *PEB {
2219/// Universal Time (UTC).2096/// Universal Time (UTC).
2220/// This function returns the number of nanoseconds since the canonical epoch,2097/// This function returns the number of nanoseconds since the canonical epoch,
2221/// which is the POSIX one (Jan 01, 1970 AD).2098/// which is the POSIX one (Jan 01, 1970 AD).
2222pub fn fromSysTime(hns: i64) i128 {2099pub fn fromSysTime(hns: i64) Io.Timestamp {
2223 const adjusted_epoch: i128 = hns + std.time.epoch.windows * (std.time.ns_per_s / 100);2100 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));
2225}2102}
22262103
2227pub fn toSysTime(ns: i128) i64 {2104pub fn toSysTime(ns: Io.Timestamp) i64 {
2228 const hns = @divFloor(ns, 100);2105 const hns = @divFloor(ns.nanoseconds, 100);
2229 return @as(i64, @intCast(hns)) - std.time.epoch.windows * (std.time.ns_per_s / 100);2106 return @as(i64, @intCast(hns)) - std.time.epoch.windows * (std.time.ns_per_s / 100);
2230}2107}
22312108
2232pub fn fileTimeToNanoSeconds(ft: FILETIME) i128 {2109pub fn fileTimeToNanoSeconds(ft: FILETIME) Io.Timestamp {
2233 const hns = (@as(i64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime;2110 const hns = (@as(i64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
2234 return fromSysTime(hns);2111 return fromSysTime(hns);
2235}2112}
22362113
2237/// Converts a number of nanoseconds since the POSIX epoch to a Windows FILETIME.2114/// 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 {
2239 const adjusted: u64 = @bitCast(toSysTime(ns));2116 const adjusted: u64 = @bitCast(toSysTime(ns));
2240 return FILETIME{2117 return .{
2241 .dwHighDateTime = @as(u32, @truncate(adjusted >> 32)),2118 .dwHighDateTime = @as(u32, @truncate(adjusted >> 32)),
2242 .dwLowDateTime = @as(u32, @truncate(adjusted)),2119 .dwLowDateTime = @as(u32, @truncate(adjusted)),
2243 };2120 };
...@@ -2425,7 +2302,7 @@ pub fn normalizePath(comptime T: type, path: []T) RemoveDotDirsError!usize {...@@ -2425,7 +2302,7 @@ pub fn normalizePath(comptime T: type, path: []T) RemoveDotDirsError!usize {
2425 return prefix_len + try removeDotDirsSanitized(T, path[prefix_len..new_len]);2302 return prefix_len + try removeDotDirsSanitized(T, path[prefix_len..new_len]);
2426}2303}
24272304
2428pub const Wtf8ToPrefixedFileWError = error{InvalidWtf8} || Wtf16ToPrefixedFileWError;2305pub const Wtf8ToPrefixedFileWError = Wtf16ToPrefixedFileWError;
24292306
2430/// Same as `sliceToPrefixedFileW` but accepts a pointer2307/// Same as `sliceToPrefixedFileW` but accepts a pointer
2431/// to a null-terminated WTF-8 encoded path.2308/// to a null-terminated WTF-8 encoded path.
...@@ -2438,7 +2315,9 @@ pub fn cStrToPrefixedFileW(dir: ?HANDLE, s: [*:0]const u8) Wtf8ToPrefixedFileWEr...@@ -2438,7 +2315,9 @@ pub fn cStrToPrefixedFileW(dir: ?HANDLE, s: [*:0]const u8) Wtf8ToPrefixedFileWEr
2438/// https://wtf-8.codeberg.page/2315/// https://wtf-8.codeberg.page/
2439pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) Wtf8ToPrefixedFileWError!PathSpace {2316pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) Wtf8ToPrefixedFileWError!PathSpace {
2440 var temp_path: PathSpace = undefined;2317 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 };
2442 temp_path.data[temp_path.len] = 0;2321 temp_path.data[temp_path.len] = 0;
2443 return wToPrefixedFileW(dir, temp_path.span());2322 return wToPrefixedFileW(dir, temp_path.span());
2444}2323}
...@@ -2812,38 +2691,6 @@ inline fn MAKELANGID(p: c_ushort, s: c_ushort) LANGID {...@@ -2812,38 +2691,6 @@ inline fn MAKELANGID(p: c_ushort, s: c_ushort) LANGID {
2812 return (s << 10) | p;2691 return (s << 10) | p;
2813}2692}
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
2847/// Call this when you made a windows DLL call or something that does SetLastError2694/// Call this when you made a windows DLL call or something that does SetLastError
2848/// and you get an unexpected error.2695/// and you get an unexpected error.
2849pub fn unexpectedError(err: Win32Error) UnexpectedError {2696pub fn unexpectedError(err: Win32Error) UnexpectedError {
...@@ -2881,6 +2728,20 @@ pub fn unexpectedStatus(status: NTSTATUS) UnexpectedError {...@@ -2881,6 +2728,20 @@ pub fn unexpectedStatus(status: NTSTATUS) UnexpectedError {
2881 return error.Unexpected;2728 return error.Unexpected;
2882}2729}
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
2884pub const Win32Error = @import("windows/win32error.zig").Win32Error;2745pub const Win32Error = @import("windows/win32error.zig").Win32Error;
2885pub const NTSTATUS = @import("windows/ntstatus.zig").NTSTATUS;2746pub const NTSTATUS = @import("windows/ntstatus.zig").NTSTATUS;
2886pub const LANG = @import("windows/lang.zig");2747pub const LANG = @import("windows/lang.zig");
...@@ -5737,3 +5598,16 @@ pub fn ProcessBaseAddress(handle: HANDLE) ProcessBaseAddressError!HMODULE {...@@ -5737,3 +5598,16 @@ pub fn ProcessBaseAddress(handle: HANDLE) ProcessBaseAddressError!HMODULE {
5737 const ppeb: *const PEB = @ptrCast(@alignCast(peb_out.ptr));5598 const ppeb: *const PEB = @ptrCast(@alignCast(peb_out.ptr));
5738 return ppeb.ImageBaseAddress;5599 return ppeb.ImageBaseAddress;
5739}5600}
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(...@@ -326,10 +326,11 @@ pub extern "kernel32" fn ExitProcess(
326 exit_code: UINT,326 exit_code: UINT,
327) callconv(.winapi) noreturn;327) callconv(.winapi) noreturn;
328328
329// TODO: SleepEx with bAlertable=false.329// TODO: implement via ntdll instead
330pub extern "kernel32" fn Sleep(330pub extern "kernel32" fn SleepEx(
331 dwMilliseconds: DWORD,331 dwMilliseconds: DWORD,
332) callconv(.winapi) void;332 bAlertable: BOOL,
333) callconv(.winapi) DWORD;
333334
334// TODO: Wrapper around NtQueryInformationProcess with `PROCESS_BASIC_INFORMATION`.335// TODO: Wrapper around NtQueryInformationProcess with `PROCESS_BASIC_INFORMATION`.
335pub extern "kernel32" fn GetExitCodeProcess(336pub extern "kernel32" fn GetExitCodeProcess(
lib/std/os/windows/test.zig-25
...@@ -237,28 +237,3 @@ test "removeDotDirs" {...@@ -237,28 +237,3 @@ test "removeDotDirs" {
237 try testRemoveDotDirs("a\\b\\..\\", "a\\");237 try testRemoveDotDirs("a\\b\\..\\", "a\\");
238 try testRemoveDotDirs("a\\b\\..\\c", "a\\c");238 try testRemoveDotDirs("a\\b\\..\\c", "a\\c");
239}239}
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;...@@ -702,28 +702,32 @@ pub const FIONBIO = -2147195266;
702pub const ADDRINFOEX_VERSION_2 = 2;702pub const ADDRINFOEX_VERSION_2 = 2;
703pub const ADDRINFOEX_VERSION_3 = 3;703pub const ADDRINFOEX_VERSION_3 = 3;
704pub const ADDRINFOEX_VERSION_4 = 4;704pub const ADDRINFOEX_VERSION_4 = 4;
705pub const NS_ALL = 0;705
706pub const NS_SAP = 1;706pub const NS = enum(u32) {
707pub const NS_NDS = 2;707 ALL = 0,
708pub const NS_PEER_BROWSE = 3;708 SAP = 1,
709pub const NS_SLP = 5;709 NDS = 2,
710pub const NS_DHCP = 6;710 PEER_BROWSE = 3,
711pub const NS_TCPIP_LOCAL = 10;711 SLP = 5,
712pub const NS_TCPIP_HOSTS = 11;712 DHCP = 6,
713pub const NS_DNS = 12;713 TCPIP_LOCAL = 10,
714pub const NS_NETBT = 13;714 TCPIP_HOSTS = 11,
715pub const NS_WINS = 14;715 DNS = 12,
716pub const NS_NLA = 15;716 NETBT = 13,
717pub const NS_NBP = 20;717 WINS = 14,
718pub const NS_MS = 30;718 NLA = 15,
719pub const NS_STDA = 31;719 NBP = 20,
720pub const NS_NTDS = 32;720 MS = 30,
721pub const NS_EMAIL = 37;721 STDA = 31,
722pub const NS_X500 = 40;722 NTDS = 32,
723pub const NS_NIS = 41;723 EMAIL = 37,
724pub const NS_NISPLUS = 42;724 X500 = 40,
725pub const NS_WRQ = 50;725 NIS = 41,
726pub const NS_NETDES = 60;726 NISPLUS = 42,
727 WRQ = 50,
728 NETDES = 60,
729};
730
727pub const NI_NOFQDN = 1;731pub const NI_NOFQDN = 1;
728pub const NI_NUMERICHOST = 2;732pub const NI_NUMERICHOST = 2;
729pub const NI_NAMEREQD = 4;733pub const NI_NAMEREQD = 4;
...@@ -1080,31 +1084,18 @@ pub const WSANETWORKEVENTS = extern struct {...@@ -1080,31 +1084,18 @@ pub const WSANETWORKEVENTS = extern struct {
1080 iErrorCode: [10]i32,1084 iErrorCode: [10]i32,
1081};1085};
10821086
1083pub const addrinfo = addrinfoa;1087pub const ADDRINFOEXW = extern struct {
1084
1085pub const addrinfoa = extern struct {
1086 flags: AI,1088 flags: AI,
1087 family: i32,1089 family: i32,
1088 socktype: i32,1090 socktype: i32,
1089 protocol: i32,1091 protocol: i32,
1090 addrlen: usize,1092 addrlen: usize,
1091 canonname: ?[*:0]u8,1093 canonname: ?[*:0]u16,
1092 addr: ?*sockaddr,1094 addr: ?*sockaddr,
1093 next: ?*addrinfo,1095 blob: ?*anyopaque,
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,
1105 bloblen: usize,1096 bloblen: usize,
1106 provider: *GUID,1097 provider: ?*GUID,
1107 next: *addrinfoexA,1098 next: ?*ADDRINFOEXW,
1108};1099};
11091100
1110pub const sockaddr = extern struct {1101pub const sockaddr = extern struct {
...@@ -1271,130 +1262,105 @@ pub const timeval = extern struct {...@@ -1271,130 +1262,105 @@ pub const timeval = extern struct {
1271 usec: LONG,1262 usec: LONG,
1272};1263};
12731264
1274// https://docs.microsoft.com/en-au/windows/win32/winsock/windows-sockets-error-codes-21265/// https://docs.microsoft.com/en-au/windows/win32/winsock/windows-sockets-error-codes-2
1275pub const WinsockError = enum(u16) {1266pub const WinsockError = enum(u16) {
1276 /// Specified event object handle is invalid.1267 /// Specified event object handle is invalid.
1277 /// An application attempts to use an event object, but the specified handle is not valid.1268 /// An application attempts to use an event object, but the specified handle is not valid.
1278 WSA_INVALID_HANDLE = 6,1269 INVALID_HANDLE = 6,
1279
1280 /// Insufficient memory available.1270 /// Insufficient memory available.
1281 /// An application used a Windows Sockets function that directly maps to a Windows function.1271 /// An application used a Windows Sockets function that directly maps to a Windows function.
1282 /// The Windows function is indicating a lack of required memory resources.1272 /// The Windows function is indicating a lack of required memory resources.
1283 WSA_NOT_ENOUGH_MEMORY = 8,1273 NOT_ENOUGH_MEMORY = 8,
1284
1285 /// One or more parameters are invalid.1274 /// One or more parameters are invalid.
1286 /// An application used a Windows Sockets function which directly maps to a Windows function.1275 /// An application used a Windows Sockets function which directly maps to a Windows function.
1287 /// The Windows function is indicating a problem with one or more parameters.1276 /// The Windows function is indicating a problem with one or more parameters.
1288 WSA_INVALID_PARAMETER = 87,1277 INVALID_PARAMETER = 87,
1289
1290 /// Overlapped operation aborted.1278 /// Overlapped operation aborted.
1291 /// An overlapped operation was canceled due to the closure of the socket, or the execution of the SIO_FLUSH command in WSAIoctl.1279 /// 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,1280 OPERATION_ABORTED = 995,
1293
1294 /// Overlapped I/O event object not in signaled state.1281 /// Overlapped I/O event object not in signaled state.
1295 /// The application has tried to determine the status of an overlapped operation which is not yet completed.1282 /// The application has tried to determine the status of an overlapped operation which is not yet completed.
1296 /// 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.1283 /// 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,1284 IO_INCOMPLETE = 996,
1298
1299 /// The application has initiated an overlapped operation that cannot be completed immediately.1285 /// The application has initiated an overlapped operation that cannot be completed immediately.
1300 /// A completion indication will be given later when the operation has been completed.1286 /// A completion indication will be given later when the operation has been completed.
1301 WSA_IO_PENDING = 997,1287 IO_PENDING = 997,
1302
1303 /// Interrupted function call.1288 /// Interrupted function call.
1304 /// A blocking operation was interrupted by a call to WSACancelBlockingCall.1289 /// A blocking operation was interrupted by a call to WSACancelBlockingCall.
1305 WSAEINTR = 10004,1290 EINTR = 10004,
1306
1307 /// File handle is not valid.1291 /// File handle is not valid.
1308 /// The file handle supplied is not valid.1292 /// The file handle supplied is not valid.
1309 WSAEBADF = 10009,1293 EBADF = 10009,
1310
1311 /// Permission denied.1294 /// Permission denied.
1312 /// An attempt was made to access a socket in a way forbidden by its access permissions.1295 /// An attempt was made to access a socket in a way forbidden by its access permissions.
1313 /// An example is using a broadcast address for sendto without broadcast permission being set using setsockopt(SO.BROADCAST).1296 /// An example is using a broadcast address for sendto without broadcast permission being set using setsockopt(SO.BROADCAST).
1314 /// 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.1297 /// 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.
1315 /// 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.1298 /// 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,1299 EACCES = 10013,
1317
1318 /// Bad address.1300 /// Bad address.
1319 /// The system detected an invalid pointer address in attempting to use a pointer argument of a call.1301 /// The system detected an invalid pointer address in attempting to use a pointer argument of a call.
1320 /// This error occurs if an application passes an invalid pointer value, or if the length of the buffer is too small.1302 /// This error occurs if an application passes an invalid pointer value, or if the length of the buffer is too small.
1321 /// For instance, if the length of an argument, which is a sockaddr structure, is smaller than the sizeof(sockaddr).1303 /// For instance, if the length of an argument, which is a sockaddr structure, is smaller than the sizeof(sockaddr).
1322 WSAEFAULT = 10014,1304 EFAULT = 10014,
1323
1324 /// Invalid argument.1305 /// Invalid argument.
1325 /// Some invalid argument was supplied (for example, specifying an invalid level to the setsockopt function).1306 /// Some invalid argument was supplied (for example, specifying an invalid level to the setsockopt function).
1326 /// In some instances, it also refers to the current state of the socket—for instance, calling accept on a socket that is not listening.1307 /// 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,1308 EINVAL = 10022,
1328
1329 /// Too many open files.1309 /// Too many open files.
1330 /// Too many open sockets. Each implementation may have a maximum number of socket handles available, either globally, per process, or per thread.1310 /// 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,1311 EMFILE = 10024,
1332
1333 /// Resource temporarily unavailable.1312 /// Resource temporarily unavailable.
1334 /// 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.1313 /// 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.
1335 /// It is a nonfatal error, and the operation should be retried later.1314 /// It is a nonfatal error, and the operation should be retried later.
1336 /// 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.1315 /// 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,1316 EWOULDBLOCK = 10035,
1338
1339 /// Operation now in progress.1317 /// Operation now in progress.
1340 /// A blocking operation is currently executing.1318 /// A blocking operation is currently executing.
1341 /// 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.1319 /// 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,1320 EINPROGRESS = 10036,
1343
1344 /// Operation already in progress.1321 /// Operation already in progress.
1345 /// 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.1322 /// 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,1323 EALREADY = 10037,
1347
1348 /// Socket operation on nonsocket.1324 /// Socket operation on nonsocket.
1349 /// An operation was attempted on something that is not a socket.1325 /// An operation was attempted on something that is not a socket.
1350 /// Either the socket handle parameter did not reference a valid socket, or for select, a member of an fd_set was not valid.1326 /// 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,1327 ENOTSOCK = 10038,
1352
1353 /// Destination address required.1328 /// Destination address required.
1354 /// A required address was omitted from an operation on a socket.1329 /// A required address was omitted from an operation on a socket.
1355 /// For example, this error is returned if sendto is called with the remote address of ADDR_ANY.1330 /// For example, this error is returned if sendto is called with the remote address of ADDR_ANY.
1356 WSAEDESTADDRREQ = 10039,1331 EDESTADDRREQ = 10039,
1357
1358 /// Message too long.1332 /// Message too long.
1359 /// 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.1333 /// 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,1334 EMSGSIZE = 10040,
1361
1362 /// Protocol wrong type for socket.1335 /// Protocol wrong type for socket.
1363 /// A protocol was specified in the socket function call that does not support the semantics of the socket type requested.1336 /// A protocol was specified in the socket function call that does not support the semantics of the socket type requested.
1364 /// For example, the ARPA Internet UDP protocol cannot be specified with a socket type of SOCK.STREAM.1337 /// For example, the ARPA Internet UDP protocol cannot be specified with a socket type of SOCK.STREAM.
1365 WSAEPROTOTYPE = 10041,1338 EPROTOTYPE = 10041,
1366
1367 /// Bad protocol option.1339 /// Bad protocol option.
1368 /// An unknown, invalid or unsupported option or level was specified in a getsockopt or setsockopt call.1340 /// An unknown, invalid or unsupported option or level was specified in a getsockopt or setsockopt call.
1369 WSAENOPROTOOPT = 10042,1341 ENOPROTOOPT = 10042,
1370
1371 /// Protocol not supported.1342 /// Protocol not supported.
1372 /// The requested protocol has not been configured into the system, or no implementation for it exists.1343 /// The requested protocol has not been configured into the system, or no implementation for it exists.
1373 /// For example, a socket call requests a SOCK.DGRAM socket, but specifies a stream protocol.1344 /// For example, a socket call requests a SOCK.DGRAM socket, but specifies a stream protocol.
1374 WSAEPROTONOSUPPORT = 10043,1345 EPROTONOSUPPORT = 10043,
1375
1376 /// Socket type not supported.1346 /// Socket type not supported.
1377 /// The support for the specified socket type does not exist in this address family.1347 /// The support for the specified socket type does not exist in this address family.
1378 /// 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.1348 /// 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,1349 ESOCKTNOSUPPORT = 10044,
1380
1381 /// Operation not supported.1350 /// Operation not supported.
1382 /// The attempted operation is not supported for the type of object referenced.1351 /// The attempted operation is not supported for the type of object referenced.
1383 /// 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.1352 /// 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,1353 EOPNOTSUPP = 10045,
1385
1386 /// Protocol family not supported.1354 /// Protocol family not supported.
1387 /// The protocol family has not been configured into the system or no implementation for it exists.1355 /// The protocol family has not been configured into the system or no implementation for it exists.
1388 /// This message has a slightly different meaning from WSAEAFNOSUPPORT.1356 /// This message has a slightly different meaning from WSAEAFNOSUPPORT.
1389 /// However, it is interchangeable in most cases, and all Windows Sockets functions that return one of these messages also specify WSAEAFNOSUPPORT.1357 /// However, it is interchangeable in most cases, and all Windows Sockets functions that return one of these messages also specify WSAEAFNOSUPPORT.
1390 WSAEPFNOSUPPORT = 10046,1358 EPFNOSUPPORT = 10046,
1391
1392 /// Address family not supported by protocol family.1359 /// Address family not supported by protocol family.
1393 /// An address incompatible with the requested protocol was used.1360 /// An address incompatible with the requested protocol was used.
1394 /// 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).1361 /// 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).
1395 /// 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.1362 /// 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,1363 EAFNOSUPPORT = 10047,
1397
1398 /// Address already in use.1364 /// Address already in use.
1399 /// Typically, only one usage of each socket address (protocol/IP address/port) is permitted.1365 /// Typically, only one usage of each socket address (protocol/IP address/port) is permitted.
1400 /// 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.1366 /// 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) {...@@ -1402,115 +1368,91 @@ pub const WinsockError = enum(u16) {
1402 /// Client applications usually need not call bind at all—connect chooses an unused port automatically.1368 /// Client applications usually need not call bind at all—connect chooses an unused port automatically.
1403 /// When bind is called with a wildcard address (involving ADDR_ANY), a WSAEADDRINUSE error could be delayed until the specific address is committed.1369 /// When bind is called with a wildcard address (involving ADDR_ANY), a WSAEADDRINUSE error could be delayed until the specific address is committed.
1404 /// This could happen with a call to another function later, including connect, listen, WSAConnect, or WSAJoinLeaf.1370 /// This could happen with a call to another function later, including connect, listen, WSAConnect, or WSAJoinLeaf.
1405 WSAEADDRINUSE = 10048,1371 EADDRINUSE = 10048,
1406
1407 /// Cannot assign requested address.1372 /// Cannot assign requested address.
1408 /// The requested address is not valid in its context.1373 /// The requested address is not valid in its context.
1409 /// This normally results from an attempt to bind to an address that is not valid for the local computer.1374 /// This normally results from an attempt to bind to an address that is not valid for the local computer.
1410 /// 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).1375 /// 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,1376 EADDRNOTAVAIL = 10049,
1412
1413 /// Network is down.1377 /// Network is down.
1414 /// A socket operation encountered a dead network.1378 /// A socket operation encountered a dead network.
1415 /// 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.1379 /// 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,1380 ENETDOWN = 10050,
1417
1418 /// Network is unreachable.1381 /// Network is unreachable.
1419 /// A socket operation was attempted to an unreachable network.1382 /// A socket operation was attempted to an unreachable network.
1420 /// This usually means the local software knows no route to reach the remote host.1383 /// This usually means the local software knows no route to reach the remote host.
1421 WSAENETUNREACH = 10051,1384 ENETUNREACH = 10051,
1422
1423 /// Network dropped connection on reset.1385 /// Network dropped connection on reset.
1424 /// The connection has been broken due to keep-alive activity detecting a failure while the operation was in progress.1386 /// The connection has been broken due to keep-alive activity detecting a failure while the operation was in progress.
1425 /// It can also be returned by setsockopt if an attempt is made to set SO.KEEPALIVE on a connection that has already failed.1387 /// 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,1388 ENETRESET = 10052,
1427
1428 /// Software caused connection abort.1389 /// Software caused connection abort.
1429 /// An established connection was aborted by the software in your host computer, possibly due to a data transmission time-out or protocol error.1390 /// 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,1391 ECONNABORTED = 10053,
1431
1432 /// Connection reset by peer.1392 /// Connection reset by peer.
1433 /// An existing connection was forcibly closed by the remote host.1393 /// An existing connection was forcibly closed by the remote host.
1434 /// 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).1394 /// 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).
1435 /// 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.1395 /// 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.
1436 /// Operations that were in progress fail with WSAENETRESET. Subsequent operations fail with WSAECONNRESET.1396 /// Operations that were in progress fail with WSAENETRESET. Subsequent operations fail with WSAECONNRESET.
1437 WSAECONNRESET = 10054,1397 ECONNRESET = 10054,
1438
1439 /// No buffer space available.1398 /// No buffer space available.
1440 /// An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full.1399 /// 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,1400 ENOBUFS = 10055,
1442
1443 /// Socket is already connected.1401 /// Socket is already connected.
1444 /// A connect request was made on an already-connected socket.1402 /// A connect request was made on an already-connected socket.
1445 /// 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.1403 /// 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,1404 EISCONN = 10056,
1447
1448 /// Socket is not connected.1405 /// Socket is not connected.
1449 /// 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.1406 /// 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.
1450 /// Any other type of operation might also return this error—for example, setsockopt setting SO.KEEPALIVE if the connection has been reset.1407 /// 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,1408 ENOTCONN = 10057,
1452
1453 /// Cannot send after socket shutdown.1409 /// Cannot send after socket shutdown.
1454 /// 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.1410 /// 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.
1455 /// By calling shutdown a partial close of a socket is requested, which is a signal that sending or receiving, or both have been discontinued.1411 /// 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,1412 ESHUTDOWN = 10058,
1457
1458 /// Too many references.1413 /// Too many references.
1459 /// Too many references to some kernel object.1414 /// Too many references to some kernel object.
1460 WSAETOOMANYREFS = 10059,1415 ETOOMANYREFS = 10059,
1461
1462 /// Connection timed out.1416 /// Connection timed out.
1463 /// 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.1417 /// 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,1418 ETIMEDOUT = 10060,
1465
1466 /// Connection refused.1419 /// Connection refused.
1467 /// No connection could be made because the target computer actively refused it.1420 /// No connection could be made because the target computer actively refused it.
1468 /// 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.1421 /// 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,1422 ECONNREFUSED = 10061,
1470
1471 /// Cannot translate name.1423 /// Cannot translate name.
1472 /// Cannot translate a name.1424 /// Cannot translate a name.
1473 WSAELOOP = 10062,1425 ELOOP = 10062,
1474
1475 /// Name too long.1426 /// Name too long.
1476 /// A name component or a name was too long.1427 /// A name component or a name was too long.
1477 WSAENAMETOOLONG = 10063,1428 ENAMETOOLONG = 10063,
1478
1479 /// Host is down.1429 /// Host is down.
1480 /// A socket operation failed because the destination host is down. A socket operation encountered a dead host.1430 /// A socket operation failed because the destination host is down. A socket operation encountered a dead host.
1481 /// Networking activity on the local host has not been initiated.1431 /// Networking activity on the local host has not been initiated.
1482 /// These conditions are more likely to be indicated by the error WSAETIMEDOUT.1432 /// These conditions are more likely to be indicated by the error WSAETIMEDOUT.
1483 WSAEHOSTDOWN = 10064,1433 EHOSTDOWN = 10064,
1484
1485 /// No route to host.1434 /// No route to host.
1486 /// A socket operation was attempted to an unreachable host. See WSAENETUNREACH.1435 /// A socket operation was attempted to an unreachable host. See WSAENETUNREACH.
1487 WSAEHOSTUNREACH = 10065,1436 EHOSTUNREACH = 10065,
1488
1489 /// Directory not empty.1437 /// Directory not empty.
1490 /// Cannot remove a directory that is not empty.1438 /// Cannot remove a directory that is not empty.
1491 WSAENOTEMPTY = 10066,1439 ENOTEMPTY = 10066,
1492
1493 /// Too many processes.1440 /// Too many processes.
1494 /// A Windows Sockets implementation may have a limit on the number of applications that can use it simultaneously.1441 /// A Windows Sockets implementation may have a limit on the number of applications that can use it simultaneously.
1495 /// WSAStartup may fail with this error if the limit has been reached.1442 /// WSAStartup may fail with this error if the limit has been reached.
1496 WSAEPROCLIM = 10067,1443 EPROCLIM = 10067,
1497
1498 /// User quota exceeded.1444 /// User quota exceeded.
1499 /// Ran out of user quota.1445 /// Ran out of user quota.
1500 WSAEUSERS = 10068,1446 EUSERS = 10068,
1501
1502 /// Disk quota exceeded.1447 /// Disk quota exceeded.
1503 /// Ran out of disk quota.1448 /// Ran out of disk quota.
1504 WSAEDQUOT = 10069,1449 EDQUOT = 10069,
1505
1506 /// Stale file handle reference.1450 /// Stale file handle reference.
1507 /// The file handle reference is no longer available.1451 /// The file handle reference is no longer available.
1508 WSAESTALE = 10070,1452 ESTALE = 10070,
1509
1510 /// Item is remote.1453 /// Item is remote.
1511 /// The item is not available locally.1454 /// The item is not available locally.
1512 WSAEREMOTE = 10071,1455 EREMOTE = 10071,
1513
1514 /// Network subsystem is unavailable.1456 /// Network subsystem is unavailable.
1515 /// 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.1457 /// 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.
1516 /// Users should check:1458 /// Users should check:
...@@ -1518,47 +1460,38 @@ pub const WinsockError = enum(u16) {...@@ -1518,47 +1460,38 @@ pub const WinsockError = enum(u16) {
1518 /// - That they are not trying to use more than one Windows Sockets implementation simultaneously.1460 /// - That they are not trying to use more than one Windows Sockets implementation simultaneously.
1519 /// - 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.1461 /// - 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.
1520 /// - The Windows Sockets implementation documentation to be sure all necessary components are currently installed and configured correctly.1462 /// - The Windows Sockets implementation documentation to be sure all necessary components are currently installed and configured correctly.
1521 WSASYSNOTREADY = 10091,1463 SYSNOTREADY = 10091,
1522
1523 /// Winsock.dll version out of range.1464 /// Winsock.dll version out of range.
1524 /// The current Windows Sockets implementation does not support the Windows Sockets specification version requested by the application.1465 /// The current Windows Sockets implementation does not support the Windows Sockets specification version requested by the application.
1525 /// Check that no old Windows Sockets DLL files are being accessed.1466 /// Check that no old Windows Sockets DLL files are being accessed.
1526 WSAVERNOTSUPPORTED = 10092,1467 VERNOTSUPPORTED = 10092,
1527
1528 /// Successful WSAStartup not yet performed.1468 /// Successful WSAStartup not yet performed.
1529 /// Either the application has not called WSAStartup or WSAStartup failed.1469 /// Either the application has not called WSAStartup or WSAStartup failed.
1530 /// 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.1470 /// 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,1471 NOTINITIALISED = 10093,
1532
1533 /// Graceful shutdown in progress.1472 /// Graceful shutdown in progress.
1534 /// Returned by WSARecv and WSARecvFrom to indicate that the remote party has initiated a graceful shutdown sequence.1473 /// Returned by WSARecv and WSARecvFrom to indicate that the remote party has initiated a graceful shutdown sequence.
1535 WSAEDISCON = 10101,1474 EDISCON = 10101,
1536
1537 /// No more results.1475 /// No more results.
1538 /// No more results can be returned by the WSALookupServiceNext function.1476 /// No more results can be returned by the WSALookupServiceNext function.
1539 WSAENOMORE = 10102,1477 ENOMORE = 10102,
1540
1541 /// Call has been canceled.1478 /// Call has been canceled.
1542 /// A call to the WSALookupServiceEnd function was made while this call was still processing. The call has been canceled.1479 /// A call to the WSALookupServiceEnd function was made while this call was still processing. The call has been canceled.
1543 WSAECANCELLED = 10103,1480 ECANCELLED = 10103,
1544
1545 /// Procedure call table is invalid.1481 /// Procedure call table is invalid.
1546 /// The service provider procedure call table is invalid.1482 /// The service provider procedure call table is invalid.
1547 /// A service provider returned a bogus procedure table to Ws2_32.dll.1483 /// A service provider returned a bogus procedure table to Ws2_32.dll.
1548 /// This is usually caused by one or more of the function pointers being NULL.1484 /// This is usually caused by one or more of the function pointers being NULL.
1549 WSAEINVALIDPROCTABLE = 10104,1485 EINVALIDPROCTABLE = 10104,
1550
1551 /// Service provider is invalid.1486 /// Service provider is invalid.
1552 /// The requested service provider is invalid.1487 /// The requested service provider is invalid.
1553 /// This error is returned by the WSCGetProviderInfo and WSCGetProviderInfo32 functions if the protocol entry specified could not be found.1488 /// This error is returned by the WSCGetProviderInfo and WSCGetProviderInfo32 functions if the protocol entry specified could not be found.
1554 /// This error is also returned if the service provider returned a version number other than 2.0.1489 /// This error is also returned if the service provider returned a version number other than 2.0.
1555 WSAEINVALIDPROVIDER = 10105,1490 EINVALIDPROVIDER = 10105,
1556
1557 /// Service provider failed to initialize.1491 /// Service provider failed to initialize.
1558 /// The requested service provider could not be loaded or initialized.1492 /// The requested service provider could not be loaded or initialized.
1559 /// 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.1493 /// 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,1494 EPROVIDERFAILEDINIT = 10106,
1561
1562 /// System call failure.1495 /// System call failure.
1563 /// A system call that should never fail has failed.1496 /// A system call that should never fail has failed.
1564 /// This is a generic error code, returned under various conditions.1497 /// This is a generic error code, returned under various conditions.
...@@ -1566,157 +1499,120 @@ pub const WinsockError = enum(u16) {...@@ -1566,157 +1499,120 @@ pub const WinsockError = enum(u16) {
1566 /// For example, if a call to WaitForMultipleEvents fails or one of the registry functions fails trying to manipulate the protocol/namespace catalogs.1499 /// For example, if a call to WaitForMultipleEvents fails or one of the registry functions fails trying to manipulate the protocol/namespace catalogs.
1567 /// Returned when a provider does not return SUCCESS and does not provide an extended error code.1500 /// Returned when a provider does not return SUCCESS and does not provide an extended error code.
1568 /// Can indicate a service provider implementation error.1501 /// Can indicate a service provider implementation error.
1569 WSASYSCALLFAILURE = 10107,1502 SYSCALLFAILURE = 10107,
1570
1571 /// Service not found.1503 /// Service not found.
1572 /// No such service is known. The service cannot be found in the specified name space.1504 /// No such service is known. The service cannot be found in the specified name space.
1573 WSASERVICE_NOT_FOUND = 10108,1505 SERVICE_NOT_FOUND = 10108,
1574
1575 /// Class type not found.1506 /// Class type not found.
1576 /// The specified class was not found.1507 /// The specified class was not found.
1577 WSATYPE_NOT_FOUND = 10109,1508 TYPE_NOT_FOUND = 10109,
1578
1579 /// No more results.1509 /// No more results.
1580 /// No more results can be returned by the WSALookupServiceNext function.1510 /// No more results can be returned by the WSALookupServiceNext function.
1581 WSA_E_NO_MORE = 10110,1511 E_NO_MORE = 10110,
1582
1583 /// Call was canceled.1512 /// Call was canceled.
1584 /// A call to the WSALookupServiceEnd function was made while this call was still processing. The call has been canceled.1513 /// A call to the WSALookupServiceEnd function was made while this call was still processing. The call has been canceled.
1585 WSA_E_CANCELLED = 10111,1514 E_CANCELLED = 10111,
1586
1587 /// Database query was refused.1515 /// Database query was refused.
1588 /// A database query failed because it was actively refused.1516 /// A database query failed because it was actively refused.
1589 WSAEREFUSED = 10112,1517 EREFUSED = 10112,
1590
1591 /// Host not found.1518 /// Host not found.
1592 /// 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.1519 /// 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.
1593 /// 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.1520 /// 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,1521 HOST_NOT_FOUND = 11001,
1595
1596 /// Nonauthoritative host not found.1522 /// Nonauthoritative host not found.
1597 /// 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.1523 /// 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,1524 TRY_AGAIN = 11002,
1599
1600 /// This is a nonrecoverable error.1525 /// This is a nonrecoverable error.
1601 /// This indicates that some sort of nonrecoverable error occurred during a database lookup.1526 /// This indicates that some sort of nonrecoverable error occurred during a database lookup.
1602 /// 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.1527 /// 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,1528 NO_RECOVERY = 11003,
1604
1605 /// Valid name, no data record of requested type.1529 /// Valid name, no data record of requested type.
1606 /// The requested name is valid and was found in the database, but it does not have the correct associated data being resolved for.1530 /// The requested name is valid and was found in the database, but it does not have the correct associated data being resolved for.
1607 /// The usual example for this is a host name-to-address translation attempt (using gethostbyname or WSAAsyncGetHostByName) which uses the DNS (Domain Name Server).1531 /// The usual example for this is a host name-to-address translation attempt (using gethostbyname or WSAAsyncGetHostByName) which uses the DNS (Domain Name Server).
1608 /// An MX record is returned but no A record—indicating the host itself exists, but is not directly reachable.1532 /// An MX record is returned but no A record—indicating the host itself exists, but is not directly reachable.
1609 WSANO_DATA = 11004,1533 NO_DATA = 11004,
1610
1611 /// QoS receivers.1534 /// QoS receivers.
1612 /// At least one QoS reserve has arrived.1535 /// At least one QoS reserve has arrived.
1613 WSA_QOS_RECEIVERS = 11005,1536 QOS_RECEIVERS = 11005,
1614
1615 /// QoS senders.1537 /// QoS senders.
1616 /// At least one QoS send path has arrived.1538 /// At least one QoS send path has arrived.
1617 WSA_QOS_SENDERS = 11006,1539 QOS_SENDERS = 11006,
1618
1619 /// No QoS senders.1540 /// No QoS senders.
1620 /// There are no QoS senders.1541 /// There are no QoS senders.
1621 WSA_QOS_NO_SENDERS = 11007,1542 QOS_NO_SENDERS = 11007,
1622
1623 /// QoS no receivers.1543 /// QoS no receivers.
1624 /// There are no QoS receivers.1544 /// There are no QoS receivers.
1625 WSA_QOS_NO_RECEIVERS = 11008,1545 QOS_NO_RECEIVERS = 11008,
1626
1627 /// QoS request confirmed.1546 /// QoS request confirmed.
1628 /// The QoS reserve request has been confirmed.1547 /// The QoS reserve request has been confirmed.
1629 WSA_QOS_REQUEST_CONFIRMED = 11009,1548 QOS_REQUEST_CONFIRMED = 11009,
1630
1631 /// QoS admission error.1549 /// QoS admission error.
1632 /// A QoS error occurred due to lack of resources.1550 /// A QoS error occurred due to lack of resources.
1633 WSA_QOS_ADMISSION_FAILURE = 11010,1551 QOS_ADMISSION_FAILURE = 11010,
1634
1635 /// QoS policy failure.1552 /// QoS policy failure.
1636 /// The QoS request was rejected because the policy system couldn't allocate the requested resource within the existing policy.1553 /// 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,1554 QOS_POLICY_FAILURE = 11011,
1638
1639 /// QoS bad style.1555 /// QoS bad style.
1640 /// An unknown or conflicting QoS style was encountered.1556 /// An unknown or conflicting QoS style was encountered.
1641 WSA_QOS_BAD_STYLE = 11012,1557 QOS_BAD_STYLE = 11012,
1642
1643 /// QoS bad object.1558 /// QoS bad object.
1644 /// A problem was encountered with some part of the filterspec or the provider-specific buffer in general.1559 /// A problem was encountered with some part of the filterspec or the provider-specific buffer in general.
1645 WSA_QOS_BAD_OBJECT = 11013,1560 QOS_BAD_OBJECT = 11013,
1646
1647 /// QoS traffic control error.1561 /// QoS traffic control error.
1648 /// An error with the underlying traffic control (TC) API as the generic QoS request was converted for local enforcement by the TC API.1562 /// An error with the underlying traffic control (TC) API as the generic QoS request was converted for local enforcement by the TC API.
1649 /// This could be due to an out of memory error or to an internal QoS provider error.1563 /// This could be due to an out of memory error or to an internal QoS provider error.
1650 WSA_QOS_TRAFFIC_CTRL_ERROR = 11014,1564 QOS_TRAFFIC_CTRL_ERROR = 11014,
1651
1652 /// QoS generic error.1565 /// QoS generic error.
1653 /// A general QoS error.1566 /// A general QoS error.
1654 WSA_QOS_GENERIC_ERROR = 11015,1567 QOS_GENERIC_ERROR = 11015,
1655
1656 /// QoS service type error.1568 /// QoS service type error.
1657 /// An invalid or unrecognized service type was found in the QoS flowspec.1569 /// An invalid or unrecognized service type was found in the QoS flowspec.
1658 WSA_QOS_ESERVICETYPE = 11016,1570 QOS_ESERVICETYPE = 11016,
1659
1660 /// QoS flowspec error.1571 /// QoS flowspec error.
1661 /// An invalid or inconsistent flowspec was found in the QOS structure.1572 /// An invalid or inconsistent flowspec was found in the QOS structure.
1662 WSA_QOS_EFLOWSPEC = 11017,1573 QOS_EFLOWSPEC = 11017,
1663
1664 /// Invalid QoS provider buffer.1574 /// Invalid QoS provider buffer.
1665 /// An invalid QoS provider-specific buffer.1575 /// An invalid QoS provider-specific buffer.
1666 WSA_QOS_EPROVSPECBUF = 11018,1576 QOS_EPROVSPECBUF = 11018,
1667
1668 /// Invalid QoS filter style.1577 /// Invalid QoS filter style.
1669 /// An invalid QoS filter style was used.1578 /// An invalid QoS filter style was used.
1670 WSA_QOS_EFILTERSTYLE = 11019,1579 QOS_EFILTERSTYLE = 11019,
1671
1672 /// Invalid QoS filter type.1580 /// Invalid QoS filter type.
1673 /// An invalid QoS filter type was used.1581 /// An invalid QoS filter type was used.
1674 WSA_QOS_EFILTERTYPE = 11020,1582 QOS_EFILTERTYPE = 11020,
1675
1676 /// Incorrect QoS filter count.1583 /// Incorrect QoS filter count.
1677 /// An incorrect number of QoS FILTERSPECs were specified in the FLOWDESCRIPTOR.1584 /// An incorrect number of QoS FILTERSPECs were specified in the FLOWDESCRIPTOR.
1678 WSA_QOS_EFILTERCOUNT = 11021,1585 QOS_EFILTERCOUNT = 11021,
1679
1680 /// Invalid QoS object length.1586 /// Invalid QoS object length.
1681 /// An object with an invalid ObjectLength field was specified in the QoS provider-specific buffer.1587 /// An object with an invalid ObjectLength field was specified in the QoS provider-specific buffer.
1682 WSA_QOS_EOBJLENGTH = 11022,1588 QOS_EOBJLENGTH = 11022,
1683
1684 /// Incorrect QoS flow count.1589 /// Incorrect QoS flow count.
1685 /// An incorrect number of flow descriptors was specified in the QoS structure.1590 /// An incorrect number of flow descriptors was specified in the QoS structure.
1686 WSA_QOS_EFLOWCOUNT = 11023,1591 QOS_EFLOWCOUNT = 11023,
1687
1688 /// Unrecognized QoS object.1592 /// Unrecognized QoS object.
1689 /// An unrecognized object was found in the QoS provider-specific buffer.1593 /// An unrecognized object was found in the QoS provider-specific buffer.
1690 WSA_QOS_EUNKOWNPSOBJ = 11024,1594 QOS_EUNKOWNPSOBJ = 11024,
1691
1692 /// Invalid QoS policy object.1595 /// Invalid QoS policy object.
1693 /// An invalid policy object was found in the QoS provider-specific buffer.1596 /// An invalid policy object was found in the QoS provider-specific buffer.
1694 WSA_QOS_EPOLICYOBJ = 11025,1597 QOS_EPOLICYOBJ = 11025,
1695
1696 /// Invalid QoS flow descriptor.1598 /// Invalid QoS flow descriptor.
1697 /// An invalid QoS flow descriptor was found in the flow descriptor list.1599 /// An invalid QoS flow descriptor was found in the flow descriptor list.
1698 WSA_QOS_EFLOWDESC = 11026,1600 QOS_EFLOWDESC = 11026,
1699
1700 /// Invalid QoS provider-specific flowspec.1601 /// Invalid QoS provider-specific flowspec.
1701 /// An invalid or inconsistent flowspec was found in the QoS provider-specific buffer.1602 /// An invalid or inconsistent flowspec was found in the QoS provider-specific buffer.
1702 WSA_QOS_EPSFLOWSPEC = 11027,1603 QOS_EPSFLOWSPEC = 11027,
1703
1704 /// Invalid QoS provider-specific filterspec.1604 /// Invalid QoS provider-specific filterspec.
1705 /// An invalid FILTERSPEC was found in the QoS provider-specific buffer.1605 /// An invalid FILTERSPEC was found in the QoS provider-specific buffer.
1706 WSA_QOS_EPSFILTERSPEC = 11028,1606 QOS_EPSFILTERSPEC = 11028,
1707
1708 /// Invalid QoS shape discard mode object.1607 /// Invalid QoS shape discard mode object.
1709 /// An invalid shape discard mode object was found in the QoS provider-specific buffer.1608 /// An invalid shape discard mode object was found in the QoS provider-specific buffer.
1710 WSA_QOS_ESDMODEOBJ = 11029,1609 QOS_ESDMODEOBJ = 11029,
1711
1712 /// Invalid QoS shaping rate object.1610 /// Invalid QoS shaping rate object.
1713 /// An invalid shaping rate object was found in the QoS provider-specific buffer.1611 /// An invalid shaping rate object was found in the QoS provider-specific buffer.
1714 WSA_QOS_ESHAPERATEOBJ = 11030,1612 QOS_ESHAPERATEOBJ = 11030,
1715
1716 /// Reserved policy QoS element type.1613 /// Reserved policy QoS element type.
1717 /// A reserved policy element was found in the QoS provider-specific buffer.1614 /// A reserved policy element was found in the QoS provider-specific buffer.
1718 WSA_QOS_RESERVED_PETYPE = 11031,1615 QOS_RESERVED_PETYPE = 11031,
1719
1720 _,1616 _,
1721};1617};
17221618
...@@ -1946,18 +1842,6 @@ pub extern "ws2_32" fn WSAConnectByNameW(...@@ -1946,18 +1842,6 @@ pub extern "ws2_32" fn WSAConnectByNameW(
1946 Reserved: *OVERLAPPED,1842 Reserved: *OVERLAPPED,
1947) callconv(.winapi) BOOL;1843) 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
1961pub extern "ws2_32" fn WSAConnectByList(1845pub extern "ws2_32" fn WSAConnectByList(
1962 s: SOCKET,1846 s: SOCKET,
1963 SocketAddress: *SOCKET_ADDRESS_LIST,1847 SocketAddress: *SOCKET_ADDRESS_LIST,
...@@ -1971,12 +1855,6 @@ pub extern "ws2_32" fn WSAConnectByList(...@@ -1971,12 +1855,6 @@ pub extern "ws2_32" fn WSAConnectByList(
19711855
1972pub extern "ws2_32" fn WSACreateEvent() callconv(.winapi) HANDLE;1856pub 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
1980pub extern "ws2_32" fn WSADuplicateSocketW(1858pub extern "ws2_32" fn WSADuplicateSocketW(
1981 s: SOCKET,1859 s: SOCKET,
1982 dwProcessId: u32,1860 dwProcessId: u32,
...@@ -1989,12 +1867,6 @@ pub extern "ws2_32" fn WSAEnumNetworkEvents(...@@ -1989,12 +1867,6 @@ pub extern "ws2_32" fn WSAEnumNetworkEvents(
1989 lpNetworkEvents: *WSANETWORKEVENTS,1867 lpNetworkEvents: *WSANETWORKEVENTS,
1990) callconv(.winapi) i32;1868) 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
1998pub extern "ws2_32" fn WSAEnumProtocolsW(1870pub extern "ws2_32" fn WSAEnumProtocolsW(
1999 lpiProtocols: ?*i32,1871 lpiProtocols: ?*i32,
2000 lpProtocolBuffer: ?*WSAPROTOCOL_INFOW,1872 lpProtocolBuffer: ?*WSAPROTOCOL_INFOW,
...@@ -2137,15 +2009,6 @@ pub extern "ws2_32" fn WSASetEvent(...@@ -2137,15 +2009,6 @@ pub extern "ws2_32" fn WSASetEvent(
2137 hEvent: HANDLE,2009 hEvent: HANDLE,
2138) callconv(.winapi) BOOL;2010) 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
2149pub extern "ws2_32" fn WSASocketW(2012pub extern "ws2_32" fn WSASocketW(
2150 af: i32,2013 af: i32,
2151 @"type": i32,2014 @"type": i32,
...@@ -2163,14 +2026,6 @@ pub extern "ws2_32" fn WSAWaitForMultipleEvents(...@@ -2163,14 +2026,6 @@ pub extern "ws2_32" fn WSAWaitForMultipleEvents(
2163 fAlertable: BOOL,2026 fAlertable: BOOL,
2164) callconv(.winapi) u32;2027) 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
2174pub extern "ws2_32" fn WSAAddressToStringW(2029pub extern "ws2_32" fn WSAAddressToStringW(
2175 lpsaAddress: *sockaddr,2030 lpsaAddress: *sockaddr,
2176 dwAddressLength: u32,2031 dwAddressLength: u32,
...@@ -2179,14 +2034,6 @@ pub extern "ws2_32" fn WSAAddressToStringW(...@@ -2179,14 +2034,6 @@ pub extern "ws2_32" fn WSAAddressToStringW(
2179 lpdwAddressStringLength: *u32,2034 lpdwAddressStringLength: *u32,
2180) callconv(.winapi) i32;2035) 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
2190pub extern "ws2_32" fn WSAStringToAddressW(2037pub extern "ws2_32" fn WSAStringToAddressW(
2191 AddressString: [*:0]const u16,2038 AddressString: [*:0]const u16,
2192 AddressFamily: i32,2039 AddressFamily: i32,
...@@ -2251,32 +2098,14 @@ pub extern "ws2_32" fn WSAProviderCompleteAsyncCall(...@@ -2251,32 +2098,14 @@ pub extern "ws2_32" fn WSAProviderCompleteAsyncCall(
2251 iRetCode: i32,2098 iRetCode: i32,
2252) callconv(.winapi) i32;2099) callconv(.winapi) i32;
22532100
2254pub extern "mswsock" fn EnumProtocolsA(
2255 lpiProtocols: ?*i32,
2256 lpProtocolBuffer: *anyopaque,
2257 lpdwBufferLength: *u32,
2258) callconv(.winapi) i32;
2259
2260pub extern "mswsock" fn EnumProtocolsW(2101pub extern "mswsock" fn EnumProtocolsW(
2261 lpiProtocols: ?*i32,2102 lpiProtocols: ?*i32,
2262 lpProtocolBuffer: *anyopaque,2103 lpProtocolBuffer: *anyopaque,
2263 lpdwBufferLength: *u32,2104 lpdwBufferLength: *u32,
2264) callconv(.winapi) i32;2105) 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
2278pub extern "mswsock" fn GetAddressByNameW(2107pub extern "mswsock" fn GetAddressByNameW(
2279 dwNameSpace: u32,2108 dwNameSpace: NS,
2280 lpServiceType: *GUID,2109 lpServiceType: *GUID,
2281 lpServiceName: ?[*:0]u16,2110 lpServiceName: ?[*:0]u16,
2282 lpiProtocols: ?*i32,2111 lpiProtocols: ?*i32,
...@@ -2288,45 +2117,28 @@ pub extern "mswsock" fn GetAddressByNameW(...@@ -2288,45 +2117,28 @@ pub extern "mswsock" fn GetAddressByNameW(
2288 lpdwAliasBufferLength: *u32,2117 lpdwAliasBufferLength: *u32,
2289) callconv(.winapi) i32;2118) callconv(.winapi) i32;
22902119
2291pub extern "mswsock" fn GetTypeByNameA(
2292 lpServiceName: [*:0]u8,
2293 lpServiceType: *GUID,
2294) callconv(.winapi) i32;
2295
2296pub extern "mswsock" fn GetTypeByNameW(2120pub extern "mswsock" fn GetTypeByNameW(
2297 lpServiceName: [*:0]u16,2121 lpServiceName: [*:0]u16,
2298 lpServiceType: *GUID,2122 lpServiceType: *GUID,
2299) callconv(.winapi) i32;2123) callconv(.winapi) i32;
23002124
2301pub extern "mswsock" fn GetNameByTypeA(
2302 lpServiceType: *GUID,
2303 lpServiceName: [*:0]u8,
2304 dwNameLength: u32,
2305) callconv(.winapi) i32;
2306
2307pub extern "mswsock" fn GetNameByTypeW(2125pub extern "mswsock" fn GetNameByTypeW(
2308 lpServiceType: *GUID,2126 lpServiceType: *GUID,
2309 lpServiceName: [*:0]u16,2127 lpServiceName: [*:0]u16,
2310 dwNameLength: u32,2128 dwNameLength: u32,
2311) callconv(.winapi) i32;2129) callconv(.winapi) i32;
23122130
2313pub extern "ws2_32" fn getaddrinfo(2131pub extern "ws2_32" fn GetAddrInfoExW(
2314 pNodeName: ?[*:0]const u8,2132 pName: ?[*:0]const u16,
2315 pServiceName: ?[*:0]const u8,2133 pServiceName: ?[*:0]const u16,
2316 pHints: ?*const addrinfoa,2134 dwNameSpace: NS,
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,
2324 lpNspId: ?*GUID,2135 lpNspId: ?*GUID,
2325 hints: ?*const addrinfoexA,2136 hints: ?*const ADDRINFOEXW,
2326 ppResult: **addrinfoexA,2137 ppResult: **ADDRINFOEXW,
2327 timeout: ?*timeval,2138 timeout: ?*timeval,
2328 lpOverlapped: ?*OVERLAPPED,2139 lpOverlapped: ?*OVERLAPPED,
2329 lpCompletionRoutine: ?LPLOOKUPSERVICE_COMPLETION_ROUTINE,2140 lpCompletionRoutine: ?LPLOOKUPSERVICE_COMPLETION_ROUTINE,
2141 lpNameHandle: ?*HANDLE,
2330) callconv(.winapi) i32;2142) callconv(.winapi) i32;
23312143
2332pub extern "ws2_32" fn GetAddrInfoExCancel(2144pub extern "ws2_32" fn GetAddrInfoExCancel(
...@@ -2337,12 +2149,8 @@ pub extern "ws2_32" fn GetAddrInfoExOverlappedResult(...@@ -2337,12 +2149,8 @@ pub extern "ws2_32" fn GetAddrInfoExOverlappedResult(
2337 lpOverlapped: *OVERLAPPED,2149 lpOverlapped: *OVERLAPPED,
2338) callconv(.winapi) i32;2150) callconv(.winapi) i32;
23392151
2340pub extern "ws2_32" fn freeaddrinfo(2152pub extern "ws2_32" fn FreeAddrInfoExW(
2341 pAddrInfo: ?*addrinfoa,2153 pAddrInfoEx: ?*ADDRINFOEXW,
2342) callconv(.winapi) void;
2343
2344pub extern "ws2_32" fn FreeAddrInfoEx(
2345 pAddrInfoEx: ?*addrinfoexA,
2346) callconv(.winapi) void;2154) callconv(.winapi) void;
23472155
2348pub extern "ws2_32" fn getnameinfo(2156pub extern "ws2_32" fn getnameinfo(
...@@ -2354,7 +2162,3 @@ pub extern "ws2_32" fn getnameinfo(...@@ -2354,7 +2162,3 @@ pub extern "ws2_32" fn getnameinfo(
2354 ServiceBufferName: u32,2162 ServiceBufferName: u32,
2355 Flags: i32,2163 Flags: i32,
2356) callconv(.winapi) i32;2164) 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) {...@@ -52,6 +52,10 @@ else switch (native_os) {
52 pub const fd_t = void;52 pub const fd_t = void;
53 pub const uid_t = void;53 pub const uid_t = void;
54 pub const gid_t = void;54 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;
55 },59 },
56};60};
5761
...@@ -98,7 +102,6 @@ pub const POSIX_FADV = system.POSIX_FADV;...@@ -98,7 +102,6 @@ pub const POSIX_FADV = system.POSIX_FADV;
98pub const PR = system.PR;102pub const PR = system.PR;
99pub const PROT = system.PROT;103pub const PROT = system.PROT;
100pub const RLIM = system.RLIM;104pub const RLIM = system.RLIM;
101pub const RR = system.RR;
102pub const S = system.S;105pub const S = system.S;
103pub const SA = system.SA;106pub const SA = system.SA;
104pub const SC = system.SC;107pub const SC = system.SC;
...@@ -357,6 +360,7 @@ pub const FChmodAtError = FChmodError || error{...@@ -357,6 +360,7 @@ pub const FChmodAtError = FChmodError || error{
357 ProcessFdQuotaExceeded,360 ProcessFdQuotaExceeded,
358 /// The procfs fallback was used but the system exceeded it open file limit.361 /// The procfs fallback was used but the system exceeded it open file limit.
359 SystemFdQuotaExceeded,362 SystemFdQuotaExceeded,
363 Canceled,
360};364};
361365
362/// Changes the `mode` of `path` relative to the directory referred to by366/// 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...@@ -486,7 +490,9 @@ fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtEr
486 const stat = fstatatZ(pathfd, "", AT.EMPTY_PATH) catch |err| switch (err) {490 const stat = fstatatZ(pathfd, "", AT.EMPTY_PATH) catch |err| switch (err) {
487 error.NameTooLong => unreachable,491 error.NameTooLong => unreachable,
488 error.FileNotFound => unreachable,492 error.FileNotFound => unreachable,
489 error.InvalidUtf8 => unreachable,493 error.Streaming => unreachable,
494 error.BadPathName => return error.Unexpected,
495 error.Canceled => return error.Canceled,
490 else => |e| return e,496 else => |e| return e,
491 };497 };
492 if ((stat.mode & S.IFMT) == S.IFLNK)498 if ((stat.mode & S.IFMT) == S.IFLNK)
...@@ -664,18 +670,22 @@ pub fn getrandom(buffer: []u8) GetRandomError!void {...@@ -664,18 +670,22 @@ pub fn getrandom(buffer: []u8) GetRandomError!void {
664 return getRandomBytesDevURandom(buffer);670 return getRandomBytesDevURandom(buffer);
665}671}
666672
667fn getRandomBytesDevURandom(buf: []u8) !void {673fn getRandomBytesDevURandom(buf: []u8) GetRandomError!void {
668 const fd = try openZ("/dev/urandom", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);674 const fd = try openZ("/dev/urandom", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
669 defer close(fd);675 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 };
672 if (!S.ISCHR(st.mode)) {681 if (!S.ISCHR(st.mode)) {
673 return error.NoDevice;682 return error.NoDevice;
674 }683 }
675684
676 const file: fs.File = .{ .handle = fd };685 var i: usize = 0;
677 var file_reader = file.readerStreaming(&.{});686 while (i < buf.len) {
678 file_reader.interface.readSliceAll(buf) catch return error.Unexpected;687 i += read(fd, buf[i..]) catch return error.Unexpected;
688 }
679}689}
680690
681/// Causes abnormal process termination.691/// Causes abnormal process termination.
...@@ -699,7 +709,7 @@ pub fn abort() noreturn {...@@ -699,7 +709,7 @@ pub fn abort() noreturn {
699 // for user-defined signal handlers that want to restore some state in709 // for user-defined signal handlers that want to restore some state in
700 // some program sections and crash in others.710 // some program sections and crash in others.
701 // So, the user-installed SIGABRT handler is run, if present.711 // So, the user-installed SIGABRT handler is run, if present.
702 raise(SIG.ABRT) catch {};712 raise(.ABRT) catch {};
703713
704 // Disable all signal handlers.714 // Disable all signal handlers.
705 const filledset = linux.sigfillset();715 const filledset = linux.sigfillset();
...@@ -719,17 +729,17 @@ pub fn abort() noreturn {...@@ -719,17 +729,17 @@ pub fn abort() noreturn {
719 .mask = sigemptyset(),729 .mask = sigemptyset(),
720 .flags = 0,730 .flags = 0,
721 };731 };
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
726 var sigabrtmask = sigemptyset();736 var sigabrtmask = sigemptyset();
727 sigaddset(&sigabrtmask, SIG.ABRT);737 sigaddset(&sigabrtmask, .ABRT);
728 sigprocmask(SIG.UNBLOCK, &sigabrtmask, null);738 sigprocmask(SIG.UNBLOCK, &sigabrtmask, null);
729739
730 // Beyond this point should be unreachable.740 // Beyond this point should be unreachable.
731 @as(*allowzero volatile u8, @ptrFromInt(0)).* = 0;741 @as(*allowzero volatile u8, @ptrFromInt(0)).* = 0;
732 raise(SIG.KILL) catch {};742 raise(.KILL) catch {};
733 exit(127); // Pid 1 might not be signalled in some containers.743 exit(127); // Pid 1 might not be signalled in some containers.
734 }744 }
735 switch (native_os) {745 switch (native_os) {
...@@ -740,7 +750,7 @@ pub fn abort() noreturn {...@@ -740,7 +750,7 @@ pub fn abort() noreturn {
740750
741pub const RaiseError = UnexpectedError;751pub const RaiseError = UnexpectedError;
742752
743pub fn raise(sig: u8) RaiseError!void {753pub fn raise(sig: SIG) RaiseError!void {
744 if (builtin.link_libc) {754 if (builtin.link_libc) {
745 switch (errno(system.raise(sig))) {755 switch (errno(system.raise(sig))) {
746 .SUCCESS => return,756 .SUCCESS => return,
...@@ -768,7 +778,7 @@ pub fn raise(sig: u8) RaiseError!void {...@@ -768,7 +778,7 @@ pub fn raise(sig: u8) RaiseError!void {
768778
769pub const KillError = error{ ProcessNotFound, PermissionDenied } || UnexpectedError;779pub 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 {
772 switch (errno(system.kill(pid, sig))) {782 switch (errno(system.kill(pid, sig))) {
773 .SUCCESS => return,783 .SUCCESS => return,
774 .INVAL => unreachable, // invalid signal784 .INVAL => unreachable, // invalid signal
...@@ -805,36 +815,7 @@ pub fn exit(status: u8) noreturn {...@@ -805,36 +815,7 @@ pub fn exit(status: u8) noreturn {
805 system.exit(status);815 system.exit(status);
806}816}
807817
808pub const ReadError = error{818pub const ReadError = std.Io.File.Reader.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;
838819
839/// Returns the number of bytes that were read, which can be less than820/// Returns the number of bytes that were read, which can be less than
840/// buf.len. If 0 bytes were read, that means EOF.821/// buf.len. If 0 bytes were read, that means EOF.
...@@ -869,9 +850,9 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {...@@ -869,9 +850,9 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
869 .ISDIR => return error.IsDir,850 .ISDIR => return error.IsDir,
870 .NOBUFS => return error.SystemResources,851 .NOBUFS => return error.SystemResources,
871 .NOMEM => return error.SystemResources,852 .NOMEM => return error.SystemResources,
872 .NOTCONN => return error.SocketNotConnected,853 .NOTCONN => return error.SocketUnconnected,
873 .CONNRESET => return error.ConnectionResetByPeer,854 .CONNRESET => return error.ConnectionResetByPeer,
874 .TIMEDOUT => return error.ConnectionTimedOut,855 .TIMEDOUT => return error.Timeout,
875 .NOTCAPABLE => return error.AccessDenied,856 .NOTCAPABLE => return error.AccessDenied,
876 else => |err| return unexpectedErrno(err),857 else => |err| return unexpectedErrno(err),
877 }858 }
...@@ -898,9 +879,9 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {...@@ -898,9 +879,9 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
898 .ISDIR => return error.IsDir,879 .ISDIR => return error.IsDir,
899 .NOBUFS => return error.SystemResources,880 .NOBUFS => return error.SystemResources,
900 .NOMEM => return error.SystemResources,881 .NOMEM => return error.SystemResources,
901 .NOTCONN => return error.SocketNotConnected,882 .NOTCONN => return error.SocketUnconnected,
902 .CONNRESET => return error.ConnectionResetByPeer,883 .CONNRESET => return error.ConnectionResetByPeer,
903 .TIMEDOUT => return error.ConnectionTimedOut,884 .TIMEDOUT => return error.Timeout,
904 else => |err| return unexpectedErrno(err),885 else => |err| return unexpectedErrno(err),
905 }886 }
906 }887 }
...@@ -921,7 +902,6 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {...@@ -921,7 +902,6 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
921/// a pointer within the address space of the application.902/// a pointer within the address space of the application.
922pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {903pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
923 if (native_os == .windows) {904 if (native_os == .windows) {
924 // TODO improve this to use ReadFileScatter
925 if (iov.len == 0) return 0;905 if (iov.len == 0) return 0;
926 const first = iov[0];906 const first = iov[0];
927 return read(fd, first.base[0..first.len]);907 return read(fd, first.base[0..first.len]);
...@@ -939,9 +919,9 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {...@@ -939,9 +919,9 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
939 .ISDIR => return error.IsDir,919 .ISDIR => return error.IsDir,
940 .NOBUFS => return error.SystemResources,920 .NOBUFS => return error.SystemResources,
941 .NOMEM => return error.SystemResources,921 .NOMEM => return error.SystemResources,
942 .NOTCONN => return error.SocketNotConnected,922 .NOTCONN => return error.SocketUnconnected,
943 .CONNRESET => return error.ConnectionResetByPeer,923 .CONNRESET => return error.ConnectionResetByPeer,
944 .TIMEDOUT => return error.ConnectionTimedOut,924 .TIMEDOUT => return error.Timeout,
945 .NOTCAPABLE => return error.AccessDenied,925 .NOTCAPABLE => return error.AccessDenied,
946 else => |err| return unexpectedErrno(err),926 else => |err| return unexpectedErrno(err),
947 }927 }
...@@ -961,15 +941,15 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {...@@ -961,15 +941,15 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
961 .ISDIR => return error.IsDir,941 .ISDIR => return error.IsDir,
962 .NOBUFS => return error.SystemResources,942 .NOBUFS => return error.SystemResources,
963 .NOMEM => return error.SystemResources,943 .NOMEM => return error.SystemResources,
964 .NOTCONN => return error.SocketNotConnected,944 .NOTCONN => return error.SocketUnconnected,
965 .CONNRESET => return error.ConnectionResetByPeer,945 .CONNRESET => return error.ConnectionResetByPeer,
966 .TIMEDOUT => return error.ConnectionTimedOut,946 .TIMEDOUT => return error.Timeout,
967 else => |err| return unexpectedErrno(err),947 else => |err| return unexpectedErrno(err),
968 }948 }
969 }949 }
970}950}
971951
972pub const PReadError = ReadError || error{Unseekable};952pub const PReadError = std.Io.File.ReadPositionalError;
973953
974/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.954/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
975///955///
...@@ -1008,9 +988,9 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {...@@ -1008,9 +988,9 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
1008 .ISDIR => return error.IsDir,988 .ISDIR => return error.IsDir,
1009 .NOBUFS => return error.SystemResources,989 .NOBUFS => return error.SystemResources,
1010 .NOMEM => return error.SystemResources,990 .NOMEM => return error.SystemResources,
1011 .NOTCONN => return error.SocketNotConnected,991 .NOTCONN => return error.SocketUnconnected,
1012 .CONNRESET => return error.ConnectionResetByPeer,992 .CONNRESET => return error.ConnectionResetByPeer,
1013 .TIMEDOUT => return error.ConnectionTimedOut,993 .TIMEDOUT => return error.Timeout,
1014 .NXIO => return error.Unseekable,994 .NXIO => return error.Unseekable,
1015 .SPIPE => return error.Unseekable,995 .SPIPE => return error.Unseekable,
1016 .OVERFLOW => return error.Unseekable,996 .OVERFLOW => return error.Unseekable,
...@@ -1041,9 +1021,9 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {...@@ -1041,9 +1021,9 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
1041 .ISDIR => return error.IsDir,1021 .ISDIR => return error.IsDir,
1042 .NOBUFS => return error.SystemResources,1022 .NOBUFS => return error.SystemResources,
1043 .NOMEM => return error.SystemResources,1023 .NOMEM => return error.SystemResources,
1044 .NOTCONN => return error.SocketNotConnected,1024 .NOTCONN => return error.SocketUnconnected,
1045 .CONNRESET => return error.ConnectionResetByPeer,1025 .CONNRESET => return error.ConnectionResetByPeer,
1046 .TIMEDOUT => return error.ConnectionTimedOut,1026 .TIMEDOUT => return error.Timeout,
1047 .NXIO => return error.Unseekable,1027 .NXIO => return error.Unseekable,
1048 .SPIPE => return error.Unseekable,1028 .SPIPE => return error.Unseekable,
1049 .OVERFLOW => return error.Unseekable,1029 .OVERFLOW => return error.Unseekable,
...@@ -1159,9 +1139,9 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {...@@ -1159,9 +1139,9 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {
1159 .ISDIR => return error.IsDir,1139 .ISDIR => return error.IsDir,
1160 .NOBUFS => return error.SystemResources,1140 .NOBUFS => return error.SystemResources,
1161 .NOMEM => return error.SystemResources,1141 .NOMEM => return error.SystemResources,
1162 .NOTCONN => return error.SocketNotConnected,1142 .NOTCONN => return error.SocketUnconnected,
1163 .CONNRESET => return error.ConnectionResetByPeer,1143 .CONNRESET => return error.ConnectionResetByPeer,
1164 .TIMEDOUT => return error.ConnectionTimedOut,1144 .TIMEDOUT => return error.Timeout,
1165 .NXIO => return error.Unseekable,1145 .NXIO => return error.Unseekable,
1166 .SPIPE => return error.Unseekable,1146 .SPIPE => return error.Unseekable,
1167 .OVERFLOW => return error.Unseekable,1147 .OVERFLOW => return error.Unseekable,
...@@ -1185,9 +1165,9 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {...@@ -1185,9 +1165,9 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {
1185 .ISDIR => return error.IsDir,1165 .ISDIR => return error.IsDir,
1186 .NOBUFS => return error.SystemResources,1166 .NOBUFS => return error.SystemResources,
1187 .NOMEM => return error.SystemResources,1167 .NOMEM => return error.SystemResources,
1188 .NOTCONN => return error.SocketNotConnected,1168 .NOTCONN => return error.SocketUnconnected,
1189 .CONNRESET => return error.ConnectionResetByPeer,1169 .CONNRESET => return error.ConnectionResetByPeer,
1190 .TIMEDOUT => return error.ConnectionTimedOut,1170 .TIMEDOUT => return error.Timeout,
1191 .NXIO => return error.Unseekable,1171 .NXIO => return error.Unseekable,
1192 .SPIPE => return error.Unseekable,1172 .SPIPE => return error.Unseekable,
1193 .OVERFLOW => return error.Unseekable,1173 .OVERFLOW => return error.Unseekable,
...@@ -1209,7 +1189,7 @@ pub const WriteError = error{...@@ -1209,7 +1189,7 @@ pub const WriteError = error{
1209 PermissionDenied,1189 PermissionDenied,
1210 BrokenPipe,1190 BrokenPipe,
1211 SystemResources,1191 SystemResources,
1212 OperationAborted,1192 Canceled,
1213 NotOpenForWriting,1193 NotOpenForWriting,
12141194
1215 /// The process cannot access the file because another process has locked1195 /// The process cannot access the file because another process has locked
...@@ -1232,7 +1212,7 @@ pub const WriteError = error{...@@ -1232,7 +1212,7 @@ pub const WriteError = error{
12321212
1233 /// The socket type requires that message be sent atomically, and the size of the message1213 /// The socket type requires that message be sent atomically, and the size of the message
1234 /// to be sent made this impossible. The message is not transmitted.1214 /// to be sent made this impossible. The message is not transmitted.
1235 MessageTooBig,1215 MessageOversize,
1236} || UnexpectedError;1216} || UnexpectedError;
12371217
1238/// Write to a file descriptor.1218/// Write to a file descriptor.
...@@ -1314,7 +1294,7 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {...@@ -1314,7 +1294,7 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
1314 .CONNRESET => return error.ConnectionResetByPeer,1294 .CONNRESET => return error.ConnectionResetByPeer,
1315 .BUSY => return error.DeviceBusy,1295 .BUSY => return error.DeviceBusy,
1316 .NXIO => return error.NoDevice,1296 .NXIO => return error.NoDevice,
1317 .MSGSIZE => return error.MessageTooBig,1297 .MSGSIZE => return error.MessageOversize,
1318 else => |err| return unexpectedErrno(err),1298 else => |err| return unexpectedErrno(err),
1319 }1299 }
1320 }1300 }
...@@ -1570,81 +1550,7 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz...@@ -1570,81 +1550,7 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz
1570 }1550 }
1571}1551}
15721552
1573pub const OpenError = error{1553pub const OpenError = std.Io.File.OpenError || error{WouldBlock};
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;
16481554
1649/// Open and possibly create a file. Keeps trying if it gets interrupted.1555/// Open and possibly create a file. Keeps trying if it gets interrupted.
1650/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).1556/// 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 {...@@ -1699,10 +1605,7 @@ pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t {
1699 .PERM => return error.PermissionDenied,1605 .PERM => return error.PermissionDenied,
1700 .EXIST => return error.PathAlreadyExists,1606 .EXIST => return error.PathAlreadyExists,
1701 .BUSY => return error.DeviceBusy,1607 .BUSY => return error.DeviceBusy,
1702 .ILSEQ => |err| if (native_os == .wasi)1608 .ILSEQ => return error.BadPathName,
1703 return error.InvalidUtf8
1704 else
1705 return unexpectedErrno(err),
1706 else => |err| return unexpectedErrno(err),1609 else => |err| return unexpectedErrno(err),
1707 }1610 }
1708 }1611 }
...@@ -1718,119 +1621,12 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenE...@@ -1718,119 +1621,12 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenE
1718 if (native_os == .windows) {1621 if (native_os == .windows) {
1719 @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");1622 @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API");
1720 } else if (native_os == .wasi and !builtin.link_libc) {1623 } else if (native_os == .wasi and !builtin.link_libc) {
1721 // `mode` is ignored on WASI, which does not support unix-style file permissions1624 @compileError("use std.Io instead");
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;
1741 }1625 }
1742 const file_path_c = try toPosixPath(file_path);1626 const file_path_c = try toPosixPath(file_path);
1743 return openatZ(dir_fd, &file_path_c, flags, mode);1627 return openatZ(dir_fd, &file_path_c, flags, mode);
1744}1628}
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
1834/// Open and possibly create a file. Keeps trying if it gets interrupted.1630/// Open and possibly create a file. Keeps trying if it gets interrupted.
1835/// `file_path` is relative to the open directory handle `dir_fd`.1631/// `file_path` is relative to the open directory handle `dir_fd`.
1836/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).1632/// 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...@@ -1875,10 +1671,7 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) O
1875 .AGAIN => return error.WouldBlock,1671 .AGAIN => return error.WouldBlock,
1876 .TXTBSY => return error.FileBusy,1672 .TXTBSY => return error.FileBusy,
1877 .NXIO => return error.NoDevice,1673 .NXIO => return error.NoDevice,
1878 .ILSEQ => |err| if (native_os == .wasi)1674 .ILSEQ => return error.BadPathName,
1879 return error.InvalidUtf8
1880 else
1881 return unexpectedErrno(err),
1882 else => |err| return unexpectedErrno(err),1675 else => |err| return unexpectedErrno(err),
1883 }1676 }
1884 }1677 }
...@@ -2132,14 +1925,9 @@ pub const SymLinkError = error{...@@ -2132,14 +1925,9 @@ pub const SymLinkError = error{
2132 ReadOnlyFileSystem,1925 ReadOnlyFileSystem,
2133 NotDir,1926 NotDir,
2134 NameTooLong,1927 NameTooLong,
21351928 /// WASI: file paths must be valid UTF-8.
2136 /// WASI-only; file paths must be valid UTF-8.1929 /// Windows: file paths provided by the user must be valid WTF-8.
2137 InvalidUtf8,
2138
2139 /// Windows-only; file paths provided by the user must be valid WTF-8.
2140 /// https://wtf-8.codeberg.page/1930 /// https://wtf-8.codeberg.page/
2141 InvalidWtf8,
2142
2143 BadPathName,1931 BadPathName,
2144} || UnexpectedError;1932} || UnexpectedError;
21451933
...@@ -2186,10 +1974,7 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin...@@ -2186,10 +1974,7 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin
2186 .NOMEM => return error.SystemResources,1974 .NOMEM => return error.SystemResources,
2187 .NOSPC => return error.NoSpaceLeft,1975 .NOSPC => return error.NoSpaceLeft,
2188 .ROFS => return error.ReadOnlyFileSystem,1976 .ROFS => return error.ReadOnlyFileSystem,
2189 .ILSEQ => |err| if (native_os == .wasi)1977 .ILSEQ => return error.BadPathName,
2190 return error.InvalidUtf8
2191 else
2192 return unexpectedErrno(err),
2193 else => |err| return unexpectedErrno(err),1978 else => |err| return unexpectedErrno(err),
2194 }1979 }
2195}1980}
...@@ -2235,7 +2020,7 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c...@@ -2235,7 +2020,7 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c
2235 .NOSPC => return error.NoSpaceLeft,2020 .NOSPC => return error.NoSpaceLeft,
2236 .ROFS => return error.ReadOnlyFileSystem,2021 .ROFS => return error.ReadOnlyFileSystem,
2237 .NOTCAPABLE => return error.AccessDenied,2022 .NOTCAPABLE => return error.AccessDenied,
2238 .ILSEQ => return error.InvalidUtf8,2023 .ILSEQ => return error.BadPathName,
2239 else => |err| return unexpectedErrno(err),2024 else => |err| return unexpectedErrno(err),
2240 }2025 }
2241}2026}
...@@ -2264,10 +2049,7 @@ pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:...@@ -2264,10 +2049,7 @@ pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:
2264 .NOMEM => return error.SystemResources,2049 .NOMEM => return error.SystemResources,
2265 .NOSPC => return error.NoSpaceLeft,2050 .NOSPC => return error.NoSpaceLeft,
2266 .ROFS => return error.ReadOnlyFileSystem,2051 .ROFS => return error.ReadOnlyFileSystem,
2267 .ILSEQ => |err| if (native_os == .wasi)2052 .ILSEQ => return error.BadPathName,
2268 return error.InvalidUtf8
2269 else
2270 return unexpectedErrno(err),
2271 else => |err| return unexpectedErrno(err),2053 else => |err| return unexpectedErrno(err),
2272 }2054 }
2273}2055}
...@@ -2286,9 +2068,7 @@ pub const LinkError = UnexpectedError || error{...@@ -2286,9 +2068,7 @@ pub const LinkError = UnexpectedError || error{
2286 NoSpaceLeft,2068 NoSpaceLeft,
2287 ReadOnlyFileSystem,2069 ReadOnlyFileSystem,
2288 NotSameFileSystem,2070 NotSameFileSystem,
22892071 BadPathName,
2290 /// WASI-only; file paths must be valid UTF-8.
2291 InvalidUtf8,
2292};2072};
22932073
2294/// On WASI, both paths should be encoded as valid UTF-8.2074/// 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 {...@@ -2314,10 +2094,7 @@ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8) LinkError!void {
2314 .ROFS => return error.ReadOnlyFileSystem,2094 .ROFS => return error.ReadOnlyFileSystem,
2315 .XDEV => return error.NotSameFileSystem,2095 .XDEV => return error.NotSameFileSystem,
2316 .INVAL => unreachable,2096 .INVAL => unreachable,
2317 .ILSEQ => |err| if (native_os == .wasi)2097 .ILSEQ => return error.BadPathName,
2318 return error.InvalidUtf8
2319 else
2320 return unexpectedErrno(err),
2321 else => |err| return unexpectedErrno(err),2098 else => |err| return unexpectedErrno(err),
2322 }2099 }
2323}2100}
...@@ -2368,10 +2145,7 @@ pub fn linkatZ(...@@ -2368,10 +2145,7 @@ pub fn linkatZ(
2368 .ROFS => return error.ReadOnlyFileSystem,2145 .ROFS => return error.ReadOnlyFileSystem,
2369 .XDEV => return error.NotSameFileSystem,2146 .XDEV => return error.NotSameFileSystem,
2370 .INVAL => unreachable,2147 .INVAL => unreachable,
2371 .ILSEQ => |err| if (native_os == .wasi)2148 .ILSEQ => return error.BadPathName,
2372 return error.InvalidUtf8
2373 else
2374 return unexpectedErrno(err),
2375 else => |err| return unexpectedErrno(err),2149 else => |err| return unexpectedErrno(err),
2376 }2150 }
2377}2151}
...@@ -2417,7 +2191,7 @@ pub fn linkat(...@@ -2417,7 +2191,7 @@ pub fn linkat(
2417 .ROFS => return error.ReadOnlyFileSystem,2191 .ROFS => return error.ReadOnlyFileSystem,
2418 .XDEV => return error.NotSameFileSystem,2192 .XDEV => return error.NotSameFileSystem,
2419 .INVAL => unreachable,2193 .INVAL => unreachable,
2420 .ILSEQ => return error.InvalidUtf8,2194 .ILSEQ => return error.BadPathName,
2421 else => |err| return unexpectedErrno(err),2195 else => |err| return unexpectedErrno(err),
2422 }2196 }
2423 }2197 }
...@@ -2442,14 +2216,10 @@ pub const UnlinkError = error{...@@ -2442,14 +2216,10 @@ pub const UnlinkError = error{
2442 SystemResources,2216 SystemResources,
2443 ReadOnlyFileSystem,2217 ReadOnlyFileSystem,
24442218
2445 /// WASI-only; file paths must be valid UTF-8.2219 /// WASI: file paths must be valid UTF-8.
2446 InvalidUtf8,2220 /// Windows: file paths provided by the user must be valid WTF-8.
2447
2448 /// Windows-only; file paths provided by the user must be valid WTF-8.
2449 /// https://wtf-8.codeberg.page/2221 /// https://wtf-8.codeberg.page/
2450 InvalidWtf8,2222 /// Windows: file paths cannot contain these characters:
2451
2452 /// On Windows, file paths cannot contain these characters:
2453 /// '/', '*', '?', '"', '<', '>', '|'2223 /// '/', '*', '?', '"', '<', '>', '|'
2454 BadPathName,2224 BadPathName,
24552225
...@@ -2500,10 +2270,7 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {...@@ -2500,10 +2270,7 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
2500 .NOTDIR => return error.NotDir,2270 .NOTDIR => return error.NotDir,
2501 .NOMEM => return error.SystemResources,2271 .NOMEM => return error.SystemResources,
2502 .ROFS => return error.ReadOnlyFileSystem,2272 .ROFS => return error.ReadOnlyFileSystem,
2503 .ILSEQ => |err| if (native_os == .wasi)2273 .ILSEQ => return error.BadPathName,
2504 return error.InvalidUtf8
2505 else
2506 return unexpectedErrno(err),
2507 else => |err| return unexpectedErrno(err),2274 else => |err| return unexpectedErrno(err),
2508 }2275 }
2509}2276}
...@@ -2562,7 +2329,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro...@@ -2562,7 +2329,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro
2562 .ROFS => return error.ReadOnlyFileSystem,2329 .ROFS => return error.ReadOnlyFileSystem,
2563 .NOTEMPTY => return error.DirNotEmpty,2330 .NOTEMPTY => return error.DirNotEmpty,
2564 .NOTCAPABLE => return error.AccessDenied,2331 .NOTCAPABLE => return error.AccessDenied,
2565 .ILSEQ => return error.InvalidUtf8,2332 .ILSEQ => return error.BadPathName,
25662333
2567 .INVAL => unreachable, // invalid flags, or pathname has . as last component2334 .INVAL => unreachable, // invalid flags, or pathname has . as last component
2568 .BADF => unreachable, // always a race condition2335 .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...@@ -2595,10 +2362,7 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
2595 .ROFS => return error.ReadOnlyFileSystem,2362 .ROFS => return error.ReadOnlyFileSystem,
2596 .EXIST => return error.DirNotEmpty,2363 .EXIST => return error.DirNotEmpty,
2597 .NOTEMPTY => return error.DirNotEmpty,2364 .NOTEMPTY => return error.DirNotEmpty,
2598 .ILSEQ => |err| if (native_os == .wasi)2365 .ILSEQ => return error.BadPathName,
2599 return error.InvalidUtf8
2600 else
2601 return unexpectedErrno(err),
26022366
2603 .INVAL => unreachable, // invalid flags, or pathname has . as last component2367 .INVAL => unreachable, // invalid flags, or pathname has . as last component
2604 .BADF => unreachable, // always a race condition2368 .BADF => unreachable, // always a race condition
...@@ -2634,11 +2398,9 @@ pub const RenameError = error{...@@ -2634,11 +2398,9 @@ pub const RenameError = error{
2634 PathAlreadyExists,2398 PathAlreadyExists,
2635 ReadOnlyFileSystem,2399 ReadOnlyFileSystem,
2636 RenameAcrossMountPoints,2400 RenameAcrossMountPoints,
2637 /// WASI-only; file paths must be valid UTF-8.2401 /// WASI: file paths must be valid UTF-8.
2638 InvalidUtf8,2402 /// Windows: file paths provided by the user must be valid WTF-8.
2639 /// Windows-only; file paths provided by the user must be valid WTF-8.
2640 /// https://wtf-8.codeberg.page/2403 /// https://wtf-8.codeberg.page/
2641 InvalidWtf8,
2642 BadPathName,2404 BadPathName,
2643 NoDevice,2405 NoDevice,
2644 SharingViolation,2406 SharingViolation,
...@@ -2700,10 +2462,7 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi...@@ -2700,10 +2462,7 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi
2700 .NOTEMPTY => return error.PathAlreadyExists,2462 .NOTEMPTY => return error.PathAlreadyExists,
2701 .ROFS => return error.ReadOnlyFileSystem,2463 .ROFS => return error.ReadOnlyFileSystem,
2702 .XDEV => return error.RenameAcrossMountPoints,2464 .XDEV => return error.RenameAcrossMountPoints,
2703 .ILSEQ => |err| if (native_os == .wasi)2465 .ILSEQ => return error.BadPathName,
2704 return error.InvalidUtf8
2705 else
2706 return unexpectedErrno(err),
2707 else => |err| return unexpectedErrno(err),2466 else => |err| return unexpectedErrno(err),
2708 }2467 }
2709}2468}
...@@ -2764,7 +2523,7 @@ fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!void {...@@ -2764,7 +2523,7 @@ fn renameatWasi(old: RelativePathWasi, new: RelativePathWasi) RenameError!void {
2764 .ROFS => return error.ReadOnlyFileSystem,2523 .ROFS => return error.ReadOnlyFileSystem,
2765 .XDEV => return error.RenameAcrossMountPoints,2524 .XDEV => return error.RenameAcrossMountPoints,
2766 .NOTCAPABLE => return error.AccessDenied,2525 .NOTCAPABLE => return error.AccessDenied,
2767 .ILSEQ => return error.InvalidUtf8,2526 .ILSEQ => return error.BadPathName,
2768 else => |err| return unexpectedErrno(err),2527 else => |err| return unexpectedErrno(err),
2769 }2528 }
2770}2529}
...@@ -2815,10 +2574,7 @@ pub fn renameatZ(...@@ -2815,10 +2574,7 @@ pub fn renameatZ(
2815 .NOTEMPTY => return error.PathAlreadyExists,2574 .NOTEMPTY => return error.PathAlreadyExists,
2816 .ROFS => return error.ReadOnlyFileSystem,2575 .ROFS => return error.ReadOnlyFileSystem,
2817 .XDEV => return error.RenameAcrossMountPoints,2576 .XDEV => return error.RenameAcrossMountPoints,
2818 .ILSEQ => |err| if (native_os == .wasi)2577 .ILSEQ => return error.BadPathName,
2819 return error.InvalidUtf8
2820 else
2821 return unexpectedErrno(err),
2822 else => |err| return unexpectedErrno(err),2578 else => |err| return unexpectedErrno(err),
2823 }2579 }
2824}2580}
...@@ -2869,7 +2625,7 @@ pub fn renameatW(...@@ -2869,7 +2625,7 @@ pub fn renameatW(
2869 if (ReplaceIfExists == windows.TRUE) flags |= windows.FILE_RENAME_REPLACE_IF_EXISTS;2625 if (ReplaceIfExists == windows.TRUE) flags |= windows.FILE_RENAME_REPLACE_IF_EXISTS;
2870 rename_info.* = .{2626 rename_info.* = .{
2871 .Flags = flags,2627 .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,
2873 .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong2629 .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong
2874 .FileName = undefined,2630 .FileName = undefined,
2875 };2631 };
...@@ -2906,7 +2662,7 @@ pub fn renameatW(...@@ -2906,7 +2662,7 @@ pub fn renameatW(
29062662
2907 rename_info.* = .{2663 rename_info.* = .{
2908 .Flags = ReplaceIfExists,2664 .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,
2910 .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong2666 .FileNameLength = @intCast(new_path_w.len * 2), // already checked error.NameTooLong
2911 .FileName = undefined,2667 .FileName = undefined,
2912 };2668 };
...@@ -2943,47 +2699,21 @@ pub fn renameatW(...@@ -2943,47 +2699,21 @@ pub fn renameatW(
2943/// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding.2699/// On other platforms, `sub_dir_path` is an opaque sequence of bytes with no particular encoding.
2944pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: mode_t) MakeDirError!void {2700pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: mode_t) MakeDirError!void {
2945 if (native_os == .windows) {2701 if (native_os == .windows) {
2946 const sub_dir_path_w = try windows.sliceToPrefixedFileW(dir_fd, sub_dir_path);2702 @compileError("use std.Io instead");
2947 return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
2948 } else if (native_os == .wasi and !builtin.link_libc) {2703 } else if (native_os == .wasi and !builtin.link_libc) {
2949 return mkdiratWasi(dir_fd, sub_dir_path, mode);2704 @compileError("use std.Io instead");
2950 } else {2705 } else {
2951 const sub_dir_path_c = try toPosixPath(sub_dir_path);2706 const sub_dir_path_c = try toPosixPath(sub_dir_path);
2952 return mkdiratZ(dir_fd, &sub_dir_path_c, mode);2707 return mkdiratZ(dir_fd, &sub_dir_path_c, mode);
2953 }2708 }
2954}2709}
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
2980/// Same as `mkdirat` except the parameters are null-terminated.2711/// Same as `mkdirat` except the parameters are null-terminated.
2981pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void {2712pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void {
2982 if (native_os == .windows) {2713 if (native_os == .windows) {
2983 const sub_dir_path_w = try windows.cStrToPrefixedFileW(dir_fd, sub_dir_path);2714 @compileError("use std.Io instead");
2984 return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
2985 } else if (native_os == .wasi and !builtin.link_libc) {2715 } 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");
2987 }2717 }
2988 switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) {2718 switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) {
2989 .SUCCESS => return,2719 .SUCCESS => return,
...@@ -3003,58 +2733,12 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: mode_t) MakeDir...@@ -3003,58 +2733,12 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: mode_t) MakeDir
3003 .ROFS => return error.ReadOnlyFileSystem,2733 .ROFS => return error.ReadOnlyFileSystem,
3004 // dragonfly: when dir_fd is unlinked from filesystem2734 // dragonfly: when dir_fd is unlinked from filesystem
3005 .NOTCONN => return error.FileNotFound,2735 .NOTCONN => return error.FileNotFound,
3006 .ILSEQ => |err| if (native_os == .wasi)2736 .ILSEQ => return error.BadPathName,
3007 return error.InvalidUtf8
3008 else
3009 return unexpectedErrno(err),
3010 else => |err| return unexpectedErrno(err),2737 else => |err| return unexpectedErrno(err),
3011 }2738 }
3012}2739}
30132740
3014/// Windows-only. Same as `mkdirat` except the parameter WTF16 LE encoded.2741pub const MakeDirError = std.Io.Dir.MakeError;
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;
30582742
3059/// Create a directory.2743/// Create a directory.
3060/// `mode` is ignored on Windows and WASI.2744/// `mode` is ignored on Windows and WASI.
...@@ -3099,10 +2783,7 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void {...@@ -3099,10 +2783,7 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: mode_t) MakeDirError!void {
3099 .NOSPC => return error.NoSpaceLeft,2783 .NOSPC => return error.NoSpaceLeft,
3100 .NOTDIR => return error.NotDir,2784 .NOTDIR => return error.NotDir,
3101 .ROFS => return error.ReadOnlyFileSystem,2785 .ROFS => return error.ReadOnlyFileSystem,
3102 .ILSEQ => |err| if (native_os == .wasi)2786 .ILSEQ => return error.BadPathName,
3103 return error.InvalidUtf8
3104 else
3105 return unexpectedErrno(err),
3106 else => |err| return unexpectedErrno(err),2787 else => |err| return unexpectedErrno(err),
3107 }2788 }
3108}2789}
...@@ -3137,11 +2818,9 @@ pub const DeleteDirError = error{...@@ -3137,11 +2818,9 @@ pub const DeleteDirError = error{
3137 NotDir,2818 NotDir,
3138 DirNotEmpty,2819 DirNotEmpty,
3139 ReadOnlyFileSystem,2820 ReadOnlyFileSystem,
3140 /// WASI-only; file paths must be valid UTF-8.2821 /// WASI: file paths must be valid UTF-8.
3141 InvalidUtf8,2822 /// Windows: file paths provided by the user must be valid WTF-8.
3142 /// Windows-only; file paths provided by the user must be valid WTF-8.
3143 /// https://wtf-8.codeberg.page/2823 /// https://wtf-8.codeberg.page/
3144 InvalidWtf8,
3145 BadPathName,2824 BadPathName,
3146 /// On Windows, `\\server` or `\\server\share` was not found.2825 /// On Windows, `\\server` or `\\server\share` was not found.
3147 NetworkNotFound,2826 NetworkNotFound,
...@@ -3193,10 +2872,7 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {...@@ -3193,10 +2872,7 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
3193 .EXIST => return error.DirNotEmpty,2872 .EXIST => return error.DirNotEmpty,
3194 .NOTEMPTY => return error.DirNotEmpty,2873 .NOTEMPTY => return error.DirNotEmpty,
3195 .ROFS => return error.ReadOnlyFileSystem,2874 .ROFS => return error.ReadOnlyFileSystem,
3196 .ILSEQ => |err| if (native_os == .wasi)2875 .ILSEQ => return error.BadPathName,
3197 return error.InvalidUtf8
3198 else
3199 return unexpectedErrno(err),
3200 else => |err| return unexpectedErrno(err),2876 else => |err| return unexpectedErrno(err),
3201 }2877 }
3202}2878}
...@@ -3217,12 +2893,10 @@ pub const ChangeCurDirError = error{...@@ -3217,12 +2893,10 @@ pub const ChangeCurDirError = error{
3217 FileNotFound,2893 FileNotFound,
3218 SystemResources,2894 SystemResources,
3219 NotDir,2895 NotDir,
3220 BadPathName,2896 /// WASI: file paths must be valid UTF-8.
3221 /// WASI-only; file paths must be valid UTF-8.2897 /// Windows: file paths provided by the user must be valid WTF-8.
3222 InvalidUtf8,
3223 /// Windows-only; file paths provided by the user must be valid WTF-8.
3224 /// https://wtf-8.codeberg.page/2898 /// https://wtf-8.codeberg.page/
3225 InvalidWtf8,2899 BadPathName,
3226} || UnexpectedError;2900} || UnexpectedError;
32272901
3228/// Changes the current working directory of the calling process.2902/// Changes the current working directory of the calling process.
...@@ -3234,10 +2908,7 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {...@@ -3234,10 +2908,7 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
3234 @compileError("WASI does not support os.chdir");2908 @compileError("WASI does not support os.chdir");
3235 } else if (native_os == .windows) {2909 } else if (native_os == .windows) {
3236 var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;2910 var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
3237 if (try std.unicode.checkWtf8ToWtf16LeOverflow(dir_path, &wtf16_dir_path)) {2911 const len = try windows.wtf8ToWtf16Le(&wtf16_dir_path, dir_path);
3238 return error.NameTooLong;
3239 }
3240 const len = try std.unicode.wtf8ToWtf16Le(&wtf16_dir_path, dir_path);
3241 return chdirW(wtf16_dir_path[0..len]);2912 return chdirW(wtf16_dir_path[0..len]);
3242 } else {2913 } else {
3243 const dir_path_c = try toPosixPath(dir_path);2914 const dir_path_c = try toPosixPath(dir_path);
...@@ -3253,10 +2924,7 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {...@@ -3253,10 +2924,7 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
3253 if (native_os == .windows) {2924 if (native_os == .windows) {
3254 const dir_path_span = mem.span(dir_path);2925 const dir_path_span = mem.span(dir_path);
3255 var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;2926 var wtf16_dir_path: [windows.PATH_MAX_WIDE]u16 = undefined;
3256 if (try std.unicode.checkWtf8ToWtf16LeOverflow(dir_path_span, &wtf16_dir_path)) {2927 const len = try windows.wtf8ToWtf16Le(&wtf16_dir_path, dir_path_span);
3257 return error.NameTooLong;
3258 }
3259 const len = try std.unicode.wtf8ToWtf16Le(&wtf16_dir_path, dir_path_span);
3260 return chdirW(wtf16_dir_path[0..len]);2928 return chdirW(wtf16_dir_path[0..len]);
3261 } else if (native_os == .wasi and !builtin.link_libc) {2929 } else if (native_os == .wasi and !builtin.link_libc) {
3262 return chdir(mem.span(dir_path));2930 return chdir(mem.span(dir_path));
...@@ -3271,10 +2939,7 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {...@@ -3271,10 +2939,7 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void {
3271 .NOENT => return error.FileNotFound,2939 .NOENT => return error.FileNotFound,
3272 .NOMEM => return error.SystemResources,2940 .NOMEM => return error.SystemResources,
3273 .NOTDIR => return error.NotDir,2941 .NOTDIR => return error.NotDir,
3274 .ILSEQ => |err| if (native_os == .wasi)2942 .ILSEQ => return error.BadPathName,
3275 return error.InvalidUtf8
3276 else
3277 return unexpectedErrno(err),
3278 else => |err| return unexpectedErrno(err),2943 else => |err| return unexpectedErrno(err),
3279 }2944 }
3280}2945}
...@@ -3320,11 +2985,9 @@ pub const ReadLinkError = error{...@@ -3320,11 +2985,9 @@ pub const ReadLinkError = error{
3320 SystemResources,2985 SystemResources,
3321 NotLink,2986 NotLink,
3322 NotDir,2987 NotDir,
3323 /// WASI-only; file paths must be valid UTF-8.2988 /// WASI: file paths must be valid UTF-8.
3324 InvalidUtf8,2989 /// Windows: file paths provided by the user must be valid WTF-8.
3325 /// Windows-only; file paths provided by the user must be valid WTF-8.
3326 /// https://wtf-8.codeberg.page/2990 /// https://wtf-8.codeberg.page/
3327 InvalidWtf8,
3328 BadPathName,2991 BadPathName,
3329 /// Windows-only. This error may occur if the opened reparse point is2992 /// Windows-only. This error may occur if the opened reparse point is
3330 /// of unsupported type.2993 /// of unsupported type.
...@@ -3380,10 +3043,7 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8...@@ -3380,10 +3043,7 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8
3380 .NOENT => return error.FileNotFound,3043 .NOENT => return error.FileNotFound,
3381 .NOMEM => return error.SystemResources,3044 .NOMEM => return error.SystemResources,
3382 .NOTDIR => return error.NotDir,3045 .NOTDIR => return error.NotDir,
3383 .ILSEQ => |err| if (native_os == .wasi)3046 .ILSEQ => return error.BadPathName,
3384 return error.InvalidUtf8
3385 else
3386 return unexpectedErrno(err),
3387 else => |err| return unexpectedErrno(err),3047 else => |err| return unexpectedErrno(err),
3388 }3048 }
3389}3049}
...@@ -3425,7 +3085,7 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read...@@ -3425,7 +3085,7 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read
3425 .NOMEM => return error.SystemResources,3085 .NOMEM => return error.SystemResources,
3426 .NOTDIR => return error.NotDir,3086 .NOTDIR => return error.NotDir,
3427 .NOTCAPABLE => return error.AccessDenied,3087 .NOTCAPABLE => return error.AccessDenied,
3428 .ILSEQ => return error.InvalidUtf8,3088 .ILSEQ => return error.BadPathName,
3429 else => |err| return unexpectedErrno(err),3089 else => |err| return unexpectedErrno(err),
3430 }3090 }
3431}3091}
...@@ -3458,10 +3118,7 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read...@@ -3458,10 +3118,7 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read
3458 .NOENT => return error.FileNotFound,3118 .NOENT => return error.FileNotFound,
3459 .NOMEM => return error.SystemResources,3119 .NOMEM => return error.SystemResources,
3460 .NOTDIR => return error.NotDir,3120 .NOTDIR => return error.NotDir,
3461 .ILSEQ => |err| if (native_os == .wasi)3121 .ILSEQ => return error.BadPathName,
3462 return error.InvalidUtf8
3463 else
3464 return unexpectedErrno(err),
3465 else => |err| return unexpectedErrno(err),3122 else => |err| return unexpectedErrno(err),
3466 }3123 }
3467}3124}
...@@ -3612,7 +3269,7 @@ pub const SocketError = error{...@@ -3612,7 +3269,7 @@ pub const SocketError = error{
3612 AccessDenied,3269 AccessDenied,
36133270
3614 /// The implementation does not support the specified address family.3271 /// The implementation does not support the specified address family.
3615 AddressFamilyNotSupported,3272 AddressFamilyUnsupported,
36163273
3617 /// Unknown protocol, or protocol family not available.3274 /// Unknown protocol, or protocol family not available.
3618 ProtocolFamilyNotAvailable,3275 ProtocolFamilyNotAvailable,
...@@ -3635,33 +3292,6 @@ pub const SocketError = error{...@@ -3635,33 +3292,6 @@ pub const SocketError = error{
3635} || UnexpectedError;3292} || UnexpectedError;
36363293
3637pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t {3294pub 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
3665 const have_sock_flags = !builtin.target.os.tag.isDarwin() and native_os != .haiku;3295 const have_sock_flags = !builtin.target.os.tag.isDarwin() and native_os != .haiku;
3666 const filtered_sock_type = if (!have_sock_flags)3296 const filtered_sock_type = if (!have_sock_flags)
3667 socket_type & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC)3297 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...@@ -3678,7 +3308,7 @@ pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t
3678 return fd;3308 return fd;
3679 },3309 },
3680 .ACCES => return error.AccessDenied,3310 .ACCES => return error.AccessDenied,
3681 .AFNOSUPPORT => return error.AddressFamilyNotSupported,3311 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
3682 .INVAL => return error.ProtocolFamilyNotAvailable,3312 .INVAL => return error.ProtocolFamilyNotAvailable,
3683 .MFILE => return error.ProcessFdQuotaExceeded,3313 .MFILE => return error.ProcessFdQuotaExceeded,
3684 .NFILE => return error.SystemFdQuotaExceeded,3314 .NFILE => return error.SystemFdQuotaExceeded,
...@@ -3718,7 +3348,7 @@ pub fn socketpair(domain: u32, socket_type: u32, protocol: u32) SocketError![2]s...@@ -3718,7 +3348,7 @@ pub fn socketpair(domain: u32, socket_type: u32, protocol: u32) SocketError![2]s
3718 return socks;3348 return socks;
3719 },3349 },
3720 .ACCES => return error.AccessDenied,3350 .ACCES => return error.AccessDenied,
3721 .AFNOSUPPORT => return error.AddressFamilyNotSupported,3351 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
3722 .INVAL => return error.ProtocolFamilyNotAvailable,3352 .INVAL => return error.ProtocolFamilyNotAvailable,
3723 .MFILE => return error.ProcessFdQuotaExceeded,3353 .MFILE => return error.ProcessFdQuotaExceeded,
3724 .NFILE => return error.SystemFdQuotaExceeded,3354 .NFILE => return error.SystemFdQuotaExceeded,
...@@ -3738,10 +3368,10 @@ pub const ShutdownError = error{...@@ -3738,10 +3368,10 @@ pub const ShutdownError = error{
3738 BlockingOperationInProgress,3368 BlockingOperationInProgress,
37393369
3740 /// The network subsystem has failed.3370 /// The network subsystem has failed.
3741 NetworkSubsystemFailed,3371 NetworkDown,
37423372
3743 /// The socket is not connected (connection-oriented sockets only).3373 /// The socket is not connected (connection-oriented sockets only).
3744 SocketNotConnected,3374 SocketUnconnected,
3745 SystemResources,3375 SystemResources,
3746} || UnexpectedError;3376} || UnexpectedError;
37473377
...@@ -3756,14 +3386,14 @@ pub fn shutdown(sock: socket_t, how: ShutdownHow) ShutdownError!void {...@@ -3756,14 +3386,14 @@ pub fn shutdown(sock: socket_t, how: ShutdownHow) ShutdownError!void {
3756 .both => windows.ws2_32.SD_BOTH,3386 .both => windows.ws2_32.SD_BOTH,
3757 });3387 });
3758 if (0 != result) switch (windows.ws2_32.WSAGetLastError()) {3388 if (0 != result) switch (windows.ws2_32.WSAGetLastError()) {
3759 .WSAECONNABORTED => return error.ConnectionAborted,3389 .ECONNABORTED => return error.ConnectionAborted,
3760 .WSAECONNRESET => return error.ConnectionResetByPeer,3390 .ECONNRESET => return error.ConnectionResetByPeer,
3761 .WSAEINPROGRESS => return error.BlockingOperationInProgress,3391 .EINPROGRESS => return error.BlockingOperationInProgress,
3762 .WSAEINVAL => unreachable,3392 .EINVAL => unreachable,
3763 .WSAENETDOWN => return error.NetworkSubsystemFailed,3393 .ENETDOWN => return error.NetworkDown,
3764 .WSAENOTCONN => return error.SocketNotConnected,3394 .ENOTCONN => return error.SocketUnconnected,
3765 .WSAENOTSOCK => unreachable,3395 .ENOTSOCK => unreachable,
3766 .WSANOTINITIALISED => unreachable,3396 .NOTINITIALISED => unreachable,
3767 else => |err| return windows.unexpectedWSAError(err),3397 else => |err| return windows.unexpectedWSAError(err),
3768 };3398 };
3769 } else {3399 } else {
...@@ -3776,7 +3406,7 @@ pub fn shutdown(sock: socket_t, how: ShutdownHow) ShutdownError!void {...@@ -3776,7 +3406,7 @@ pub fn shutdown(sock: socket_t, how: ShutdownHow) ShutdownError!void {
3776 .SUCCESS => return,3406 .SUCCESS => return,
3777 .BADF => unreachable,3407 .BADF => unreachable,
3778 .INVAL => unreachable,3408 .INVAL => unreachable,
3779 .NOTCONN => return error.SocketNotConnected,3409 .NOTCONN => return error.SocketUnconnected,
3780 .NOTSOCK => unreachable,3410 .NOTSOCK => unreachable,
3781 .NOBUFS => return error.SystemResources,3411 .NOBUFS => return error.SystemResources,
3782 else => |err| return unexpectedErrno(err),3412 else => |err| return unexpectedErrno(err),
...@@ -3785,70 +3415,17 @@ pub fn shutdown(sock: socket_t, how: ShutdownHow) ShutdownError!void {...@@ -3785,70 +3415,17 @@ pub fn shutdown(sock: socket_t, how: ShutdownHow) ShutdownError!void {
3785}3415}
37863416
3787pub const BindError = error{3417pub 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.
3807 SymLinkLoop,3418 SymLinkLoop,
3808
3809 /// addr is too long.
3810 NameTooLong,3419 NameTooLong,
3811
3812 /// A component in the directory prefix of the socket pathname does not exist.
3813 FileNotFound,3420 FileNotFound,
3814
3815 /// Insufficient kernel memory was available.
3816 SystemResources,
3817
3818 /// A component of the path prefix is not a directory.
3819 NotDir,3421 NotDir,
3820
3821 /// The socket inode would reside on a read-only filesystem.
3822 ReadOnlyFileSystem,3422 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
3833pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!void {3426pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!void {
3834 if (native_os == .windows) {3427 if (native_os == .windows) {
3835 const rc = windows.bind(sock, addr, len);3428 @compileError("use std.Io instead");
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;
3852 } else {3429 } else {
3853 const rc = system.bind(sock, addr, len);3430 const rc = system.bind(sock, addr, len);
3854 switch (errno(rc)) {3431 switch (errno(rc)) {
...@@ -3858,8 +3435,8 @@ pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!voi...@@ -3858,8 +3435,8 @@ pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!voi
3858 .BADF => unreachable, // always a race condition if this error is returned3435 .BADF => unreachable, // always a race condition if this error is returned
3859 .INVAL => unreachable, // invalid parameters3436 .INVAL => unreachable, // invalid parameters
3860 .NOTSOCK => unreachable, // invalid `sockfd`3437 .NOTSOCK => unreachable, // invalid `sockfd`
3861 .AFNOSUPPORT => return error.AddressFamilyNotSupported,3438 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
3862 .ADDRNOTAVAIL => return error.AddressNotAvailable,3439 .ADDRNOTAVAIL => return error.AddressUnavailable,
3863 .FAULT => unreachable, // invalid `addr` pointer3440 .FAULT => unreachable, // invalid `addr` pointer
3864 .LOOP => return error.SymLinkLoop,3441 .LOOP => return error.SymLinkLoop,
3865 .NAMETOOLONG => return error.NameTooLong,3442 .NAMETOOLONG => return error.NameTooLong,
...@@ -3874,51 +3451,13 @@ pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!voi...@@ -3874,51 +3451,13 @@ pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!voi
3874}3451}
38753452
3876pub const ListenError = error{3453pub 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.
3885 FileDescriptorNotASocket,3454 FileDescriptorNotASocket,
3886
3887 /// The socket is not of a type that supports the listen() operation.
3888 OperationNotSupported,3455 OperationNotSupported,
38893456} || std.Io.net.IpAddress.ListenError || std.Io.net.UnixAddress.ListenError;
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;
39033457
3904pub fn listen(sock: socket_t, backlog: u31) ListenError!void {3458pub fn listen(sock: socket_t, backlog: u31) ListenError!void {
3905 if (native_os == .windows) {3459 if (native_os == .windows) {
3906 const rc = windows.listen(sock, backlog);3460 @compileError("use std.Io instead");
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;
3922 } else {3461 } else {
3923 const rc = system.listen(sock, backlog);3462 const rc = system.listen(sock, backlog);
3924 switch (errno(rc)) {3463 switch (errno(rc)) {
...@@ -3932,70 +3471,12 @@ pub fn listen(sock: socket_t, backlog: u31) ListenError!void {...@@ -3932,70 +3471,12 @@ pub fn listen(sock: socket_t, backlog: u31) ListenError!void {
3932 }3471 }
3933}3472}
39343473
3935pub const AcceptError = error{3474pub const AcceptError = std.Io.net.Server.AcceptError;
3936 ConnectionAborted,
3937
3938 /// The file descriptor sockfd does not refer to a socket.
3939 FileDescriptorNotASocket,
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.
3977pub fn accept(3476pub 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`.
3980 sock: socket_t,3477 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).
3985 addr: ?*sockaddr,3478 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.
3992 addr_size: ?*socklen_t,3479 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.
3999 flags: u32,3480 flags: u32,
4000) AcceptError!socket_t {3481) AcceptError!socket_t {
4001 const have_accept4 = !(builtin.target.os.tag.isDarwin() or native_os == .windows or native_os == .haiku);3482 const have_accept4 = !(builtin.target.os.tag.isDarwin() or native_os == .windows or native_os == .haiku);
...@@ -4004,29 +3485,11 @@ pub fn accept(...@@ -4004,29 +3485,11 @@ pub fn accept(
4004 const accepted_sock: socket_t = while (true) {3485 const accepted_sock: socket_t = while (true) {
4005 const rc = if (have_accept4)3486 const rc = if (have_accept4)
4006 system.accept4(sock, addr, addr_size, flags)3487 system.accept4(sock, addr, addr_size, flags)
4007 else if (native_os == .windows)
4008 windows.accept(sock, addr, addr_size)
4009 else3488 else
4010 system.accept(sock, addr, addr_size);3489 system.accept(sock, addr, addr_size);
40113490
4012 if (native_os == .windows) {3491 if (native_os == .windows) {
4013 if (rc == windows.ws2_32.INVALID_SOCKET) {3492 @compileError("use std.Io instead");
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 }
4030 } else {3493 } else {
4031 switch (errno(rc)) {3494 switch (errno(rc)) {
4032 .SUCCESS => break @intCast(rc),3495 .SUCCESS => break @intCast(rc),
...@@ -4088,9 +3551,9 @@ fn setSockFlags(sock: socket_t, flags: u32) !void {...@@ -4088,9 +3551,9 @@ fn setSockFlags(sock: socket_t, flags: u32) !void {
4088 var mode: c_ulong = 1;3551 var mode: c_ulong = 1;
4089 if (windows.ws2_32.ioctlsocket(sock, windows.ws2_32.FIONBIO, &mode) == windows.ws2_32.SOCKET_ERROR) {3552 if (windows.ws2_32.ioctlsocket(sock, windows.ws2_32.FIONBIO, &mode) == windows.ws2_32.SOCKET_ERROR) {
4090 switch (windows.ws2_32.WSAGetLastError()) {3553 switch (windows.ws2_32.WSAGetLastError()) {
4091 .WSANOTINITIALISED => unreachable,3554 .NOTINITIALISED => unreachable,
4092 .WSAENETDOWN => return error.NetworkSubsystemFailed,3555 .ENETDOWN => return error.NetworkDown,
4093 .WSAENOTSOCK => return error.FileDescriptorNotASocket,3556 .ENOTSOCK => return error.FileDescriptorNotASocket,
4094 // TODO: handle more errors3557 // TODO: handle more errors
4095 else => |err| return windows.unexpectedWSAError(err),3558 else => |err| return windows.unexpectedWSAError(err),
4096 }3559 }
...@@ -4230,7 +3693,7 @@ pub const GetSockNameError = error{...@@ -4230,7 +3693,7 @@ pub const GetSockNameError = error{
4230 SystemResources,3693 SystemResources,
42313694
4232 /// The network subsystem has failed.3695 /// The network subsystem has failed.
4233 NetworkSubsystemFailed,3696 NetworkDown,
42343697
4235 /// Socket hasn't been bound yet3698 /// Socket hasn't been bound yet
4236 SocketNotBound,3699 SocketNotBound,
...@@ -4243,11 +3706,11 @@ pub fn getsockname(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSock...@@ -4243,11 +3706,11 @@ pub fn getsockname(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSock
4243 const rc = windows.getsockname(sock, addr, addrlen);3706 const rc = windows.getsockname(sock, addr, addrlen);
4244 if (rc == windows.ws2_32.SOCKET_ERROR) {3707 if (rc == windows.ws2_32.SOCKET_ERROR) {
4245 switch (windows.ws2_32.WSAGetLastError()) {3708 switch (windows.ws2_32.WSAGetLastError()) {
4246 .WSANOTINITIALISED => unreachable,3709 .NOTINITIALISED => unreachable,
4247 .WSAENETDOWN => return error.NetworkSubsystemFailed,3710 .ENETDOWN => return error.NetworkDown,
4248 .WSAEFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value3711 .EFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value
4249 .WSAENOTSOCK => return error.FileDescriptorNotASocket,3712 .ENOTSOCK => return error.FileDescriptorNotASocket,
4250 .WSAEINVAL => return error.SocketNotBound,3713 .EINVAL => return error.SocketNotBound,
4251 else => |err| return windows.unexpectedWSAError(err),3714 else => |err| return windows.unexpectedWSAError(err),
4252 }3715 }
4253 }3716 }
...@@ -4272,11 +3735,11 @@ pub fn getpeername(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSock...@@ -4272,11 +3735,11 @@ pub fn getpeername(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSock
4272 const rc = windows.getpeername(sock, addr, addrlen);3735 const rc = windows.getpeername(sock, addr, addrlen);
4273 if (rc == windows.ws2_32.SOCKET_ERROR) {3736 if (rc == windows.ws2_32.SOCKET_ERROR) {
4274 switch (windows.ws2_32.WSAGetLastError()) {3737 switch (windows.ws2_32.WSAGetLastError()) {
4275 .WSANOTINITIALISED => unreachable,3738 .NOTINITIALISED => unreachable,
4276 .WSAENETDOWN => return error.NetworkSubsystemFailed,3739 .ENETDOWN => return error.NetworkDown,
4277 .WSAEFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value3740 .EFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value
4278 .WSAENOTSOCK => return error.FileDescriptorNotASocket,3741 .ENOTSOCK => return error.FileDescriptorNotASocket,
4279 .WSAEINVAL => return error.SocketNotBound,3742 .EINVAL => return error.SocketNotBound,
4280 else => |err| return windows.unexpectedWSAError(err),3743 else => |err| return windows.unexpectedWSAError(err),
4281 }3744 }
4282 }3745 }
...@@ -4296,86 +3759,11 @@ pub fn getpeername(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSock...@@ -4296,86 +3759,11 @@ pub fn getpeername(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSock
4296 }3759 }
4297}3760}
42983761
4299pub const ConnectError = error{3762pub const ConnectError = std.Io.net.IpAddress.ConnectError || std.Io.net.UnixAddress.ConnectError;
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,
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.
4355pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) ConnectError!void {3764pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) ConnectError!void {
4356 if (native_os == .windows) {3765 if (native_os == .windows) {
4357 const rc = windows.ws2_32.connect(sock, sock_addr, @intCast(len));3766 @compileError("use std.Io instead");
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;
4379 }3767 }
43803768
4381 while (true) {3769 while (true) {
...@@ -4383,9 +3771,8 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne...@@ -4383,9 +3771,8 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne
4383 .SUCCESS => return,3771 .SUCCESS => return,
4384 .ACCES => return error.AccessDenied,3772 .ACCES => return error.AccessDenied,
4385 .PERM => return error.PermissionDenied,3773 .PERM => return error.PermissionDenied,
4386 .ADDRINUSE => return error.AddressInUse,3774 .ADDRNOTAVAIL => return error.AddressUnavailable,
4387 .ADDRNOTAVAIL => return error.AddressNotAvailable,3775 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
4388 .AFNOSUPPORT => return error.AddressFamilyNotSupported,
4389 .AGAIN, .INPROGRESS => return error.WouldBlock,3776 .AGAIN, .INPROGRESS => return error.WouldBlock,
4390 .ALREADY => return error.ConnectionPending,3777 .ALREADY => return error.ConnectionPending,
4391 .BADF => unreachable, // sockfd is not a valid open file descriptor.3778 .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...@@ -4393,12 +3780,12 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne
4393 .CONNRESET => return error.ConnectionResetByPeer,3780 .CONNRESET => return error.ConnectionResetByPeer,
4394 .FAULT => unreachable, // The socket structure address is outside the user's address space.3781 .FAULT => unreachable, // The socket structure address is outside the user's address space.
4395 .INTR => continue,3782 .INTR => continue,
4396 .ISCONN => return error.AlreadyConnected, // The socket is already connected.3783 .ISCONN => @panic("AlreadyConnected"), // The socket is already connected.
4397 .HOSTUNREACH => return error.NetworkUnreachable,3784 .HOSTUNREACH => return error.NetworkUnreachable,
4398 .NETUNREACH => return error.NetworkUnreachable,3785 .NETUNREACH => return error.NetworkUnreachable,
4399 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.3786 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
4400 .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.3787 .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
4401 .TIMEDOUT => return error.ConnectionTimedOut,3788 .TIMEDOUT => return error.Timeout,
4402 .NOENT => return error.FileNotFound, // Returned when socket is AF.UNIX and the given path does not exist.3789 .NOENT => return error.FileNotFound, // Returned when socket is AF.UNIX and the given path does not exist.
4403 .CONNABORTED => unreachable, // Tried to reuse socket that previously received error.ConnectionRefused.3790 .CONNABORTED => unreachable, // Tried to reuse socket that previously received error.ConnectionRefused.
4404 else => |err| return unexpectedErrno(err),3791 else => |err| return unexpectedErrno(err),
...@@ -4446,8 +3833,8 @@ pub fn getsockoptError(sockfd: fd_t) ConnectError!void {...@@ -4446,8 +3833,8 @@ pub fn getsockoptError(sockfd: fd_t) ConnectError!void {
4446 .ACCES => return error.AccessDenied,3833 .ACCES => return error.AccessDenied,
4447 .PERM => return error.PermissionDenied,3834 .PERM => return error.PermissionDenied,
4448 .ADDRINUSE => return error.AddressInUse,3835 .ADDRINUSE => return error.AddressInUse,
4449 .ADDRNOTAVAIL => return error.AddressNotAvailable,3836 .ADDRNOTAVAIL => return error.AddressUnavailable,
4450 .AFNOSUPPORT => return error.AddressFamilyNotSupported,3837 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
4451 .AGAIN => return error.SystemResources,3838 .AGAIN => return error.SystemResources,
4452 .ALREADY => return error.ConnectionPending,3839 .ALREADY => return error.ConnectionPending,
4453 .BADF => unreachable, // sockfd is not a valid open file descriptor.3840 .BADF => unreachable, // sockfd is not a valid open file descriptor.
...@@ -4458,7 +3845,7 @@ pub fn getsockoptError(sockfd: fd_t) ConnectError!void {...@@ -4458,7 +3845,7 @@ pub fn getsockoptError(sockfd: fd_t) ConnectError!void {
4458 .NETUNREACH => return error.NetworkUnreachable,3845 .NETUNREACH => return error.NetworkUnreachable,
4459 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.3846 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
4460 .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.3847 .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
4461 .TIMEDOUT => return error.ConnectionTimedOut,3848 .TIMEDOUT => return error.Timeout,
4462 .CONNRESET => return error.ConnectionResetByPeer,3849 .CONNRESET => return error.ConnectionResetByPeer,
4463 else => |err| return unexpectedErrno(err),3850 else => |err| return unexpectedErrno(err),
4464 },3851 },
...@@ -4512,14 +3899,7 @@ pub fn wait4(pid: pid_t, flags: u32, ru: ?*rusage) WaitPidResult {...@@ -4512,14 +3899,7 @@ pub fn wait4(pid: pid_t, flags: u32, ru: ?*rusage) WaitPidResult {
4512 }3899 }
4513}3900}
45143901
4515pub const FStatError = error{3902pub const FStatError = std.Io.File.StatError;
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;
45233903
4524/// Return information about a file descriptor.3904/// Return information about a file descriptor.
4525pub fn fstat(fd: fd_t) FStatError!Stat {3905pub fn fstat(fd: fd_t) FStatError!Stat {
...@@ -4546,21 +3926,17 @@ pub const FStatAtError = FStatError || error{...@@ -4546,21 +3926,17 @@ pub const FStatAtError = FStatError || error{
4546 NameTooLong,3926 NameTooLong,
4547 FileNotFound,3927 FileNotFound,
4548 SymLinkLoop,3928 SymLinkLoop,
4549 /// WASI-only; file paths must be valid UTF-8.3929 BadPathName,
4550 InvalidUtf8,
4551};3930};
45523931
4553/// Similar to `fstat`, but returns stat of a resource pointed to by `pathname`3932/// Similar to `fstat`, but returns stat of a resource pointed to by `pathname`
4554/// which is relative to `dirfd` handle.3933/// which is relative to `dirfd` handle.
4555/// On WASI, `pathname` should be encoded as valid UTF-8.3934/// On WASI, `pathname` should be encoded as valid UTF-8.
4556/// On other platforms, `pathname` is an opaque sequence of bytes with no particular encoding.3935/// 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`.
4558pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {3937pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat {
4559 if (native_os == .wasi and !builtin.link_libc) {3938 if (native_os == .wasi and !builtin.link_libc) {
4560 const filestat = try std.os.fstatat_wasi(dirfd, pathname, .{3939 @compileError("use std.Io instead");
4561 .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0,
4562 });
4563 return Stat.fromFilestat(filestat);
4564 } else if (native_os == .windows) {3940 } else if (native_os == .windows) {
4565 @compileError("fstatat is not yet implemented on Windows");3941 @compileError("fstatat is not yet implemented on Windows");
4566 } else {3942 } else {
...@@ -4573,10 +3949,7 @@ pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat...@@ -4573,10 +3949,7 @@ pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat
4573/// See also `fstatat`.3949/// See also `fstatat`.
4574pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat {3950pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat {
4575 if (native_os == .wasi and !builtin.link_libc) {3951 if (native_os == .wasi and !builtin.link_libc) {
4576 const filestat = try std.os.fstatat_wasi(dirfd, mem.sliceTo(pathname, 0), .{3952 @compileError("use std.Io instead");
4577 .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0,
4578 });
4579 return Stat.fromFilestat(filestat);
4580 }3953 }
45813954
4582 const fstatat_sym = if (lfs64_abi) system.fstatat64 else system.fstatat;3955 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...@@ -4593,10 +3966,7 @@ pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!S
4593 .LOOP => return error.SymLinkLoop,3966 .LOOP => return error.SymLinkLoop,
4594 .NOENT => return error.FileNotFound,3967 .NOENT => return error.FileNotFound,
4595 .NOTDIR => return error.FileNotFound,3968 .NOTDIR => return error.FileNotFound,
4596 .ILSEQ => |err| if (native_os == .wasi)3969 .ILSEQ => return error.BadPathName,
4597 return error.InvalidUtf8
4598 else
4599 return unexpectedErrno(err),
4600 else => |err| return unexpectedErrno(err),3970 else => |err| return unexpectedErrno(err),
4601 }3971 }
4602}3972}
...@@ -5069,32 +4439,29 @@ pub const AccessError = error{...@@ -5069,32 +4439,29 @@ pub const AccessError = error{
5069 NameTooLong,4439 NameTooLong,
5070 InputOutput,4440 InputOutput,
5071 SystemResources,4441 SystemResources,
5072 BadPathName,
5073 FileBusy,4442 FileBusy,
5074 SymLinkLoop,4443 SymLinkLoop,
5075 ReadOnlyFileSystem,4444 ReadOnlyFileSystem,
5076 /// WASI-only; file paths must be valid UTF-8.4445 /// WASI: file paths must be valid UTF-8.
5077 InvalidUtf8,4446 /// Windows: file paths provided by the user must be valid WTF-8.
5078 /// Windows-only; file paths provided by the user must be valid WTF-8.
5079 /// https://wtf-8.codeberg.page/4447 /// https://wtf-8.codeberg.page/
5080 InvalidWtf8,4448 BadPathName,
4449 Canceled,
5081} || UnexpectedError;4450} || UnexpectedError;
50824451
5083/// check user's permissions for a file4452/// check user's permissions for a file
5084///4453///
5085/// * On Windows, asserts `path` is valid [WTF-8](https://wtf-8.codeberg.page/).4454/// * 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`.
5087/// * On other platforms, `path` is an opaque sequence of bytes with no particular encoding.4456/// * On other platforms, `path` is an opaque sequence of bytes with no particular encoding.
5088///4457///
5089/// On Windows, `mode` is ignored. This is a POSIX API that is only partially supported by4458/// On Windows, `mode` is ignored. This is a POSIX API that is only partially supported by
5090/// Windows. See `fs` for the cross-platform file system API.4459/// Windows. See `fs` for the cross-platform file system API.
5091pub fn access(path: []const u8, mode: u32) AccessError!void {4460pub fn access(path: []const u8, mode: u32) AccessError!void {
5092 if (native_os == .windows) {4461 if (native_os == .windows) {
5093 const path_w = try windows.sliceToPrefixedFileW(null, path);4462 @compileError("use std.Io instead");
5094 _ = try windows.GetFileAttributesW(path_w.span().ptr);
5095 return;
5096 } else if (native_os == .wasi and !builtin.link_libc) {4463 } 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");
5098 }4465 }
5099 const path_c = try toPosixPath(path);4466 const path_c = try toPosixPath(path);
5100 return accessZ(&path_c, mode);4467 return accessZ(&path_c, mode);
...@@ -5103,9 +4470,7 @@ pub fn access(path: []const u8, mode: u32) AccessError!void {...@@ -5103,9 +4470,7 @@ pub fn access(path: []const u8, mode: u32) AccessError!void {
5103/// Same as `access` except `path` is null-terminated.4470/// Same as `access` except `path` is null-terminated.
5104pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {4471pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
5105 if (native_os == .windows) {4472 if (native_os == .windows) {
5106 const path_w = try windows.cStrToPrefixedFileW(null, path);4473 @compileError("use std.Io instead");
5107 _ = try windows.GetFileAttributesW(path_w.span().ptr);
5108 return;
5109 } else if (native_os == .wasi and !builtin.link_libc) {4474 } else if (native_os == .wasi and !builtin.link_libc) {
5110 return access(mem.sliceTo(path, 0), mode);4475 return access(mem.sliceTo(path, 0), mode);
5111 }4476 }
...@@ -5123,132 +4488,11 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {...@@ -5123,132 +4488,11 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void {
5123 .FAULT => unreachable,4488 .FAULT => unreachable,
5124 .IO => return error.InputOutput,4489 .IO => return error.InputOutput,
5125 .NOMEM => return error.SystemResources,4490 .NOMEM => return error.SystemResources,
5126 .ILSEQ => |err| if (native_os == .wasi)4491 .ILSEQ => return error.BadPathName,
5127 return error.InvalidUtf8
5128 else
5129 return unexpectedErrno(err),
5130 else => |err| return unexpectedErrno(err),4492 else => |err| return unexpectedErrno(err),
5131 }4493 }
5132}4494}
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
5252pub const PipeError = error{4496pub const PipeError = error{
5253 SystemFdQuotaExceeded,4497 SystemFdQuotaExceeded,
5254 ProcessFdQuotaExceeded,4498 ProcessFdQuotaExceeded,
...@@ -5393,15 +4637,8 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {...@@ -5393,15 +4637,8 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {
5393 }4637 }
5394}4638}
53954639
5396pub const SeekError = error{4640pub const SeekError = std.Io.File.SeekError;
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;
54034641
5404/// Repositions read/write file offset relative to the beginning.
5405pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void {4642pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void {
5406 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {4643 if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) {
5407 var result: u64 = undefined;4644 var result: u64 = undefined;
...@@ -5645,16 +4882,15 @@ pub const RealPathError = error{...@@ -5645,16 +4882,15 @@ pub const RealPathError = error{
5645 SystemResources,4882 SystemResources,
5646 NoSpaceLeft,4883 NoSpaceLeft,
5647 FileSystem,4884 FileSystem,
5648 BadPathName,
5649 DeviceBusy,4885 DeviceBusy,
5650 ProcessNotFound,4886 ProcessNotFound,
56514887
5652 SharingViolation,4888 SharingViolation,
5653 PipeBusy,4889 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.
5656 /// https://wtf-8.codeberg.page/4892 /// https://wtf-8.codeberg.page/
5657 InvalidWtf8,4893 BadPathName,
56584894
5659 /// On Windows, `\\server` or `\\server\share` was not found.4895 /// On Windows, `\\server` or `\\server\share` was not found.
5660 NetworkNotFound,4896 NetworkNotFound,
...@@ -5671,6 +4907,8 @@ pub const RealPathError = error{...@@ -5671,6 +4907,8 @@ pub const RealPathError = error{
5671 /// On Windows, the volume does not contain a recognized file system. File4907 /// On Windows, the volume does not contain a recognized file system. File
5672 /// system drivers might not be loaded, or the volume may be corrupt.4908 /// system drivers might not be loaded, or the volume may be corrupt.
5673 UnrecognizedVolume,4909 UnrecognizedVolume,
4910
4911 Canceled,
5674} || UnexpectedError;4912} || UnexpectedError;
56754913
5676/// Return the canonicalized absolute pathname.4914/// Return the canonicalized absolute pathname.
...@@ -5735,7 +4973,6 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[max_path_bytes]u8) RealP...@@ -5735,7 +4973,6 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[max_path_bytes]u8) RealP
5735 error.FileLocksNotSupported => unreachable,4973 error.FileLocksNotSupported => unreachable,
5736 error.WouldBlock => unreachable,4974 error.WouldBlock => unreachable,
5737 error.FileBusy => unreachable, // not asking for write permissions4975 error.FileBusy => unreachable, // not asking for write permissions
5738 error.InvalidUtf8 => unreachable, // WASI-only
5739 else => |e| return e,4976 else => |e| return e,
5740 };4977 };
5741 defer close(fd);4978 defer close(fd);
...@@ -5999,7 +5236,7 @@ pub fn sigemptyset() sigset_t {...@@ -5999,7 +5236,7 @@ pub fn sigemptyset() sigset_t {
5999 return system.sigemptyset();5236 return system.sigemptyset();
6000}5237}
60015238
6002pub fn sigaddset(set: *sigset_t, sig: u8) void {5239pub fn sigaddset(set: *sigset_t, sig: SIG) void {
6003 if (builtin.link_libc) {5240 if (builtin.link_libc) {
6004 switch (errno(system.sigaddset(set, sig))) {5241 switch (errno(system.sigaddset(set, sig))) {
6005 .SUCCESS => return,5242 .SUCCESS => return,
...@@ -6009,7 +5246,7 @@ pub fn sigaddset(set: *sigset_t, sig: u8) void {...@@ -6009,7 +5246,7 @@ pub fn sigaddset(set: *sigset_t, sig: u8) void {
6009 system.sigaddset(set, sig);5246 system.sigaddset(set, sig);
6010}5247}
60115248
6012pub fn sigdelset(set: *sigset_t, sig: u8) void {5249pub fn sigdelset(set: *sigset_t, sig: SIG) void {
6013 if (builtin.link_libc) {5250 if (builtin.link_libc) {
6014 switch (errno(system.sigdelset(set, sig))) {5251 switch (errno(system.sigdelset(set, sig))) {
6015 .SUCCESS => return,5252 .SUCCESS => return,
...@@ -6019,7 +5256,7 @@ pub fn sigdelset(set: *sigset_t, sig: u8) void {...@@ -6019,7 +5256,7 @@ pub fn sigdelset(set: *sigset_t, sig: u8) void {
6019 system.sigdelset(set, sig);5256 system.sigdelset(set, sig);
6020}5257}
60215258
6022pub fn sigismember(set: *const sigset_t, sig: u8) bool {5259pub fn sigismember(set: *const sigset_t, sig: SIG) bool {
6023 if (builtin.link_libc) {5260 if (builtin.link_libc) {
6024 const rc = system.sigismember(set, sig);5261 const rc = system.sigismember(set, sig);
6025 switch (errno(rc)) {5262 switch (errno(rc)) {
...@@ -6031,7 +5268,7 @@ pub fn sigismember(set: *const sigset_t, sig: u8) bool {...@@ -6031,7 +5268,7 @@ pub fn sigismember(set: *const sigset_t, sig: u8) bool {
6031}5268}
60325269
6033/// Examine and change a signal action.5270/// 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 {
6035 switch (errno(system.sigaction(sig, act, oact))) {5272 switch (errno(system.sigaction(sig, act, oact))) {
6036 .SUCCESS => return,5273 .SUCCESS => return,
6037 // EINVAL means the signal is either invalid or some signal that cannot have its action5274 // EINVAL means the signal is either invalid or some signal that cannot have its action
...@@ -6152,55 +5389,6 @@ pub fn uname() utsname {...@@ -6152,55 +5389,6 @@ pub fn uname() utsname {
6152 }5389 }
6153}5390}
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
6204pub const SendError = error{5392pub const SendError = error{
6205 /// (For UNIX domain sockets, which are identified by pathname) Write permission is denied5393 /// (For UNIX domain sockets, which are identified by pathname) Write permission is denied
6206 /// on the destination socket file, or search permission is denied for one of the5394 /// on the destination socket file, or search permission is denied for one of the
...@@ -6226,7 +5414,7 @@ pub const SendError = error{...@@ -6226,7 +5414,7 @@ pub const SendError = error{
62265414
6227 /// The socket type requires that message be sent atomically, and the size of the message5415 /// The socket type requires that message be sent atomically, and the size of the message
6228 /// to be sent made this impossible. The message is not transmitted.5416 /// to be sent made this impossible. The message is not transmitted.
6229 MessageTooBig,5417 MessageOversize,
62305418
6231 /// The output queue for a network interface was full. This generally indicates that the5419 /// The output queue for a network interface was full. This generally indicates that the
6232 /// interface has stopped sending, but may be caused by transient congestion. (Normally,5420 /// interface has stopped sending, but may be caused by transient congestion. (Normally,
...@@ -6245,7 +5433,7 @@ pub const SendError = error{...@@ -6245,7 +5433,7 @@ pub const SendError = error{
6245 NetworkUnreachable,5433 NetworkUnreachable,
62465434
6247 /// The local network interface used to reach the destination is down.5435 /// The local network interface used to reach the destination is down.
6248 NetworkSubsystemFailed,5436 NetworkDown,
62495437
6250 /// The destination address is not listening.5438 /// The destination address is not listening.
6251 ConnectionRefused,5439 ConnectionRefused,
...@@ -6253,7 +5441,7 @@ pub const SendError = error{...@@ -6253,7 +5441,7 @@ pub const SendError = error{
62535441
6254pub const SendMsgError = SendError || error{5442pub const SendMsgError = SendError || error{
6255 /// The passed address didn't have the correct address family in its sa_family field.5443 /// The passed address didn't have the correct address family in its sa_family field.
6256 AddressFamilyNotSupported,5444 AddressFamilyUnsupported,
62575445
6258 /// Returned when socket is AF.UNIX and the given path has a symlink loop.5446 /// Returned when socket is AF.UNIX and the given path has a symlink loop.
6259 SymLinkLoop,5447 SymLinkLoop,
...@@ -6266,8 +5454,8 @@ pub const SendMsgError = SendError || error{...@@ -6266,8 +5454,8 @@ pub const SendMsgError = SendError || error{
6266 NotDir,5454 NotDir,
62675455
6268 /// The socket is not connected (connection-oriented sockets only).5456 /// The socket is not connected (connection-oriented sockets only).
6269 SocketNotConnected,5457 SocketUnconnected,
6270 AddressNotAvailable,5458 AddressUnavailable,
6271};5459};
62725460
6273pub fn sendmsg(5461pub fn sendmsg(
...@@ -6282,25 +5470,25 @@ pub fn sendmsg(...@@ -6282,25 +5470,25 @@ pub fn sendmsg(
6282 if (native_os == .windows) {5470 if (native_os == .windows) {
6283 if (rc == windows.ws2_32.SOCKET_ERROR) {5471 if (rc == windows.ws2_32.SOCKET_ERROR) {
6284 switch (windows.ws2_32.WSAGetLastError()) {5472 switch (windows.ws2_32.WSAGetLastError()) {
6285 .WSAEACCES => return error.AccessDenied,5473 .EACCES => return error.AccessDenied,
6286 .WSAEADDRNOTAVAIL => return error.AddressNotAvailable,5474 .EADDRNOTAVAIL => return error.AddressUnavailable,
6287 .WSAECONNRESET => return error.ConnectionResetByPeer,5475 .ECONNRESET => return error.ConnectionResetByPeer,
6288 .WSAEMSGSIZE => return error.MessageTooBig,5476 .EMSGSIZE => return error.MessageOversize,
6289 .WSAENOBUFS => return error.SystemResources,5477 .ENOBUFS => return error.SystemResources,
6290 .WSAENOTSOCK => return error.FileDescriptorNotASocket,5478 .ENOTSOCK => return error.FileDescriptorNotASocket,
6291 .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported,5479 .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
6292 .WSAEDESTADDRREQ => unreachable, // A destination address is required.5480 .EDESTADDRREQ => 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.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.
6294 .WSAEHOSTUNREACH => return error.NetworkUnreachable,5482 .EHOSTUNREACH => return error.NetworkUnreachable,
6295 // TODO: WSAEINPROGRESS, WSAEINTR5483 // TODO: EINPROGRESS, EINTR
6296 .WSAEINVAL => unreachable,5484 .EINVAL => unreachable,
6297 .WSAENETDOWN => return error.NetworkSubsystemFailed,5485 .ENETDOWN => return error.NetworkDown,
6298 .WSAENETRESET => return error.ConnectionResetByPeer,5486 .ENETRESET => return error.ConnectionResetByPeer,
6299 .WSAENETUNREACH => return error.NetworkUnreachable,5487 .ENETUNREACH => return error.NetworkUnreachable,
6300 .WSAENOTCONN => return error.SocketNotConnected,5488 .ENOTCONN => return error.SocketUnconnected,
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.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.
6302 .WSAEWOULDBLOCK => return error.WouldBlock,5490 .EWOULDBLOCK => return error.WouldBlock,
6303 .WSANOTINITIALISED => unreachable, // A successful WSAStartup call must occur before using this function.5491 .NOTINITIALISED => unreachable, // A successful WSAStartup call must occur before using this function.
6304 else => |err| return windows.unexpectedWSAError(err),5492 else => |err| return windows.unexpectedWSAError(err),
6305 }5493 }
6306 } else {5494 } else {
...@@ -6320,21 +5508,21 @@ pub fn sendmsg(...@@ -6320,21 +5508,21 @@ pub fn sendmsg(
6320 .INTR => continue,5508 .INTR => continue,
6321 .INVAL => unreachable, // Invalid argument passed.5509 .INVAL => unreachable, // Invalid argument passed.
6322 .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified5510 .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified
6323 .MSGSIZE => return error.MessageTooBig,5511 .MSGSIZE => return error.MessageOversize,
6324 .NOBUFS => return error.SystemResources,5512 .NOBUFS => return error.SystemResources,
6325 .NOMEM => return error.SystemResources,5513 .NOMEM => return error.SystemResources,
6326 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.5514 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
6327 .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type.5515 .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type.
6328 .PIPE => return error.BrokenPipe,5516 .PIPE => return error.BrokenPipe,
6329 .AFNOSUPPORT => return error.AddressFamilyNotSupported,5517 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
6330 .LOOP => return error.SymLinkLoop,5518 .LOOP => return error.SymLinkLoop,
6331 .NAMETOOLONG => return error.NameTooLong,5519 .NAMETOOLONG => return error.NameTooLong,
6332 .NOENT => return error.FileNotFound,5520 .NOENT => return error.FileNotFound,
6333 .NOTDIR => return error.NotDir,5521 .NOTDIR => return error.NotDir,
6334 .HOSTUNREACH => return error.NetworkUnreachable,5522 .HOSTUNREACH => return error.NetworkUnreachable,
6335 .NETUNREACH => return error.NetworkUnreachable,5523 .NETUNREACH => return error.NetworkUnreachable,
6336 .NOTCONN => return error.SocketNotConnected,5524 .NOTCONN => return error.SocketUnconnected,
6337 .NETDOWN => return error.NetworkSubsystemFailed,5525 .NETDOWN => return error.NetworkDown,
6338 else => |err| return unexpectedErrno(err),5526 else => |err| return unexpectedErrno(err),
6339 }5527 }
6340 }5528 }
...@@ -6365,7 +5553,7 @@ pub const SendToError = SendMsgError || error{...@@ -6365,7 +5553,7 @@ pub const SendToError = SendMsgError || error{
6365/// Otherwise, the address of the target is given by `dest_addr` with `addrlen` specifying its size.5553/// Otherwise, the address of the target is given by `dest_addr` with `addrlen` specifying its size.
6366///5554///
6367/// If the message is too long to pass atomically through the underlying protocol,5555/// 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.
6369///5557///
6370/// There is no indication of failure to deliver.5558/// There is no indication of failure to deliver.
6371///5559///
...@@ -6385,25 +5573,25 @@ pub fn sendto(...@@ -6385,25 +5573,25 @@ pub fn sendto(
6385 if (native_os == .windows) {5573 if (native_os == .windows) {
6386 switch (windows.sendto(sockfd, buf.ptr, buf.len, flags, dest_addr, addrlen)) {5574 switch (windows.sendto(sockfd, buf.ptr, buf.len, flags, dest_addr, addrlen)) {
6387 windows.ws2_32.SOCKET_ERROR => switch (windows.ws2_32.WSAGetLastError()) {5575 windows.ws2_32.SOCKET_ERROR => switch (windows.ws2_32.WSAGetLastError()) {
6388 .WSAEACCES => return error.AccessDenied,5576 .EACCES => return error.AccessDenied,
6389 .WSAEADDRNOTAVAIL => return error.AddressNotAvailable,5577 .EADDRNOTAVAIL => return error.AddressUnavailable,
6390 .WSAECONNRESET => return error.ConnectionResetByPeer,5578 .ECONNRESET => return error.ConnectionResetByPeer,
6391 .WSAEMSGSIZE => return error.MessageTooBig,5579 .EMSGSIZE => return error.MessageOversize,
6392 .WSAENOBUFS => return error.SystemResources,5580 .ENOBUFS => return error.SystemResources,
6393 .WSAENOTSOCK => return error.FileDescriptorNotASocket,5581 .ENOTSOCK => return error.FileDescriptorNotASocket,
6394 .WSAEAFNOSUPPORT => return error.AddressFamilyNotSupported,5582 .EAFNOSUPPORT => return error.AddressFamilyUnsupported,
6395 .WSAEDESTADDRREQ => unreachable, // A destination address is required.5583 .EDESTADDRREQ => 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.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.
6397 .WSAEHOSTUNREACH => return error.NetworkUnreachable,5585 .EHOSTUNREACH => return error.NetworkUnreachable,
6398 // TODO: WSAEINPROGRESS, WSAEINTR5586 // TODO: EINPROGRESS, EINTR
6399 .WSAEINVAL => unreachable,5587 .EINVAL => unreachable,
6400 .WSAENETDOWN => return error.NetworkSubsystemFailed,5588 .ENETDOWN => return error.NetworkDown,
6401 .WSAENETRESET => return error.ConnectionResetByPeer,5589 .ENETRESET => return error.ConnectionResetByPeer,
6402 .WSAENETUNREACH => return error.NetworkUnreachable,5590 .ENETUNREACH => return error.NetworkUnreachable,
6403 .WSAENOTCONN => return error.SocketNotConnected,5591 .ENOTCONN => return error.SocketUnconnected,
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.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.
6405 .WSAEWOULDBLOCK => return error.WouldBlock,5593 .EWOULDBLOCK => return error.WouldBlock,
6406 .WSANOTINITIALISED => unreachable, // A successful WSAStartup call must occur before using this function.5594 .NOTINITIALISED => unreachable, // A successful WSAStartup call must occur before using this function.
6407 else => |err| return windows.unexpectedWSAError(err),5595 else => |err| return windows.unexpectedWSAError(err),
6408 },5596 },
6409 else => |rc| return @intCast(rc),5597 else => |rc| return @intCast(rc),
...@@ -6425,21 +5613,21 @@ pub fn sendto(...@@ -6425,21 +5613,21 @@ pub fn sendto(
6425 .INTR => continue,5613 .INTR => continue,
6426 .INVAL => return error.UnreachableAddress,5614 .INVAL => return error.UnreachableAddress,
6427 .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified5615 .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified
6428 .MSGSIZE => return error.MessageTooBig,5616 .MSGSIZE => return error.MessageOversize,
6429 .NOBUFS => return error.SystemResources,5617 .NOBUFS => return error.SystemResources,
6430 .NOMEM => return error.SystemResources,5618 .NOMEM => return error.SystemResources,
6431 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.5619 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
6432 .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type.5620 .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type.
6433 .PIPE => return error.BrokenPipe,5621 .PIPE => return error.BrokenPipe,
6434 .AFNOSUPPORT => return error.AddressFamilyNotSupported,5622 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
6435 .LOOP => return error.SymLinkLoop,5623 .LOOP => return error.SymLinkLoop,
6436 .NAMETOOLONG => return error.NameTooLong,5624 .NAMETOOLONG => return error.NameTooLong,
6437 .NOENT => return error.FileNotFound,5625 .NOENT => return error.FileNotFound,
6438 .NOTDIR => return error.NotDir,5626 .NOTDIR => return error.NotDir,
6439 .HOSTUNREACH => return error.NetworkUnreachable,5627 .HOSTUNREACH => return error.NetworkUnreachable,
6440 .NETUNREACH => return error.NetworkUnreachable,5628 .NETUNREACH => return error.NetworkUnreachable,
6441 .NOTCONN => return error.SocketNotConnected,5629 .NOTCONN => return error.SocketUnconnected,
6442 .NETDOWN => return error.NetworkSubsystemFailed,5630 .NETDOWN => return error.NetworkDown,
6443 else => |err| return unexpectedErrno(err),5631 else => |err| return unexpectedErrno(err),
6444 }5632 }
6445 }5633 }
...@@ -6471,14 +5659,14 @@ pub fn send(...@@ -6471,14 +5659,14 @@ pub fn send(
6471 flags: u32,5659 flags: u32,
6472) SendError!usize {5660) SendError!usize {
6473 return sendto(sockfd, buf, flags, null, 0) catch |err| switch (err) {5661 return sendto(sockfd, buf, flags, null, 0) catch |err| switch (err) {
6474 error.AddressFamilyNotSupported => unreachable,5662 error.AddressFamilyUnsupported => unreachable,
6475 error.SymLinkLoop => unreachable,5663 error.SymLinkLoop => unreachable,
6476 error.NameTooLong => unreachable,5664 error.NameTooLong => unreachable,
6477 error.FileNotFound => unreachable,5665 error.FileNotFound => unreachable,
6478 error.NotDir => unreachable,5666 error.NotDir => unreachable,
6479 error.NetworkUnreachable => unreachable,5667 error.NetworkUnreachable => unreachable,
6480 error.AddressNotAvailable => unreachable,5668 error.AddressUnavailable => unreachable,
6481 error.SocketNotConnected => unreachable,5669 error.SocketUnconnected => unreachable,
6482 error.UnreachableAddress => unreachable,5670 error.UnreachableAddress => unreachable,
6483 else => |e| return e,5671 else => |e| return e,
6484 };5672 };
...@@ -6578,7 +5766,7 @@ pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len...@@ -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
6579pub const PollError = error{5767pub const PollError = error{
6580 /// The network subsystem has failed.5768 /// The network subsystem has failed.
6581 NetworkSubsystemFailed,5769 NetworkDown,
65825770
6583 /// The kernel had no space to allocate file descriptor tables.5771 /// The kernel had no space to allocate file descriptor tables.
6584 SystemResources,5772 SystemResources,
...@@ -6588,9 +5776,9 @@ pub fn poll(fds: []pollfd, timeout: i32) PollError!usize {...@@ -6588,9 +5776,9 @@ pub fn poll(fds: []pollfd, timeout: i32) PollError!usize {
6588 if (native_os == .windows) {5776 if (native_os == .windows) {
6589 switch (windows.poll(fds.ptr, @intCast(fds.len), timeout)) {5777 switch (windows.poll(fds.ptr, @intCast(fds.len), timeout)) {
6590 windows.ws2_32.SOCKET_ERROR => switch (windows.ws2_32.WSAGetLastError()) {5778 windows.ws2_32.SOCKET_ERROR => switch (windows.ws2_32.WSAGetLastError()) {
6591 .WSANOTINITIALISED => unreachable,5779 .NOTINITIALISED => unreachable,
6592 .WSAENETDOWN => return error.NetworkSubsystemFailed,5780 .ENETDOWN => return error.NetworkDown,
6593 .WSAENOBUFS => return error.SystemResources,5781 .ENOBUFS => return error.SystemResources,
6594 // TODO: handle more errors5782 // TODO: handle more errors
6595 else => |err| return windows.unexpectedWSAError(err),5783 else => |err| return windows.unexpectedWSAError(err),
6596 },5784 },
...@@ -6652,19 +5840,19 @@ pub const RecvFromError = error{...@@ -6652,19 +5840,19 @@ pub const RecvFromError = error{
6652 SystemResources,5840 SystemResources,
66535841
6654 ConnectionResetByPeer,5842 ConnectionResetByPeer,
6655 ConnectionTimedOut,5843 Timeout,
66565844
6657 /// The socket has not been bound.5845 /// The socket has not been bound.
6658 SocketNotBound,5846 SocketNotBound,
66595847
6660 /// The UDP message was too big for the buffer and part of it has been discarded5848 /// The UDP message was too big for the buffer and part of it has been discarded
6661 MessageTooBig,5849 MessageOversize,
66625850
6663 /// The network subsystem has failed.5851 /// The network subsystem has failed.
6664 NetworkSubsystemFailed,5852 NetworkDown,
66655853
6666 /// The socket is not connected (connection-oriented sockets only).5854 /// The socket is not connected (connection-oriented sockets only).
6667 SocketNotConnected,5855 SocketUnconnected,
66685856
6669 /// The other end closed the socket unexpectedly or a read is executed on a shut down socket5857 /// The other end closed the socket unexpectedly or a read is executed on a shut down socket
6670 BrokenPipe,5858 BrokenPipe,
...@@ -6688,14 +5876,14 @@ pub fn recvfrom(...@@ -6688,14 +5876,14 @@ pub fn recvfrom(
6688 if (native_os == .windows) {5876 if (native_os == .windows) {
6689 if (rc == windows.ws2_32.SOCKET_ERROR) {5877 if (rc == windows.ws2_32.SOCKET_ERROR) {
6690 switch (windows.ws2_32.WSAGetLastError()) {5878 switch (windows.ws2_32.WSAGetLastError()) {
6691 .WSANOTINITIALISED => unreachable,5879 .NOTINITIALISED => unreachable,
6692 .WSAECONNRESET => return error.ConnectionResetByPeer,5880 .ECONNRESET => return error.ConnectionResetByPeer,
6693 .WSAEINVAL => return error.SocketNotBound,5881 .EINVAL => return error.SocketNotBound,
6694 .WSAEMSGSIZE => return error.MessageTooBig,5882 .EMSGSIZE => return error.MessageOversize,
6695 .WSAENETDOWN => return error.NetworkSubsystemFailed,5883 .ENETDOWN => return error.NetworkDown,
6696 .WSAENOTCONN => return error.SocketNotConnected,5884 .ENOTCONN => return error.SocketUnconnected,
6697 .WSAEWOULDBLOCK => return error.WouldBlock,5885 .EWOULDBLOCK => return error.WouldBlock,
6698 .WSAETIMEDOUT => return error.ConnectionTimedOut,5886 .ETIMEDOUT => return error.Timeout,
6699 // TODO: handle more errors5887 // TODO: handle more errors
6700 else => |err| return windows.unexpectedWSAError(err),5888 else => |err| return windows.unexpectedWSAError(err),
6701 }5889 }
...@@ -6708,14 +5896,14 @@ pub fn recvfrom(...@@ -6708,14 +5896,14 @@ pub fn recvfrom(
6708 .BADF => unreachable, // always a race condition5896 .BADF => unreachable, // always a race condition
6709 .FAULT => unreachable,5897 .FAULT => unreachable,
6710 .INVAL => unreachable,5898 .INVAL => unreachable,
6711 .NOTCONN => return error.SocketNotConnected,5899 .NOTCONN => return error.SocketUnconnected,
6712 .NOTSOCK => unreachable,5900 .NOTSOCK => unreachable,
6713 .INTR => continue,5901 .INTR => continue,
6714 .AGAIN => return error.WouldBlock,5902 .AGAIN => return error.WouldBlock,
6715 .NOMEM => return error.SystemResources,5903 .NOMEM => return error.SystemResources,
6716 .CONNREFUSED => return error.ConnectionRefused,5904 .CONNREFUSED => return error.ConnectionRefused,
6717 .CONNRESET => return error.ConnectionResetByPeer,5905 .CONNRESET => return error.ConnectionResetByPeer,
6718 .TIMEDOUT => return error.ConnectionTimedOut,5906 .TIMEDOUT => return error.Timeout,
6719 .PIPE => return error.BrokenPipe,5907 .PIPE => return error.BrokenPipe,
6720 else => |err| return unexpectedErrno(err),5908 else => |err| return unexpectedErrno(err),
6721 }5909 }
...@@ -6760,68 +5948,18 @@ pub fn recvmsg(...@@ -6760,68 +5948,18 @@ pub fn recvmsg(
6760 .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified5948 .ISCONN => unreachable, // connection-mode socket was connected already but a recipient was specified
6761 .NOBUFS => return error.SystemResources,5949 .NOBUFS => return error.SystemResources,
6762 .NOMEM => return error.SystemResources,5950 .NOMEM => return error.SystemResources,
6763 .NOTCONN => return error.SocketNotConnected,5951 .NOTCONN => return error.SocketUnconnected,
6764 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.5952 .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
6765 .MSGSIZE => return error.MessageTooBig,5953 .MSGSIZE => return error.MessageOversize,
6766 .PIPE => return error.BrokenPipe,5954 .PIPE => return error.BrokenPipe,
6767 .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type.5955 .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type.
6768 .CONNRESET => return error.ConnectionResetByPeer,5956 .CONNRESET => return error.ConnectionResetByPeer,
6769 .NETDOWN => return error.NetworkSubsystemFailed,5957 .NETDOWN => return error.NetworkDown,
6770 else => |err| return unexpectedErrno(err),5958 else => |err| return unexpectedErrno(err),
6771 }5959 }
6772 }5960 }
6773}5961}
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
6825pub const SetSockOptError = error{5963pub const SetSockOptError = error{
6826 /// The socket is already connected, and a specified option cannot be set while the socket is connected.5964 /// The socket is already connected, and a specified option cannot be set while the socket is connected.
6827 AlreadyConnected,5965 AlreadyConnected,
...@@ -6839,7 +5977,7 @@ pub const SetSockOptError = error{...@@ -6839,7 +5977,7 @@ pub const SetSockOptError = error{
6839 PermissionDenied,5977 PermissionDenied,
68405978
6841 OperationNotSupported,5979 OperationNotSupported,
6842 NetworkSubsystemFailed,5980 NetworkDown,
6843 FileDescriptorNotASocket,5981 FileDescriptorNotASocket,
6844 SocketNotBound,5982 SocketNotBound,
6845 NoDevice,5983 NoDevice,
...@@ -6851,11 +5989,11 @@ pub fn setsockopt(fd: socket_t, level: i32, optname: u32, opt: []const u8) SetSo...@@ -6851,11 +5989,11 @@ pub fn setsockopt(fd: socket_t, level: i32, optname: u32, opt: []const u8) SetSo
6851 const rc = windows.ws2_32.setsockopt(fd, level, @intCast(optname), opt.ptr, @intCast(opt.len));5989 const rc = windows.ws2_32.setsockopt(fd, level, @intCast(optname), opt.ptr, @intCast(opt.len));
6852 if (rc == windows.ws2_32.SOCKET_ERROR) {5990 if (rc == windows.ws2_32.SOCKET_ERROR) {
6853 switch (windows.ws2_32.WSAGetLastError()) {5991 switch (windows.ws2_32.WSAGetLastError()) {
6854 .WSANOTINITIALISED => unreachable,5992 .NOTINITIALISED => unreachable,
6855 .WSAENETDOWN => return error.NetworkSubsystemFailed,5993 .ENETDOWN => return error.NetworkDown,
6856 .WSAEFAULT => unreachable,5994 .EFAULT => unreachable,
6857 .WSAENOTSOCK => return error.FileDescriptorNotASocket,5995 .ENOTSOCK => return error.FileDescriptorNotASocket,
6858 .WSAEINVAL => return error.SocketNotBound,5996 .EINVAL => return error.SocketNotBound,
6859 else => |err| return windows.unexpectedWSAError(err),5997 else => |err| return windows.unexpectedWSAError(err),
6860 }5998 }
6861 }5999 }
...@@ -7572,7 +6710,7 @@ pub fn ioctl_SIOCGIFINDEX(fd: fd_t, ifr: *ifreq) IoCtl_SIOCGIFINDEX_Error!void {...@@ -7572,7 +6710,7 @@ pub fn ioctl_SIOCGIFINDEX(fd: fd_t, ifr: *ifreq) IoCtl_SIOCGIFINDEX_Error!void {
7572 }6710 }
7573}6711}
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
7577/// Whether or not `error.Unexpected` will print its value and a stack trace.6715/// Whether or not `error.Unexpected` will print its value and a stack trace.
7578///6716///
...@@ -7584,17 +6722,7 @@ pub const unexpected_error_tracing = builtin.mode == .Debug and switch (builtin....@@ -7584,17 +6722,7 @@ pub const unexpected_error_tracing = builtin.mode == .Debug and switch (builtin.
7584 else => false,6722 else => false,
7585};6723};
75866724
7587pub const UnexpectedError = error{6725pub const UnexpectedError = std.Io.UnexpectedError;
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};
75986726
7599/// Call this when you made a syscall or something that sets errno6727/// Call this when you made a syscall or something that sets errno
7600/// and you get an unexpected error.6728/// and you get an unexpected error.
lib/std/posix/test.zig+16-147
...@@ -109,64 +109,6 @@ test "open smoke test" {...@@ -109,64 +109,6 @@ test "open smoke test" {
109 }109 }
110}110}
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
170test "readlink on Windows" {112test "readlink on Windows" {
171 if (native_os != .windows) return error.SkipZigTest;113 if (native_os != .windows) return error.SkipZigTest;
172114
...@@ -226,49 +168,6 @@ test "linkat with different directories" {...@@ -226,49 +168,6 @@ test "linkat with different directories" {
226 }168 }
227}169}
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
272test "readlinkat" {171test "readlinkat" {
273 var tmp = tmpDir(.{});172 var tmp = tmpDir(.{});
274 defer tmp.cleanup();173 defer tmp.cleanup();
...@@ -621,25 +520,6 @@ test "getrlimit and setrlimit" {...@@ -621,25 +520,6 @@ test "getrlimit and setrlimit" {
621 }520 }
622}521}
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
643test "sigrtmin/max" {523test "sigrtmin/max" {
644 if (native_os == .wasi or native_os == .windows or native_os == .macos) {524 if (native_os == .wasi or native_os == .windows or native_os == .macos) {
645 return error.SkipZigTest;525 return error.SkipZigTest;
...@@ -656,14 +536,15 @@ test "sigset empty/full" {...@@ -656,14 +536,15 @@ test "sigset empty/full" {
656536
657 var set: posix.sigset_t = posix.sigemptyset();537 var set: posix.sigset_t = posix.sigemptyset();
658 for (1..posix.NSIG) |i| {538 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));
660 }541 }
661542
662 // The C library can reserve some (unnamed) signals, so can't check the full543 // The C library can reserve some (unnamed) signals, so can't check the full
663 // NSIG set is defined, but just test a couple:544 // NSIG set is defined, but just test a couple:
664 set = posix.sigfillset();545 set = posix.sigfillset();
665 try expectEqual(true, posix.sigismember(&set, @truncate(posix.SIG.CHLD)));546 try expectEqual(true, posix.sigismember(&set, .CHLD));
666 try expectEqual(true, posix.sigismember(&set, @truncate(posix.SIG.INT)));547 try expectEqual(true, posix.sigismember(&set, .INT));
667}548}
668549
669// Some signals (i.e., 32 - 34 on glibc/musl) are not allowed to be added to a550// 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" {...@@ -684,25 +565,30 @@ test "sigset add/del" {
684 // See that none are set, then set each one, see that they're all set, then565 // See that none are set, then set each one, see that they're all set, then
685 // remove them all, and then see that none are set.566 // remove them all, and then see that none are set.
686 for (1..posix.NSIG) |i| {567 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));
688 }570 }
689 for (1..posix.NSIG) |i| {571 for (1..posix.NSIG) |i| {
690 if (!reserved_signo(i)) {572 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);
692 }575 }
693 }576 }
694 for (1..posix.NSIG) |i| {577 for (1..posix.NSIG) |i| {
695 if (!reserved_signo(i)) {578 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));
697 }581 }
698 }582 }
699 for (1..posix.NSIG) |i| {583 for (1..posix.NSIG) |i| {
700 if (!reserved_signo(i)) {584 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);
702 }587 }
703 }588 }
704 for (1..posix.NSIG) |i| {589 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));
706 }592 }
707}593}
708594
...@@ -731,11 +617,8 @@ test "dup & dup2" {...@@ -731,11 +617,8 @@ test "dup & dup2" {
731 try dup2ed.writeAll("dup2");617 try dup2ed.writeAll("dup2");
732 }618 }
733619
734 var file = try tmp.dir.openFile("os_dup_test", .{});620 var buffer: [8]u8 = undefined;
735 defer file.close();621 try testing.expectEqualStrings("dupdup2", try tmp.dir.readFile("os_dup_test", &buffer));
736
737 var buf: [7]u8 = undefined;
738 try testing.expectEqualStrings("dupdup2", buf[0..try file.readAll(&buf)]);
739}622}
740623
741test "writev longer than IOV_MAX" {624test "writev longer than IOV_MAX" {
...@@ -966,20 +849,6 @@ test "isatty" {...@@ -966,20 +849,6 @@ test "isatty" {
966 try expectEqual(posix.isatty(file.handle), false);849 try expectEqual(posix.isatty(file.handle), false);
967}850}
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
983test "pread with empty buffer" {852test "pread with empty buffer" {
984 var tmp = tmpDir(.{});853 var tmp = tmpDir(.{});
985 defer tmp.cleanup();854 defer tmp.cleanup();
lib/std/process/Child.zig+42-15
...@@ -1,5 +1,9 @@...@@ -1,5 +1,9 @@
1const std = @import("../std.zig");1const ChildProcess = @This();
2
2const builtin = @import("builtin");3const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5
6const std = @import("../std.zig");
3const unicode = std.unicode;7const unicode = std.unicode;
4const fs = std.fs;8const fs = std.fs;
5const process = std.process;9const process = std.process;
...@@ -11,9 +15,7 @@ const mem = std.mem;...@@ -11,9 +15,7 @@ const mem = std.mem;
11const EnvMap = std.process.EnvMap;15const EnvMap = std.process.EnvMap;
12const maxInt = std.math.maxInt;16const maxInt = std.math.maxInt;
13const assert = std.debug.assert;17const assert = std.debug.assert;
14const native_os = builtin.os.tag;
15const Allocator = std.mem.Allocator;18const Allocator = std.mem.Allocator;
16const ChildProcess = @This();
17const ArrayList = std.ArrayList;19const ArrayList = std.ArrayList;
1820
19pub const Id = switch (native_os) {21pub const Id = switch (native_os) {
...@@ -317,16 +319,23 @@ pub fn waitForSpawn(self: *ChildProcess) SpawnError!void {...@@ -317,16 +319,23 @@ pub fn waitForSpawn(self: *ChildProcess) SpawnError!void {
317319
318 const err_pipe = self.err_pipe orelse return;320 const err_pipe = self.err_pipe orelse return;
319 self.err_pipe = null;321 self.err_pipe = null;
320
321 // Wait for the child to report any errors in or before `execvpe`.322 // Wait for the child to report any errors in or before `execvpe`.
322 if (readIntFd(err_pipe)) |child_err_int| {323 const report = readIntFd(err_pipe);
323 posix.close(err_pipe);324 posix.close(err_pipe);
325 if (report) |child_err_int| {
324 const child_err: SpawnError = @errorCast(@errorFromInt(child_err_int));326 const child_err: SpawnError = @errorCast(@errorFromInt(child_err_int));
325 self.term = child_err;327 self.term = child_err;
326 return child_err;328 return child_err;
327 } else |_| {329 } else |read_err| switch (read_err) {
328 // Write end closed by CLOEXEC at the time of the `execvpe` call, indicating success!330 error.EndOfStream => {
329 posix.close(err_pipe);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 },
330 }339 }
331}340}
332341
...@@ -563,6 +572,10 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {...@@ -563,6 +572,10 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
563 error.BadPathName => unreachable, // Windows-only572 error.BadPathName => unreachable, // Windows-only
564 error.WouldBlock => unreachable,573 error.WouldBlock => unreachable,
565 error.NetworkNotFound => unreachable, // Windows-only574 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
566 else => |e| return e,579 else => |e| return e,
567 }580 }
568 else581 else
...@@ -1014,8 +1027,14 @@ fn writeIntFd(fd: i32, value: ErrInt) !void {...@@ -1014,8 +1027,14 @@ fn writeIntFd(fd: i32, value: ErrInt) !void {
10141027
1015fn readIntFd(fd: i32) !ErrInt {1028fn readIntFd(fd: i32) !ErrInt {
1016 var buffer: [8]u8 = undefined;1029 var buffer: [8]u8 = undefined;
1017 var fr: std.fs.File.Reader = .initStreaming(.{ .handle = fd }, &buffer);1030 var i: usize = 0;
1018 return @intCast(fr.interface.takeInt(u64, .little) catch return error.SystemResources);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);
1019}1038}
10201039
1021const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8);1040const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8);
...@@ -1065,16 +1084,24 @@ fn windowsCreateProcessPathExt(...@@ -1065,16 +1084,24 @@ fn windowsCreateProcessPathExt(
1065 // or a version with a supported PATHEXT appended. We then try calling CreateProcessW1084 // or a version with a supported PATHEXT appended. We then try calling CreateProcessW
1066 // with the found versions in the appropriate order.1085 // 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
1068 var dir = dir: {1094 var dir = dir: {
1069 // needs to be null-terminated1095 // needs to be null-terminated
1070 try dir_buf.append(allocator, 0);1096 try dir_buf.append(allocator, 0);
1071 defer dir_buf.shrinkRetainingCapacity(dir_path_len);1097 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
1072 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];1098 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
1073 const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);1099 const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);
1074 break :dir fs.cwd().openDirW(prefixed_path.span().ptr, .{ .iterate = true }) catch1100 break :dir threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{
1075 return error.FileNotFound;1101 .iterate = true,
1102 }) catch return error.FileNotFound;
1076 };1103 };
1077 defer dir.close();1104 defer dir.close(io);
10781105
1079 // Add wildcard and null-terminator1106 // Add wildcard and null-terminator
1080 try app_buf.append(allocator, '*');1107 try app_buf.append(allocator, '*');
...@@ -1108,7 +1135,7 @@ fn windowsCreateProcessPathExt(...@@ -1108,7 +1135,7 @@ fn windowsCreateProcessPathExt(
1108 .Buffer = @constCast(app_name_wildcard.ptr),1135 .Buffer = @constCast(app_name_wildcard.ptr),
1109 };1136 };
1110 const rc = windows.ntdll.NtQueryDirectoryFile(1137 const rc = windows.ntdll.NtQueryDirectoryFile(
1111 dir.fd,1138 dir.handle,
1112 null,1139 null,
1113 null,1140 null,
1114 null,1141 null,
lib/std/start.zig-37
...@@ -652,7 +652,6 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {...@@ -652,7 +652,6 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {
652 std.os.environ = envp;652 std.os.environ = envp;
653653
654 std.debug.maybeEnableSegfaultHandler();654 std.debug.maybeEnableSegfaultHandler();
655 maybeIgnoreSigpipe();
656655
657 return callMain();656 return callMain();
658}657}
...@@ -756,39 +755,3 @@ pub fn call_wWinMain() std.os.windows.INT {...@@ -756,39 +755,3 @@ pub fn call_wWinMain() std.os.windows.INT {
756 // second parameter hPrevInstance, MSDN: "This parameter is always NULL"755 // second parameter hPrevInstance, MSDN: "This parameter is always NULL"
757 return root.wWinMain(hInstance, null, lpCmdLine, nCmdShow);756 return root.wWinMain(hInstance, null, lpCmdLine, nCmdShow);
758}757}
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");...@@ -85,7 +85,6 @@ pub const macho = @import("macho.zig");
85pub const math = @import("math.zig");85pub const math = @import("math.zig");
86pub const mem = @import("mem.zig");86pub const mem = @import("mem.zig");
87pub const meta = @import("meta.zig");87pub const meta = @import("meta.zig");
88pub const net = @import("net.zig");
89pub const os = @import("os.zig");88pub const os = @import("os.zig");
90pub const once = @import("once.zig").once;89pub const once = @import("once.zig").once;
91pub const pdb = @import("pdb.zig");90pub const pdb = @import("pdb.zig");
...@@ -145,19 +144,6 @@ pub const Options = struct {...@@ -145,19 +144,6 @@ pub const Options = struct {
145144
146 crypto_fork_safety: bool = true,145 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
161 /// By default, std.http.Client will support HTTPS connections. Set this option to `true` to147 /// By default, std.http.Client will support HTTPS connections. Set this option to `true` to
162 /// disable TLS support.148 /// disable TLS support.
163 ///149 ///
lib/std/tar.zig+5-5
...@@ -977,7 +977,7 @@ test pipeToFileSystem {...@@ -977,7 +977,7 @@ test pipeToFileSystem {
977 const data = @embedFile("tar/testdata/example.tar");977 const data = @embedFile("tar/testdata/example.tar");
978 var reader: std.Io.Reader = .fixed(data);978 var reader: std.Io.Reader = .fixed(data);
979979
980 var tmp = testing.tmpDir(.{ .no_follow = true });980 var tmp = testing.tmpDir(.{ .follow_symlinks = false });
981 defer tmp.cleanup();981 defer tmp.cleanup();
982 const dir = tmp.dir;982 const dir = tmp.dir;
983983
...@@ -1010,7 +1010,7 @@ test "pipeToFileSystem root_dir" {...@@ -1010,7 +1010,7 @@ test "pipeToFileSystem root_dir" {
10101010
1011 // with strip_components = 11011 // with strip_components = 1
1012 {1012 {
1013 var tmp = testing.tmpDir(.{ .no_follow = true });1013 var tmp = testing.tmpDir(.{ .follow_symlinks = false });
1014 defer tmp.cleanup();1014 defer tmp.cleanup();
1015 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };1015 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };
1016 defer diagnostics.deinit();1016 defer diagnostics.deinit();
...@@ -1032,7 +1032,7 @@ test "pipeToFileSystem root_dir" {...@@ -1032,7 +1032,7 @@ test "pipeToFileSystem root_dir" {
1032 // with strip_components = 01032 // with strip_components = 0
1033 {1033 {
1034 reader = .fixed(data);1034 reader = .fixed(data);
1035 var tmp = testing.tmpDir(.{ .no_follow = true });1035 var tmp = testing.tmpDir(.{ .follow_symlinks = false });
1036 defer tmp.cleanup();1036 defer tmp.cleanup();
1037 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };1037 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };
1038 defer diagnostics.deinit();1038 defer diagnostics.deinit();
...@@ -1084,7 +1084,7 @@ test "pipeToFileSystem strip_components" {...@@ -1084,7 +1084,7 @@ test "pipeToFileSystem strip_components" {
1084 const data = @embedFile("tar/testdata/example.tar");1084 const data = @embedFile("tar/testdata/example.tar");
1085 var reader: std.Io.Reader = .fixed(data);1085 var reader: std.Io.Reader = .fixed(data);
10861086
1087 var tmp = testing.tmpDir(.{ .no_follow = true });1087 var tmp = testing.tmpDir(.{ .follow_symlinks = false });
1088 defer tmp.cleanup();1088 defer tmp.cleanup();
1089 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };1089 var diagnostics: Diagnostics = .{ .allocator = testing.allocator };
1090 defer diagnostics.deinit();1090 defer diagnostics.deinit();
...@@ -1145,7 +1145,7 @@ test "executable bit" {...@@ -1145,7 +1145,7 @@ test "executable bit" {
1145 for ([_]PipeOptions.ModeMode{ .ignore, .executable_bit_only }) |opt| {1145 for ([_]PipeOptions.ModeMode{ .ignore, .executable_bit_only }) |opt| {
1146 var reader: std.Io.Reader = .fixed(data);1146 var reader: std.Io.Reader = .fixed(data);
11471147
1148 var tmp = testing.tmpDir(.{ .no_follow = true });1148 var tmp = testing.tmpDir(.{ .follow_symlinks = false });
1149 //defer tmp.cleanup();1149 //defer tmp.cleanup();
11501150
1151 pipeToFileSystem(tmp.dir, &reader, .{1151 pipeToFileSystem(tmp.dir, &reader, .{
lib/std/tar/Writer.zig+33-22
...@@ -1,7 +1,9 @@...@@ -1,7 +1,9 @@
1const Writer = @This();
2
1const std = @import("std");3const std = @import("std");
4const Io = std.Io;
2const assert = std.debug.assert;5const assert = std.debug.assert;
3const testing = std.testing;6const testing = std.testing;
4const Writer = @This();
57
6const block_size = @sizeOf(Header);8const block_size = @sizeOf(Header);
79
...@@ -14,9 +16,8 @@ pub const Options = struct {...@@ -14,9 +16,8 @@ pub const Options = struct {
14 mtime: u64 = 0,16 mtime: u64 = 0,
15};17};
1618
17underlying_writer: *std.Io.Writer,19underlying_writer: *Io.Writer,
18prefix: []const u8 = "",20prefix: []const u8 = "",
19mtime_now: u64 = 0,
2021
21const Error = error{22const Error = error{
22 WriteFailed,23 WriteFailed,
...@@ -36,16 +37,27 @@ pub fn writeDir(w: *Writer, sub_path: []const u8, options: Options) Error!void {...@@ -36,16 +37,27 @@ pub fn writeDir(w: *Writer, sub_path: []const u8, options: Options) Error!void {
36 try w.writeHeader(.directory, sub_path, "", 0, options);37 try w.writeHeader(.directory, sub_path, "", 0, options);
37}38}
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
41pub fn writeFile(51pub fn writeFile(
42 w: *Writer,52 w: *Writer,
43 sub_path: []const u8,53 sub_path: []const u8,
44 file_reader: *std.fs.File.Reader,54 file_reader: *Io.File.Reader,
45 stat_mtime: i128,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,
46) WriteFileError!void {59) WriteFileError!void {
47 const size = try file_reader.getSize();60 const size = try file_reader.getSize();
48 const mtime: u64 = @intCast(@divFloor(stat_mtime, std.time.ns_per_s));
4961
50 var header: Header = .{};62 var header: Header = .{};
51 try w.setPath(&header, sub_path);63 try w.setPath(&header, sub_path);
...@@ -58,7 +70,7 @@ pub fn writeFile(...@@ -58,7 +70,7 @@ pub fn writeFile(
58 try w.writePadding64(size);70 try w.writePadding64(size);
59}71}
6072
61pub const WriteFileStreamError = Error || std.Io.Reader.StreamError;73pub const WriteFileStreamError = Error || Io.Reader.StreamError;
6274
63/// Writes file reading file content from `reader`. Reads exactly `size` bytes75/// Writes file reading file content from `reader`. Reads exactly `size` bytes
64/// from `reader`, or returns `error.EndOfStream`.76/// from `reader`, or returns `error.EndOfStream`.
...@@ -66,7 +78,7 @@ pub fn writeFileStream(...@@ -66,7 +78,7 @@ pub fn writeFileStream(
66 w: *Writer,78 w: *Writer,
67 sub_path: []const u8,79 sub_path: []const u8,
68 size: u64,80 size: u64,
69 reader: *std.Io.Reader,81 reader: *Io.Reader,
70 options: Options,82 options: Options,
71) WriteFileStreamError!void {83) WriteFileStreamError!void {
72 try w.writeHeader(.regular, sub_path, "", size, options);84 try w.writeHeader(.regular, sub_path, "", size, options);
...@@ -136,15 +148,15 @@ fn writeExtendedHeader(w: *Writer, typeflag: Header.FileType, buffers: []const [...@@ -136,15 +148,15 @@ fn writeExtendedHeader(w: *Writer, typeflag: Header.FileType, buffers: []const [
136 try w.writePadding(len);148 try w.writePadding(len);
137}149}
138150
139fn writePadding(w: *Writer, bytes: usize) std.Io.Writer.Error!void {151fn writePadding(w: *Writer, bytes: usize) Io.Writer.Error!void {
140 return writePaddingPos(w, bytes % block_size);152 return writePaddingPos(w, bytes % block_size);
141}153}
142154
143fn writePadding64(w: *Writer, bytes: u64) std.Io.Writer.Error!void {155fn writePadding64(w: *Writer, bytes: u64) Io.Writer.Error!void {
144 return writePaddingPos(w, @intCast(bytes % block_size));156 return writePaddingPos(w, @intCast(bytes % block_size));
145}157}
146158
147fn writePaddingPos(w: *Writer, pos: usize) std.Io.Writer.Error!void {159fn writePaddingPos(w: *Writer, pos: usize) Io.Writer.Error!void {
148 if (pos == 0) return;160 if (pos == 0) return;
149 try w.underlying_writer.splatByteAll(0, block_size - pos);161 try w.underlying_writer.splatByteAll(0, block_size - pos);
150}162}
...@@ -153,7 +165,7 @@ fn writePaddingPos(w: *Writer, pos: usize) std.Io.Writer.Error!void {...@@ -153,7 +165,7 @@ fn writePaddingPos(w: *Writer, pos: usize) std.Io.Writer.Error!void {
153/// "reasonable system must not assume that such a block exists when reading an165/// "reasonable system must not assume that such a block exists when reading an
154/// archive". Therefore, the Zig standard library recommends to not call this166/// archive". Therefore, the Zig standard library recommends to not call this
155/// function.167/// function.
156pub fn finishPedantically(w: *Writer) std.Io.Writer.Error!void {168pub fn finishPedantically(w: *Writer) Io.Writer.Error!void {
157 try w.underlying_writer.splatByteAll(0, block_size * 2);169 try w.underlying_writer.splatByteAll(0, block_size * 2);
158}170}
159171
...@@ -236,7 +248,6 @@ pub const Header = extern struct {...@@ -236,7 +248,6 @@ pub const Header = extern struct {
236 }248 }
237249
238 // Integer number of seconds since January 1, 1970, 00:00 Coordinated Universal Time.250 // Integer number of seconds since January 1, 1970, 00:00 Coordinated Universal Time.
239 // mtime == 0 will use current time
240 pub fn setMtime(w: *Header, mtime: u64) error{OctalOverflow}!void {251 pub fn setMtime(w: *Header, mtime: u64) error{OctalOverflow}!void {
241 try octal(&w.mtime, mtime);252 try octal(&w.mtime, mtime);
242 }253 }
...@@ -248,7 +259,7 @@ pub const Header = extern struct {...@@ -248,7 +259,7 @@ pub const Header = extern struct {
248 try octal(&w.checksum, checksum);259 try octal(&w.checksum, checksum);
249 }260 }
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 {
252 try h.updateChecksum();263 try h.updateChecksum();
253 try bw.writeAll(std.mem.asBytes(h));264 try bw.writeAll(std.mem.asBytes(h));
254 }265 }
...@@ -396,14 +407,14 @@ test "write files" {...@@ -396,14 +407,14 @@ test "write files" {
396 {407 {
397 const root = "root";408 const root = "root";
398409
399 var output: std.Io.Writer.Allocating = .init(testing.allocator);410 var output: Io.Writer.Allocating = .init(testing.allocator);
400 var w: Writer = .{ .underlying_writer = &output.writer };411 var w: Writer = .{ .underlying_writer = &output.writer };
401 defer output.deinit();412 defer output.deinit();
402 try w.setRoot(root);413 try w.setRoot(root);
403 for (files) |file|414 for (files) |file|
404 try w.writeFileBytes(file.path, file.content, .{});415 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());
407 var it: std.tar.Iterator = .init(&input, .{418 var it: std.tar.Iterator = .init(&input, .{
408 .file_name_buffer = &file_name_buffer,419 .file_name_buffer = &file_name_buffer,
409 .link_name_buffer = &link_name_buffer,420 .link_name_buffer = &link_name_buffer,
...@@ -424,7 +435,7 @@ test "write files" {...@@ -424,7 +435,7 @@ test "write files" {
424 try testing.expectEqual('/', actual.name[root.len..][0]);435 try testing.expectEqual('/', actual.name[root.len..][0]);
425 try testing.expectEqualStrings(expected.path, actual.name[root.len + 1 ..]);436 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);
428 defer content.deinit();439 defer content.deinit();
429 try it.streamRemaining(actual, &content.writer);440 try it.streamRemaining(actual, &content.writer);
430 try testing.expectEqualSlices(u8, expected.content, content.written());441 try testing.expectEqualSlices(u8, expected.content, content.written());
...@@ -432,15 +443,15 @@ test "write files" {...@@ -432,15 +443,15 @@ test "write files" {
432 }443 }
433 // without root444 // without root
434 {445 {
435 var output: std.Io.Writer.Allocating = .init(testing.allocator);446 var output: Io.Writer.Allocating = .init(testing.allocator);
436 var w: Writer = .{ .underlying_writer = &output.writer };447 var w: Writer = .{ .underlying_writer = &output.writer };
437 defer output.deinit();448 defer output.deinit();
438 for (files) |file| {449 for (files) |file| {
439 var content: std.Io.Reader = .fixed(file.content);450 var content: Io.Reader = .fixed(file.content);
440 try w.writeFileStream(file.path, file.content.len, &content, .{});451 try w.writeFileStream(file.path, file.content.len, &content, .{});
441 }452 }
442453
443 var input: std.Io.Reader = .fixed(output.written());454 var input: Io.Reader = .fixed(output.written());
444 var it: std.tar.Iterator = .init(&input, .{455 var it: std.tar.Iterator = .init(&input, .{
445 .file_name_buffer = &file_name_buffer,456 .file_name_buffer = &file_name_buffer,
446 .link_name_buffer = &link_name_buffer,457 .link_name_buffer = &link_name_buffer,
...@@ -452,7 +463,7 @@ test "write files" {...@@ -452,7 +463,7 @@ test "write files" {
452 const expected = files[i];463 const expected = files[i];
453 try testing.expectEqualStrings(expected.path, actual.name);464 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);
456 defer content.deinit();467 defer content.deinit();
457 try it.streamRemaining(actual, &content.writer);468 try it.streamRemaining(actual, &content.writer);
458 try testing.expectEqualSlices(u8, expected.content, content.written());469 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(.{...@@ -28,6 +28,9 @@ pub var allocator_instance: std.heap.GeneralPurposeAllocator(.{
28 break :b .init;28 break :b .init;
29};29};
3030
31pub var io_instance: std.Io.Threaded = undefined;
32pub const io = io_instance.io();
33
31/// TODO https://github.com/ziglang/zig/issues/573834/// TODO https://github.com/ziglang/zig/issues/5738
32pub var log_level = std.log.Level.warn;35pub var log_level = std.log.Level.warn;
3336
...@@ -1145,6 +1148,7 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime...@@ -1145,6 +1148,7 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime
1145 } else |err| switch (err) {1148 } else |err| switch (err) {
1146 error.OutOfMemory => {1149 error.OutOfMemory => {
1147 if (failing_allocator_inst.allocated_bytes != failing_allocator_inst.freed_bytes) {1150 if (failing_allocator_inst.allocated_bytes != failing_allocator_inst.freed_bytes) {
1151 const tty_config = std.Io.tty.detectConfig(.stderr());
1148 print(1152 print(
1149 "\nfail_index: {d}/{d}\nallocated bytes: {d}\nfreed bytes: {d}\nallocations: {d}\ndeallocations: {d}\nallocation that was made to fail: {f}",1153 "\nfail_index: {d}/{d}\nallocated bytes: {d}\nfreed bytes: {d}\nallocations: {d}\ndeallocations: {d}\nallocation that was made to fail: {f}",
1150 .{1154 .{
...@@ -1154,7 +1158,10 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime...@@ -1154,7 +1158,10 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime
1154 failing_allocator_inst.freed_bytes,1158 failing_allocator_inst.freed_bytes,
1155 failing_allocator_inst.allocations,1159 failing_allocator_inst.allocations,
1156 failing_allocator_inst.deallocations,1160 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 },
1158 },1165 },
1159 );1166 );
1160 return error.MemoryLeakDetected;1167 return error.MemoryLeakDetected;
lib/std/time.zig+3-69
...@@ -8,74 +8,6 @@ const posix = std.posix;...@@ -8,74 +8,6 @@ const posix = std.posix;
88
9pub const epoch = @import("time/epoch.zig");9pub 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
79// Divisions of a nanosecond.11// Divisions of a nanosecond.
80pub const ns_per_us = 1000;12pub const ns_per_us = 1000;
81pub const ns_per_ms = 1000 * ns_per_us;13pub const ns_per_ms = 1000 * ns_per_us;
...@@ -268,9 +200,11 @@ pub const Timer = struct {...@@ -268,9 +200,11 @@ pub const Timer = struct {
268};200};
269201
270test Timer {202test Timer {
203 const io = std.testing.io;
204
271 var timer = try Timer.start();205 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);
274 const time_0 = timer.read();208 const time_0 = timer.read();
275 try testing.expect(time_0 > 0);209 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...@@ -1809,30 +1809,6 @@ pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) error{InvalidWtf8}!usize
1809 return utf8ToUtf16LeImpl(wtf16le, wtf8, .can_encode_surrogate_half);1809 return utf8ToUtf16LeImpl(wtf16le, wtf8, .can_encode_surrogate_half);
1810}1810}
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
1836/// Surrogate codepoints (U+D800 to U+DFFF) are replaced by the Unicode replacement1812/// Surrogate codepoints (U+D800 to U+DFFF) are replaced by the Unicode replacement
1837/// character (U+FFFD).1813/// character (U+FFFD).
1838/// All surrogate codepoints and the replacement character are encoded as three1814/// All surrogate codepoints and the replacement character are encoded as three
...@@ -2039,7 +2015,6 @@ fn testRoundtripWtf8(wtf8: []const u8) !void {...@@ -2039,7 +2015,6 @@ fn testRoundtripWtf8(wtf8: []const u8) !void {
2039 var wtf16_buf: [32]u16 = undefined;2015 var wtf16_buf: [32]u16 = undefined;
2040 const wtf16_len = try wtf8ToWtf16Le(&wtf16_buf, wtf8);2016 const wtf16_len = try wtf8ToWtf16Le(&wtf16_buf, wtf8);
2041 try testing.expectEqual(wtf16_len, calcWtf16LeLen(wtf8));2017 try testing.expectEqual(wtf16_len, calcWtf16LeLen(wtf8));
2042 try testing.expectEqual(false, checkWtf8ToWtf16LeOverflow(wtf8, &wtf16_buf));
2043 const wtf16 = wtf16_buf[0..wtf16_len];2018 const wtf16 = wtf16_buf[0..wtf16_len];
20442019
2045 var roundtripped_buf: [32]u8 = undefined;2020 var roundtripped_buf: [32]u8 = undefined;
lib/std/zig.zig+7-6
...@@ -6,6 +6,7 @@ const std = @import("std.zig");...@@ -6,6 +6,7 @@ const std = @import("std.zig");
6const tokenizer = @import("zig/tokenizer.zig");6const tokenizer = @import("zig/tokenizer.zig");
7const assert = std.debug.assert;7const assert = std.debug.assert;
8const Allocator = std.mem.Allocator;8const Allocator = std.mem.Allocator;
9const Io = std.Io;
9const Writer = std.Io.Writer;10const Writer = std.Io.Writer;
1011
11pub const ErrorBundle = @import("zig/ErrorBundle.zig");12pub const ErrorBundle = @import("zig/ErrorBundle.zig");
...@@ -52,9 +53,9 @@ pub const Color = enum {...@@ -52,9 +53,9 @@ pub const Color = enum {
52 /// Assume stderr is a terminal.53 /// Assume stderr is a terminal.
53 on,54 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 {
56 return switch (color) {57 return switch (color) {
57 .auto => std.Io.tty.detectConfig(std.fs.File.stderr()),58 .auto => Io.tty.detectConfig(std.fs.File.stderr()),
58 .on => .escape_codes,59 .on => .escape_codes,
59 .off => .no_color,60 .off => .no_color,
60 };61 };
...@@ -323,7 +324,7 @@ pub const BuildId = union(enum) {...@@ -323,7 +324,7 @@ pub const BuildId = union(enum) {
323 try std.testing.expectError(error.InvalidBuildIdStyle, parse("yaddaxxx"));324 try std.testing.expectError(error.InvalidBuildIdStyle, parse("yaddaxxx"));
324 }325 }
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 {
327 switch (id) {328 switch (id) {
328 .none, .fast, .uuid, .sha1, .md5 => {329 .none, .fast, .uuid, .sha1, .md5 => {
329 try writer.writeAll(@tagName(id));330 try writer.writeAll(@tagName(id));
...@@ -558,7 +559,7 @@ test isUnderscore {...@@ -558,7 +559,7 @@ test isUnderscore {
558/// If the source can be UTF-16LE encoded, this function asserts that `gpa`559/// If the source can be UTF-16LE encoded, this function asserts that `gpa`
559/// will align a byte-sized allocation to at least 2. Allocators that don't do560/// will align a byte-sized allocation to at least 2. Allocators that don't do
560/// this are rare.561/// 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 {
562 var buffer: std.ArrayList(u8) = .empty;563 var buffer: std.ArrayList(u8) = .empty;
563 defer buffer.deinit(gpa);564 defer buffer.deinit(gpa);
564565
...@@ -620,8 +621,8 @@ pub fn putAstErrorsIntoBundle(...@@ -620,8 +621,8 @@ pub fn putAstErrorsIntoBundle(
620 try wip_errors.addZirErrorMessages(zir, tree, tree.source, path);621 try wip_errors.addZirErrorMessages(zir, tree, tree.source, path);
621}622}
622623
623pub fn resolveTargetQueryOrFatal(target_query: std.Target.Query) std.Target {624pub fn resolveTargetQueryOrFatal(io: Io, target_query: std.Target.Query) std.Target {
624 return std.zig.system.resolveTargetQuery(target_query) catch |err|625 return std.zig.system.resolveTargetQuery(io, target_query) catch |err|
625 std.process.fatal("unable to resolve target: {s}", .{@errorName(err)});626 std.process.fatal("unable to resolve target: {s}", .{@errorName(err)});
626}627}
627628
lib/std/zig/ErrorBundle.zig+6-5
...@@ -6,12 +6,13 @@...@@ -6,12 +6,13 @@
6//! There is one special encoding for this data structure. If both arrays are6//! There is one special encoding for this data structure. If both arrays are
7//! empty, it means there are no errors. This special encoding exists so that7//! empty, it means there are no errors. This special encoding exists so that
8//! heap allocation is not needed in the common case of no errors.8//! heap allocation is not needed in the common case of no errors.
9const ErrorBundle = @This();
910
10const std = @import("std");11const std = @import("std");
11const ErrorBundle = @This();12const Io = std.Io;
13const Writer = std.Io.Writer;
12const Allocator = std.mem.Allocator;14const Allocator = std.mem.Allocator;
13const assert = std.debug.assert;15const assert = std.debug.assert;
14const Writer = std.Io.Writer;
1516
16string_bytes: []const u8,17string_bytes: []const u8,
17/// The first thing in this array is an `ErrorMessageList`.18/// The first thing in this array is an `ErrorMessageList`.
...@@ -156,7 +157,7 @@ pub fn nullTerminatedString(eb: ErrorBundle, index: String) [:0]const u8 {...@@ -156,7 +157,7 @@ pub fn nullTerminatedString(eb: ErrorBundle, index: String) [:0]const u8 {
156}157}
157158
158pub const RenderOptions = struct {159pub const RenderOptions = struct {
159 ttyconf: std.Io.tty.Config,160 ttyconf: Io.tty.Config,
160 include_reference_trace: bool = true,161 include_reference_trace: bool = true,
161 include_source_line: bool = true,162 include_source_line: bool = true,
162 include_log_text: bool = true,163 include_log_text: bool = true,
...@@ -190,7 +191,7 @@ fn renderErrorMessageToWriter(...@@ -190,7 +191,7 @@ fn renderErrorMessageToWriter(
190 err_msg_index: MessageIndex,191 err_msg_index: MessageIndex,
191 w: *Writer,192 w: *Writer,
192 kind: []const u8,193 kind: []const u8,
193 color: std.Io.tty.Color,194 color: Io.tty.Color,
194 indent: usize,195 indent: usize,
195) (Writer.Error || std.posix.UnexpectedError)!void {196) (Writer.Error || std.posix.UnexpectedError)!void {
196 const ttyconf = options.ttyconf;197 const ttyconf = options.ttyconf;
...@@ -806,7 +807,7 @@ pub const Wip = struct {...@@ -806,7 +807,7 @@ pub const Wip = struct {
806 };807 };
807 defer bundle.deinit(std.testing.allocator);808 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
811 var bundle_buf: Writer.Allocating = .init(std.testing.allocator);812 var bundle_buf: Writer.Allocating = .init(std.testing.allocator);
812 const bundle_bw = &bundle_buf.interface;813 const bundle_bw = &bundle_buf.interface;
lib/std/zig/LibCInstallation.zig+6-6
...@@ -329,7 +329,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F...@@ -329,7 +329,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
329 defer search_dir.close();329 defer search_dir.close();
330330
331 if (self.include_dir == null) {331 if (self.include_dir == null) {
332 if (search_dir.accessZ(include_dir_example_file, .{})) |_| {332 if (search_dir.access(include_dir_example_file, .{})) |_| {
333 self.include_dir = try allocator.dupeZ(u8, search_path);333 self.include_dir = try allocator.dupeZ(u8, search_path);
334 } else |err| switch (err) {334 } else |err| switch (err) {
335 error.FileNotFound => {},335 error.FileNotFound => {},
...@@ -338,7 +338,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F...@@ -338,7 +338,7 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, args: FindNativeOptions) F
338 }338 }
339339
340 if (self.sys_include_dir == null) {340 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, .{})) |_| {
342 self.sys_include_dir = try allocator.dupeZ(u8, search_path);342 self.sys_include_dir = try allocator.dupeZ(u8, search_path);
343 } else |err| switch (err) {343 } else |err| switch (err) {
344 error.FileNotFound => {},344 error.FileNotFound => {},
...@@ -382,7 +382,7 @@ fn findNativeIncludeDirWindows(...@@ -382,7 +382,7 @@ fn findNativeIncludeDirWindows(
382 };382 };
383 defer dir.close();383 defer dir.close();
384384
385 dir.accessZ("stdlib.h", .{}) catch |err| switch (err) {385 dir.access("stdlib.h", .{}) catch |err| switch (err) {
386 error.FileNotFound => continue,386 error.FileNotFound => continue,
387 else => return error.FileSystem,387 else => return error.FileSystem,
388 };388 };
...@@ -429,7 +429,7 @@ fn findNativeCrtDirWindows(...@@ -429,7 +429,7 @@ fn findNativeCrtDirWindows(
429 };429 };
430 defer dir.close();430 defer dir.close();
431431
432 dir.accessZ("ucrt.lib", .{}) catch |err| switch (err) {432 dir.access("ucrt.lib", .{}) catch |err| switch (err) {
433 error.FileNotFound => continue,433 error.FileNotFound => continue,
434 else => return error.FileSystem,434 else => return error.FileSystem,
435 };435 };
...@@ -496,7 +496,7 @@ fn findNativeKernel32LibDir(...@@ -496,7 +496,7 @@ fn findNativeKernel32LibDir(
496 };496 };
497 defer dir.close();497 defer dir.close();
498498
499 dir.accessZ("kernel32.lib", .{}) catch |err| switch (err) {499 dir.access("kernel32.lib", .{}) catch |err| switch (err) {
500 error.FileNotFound => continue,500 error.FileNotFound => continue,
501 else => return error.FileSystem,501 else => return error.FileSystem,
502 };502 };
...@@ -531,7 +531,7 @@ fn findNativeMsvcIncludeDir(...@@ -531,7 +531,7 @@ fn findNativeMsvcIncludeDir(
531 };531 };
532 defer dir.close();532 defer dir.close();
533533
534 dir.accessZ("vcruntime.h", .{}) catch |err| switch (err) {534 dir.access("vcruntime.h", .{}) catch |err| switch (err) {
535 error.FileNotFound => return error.LibCStdLibHeaderNotFound,535 error.FileNotFound => return error.LibCStdLibHeaderNotFound,
536 else => return error.FileSystem,536 else => return error.FileSystem,
537 };537 };
lib/std/zig/system.zig+310-494
...@@ -1,3 +1,14 @@...@@ -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
1pub const NativePaths = @import("system/NativePaths.zig");12pub const NativePaths = @import("system/NativePaths.zig");
213
3pub const windows = @import("system/windows.zig");14pub const windows = @import("system/windows.zig");
...@@ -199,14 +210,14 @@ pub const DetectError = error{...@@ -199,14 +210,14 @@ pub const DetectError = error{
199 OSVersionDetectionFail,210 OSVersionDetectionFail,
200 Unexpected,211 Unexpected,
201 ProcessNotFound,212 ProcessNotFound,
202};213} || Io.Cancelable;
203214
204/// Given a `Target.Query`, which specifies in detail which parts of the215/// Given a `Target.Query`, which specifies in detail which parts of the
205/// target should be detected natively, which should be standard or default,216/// target should be detected natively, which should be standard or default,
206/// and which are provided explicitly, this function resolves the native217/// and which are provided explicitly, this function resolves the native
207/// components by detecting the native system, and then resolves218/// components by detecting the native system, and then resolves
208/// standard/default parts relative to that.219/// standard/default parts relative to that.
209pub fn resolveTargetQuery(query: Target.Query) DetectError!Target {220pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
210 // Until https://github.com/ziglang/zig/issues/4592 is implemented (support detecting the221 // Until https://github.com/ziglang/zig/issues/4592 is implemented (support detecting the
211 // native CPU architecture as being different than the current target), we use this:222 // native CPU architecture as being different than the current target), we use this:
212 const query_cpu_arch = query.cpu_arch orelse builtin.cpu.arch;223 const query_cpu_arch = query.cpu_arch orelse builtin.cpu.arch;
...@@ -356,10 +367,10 @@ pub fn resolveTargetQuery(query: Target.Query) DetectError!Target {...@@ -356,10 +367,10 @@ pub fn resolveTargetQuery(query: Target.Query) DetectError!Target {
356 }367 }
357368
358 var cpu = switch (query.cpu_model) {369 var cpu = switch (query.cpu_model) {
359 .native => detectNativeCpuAndFeatures(query_cpu_arch, os, query),370 .native => detectNativeCpuAndFeatures(io, query_cpu_arch, os, query),
360 .baseline => Target.Cpu.baseline(query_cpu_arch, os),371 .baseline => Target.Cpu.baseline(query_cpu_arch, os),
361 .determined_by_arch_os => if (query.cpu_arch == null)372 .determined_by_arch_os => if (query.cpu_arch == null)
362 detectNativeCpuAndFeatures(query_cpu_arch, os, query)373 detectNativeCpuAndFeatures(io, query_cpu_arch, os, query)
363 else374 else
364 Target.Cpu.baseline(query_cpu_arch, os),375 Target.Cpu.baseline(query_cpu_arch, os),
365 .explicit => |model| model.toCpu(query_cpu_arch),376 .explicit => |model| model.toCpu(query_cpu_arch),
...@@ -411,7 +422,34 @@ pub fn resolveTargetQuery(query: Target.Query) DetectError!Target {...@@ -411,7 +422,34 @@ pub fn resolveTargetQuery(query: Target.Query) DetectError!Target {
411 query.cpu_features_sub,422 query.cpu_features_sub,
412 );423 );
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
416 // These CPU feature hacks have to come after ABI detection.454 // These CPU feature hacks have to come after ABI detection.
417 {455 {
...@@ -483,7 +521,7 @@ fn updateCpuFeatures(...@@ -483,7 +521,7 @@ fn updateCpuFeatures(
483 set.removeFeatureSet(sub_set);521 set.removeFeatureSet(sub_set);
484}522}
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 {
487 // Here we switch on a comptime value rather than `cpu_arch`. This is valid because `cpu_arch`,525 // Here we switch on a comptime value rather than `cpu_arch`. This is valid because `cpu_arch`,
488 // although it is a runtime value, is guaranteed to be one of the architectures in the set526 // although it is a runtime value, is guaranteed to be one of the architectures in the set
489 // of the respective switch prong.527 // of the respective switch prong.
...@@ -494,7 +532,7 @@ fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, query: T...@@ -494,7 +532,7 @@ fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, query: T
494 }532 }
495533
496 switch (builtin.os.tag) {534 switch (builtin.os.tag) {
497 .linux => return linux.detectNativeCpuAndFeatures(),535 .linux => return linux.detectNativeCpuAndFeatures(io),
498 .macos => return darwin.macos.detectNativeCpuAndFeatures(),536 .macos => return darwin.macos.detectNativeCpuAndFeatures(),
499 .windows => return windows.detectNativeCpuAndFeatures(),537 .windows => return windows.detectNativeCpuAndFeatures(),
500 else => {},538 else => {},
...@@ -506,53 +544,42 @@ fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, query: T...@@ -506,53 +544,42 @@ fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, query: T
506}544}
507545
508pub const AbiAndDynamicLinkerFromFileError = error{546pub const AbiAndDynamicLinkerFromFileError = error{
509 FileSystem,547 Canceled,
510 SystemResources,548 AccessDenied,
549 Unexpected,
550 Unseekable,
551 ReadFailed,
552 EndOfStream,
553 NameTooLong,
554 StaticElfFile,
555 InvalidElfFile,
556 StreamTooLong,
557 Timeout,
511 SymLinkLoop,558 SymLinkLoop,
559 SystemResources,
512 ProcessFdQuotaExceeded,560 ProcessFdQuotaExceeded,
513 SystemFdQuotaExceeded,561 SystemFdQuotaExceeded,
514 UnableToReadElfFile,
515 InvalidElfClass,
516 InvalidElfVersion,
517 InvalidElfEndian,
518 InvalidElfFile,
519 InvalidElfMagic,
520 Unexpected,
521 UnexpectedEndOfFile,
522 NameTooLong,
523 ProcessNotFound,562 ProcessNotFound,
524 StaticElfFile,563 IsDir,
564 WouldBlock,
565 InputOutput,
566 BrokenPipe,
567 ConnectionResetByPeer,
568 NotOpenForReading,
569 SocketUnconnected,
570 LockViolation,
571 FileSystem,
525};572};
526573
527pub fn abiAndDynamicLinkerFromFile(574fn abiAndDynamicLinkerFromFile(
528 file: fs.File,575 file_reader: *Io.File.Reader,
576 header: *const elf.Header,
529 cpu: Target.Cpu,577 cpu: Target.Cpu,
530 os: Target.Os,578 os: Target.Os,
531 ld_info_list: []const LdInfo,579 ld_info_list: []const LdInfo,
532 query: Target.Query,580 query: Target.Query,
533) AbiAndDynamicLinkerFromFileError!Target {581) AbiAndDynamicLinkerFromFileError!Target {
534 var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;582 const io = file_reader.io;
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
556 var result: Target = .{583 var result: Target = .{
557 .cpu = cpu,584 .cpu = cpu,
558 .os = os,585 .os = os,
...@@ -563,170 +590,90 @@ pub fn abiAndDynamicLinkerFromFile(...@@ -563,170 +590,90 @@ pub fn abiAndDynamicLinkerFromFile(
563 var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC590 var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC
564 const look_for_ld = query.dynamic_linker.get() == null;591 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;
570 var got_dyn_section: bool = false;593 var got_dyn_section: bool = false;
571594 {
572 while (ph_i < phnum) {595 var it = header.iterateProgramHeaders(file_reader);
573 // Reserve some bytes so that we can deref the 64-bit struct fields596 while (try it.next()) |phdr| switch (phdr.p_type) {
574 // even when the ELF file is 32-bits.597 elf.PT_INTERP => {
575 const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr);598 got_dyn_section = true;
576 const ph_read_byte_len = try preadAtLeast(file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize);599
577 var ph_buf_i: usize = 0;600 if (look_for_ld) {
578 while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({601 const p_filesz = phdr.p_filesz;
579 ph_i += 1;602 if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong;
580 phoff += phentsize;603 const filesz: usize = @intCast(p_filesz);
581 ph_buf_i += phentsize;604 try file_reader.seekTo(phdr.p_offset);
582 }) {605 try file_reader.interface.readSliceAll(result.dynamic_linker.buffer[0..filesz]);
583 const ph32: *elf.Elf32_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i]));606 // PT_INTERP includes a null byte in filesz.
584 const ph64: *elf.Elf64_Phdr = @ptrCast(@alignCast(&ph_buf[ph_buf_i]));607 const len = filesz - 1;
585 const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type);608 // dynamic_linker.max_byte is "max", not "len".
586 switch (p_type) {609 // We know it will fit in u8 because we check against dynamic_linker.buffer.len above.
587 elf.PT_INTERP => {610 result.dynamic_linker.len = @intCast(len);
588 got_dyn_section = true;611
589612 // Use it to determine ABI.
590 if (look_for_ld) {613 const full_ld_path = result.dynamic_linker.buffer[0..len];
591 const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);614 for (ld_info_list) |ld_info| {
592 const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);615 const standard_ld_basename = fs.path.basename(ld_info.ld.get().?);
593 if (p_filesz > result.dynamic_linker.buffer.len) return error.NameTooLong;616 if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) {
594 const filesz: usize = @intCast(p_filesz);617 result.abi = ld_info.abi;
595 _ = try preadAtLeast(file, result.dynamic_linker.buffer[0..filesz], p_offset, filesz);618 break;
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 }
610 }619 }
611 }620 }
612 },621 }
613 // We only need this for detecting glibc version.622 },
614 elf.PT_DYNAMIC => {623 // We only need this for detecting glibc version.
615 got_dyn_section = true;624 elf.PT_DYNAMIC => {
616625 got_dyn_section = true;
617 if (builtin.target.os.tag == .linux and result.isGnuLibC() and626
618 query.glibc_version == null)627 if (builtin.target.os.tag == .linux and result.isGnuLibC() and query.glibc_version == null) {
619 {628 var dyn_it = header.iterateDynamicSection(file_reader, phdr.p_offset, phdr.p_filesz);
620 var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset);629 while (try dyn_it.next()) |dyn| {
621 const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz);630 if (dyn.d_tag == elf.DT_RUNPATH) {
622 const dyn_size: usize = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn);631 rpath_offset = dyn.d_val;
623 const dyn_num = p_filesz / dyn_size;632 break;
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 }
651 }633 }
652 }634 }
653 },635 }
654 else => continue,636 },
655 }637 else => continue,
656 }638 };
657 }639 }
658640
659 if (!got_dyn_section) {641 if (!got_dyn_section) {
660 return error.StaticElfFile;642 return error.StaticElfFile;
661 }643 }
662644
663 if (builtin.target.os.tag == .linux and result.isGnuLibC() and645 if (builtin.target.os.tag == .linux and result.isGnuLibC() and query.glibc_version == null) {
664 query.glibc_version == null)646 const str_section_off = header.shoff + @as(u64, header.shentsize) * @as(u64, header.shstrndx);
665 {647 try file_reader.seekTo(str_section_off);
666 const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx);648 const shstr = try elf.takeSectionHeader(&file_reader.interface, header.is_64, header.endian);
667649 var strtab_buf: [4096]u8 = undefined;
668 var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff);650 const shstrtab = strtab_buf[0..@min(shstr.sh_size, strtab_buf.len)];
669 const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize);651 try file_reader.seekTo(shstr.sh_offset);
670 const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx);652 try file_reader.interface.readSliceAll(shstrtab);
671653 const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: {
672 var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined;654 var it = header.iterateSectionHeaders(file_reader);
673 if (sh_buf.len < shentsize) return error.InvalidElfFile;655 while (try it.next()) |shdr| {
674656 const end = mem.findScalarPos(u8, shstrtab, shdr.sh_name, 0) orelse continue;
675 _ = try preadAtLeast(file, &sh_buf, str_section_off, shentsize);657 const sh_name = shstrtab[shdr.sh_name..end :0];
676 const shstr32: *elf.Elf32_Shdr = @ptrCast(@alignCast(&sh_buf));658 if (mem.eql(u8, sh_name, ".dynstr")) break :find_dyn_str .{
677 const shstr64: *elf.Elf64_Shdr = @ptrCast(@alignCast(&sh_buf));659 .offset = shdr.sh_offset,
678 const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset);660 .size = shdr.sh_size,
679 const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size);661 };
680 var strtab_buf: [4096:0]u8 = undefined;662 } else break :find_dyn_str null;
681 const shstrtab_len = @min(shstrtab_size, strtab_buf.len);663 };
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
716 if (dynstr) |ds| {664 if (dynstr) |ds| {
717 if (rpath_offset) |rpoff| {665 if (rpath_offset) |rpoff| {
718 if (rpoff > ds.size) return error.InvalidElfFile;666 if (rpoff > ds.size) return error.InvalidElfFile;
719 const rpoff_file = ds.offset + rpoff;667 const rpoff_file = ds.offset + rpoff;
720 const rp_max_size = ds.size - rpoff;668 const rp_max_size = ds.size - rpoff;
721669
722 const strtab_len = @min(rp_max_size, strtab_buf.len);670 try file_reader.seekTo(rpoff_file);
723 const strtab_read_len = try preadAtLeast(file, &strtab_buf, rpoff_file, strtab_len);671 const rpath_list = try file_reader.interface.takeSentinel(0);
724 const strtab = strtab_buf[0..strtab_read_len];672 if (rpath_list.len > rp_max_size) return error.StreamTooLong;
725673
726 const rpath_list = mem.sliceTo(strtab, 0);
727 var it = mem.tokenizeScalar(u8, rpath_list, ':');674 var it = mem.tokenizeScalar(u8, rpath_list, ':');
728 while (it.next()) |rpath| {675 while (it.next()) |rpath| {
729 if (glibcVerFromRPath(rpath)) |ver| {676 if (glibcVerFromRPath(io, rpath)) |ver| {
730 result.os.version_range.linux.glibc = ver;677 result.os.version_range.linux.glibc = ver;
731 return result;678 return result;
732 } else |err| switch (err) {679 } else |err| switch (err) {
...@@ -741,7 +688,7 @@ pub fn abiAndDynamicLinkerFromFile(...@@ -741,7 +688,7 @@ pub fn abiAndDynamicLinkerFromFile(
741 // There is no DT_RUNPATH so we try to find libc.so.6 inside the same688 // There is no DT_RUNPATH so we try to find libc.so.6 inside the same
742 // directory as the dynamic linker.689 // directory as the dynamic linker.
743 if (fs.path.dirname(dl_path)) |rpath| {690 if (fs.path.dirname(dl_path)) |rpath| {
744 if (glibcVerFromRPath(rpath)) |ver| {691 if (glibcVerFromRPath(io, rpath)) |ver| {
745 result.os.version_range.linux.glibc = ver;692 result.os.version_range.linux.glibc = ver;
746 return result;693 return result;
747 } else |err| switch (err) {694 } else |err| switch (err) {
...@@ -755,8 +702,6 @@ pub fn abiAndDynamicLinkerFromFile(...@@ -755,8 +702,6 @@ pub fn abiAndDynamicLinkerFromFile(
755 var link_buf: [posix.PATH_MAX]u8 = undefined;702 var link_buf: [posix.PATH_MAX]u8 = undefined;
756 const link_name = posix.readlink(dl_path, &link_buf) catch |err| switch (err) {703 const link_name = posix.readlink(dl_path, &link_buf) catch |err| switch (err) {
757 error.NameTooLong => unreachable,704 error.NameTooLong => unreachable,
758 error.InvalidUtf8 => unreachable, // WASI only
759 error.InvalidWtf8 => unreachable, // Windows only
760 error.BadPathName => unreachable, // Windows only705 error.BadPathName => unreachable, // Windows only
761 error.UnsupportedReparsePointType => unreachable, // Windows only706 error.UnsupportedReparsePointType => unreachable, // Windows only
762 error.NetworkNotFound => unreachable, // Windows only707 error.NetworkNotFound => unreachable, // Windows only
...@@ -806,7 +751,7 @@ pub fn abiAndDynamicLinkerFromFile(...@@ -806,7 +751,7 @@ pub fn abiAndDynamicLinkerFromFile(
806 @memcpy(path_buf[index..][0..abi.len], abi);751 @memcpy(path_buf[index..][0..abi.len], abi);
807 index += abi.len;752 index += abi.len;
808 const rpath = path_buf[0..index];753 const rpath = path_buf[0..index];
809 if (glibcVerFromRPath(rpath)) |ver| {754 if (glibcVerFromRPath(io, rpath)) |ver| {
810 result.os.version_range.linux.glibc = ver;755 result.os.version_range.linux.glibc = ver;
811 return result;756 return result;
812 } else |err| switch (err) {757 } else |err| switch (err) {
...@@ -845,29 +790,25 @@ test glibcVerFromLinkName {...@@ -845,29 +790,25 @@ test glibcVerFromLinkName {
845 try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.37.4.5.so", "ld-"));790 try std.testing.expectError(error.InvalidGnuLibCVersion, glibcVerFromLinkName("ld-2.37.4.5.so", "ld-"));
846}791}
847792
848fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {793fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
849 var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {794 var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) {
850 error.NameTooLong => unreachable,795 error.NameTooLong => return error.Unexpected,
851 error.InvalidUtf8 => unreachable, // WASI only796 error.BadPathName => return error.Unexpected,
852 error.InvalidWtf8 => unreachable, // Windows-only797 error.DeviceBusy => return error.Unexpected,
853 error.BadPathName => unreachable,798 error.NetworkNotFound => return error.Unexpected, // Windows-only
854 error.DeviceBusy => unreachable,799
855 error.NetworkNotFound => unreachable, // Windows-only800 error.FileNotFound => return error.GLibCNotFound,
856801 error.NotDir => return error.GLibCNotFound,
857 error.FileNotFound,802 error.AccessDenied => return error.GLibCNotFound,
858 error.NotDir,803 error.PermissionDenied => return error.GLibCNotFound,
859 error.AccessDenied,804 error.NoDevice => return error.GLibCNotFound,
860 error.PermissionDenied,805
861 error.NoDevice,806 error.ProcessFdQuotaExceeded => |e| return e,
862 => return error.GLibCNotFound,807 error.SystemFdQuotaExceeded => |e| return e,
863808 error.SystemResources => |e| return e,
864 error.ProcessNotFound,809 error.SymLinkLoop => |e| return e,
865 error.ProcessFdQuotaExceeded,810 error.Unexpected => |e| return e,
866 error.SystemFdQuotaExceeded,811 error.Canceled => |e| return e,
867 error.SystemResources,
868 error.SymLinkLoop,
869 error.Unexpected,
870 => |e| return e,
871 };812 };
872 defer dir.close();813 defer dir.close();
873814
...@@ -879,155 +820,103 @@ fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {...@@ -879,155 +820,103 @@ fn glibcVerFromRPath(rpath: []const u8) !std.SemanticVersion {
879 // .dynstr section, and finding the max version number of symbols820 // .dynstr section, and finding the max version number of symbols
880 // that start with "GLIBC_2.".821 // that start with "GLIBC_2.".
881 const glibc_so_basename = "libc.so.6";822 const glibc_so_basename = "libc.so.6";
882 var f = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {823 var file = dir.openFile(glibc_so_basename, .{}) catch |err| switch (err) {
883 error.NameTooLong => unreachable,824 error.NameTooLong => return error.Unexpected,
884 error.InvalidUtf8 => unreachable, // WASI only825 error.BadPathName => return error.Unexpected,
885 error.InvalidWtf8 => unreachable, // Windows only826 error.PipeBusy => return error.Unexpected, // Windows-only
886 error.BadPathName => unreachable, // Windows only827 error.SharingViolation => return error.Unexpected, // Windows-only
887 error.PipeBusy => unreachable, // Windows-only828 error.NetworkNotFound => return error.Unexpected, // Windows-only
888 error.SharingViolation => unreachable, // Windows-only829 error.AntivirusInterference => return error.Unexpected, // Windows-only
889 error.NetworkNotFound => unreachable, // Windows-only830 error.FileLocksNotSupported => return error.Unexpected, // No lock requested.
890 error.AntivirusInterference => unreachable, // Windows-only831 error.NoSpaceLeft => return error.Unexpected, // read-only
891 error.FileLocksNotSupported => unreachable, // No lock requested.832 error.PathAlreadyExists => return error.Unexpected, // read-only
892 error.NoSpaceLeft => unreachable, // read-only833 error.DeviceBusy => return error.Unexpected, // read-only
893 error.PathAlreadyExists => unreachable, // read-only834 error.FileBusy => return error.Unexpected, // read-only
894 error.DeviceBusy => unreachable, // read-only835 error.NoDevice => return error.Unexpected, // not asking for a special device
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
906 error.FileTooBig => return error.Unexpected,836 error.FileTooBig => return error.Unexpected,
907837 error.WouldBlock => return error.Unexpected, // not opened in non-blocking
908 error.ProcessNotFound,838
909 error.ProcessFdQuotaExceeded,839 error.AccessDenied => return error.GLibCNotFound,
910 error.SystemFdQuotaExceeded,840 error.PermissionDenied => return error.GLibCNotFound,
911 error.SystemResources,841 error.FileNotFound => return error.GLibCNotFound,
912 error.SymLinkLoop,842 error.NotDir => return error.GLibCNotFound,
913 error.Unexpected,843 error.IsDir => return error.GLibCNotFound,
914 => |e| return e,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,
915 };852 };
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) {
919 error.InvalidElfMagic,860 error.InvalidElfMagic,
920 error.InvalidElfEndian,861 error.InvalidElfEndian,
921 error.InvalidElfClass,862 error.InvalidElfClass,
922 error.InvalidElfFile,
923 error.InvalidElfVersion,863 error.InvalidElfVersion,
924 error.InvalidGnuLibCVersion,864 error.InvalidGnuLibCVersion,
925 error.UnexpectedEndOfFile,865 error.EndOfStream,
926 => return error.GLibCNotFound,866 => return error.GLibCNotFound,
927867
928 error.SystemResources,868 error.ReadFailed => return file_reader.err.?,
929 error.UnableToReadElfFile,869 else => |e| return e,
930 error.Unexpected,
931 error.FileSystem,
932 error.ProcessNotFound,
933 => |e| return e,
934 };870 };
935}871}
936872
937fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion {873fn glibcVerFromSoFile(file_reader: *Io.File.Reader) !std.SemanticVersion {
938 var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined;874 const header = try elf.Header.read(&file_reader.interface);
939 _ = try preadAtLeast(file, &hdr_buf, 0, hdr_buf.len);875 const str_section_off = header.shoff + @as(u64, header.shentsize) * @as(u64, header.shstrndx);
940 const hdr32: *elf.Elf32_Ehdr = @ptrCast(&hdr_buf);876 try file_reader.seekTo(str_section_off);
941 const hdr64: *elf.Elf64_Ehdr = @ptrCast(&hdr_buf);877 const shstr = try elf.takeSectionHeader(&file_reader.interface, header.is_64, header.endian);
942 if (!mem.eql(u8, hdr32.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;878 var strtab_buf: [4096]u8 = undefined;
943 const elf_endian: std.builtin.Endian = switch (hdr32.e_ident[elf.EI.DATA]) {879 const shstrtab = strtab_buf[0..@min(shstr.sh_size, strtab_buf.len)];
944 elf.ELFDATA2LSB => .little,880 try file_reader.seekTo(shstr.sh_offset);
945 elf.ELFDATA2MSB => .big,881 try file_reader.interface.readSliceAll(shstrtab);
946 else => return error.InvalidElfEndian,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;
947 };892 };
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
1003 // Here we loop over all the strings in the dynstr string table, assuming that any894 // Here we loop over all the strings in the dynstr string table, assuming that any
1004 // strings that start with "GLIBC_2." indicate the existence of such a glibc version,895 // strings that start with "GLIBC_2." indicate the existence of such a glibc version,
1005 // and furthermore, that the system-installed glibc is at minimum that version.896 // 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);
1017 var max_ver: std.SemanticVersion = .{ .major = 2, .minor = 2, .patch = 5 };897 var max_ver: std.SemanticVersion = .{ .major = 2, .minor = 2, .patch = 5 };
1018 while (it.next()) |s| {898 var offset: u64 = 0;
1019 if (mem.startsWith(u8, s, "GLIBC_2.")) {899 try file_reader.seekTo(dynstr.offset);
1020 const chopped = s["GLIBC_".len..];900 while (offset < dynstr.size) {
1021 const ver = Target.Query.parseVersion(chopped) catch |err| switch (err) {901 if (file_reader.interface.takeSentinel(0)) |s| {
1022 error.Overflow => return error.InvalidGnuLibCVersion,902 if (mem.startsWith(u8, s, "GLIBC_2.")) {
1023 error.InvalidVersion => return error.InvalidGnuLibCVersion,903 const chopped = s["GLIBC_".len..];
1024 };904 const ver = Target.Query.parseVersion(chopped) catch |err| switch (err) {
1025 switch (ver.order(max_ver)) {905 error.Overflow => return error.InvalidGnuLibCVersion,
1026 .gt => max_ver = ver,906 error.InvalidVersion => return error.InvalidGnuLibCVersion,
1027 .lt, .eq => continue,907 };
908 switch (ver.order(max_ver)) {
909 .gt => max_ver = ver,
910 .lt, .eq => continue,
911 }
1028 }912 }
913 offset += s.len + 1;
914 } else |err| switch (err) {
915 error.EndOfStream, error.StreamTooLong => break,
916 error.ReadFailed => |e| return e,
1029 }917 }
1030 }918 }
919
1031 return max_ver;920 return max_ver;
1032}921}
1033922
...@@ -1044,11 +933,7 @@ fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion {...@@ -1044,11 +933,7 @@ fn glibcVerFromSoFile(file: fs.File) !std.SemanticVersion {
1044/// answer to these questions, or if there is a shebang line, then it chases the referenced933/// answer to these questions, or if there is a shebang line, then it chases the referenced
1045/// file recursively. If that does not provide the answer, then the function falls back to934/// file recursively. If that does not provide the answer, then the function falls back to
1046/// defaults.935/// defaults.
1047fn detectAbiAndDynamicLinker(936fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Target.Query) !Target {
1048 cpu: Target.Cpu,
1049 os: Target.Os,
1050 query: Target.Query,
1051) DetectError!Target {
1052 const native_target_has_ld = comptime Target.DynamicLinker.kind(builtin.os.tag) != .none;937 const native_target_has_ld = comptime Target.DynamicLinker.kind(builtin.os.tag) != .none;
1053 const is_linux = builtin.target.os.tag == .linux;938 const is_linux = builtin.target.os.tag == .linux;
1054 const is_illumos = builtin.target.os.tag == .illumos;939 const is_illumos = builtin.target.os.tag == .illumos;
...@@ -1111,49 +996,49 @@ fn detectAbiAndDynamicLinker(...@@ -1111,49 +996,49 @@ fn detectAbiAndDynamicLinker(
1111996
1112 const ld_info_list = ld_info_list_buffer[0..ld_info_list_len];997 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
1114 // Best case scenario: the executable is dynamically linked, and we can iterate1015 // Best case scenario: the executable is dynamically linked, and we can iterate
1115 // over our own shared objects and find a dynamic linker.1016 // over our own shared objects and find a dynamic linker.
1116 const elf_file = elf_file: {1017 const header = elf_file: {
1117 // This block looks for a shebang line in /usr/bin/env,1018 // This block looks for a shebang line in "/usr/bin/env". If it finds
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,1019 // one, then instead of using "/usr/bin/env" as the ELF file to examine,
1119 // doing the same logic recursively in case it finds another shebang line.1020 // it uses the file it references instead, doing the same logic
1021 // recursively in case it finds another shebang line.
11201022
1121 var file_name: []const u8 = switch (os.tag) {1023 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 a1024 // Since /usr/bin/env is hard-coded into the shebang line of many
1123 // reasonably reliable path to start with.1025 // portable scripts, it's a reasonably reliable path to start with.
1124 else => "/usr/bin/env",1026 else => "/usr/bin/env",
1125 // Haiku does not have a /usr root directory.1027 // Haiku does not have a /usr root directory.
1126 .haiku => "/bin/env",1028 .haiku => "/bin/env",
1127 };1029 };
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;
1141 while (true) {1031 while (true) {
1142 // Interpreter path can be relative on Linux, but
1143 // for simplicity we are asserting it is an absolute path.
1144 const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) {1032 const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) {
1145 error.NoSpaceLeft => unreachable,1033 error.NoSpaceLeft => return error.Unexpected,
1146 error.NameTooLong => unreachable,1034 error.NameTooLong => return error.Unexpected,
1147 error.PathAlreadyExists => unreachable,1035 error.PathAlreadyExists => return error.Unexpected,
1148 error.SharingViolation => unreachable,1036 error.SharingViolation => return error.Unexpected,
1149 error.InvalidUtf8 => unreachable, // WASI only1037 error.BadPathName => return error.Unexpected,
1150 error.InvalidWtf8 => unreachable, // Windows only1038 error.PipeBusy => return error.Unexpected,
1151 error.BadPathName => unreachable,1039 error.FileLocksNotSupported => return error.Unexpected,
1152 error.PipeBusy => unreachable,1040 error.FileBusy => return error.Unexpected, // opened without write permissions
1153 error.FileLocksNotSupported => unreachable,1041 error.AntivirusInterference => return error.Unexpected, // Windows-only error
1154 error.WouldBlock => unreachable,
1155 error.FileBusy => unreachable, // opened without write permissions
1156 error.AntivirusInterference => unreachable, // Windows-only error
11571042
1158 error.IsDir,1043 error.IsDir,
1159 error.NotDir,1044 error.NotDir,
...@@ -1164,87 +1049,71 @@ fn detectAbiAndDynamicLinker(...@@ -1164,87 +1049,71 @@ fn detectAbiAndDynamicLinker(
1164 error.NetworkNotFound,1049 error.NetworkNotFound,
1165 error.FileTooBig,1050 error.FileTooBig,
1166 error.Unexpected,1051 error.Unexpected,
1167 => |e| {1052 => return error.UnableToOpenElfFile,
1168 std.log.warn("Encountered error: {s}, falling back to default ABI and dynamic linker.", .{@errorName(e)});
1169 return defaultAbiAndDynamicLinker(cpu, os, query);
1170 },
11711053
1172 else => |e| return e,1054 else => |e| return e,
1173 };1055 };
1174 var is_elf_file = false;1056 var is_elf_file = false;
1175 defer if (is_elf_file == false) file.close();1057 defer if (!is_elf_file) file.close();
11761058
1177 // Shortest working interpreter path is "#!/i" (4)1059 file_reader = .initAdapted(file, io, &file_reader_buffer);
1178 // (interpreter is "/i", assuming all paths are absolute, like in above comment).1060 file_name = undefined; // it aliases file_reader_buffer
1179 // ELF magic number length is also 4.1061
1180 //1062 const header = elf.Header.read(&file_reader.interface) catch |hdr_err| switch (hdr_err) {
1181 // If file is shorter than that, it is definitely not ELF file1063 error.EndOfStream,
1182 // nor file with "shebang" line.1064 error.InvalidElfMagic,
1183 const min_len: usize = 4;1065 => {
11841066 const shebang_line = file_reader.interface.takeSentinel('\n') catch |err| switch (err) {
1185 const len = preadAtLeast(file, &buffer, 0, min_len) catch |err| switch (err) {1067 error.ReadFailed => return file_reader.err.?,
1186 error.UnexpectedEndOfFile,1068 // It's neither an ELF file nor file with shebang line.
1187 error.UnableToReadElfFile,1069 error.EndOfStream, error.StreamTooLong => return error.UnhelpfulFile,
1188 error.ProcessNotFound,1070 };
1189 => return defaultAbiAndDynamicLinker(cpu, os, query),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.?,
1192 };1096 };
1193 const content = buffer[0..len];1097 is_elf_file = true;
11941098 break :elf_file header;
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 }
1220 }1099 }
1221 };1100 };
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 find1103 return abiAndDynamicLinkerFromFile(&file_reader, &header, cpu, os, ld_info_list, query) catch |err| switch (err) {
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) {
1227 error.FileSystem,1104 error.FileSystem,
1228 error.SystemResources,1105 error.SystemResources,
1229 error.SymLinkLoop,1106 error.SymLinkLoop,
1230 error.ProcessFdQuotaExceeded,1107 error.ProcessFdQuotaExceeded,
1231 error.SystemFdQuotaExceeded,1108 error.SystemFdQuotaExceeded,
1232 error.ProcessNotFound,1109 error.ProcessNotFound,
1110 error.Canceled,
1233 => |e| return e,1111 => |e| return e,
12341112
1235 error.UnableToReadElfFile,1113 error.ReadFailed => return file_reader.err.?,
1236 error.InvalidElfClass,1114
1237 error.InvalidElfVersion,1115 else => |e| {
1238 error.InvalidElfEndian,1116 std.log.warn("encountered {t}; falling back to default ABI and dynamic linker", .{e});
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)});
1248 return defaultAbiAndDynamicLinker(cpu, os, query);1117 return defaultAbiAndDynamicLinker(cpu, os, query);
1249 },1118 },
1250 };1119 };
...@@ -1269,59 +1138,6 @@ const LdInfo = struct {...@@ -1269,59 +1138,6 @@ const LdInfo = struct {
1269 abi: Target.Abi,1138 abi: Target.Abi,
1270};1139};
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
1325test {1141test {
1326 _ = NativePaths;1142 _ = NativePaths;
13271143
lib/std/zig/system/linux.zig+7-5
...@@ -1,5 +1,7 @@...@@ -1,5 +1,7 @@
1const std = @import("std");
2const builtin = @import("builtin");1const builtin = @import("builtin");
2
3const std = @import("std");
4const Io = std.Io;
3const mem = std.mem;5const mem = std.mem;
4const fs = std.fs;6const fs = std.fs;
5const fmt = std.fmt;7const fmt = std.fmt;
...@@ -344,7 +346,7 @@ fn testParser(...@@ -344,7 +346,7 @@ fn testParser(
344 expected_model: *const Target.Cpu.Model,346 expected_model: *const Target.Cpu.Model,
345 input: []const u8,347 input: []const u8,
346) !void {348) !void {
347 var r: std.Io.Reader = .fixed(input);349 var r: Io.Reader = .fixed(input);
348 const result = try parser.parse(arch, &r);350 const result = try parser.parse(arch, &r);
349 try testing.expectEqual(expected_model, result.?.model);351 try testing.expectEqual(expected_model, result.?.model);
350 try testing.expect(expected_model.features.eql(result.?.features));352 try testing.expect(expected_model.features.eql(result.?.features));
...@@ -357,7 +359,7 @@ fn testParser(...@@ -357,7 +359,7 @@ fn testParser(
357// When all the lines have been analyzed the finalize method is called.359// When all the lines have been analyzed the finalize method is called.
358fn CpuinfoParser(comptime impl: anytype) type {360fn CpuinfoParser(comptime impl: anytype) type {
359 return struct {361 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 {
361 var obj: impl = .{};363 var obj: impl = .{};
362 while (try reader.takeDelimiter('\n')) |line| {364 while (try reader.takeDelimiter('\n')) |line| {
363 const colon_pos = mem.indexOfScalar(u8, line, ':') orelse continue;365 const colon_pos = mem.indexOfScalar(u8, line, ':') orelse continue;
...@@ -376,14 +378,14 @@ inline fn getAArch64CpuFeature(comptime feat_reg: []const u8) u64 {...@@ -376,14 +378,14 @@ inline fn getAArch64CpuFeature(comptime feat_reg: []const u8) u64 {
376 );378 );
377}379}
378380
379pub fn detectNativeCpuAndFeatures() ?Target.Cpu {381pub fn detectNativeCpuAndFeatures(io: Io) ?Target.Cpu {
380 var file = fs.openFileAbsolute("/proc/cpuinfo", .{}) catch |err| switch (err) {382 var file = fs.openFileAbsolute("/proc/cpuinfo", .{}) catch |err| switch (err) {
381 else => return null,383 else => return null,
382 };384 };
383 defer file.close();385 defer file.close();
384386
385 var buffer: [4096]u8 = undefined; // "flags" lines can get pretty long.387 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
388 const current_arch = builtin.cpu.arch;390 const current_arch = builtin.cpu.arch;
389 switch (current_arch) {391 switch (current_arch) {
src/Builtin.zig+1-1
...@@ -360,7 +360,7 @@ pub fn updateFileOnDisk(file: *File, comp: *Compilation) !void {...@@ -360,7 +360,7 @@ pub fn updateFileOnDisk(file: *File, comp: *Compilation) !void {
360 file.stat = .{360 file.stat = .{
361 .size = file.source.?.len,361 .size = file.source.?.len,
362 .inode = 0, // dummy value362 .inode = 0, // dummy value
363 .mtime = 0, // dummy value363 .mtime = .zero, // dummy value
364 };364 };
365}365}
366366
src/Compilation.zig+41-28
...@@ -1,7 +1,9 @@...@@ -1,7 +1,9 @@
1const Compilation = @This();1const Compilation = @This();
2const builtin = @import("builtin");
23
3const std = @import("std");4const std = @import("std");
4const builtin = @import("builtin");5const Io = std.Io;
6const Writer = std.Io.Writer;
5const fs = std.fs;7const fs = std.fs;
6const mem = std.mem;8const mem = std.mem;
7const Allocator = std.mem.Allocator;9const Allocator = std.mem.Allocator;
...@@ -12,7 +14,6 @@ const ThreadPool = std.Thread.Pool;...@@ -12,7 +14,6 @@ const ThreadPool = std.Thread.Pool;
12const WaitGroup = std.Thread.WaitGroup;14const WaitGroup = std.Thread.WaitGroup;
13const ErrorBundle = std.zig.ErrorBundle;15const ErrorBundle = std.zig.ErrorBundle;
14const fatal = std.process.fatal;16const fatal = std.process.fatal;
15const Writer = std.Io.Writer;
1617
17const Value = @import("Value.zig");18const Value = @import("Value.zig");
18const Type = @import("Type.zig");19const Type = @import("Type.zig");
...@@ -54,6 +55,7 @@ gpa: Allocator,...@@ -54,6 +55,7 @@ gpa: Allocator,
54/// Not thread-safe - lock `mutex` if potentially accessing from multiple55/// Not thread-safe - lock `mutex` if potentially accessing from multiple
55/// threads at once.56/// threads at once.
56arena: Allocator,57arena: Allocator,
58io: Io,
57/// Not every Compilation compiles .zig code! For example you could do `zig build-exe foo.o`.59/// Not every Compilation compiles .zig code! For example you could do `zig build-exe foo.o`.
58zcu: ?*Zcu,60zcu: ?*Zcu,
59/// Contains different state depending on the `CacheMode` used by this `Compilation`.61/// Contains different state depending on the `CacheMode` used by this `Compilation`.
...@@ -1076,21 +1078,22 @@ pub const CObject = struct {...@@ -1076,21 +1078,22 @@ pub const CObject = struct {
1076 diag.* = undefined;1078 diag.* = undefined;
1077 }1079 }
10781080
1079 pub fn count(diag: Diag) u32 {1081 pub fn count(diag: *const Diag) u32 {
1080 var total: u32 = 1;1082 var total: u32 = 1;
1081 for (diag.sub_diags) |sub_diag| total += sub_diag.count();1083 for (diag.sub_diags) |sub_diag| total += sub_diag.count();
1082 return total;1084 return total;
1083 }1085 }
10841086
1085 pub fn addToErrorBundle(diag: Diag, eb: *ErrorBundle.Wip, bundle: Bundle, note: *u32) !void {1087 pub fn addToErrorBundle(diag: *const Diag, io: Io, eb: *ErrorBundle.Wip, bundle: Bundle, note: *u32) !void {
1086 const err_msg = try eb.addErrorMessage(try diag.toErrorMessage(eb, bundle, 0));1088 const err_msg = try eb.addErrorMessage(try diag.toErrorMessage(io, eb, bundle, 0));
1087 eb.extra.items[note.*] = @intFromEnum(err_msg);1089 eb.extra.items[note.*] = @intFromEnum(err_msg);
1088 note.* += 1;1090 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);
1090 }1092 }
10911093
1092 pub fn toErrorMessage(1094 pub fn toErrorMessage(
1093 diag: Diag,1095 diag: *const Diag,
1096 io: Io,
1094 eb: *ErrorBundle.Wip,1097 eb: *ErrorBundle.Wip,
1095 bundle: Bundle,1098 bundle: Bundle,
1096 notes_len: u32,1099 notes_len: u32,
...@@ -1117,7 +1120,7 @@ pub const CObject = struct {...@@ -1117,7 +1120,7 @@ pub const CObject = struct {
1117 const file = fs.cwd().openFile(file_name, .{}) catch break :source_line 0;1120 const file = fs.cwd().openFile(file_name, .{}) catch break :source_line 0;
1118 defer file.close();1121 defer file.close();
1119 var buffer: [1024]u8 = undefined;1122 var buffer: [1024]u8 = undefined;
1120 var file_reader = file.reader(&buffer);1123 var file_reader = file.reader(io, &buffer);
1121 file_reader.seekTo(diag.src_loc.offset + 1 - diag.src_loc.column) catch break :source_line 0;1124 file_reader.seekTo(diag.src_loc.offset + 1 - diag.src_loc.column) catch break :source_line 0;
1122 var aw: Writer.Allocating = .init(eb.gpa);1125 var aw: Writer.Allocating = .init(eb.gpa);
1123 defer aw.deinit();1126 defer aw.deinit();
...@@ -1155,7 +1158,7 @@ pub const CObject = struct {...@@ -1155,7 +1158,7 @@ pub const CObject = struct {
1155 gpa.destroy(bundle);1158 gpa.destroy(bundle);
1156 }1159 }
11571160
1158 pub fn parse(gpa: Allocator, path: []const u8) !*Bundle {1161 pub fn parse(gpa: Allocator, io: Io, path: []const u8) !*Bundle {
1159 const BlockId = enum(u32) {1162 const BlockId = enum(u32) {
1160 Meta = 8,1163 Meta = 8,
1161 Diag,1164 Diag,
...@@ -1191,7 +1194,7 @@ pub const CObject = struct {...@@ -1191,7 +1194,7 @@ pub const CObject = struct {
1191 var buffer: [1024]u8 = undefined;1194 var buffer: [1024]u8 = undefined;
1192 const file = try fs.cwd().openFile(path, .{});1195 const file = try fs.cwd().openFile(path, .{});
1193 defer file.close();1196 defer file.close();
1194 var file_reader = file.reader(&buffer);1197 var file_reader = file.reader(io, &buffer);
1195 var bc = std.zig.llvm.BitcodeReader.init(gpa, .{ .reader = &file_reader.interface });1198 var bc = std.zig.llvm.BitcodeReader.init(gpa, .{ .reader = &file_reader.interface });
1196 defer bc.deinit();1199 defer bc.deinit();
11971200
...@@ -1305,14 +1308,14 @@ pub const CObject = struct {...@@ -1305,14 +1308,14 @@ pub const CObject = struct {
1305 return bundle;1308 return bundle;
1306 }1309 }
13071310
1308 pub fn addToErrorBundle(bundle: Bundle, eb: *ErrorBundle.Wip) !void {1311 pub fn addToErrorBundle(bundle: Bundle, io: Io, eb: *ErrorBundle.Wip) !void {
1309 for (bundle.diags) |diag| {1312 for (bundle.diags) |diag| {
1310 const notes_len = diag.count() - 1;1313 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));
1312 if (notes_len > 0) {1315 if (notes_len > 0) {
1313 var note = try eb.reserveNotes(notes_len);1316 var note = try eb.reserveNotes(notes_len);
1314 for (diag.sub_diags) |sub_diag|1317 for (diag.sub_diags) |sub_diag|
1315 try sub_diag.addToErrorBundle(eb, bundle, &note);1318 try sub_diag.addToErrorBundle(io, eb, bundle, &note);
1316 }1319 }
1317 }1320 }
1318 }1321 }
...@@ -1904,7 +1907,7 @@ pub const CreateDiagnostic = union(enum) {...@@ -1904,7 +1907,7 @@ pub const CreateDiagnostic = union(enum) {
1904 return error.CreateFail;1907 return error.CreateFail;
1905 }1908 }
1906};1909};
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{
1908 OutOfMemory,1911 OutOfMemory,
1909 Unexpected,1912 Unexpected,
1910 CurrentWorkingDirectoryUnlinked,1913 CurrentWorkingDirectoryUnlinked,
...@@ -2112,6 +2115,7 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options...@@ -2112,6 +2115,7 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options
2112 const cache = try arena.create(Cache);2115 const cache = try arena.create(Cache);
2113 cache.* = .{2116 cache.* = .{
2114 .gpa = gpa,2117 .gpa = gpa,
2118 .io = io,
2115 .manifest_dir = options.dirs.local_cache.handle.makeOpenPath("h", .{}) catch |err| {2119 .manifest_dir = options.dirs.local_cache.handle.makeOpenPath("h", .{}) catch |err| {
2116 return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = "h", .err = err } });2120 return diag.fail(.{ .create_cache_path = .{ .which = .local, .sub = "h", .err = err } });
2117 },2121 },
...@@ -2230,6 +2234,7 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options...@@ -2230,6 +2234,7 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options
2230 comp.* = .{2234 comp.* = .{
2231 .gpa = gpa,2235 .gpa = gpa,
2232 .arena = arena,2236 .arena = arena,
2237 .io = io,
2233 .zcu = opt_zcu,2238 .zcu = opt_zcu,
2234 .cache_use = undefined, // populated below2239 .cache_use = undefined, // populated below
2235 .bin_file = null, // populated below if necessary2240 .bin_file = null, // populated below if necessary
...@@ -3917,13 +3922,14 @@ fn addBuf(list: *std.array_list.Managed([]const u8), buf: []const u8) void {...@@ -3917,13 +3922,14 @@ fn addBuf(list: *std.array_list.Managed([]const u8), buf: []const u8) void {
3917/// This function is temporally single-threaded.3922/// This function is temporally single-threaded.
3918pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle {3923pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle {
3919 const gpa = comp.gpa;3924 const gpa = comp.gpa;
3925 const io = comp.io;
39203926
3921 var bundle: ErrorBundle.Wip = undefined;3927 var bundle: ErrorBundle.Wip = undefined;
3922 try bundle.init(gpa);3928 try bundle.init(gpa);
3923 defer bundle.deinit();3929 defer bundle.deinit();
39243930
3925 for (comp.failed_c_objects.values()) |diag_bundle| {3931 for (comp.failed_c_objects.values()) |diag_bundle| {
3926 try diag_bundle.addToErrorBundle(&bundle);3932 try diag_bundle.addToErrorBundle(io, &bundle);
3927 }3933 }
39283934
3929 for (comp.failed_win32_resources.values()) |error_bundle| {3935 for (comp.failed_win32_resources.values()) |error_bundle| {
...@@ -5308,6 +5314,7 @@ fn docsCopyModule(...@@ -5308,6 +5314,7 @@ fn docsCopyModule(
5308 name: []const u8,5314 name: []const u8,
5309 tar_file_writer: *fs.File.Writer,5315 tar_file_writer: *fs.File.Writer,
5310) !void {5316) !void {
5317 const io = comp.io;
5311 const root = module.root;5318 const root = module.root;
5312 var mod_dir = d: {5319 var mod_dir = d: {
5313 const root_dir, const sub_path = root.openInfo(comp.dirs);5320 const root_dir, const sub_path = root.openInfo(comp.dirs);
...@@ -5341,9 +5348,9 @@ fn docsCopyModule(...@@ -5341,9 +5348,9 @@ fn docsCopyModule(
5341 };5348 };
5342 defer file.close();5349 defer file.close();
5343 const stat = try file.stat();5350 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| {
5347 return comp.lockAndSetMiscFailure(.docs_copy, "unable to archive {f}{s}: {t}", .{5354 return comp.lockAndSetMiscFailure(.docs_copy, "unable to archive {f}{s}: {t}", .{
5348 root.fmt(comp), entry.path, err,5355 root.fmt(comp), entry.path, err,
5349 });5356 });
...@@ -5363,6 +5370,7 @@ fn workerDocsWasm(comp: *Compilation, parent_prog_node: std.Progress.Node) void...@@ -5363,6 +5370,7 @@ fn workerDocsWasm(comp: *Compilation, parent_prog_node: std.Progress.Node) void
53635370
5364fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubUpdateError!void {5371fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubUpdateError!void {
5365 const gpa = comp.gpa;5372 const gpa = comp.gpa;
5373 const io = comp.io;
53665374
5367 var arena_allocator = std.heap.ArenaAllocator.init(gpa);5375 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
5368 defer arena_allocator.deinit();5376 defer arena_allocator.deinit();
...@@ -5371,7 +5379,7 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU...@@ -5371,7 +5379,7 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU
5371 const optimize_mode = std.builtin.OptimizeMode.ReleaseSmall;5379 const optimize_mode = std.builtin.OptimizeMode.ReleaseSmall;
5372 const output_mode = std.builtin.OutputMode.Exe;5380 const output_mode = std.builtin.OutputMode.Exe;
5373 const resolved_target: Package.Module.ResolvedTarget = .{5381 const resolved_target: Package.Module.ResolvedTarget = .{
5374 .result = std.zig.system.resolveTargetQuery(.{5382 .result = std.zig.system.resolveTargetQuery(io, .{
5375 .cpu_arch = .wasm32,5383 .cpu_arch = .wasm32,
5376 .os_tag = .freestanding,5384 .os_tag = .freestanding,
5377 .cpu_features_add = std.Target.wasm.featureSet(&.{5385 .cpu_features_add = std.Target.wasm.featureSet(&.{
...@@ -5447,7 +5455,7 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU...@@ -5447,7 +5455,7 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) SubU
5447 try root_mod.deps.put(arena, "Walk", walk_mod);5455 try root_mod.deps.put(arena, "Walk", walk_mod);
54485456
5449 var sub_create_diag: CreateDiagnostic = undefined;5457 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, .{
5451 .dirs = dirs,5459 .dirs = dirs,
5452 .self_exe_path = comp.self_exe_path,5460 .self_exe_path = comp.self_exe_path,
5453 .config = config,5461 .config = config,
...@@ -5665,6 +5673,8 @@ pub fn translateC(...@@ -5665,6 +5673,8 @@ pub fn translateC(
5665) !CImportResult {5673) !CImportResult {
5666 dev.check(.translate_c_command);5674 dev.check(.translate_c_command);
56675675
5676 const gpa = comp.gpa;
5677 const io = comp.io;
5668 const tmp_basename = std.fmt.hex(std.crypto.random.int(u64));5678 const tmp_basename = std.fmt.hex(std.crypto.random.int(u64));
5669 const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ tmp_basename;5679 const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ tmp_basename;
5670 const cache_dir = comp.dirs.local_cache.handle;5680 const cache_dir = comp.dirs.local_cache.handle;
...@@ -5704,9 +5714,9 @@ pub fn translateC(...@@ -5704,9 +5714,9 @@ pub fn translateC(
57045714
5705 const mcpu = mcpu: {5715 const mcpu = mcpu: {
5706 var buf: std.ArrayListUnmanaged(u8) = .empty;5716 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
5711 // TODO better serialization https://github.com/ziglang/zig/issues/45845721 // TODO better serialization https://github.com/ziglang/zig/issues/4584
5712 const all_features_list = target.cpu.arch.allFeaturesList();5722 const all_features_list = target.cpu.arch.allFeaturesList();
...@@ -5716,7 +5726,7 @@ pub fn translateC(...@@ -5716,7 +5726,7 @@ pub fn translateC(
5716 const is_enabled = target.cpu.features.isEnabled(index);5726 const is_enabled = target.cpu.features.isEnabled(index);
57175727
5718 const plus_or_minus = "-+"[@intFromBool(is_enabled)];5728 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 });
5720 }5730 }
5721 break :mcpu try buf.toOwnedSlice(arena);5731 break :mcpu try buf.toOwnedSlice(arena);
5722 };5732 };
...@@ -5729,7 +5739,7 @@ pub fn translateC(...@@ -5729,7 +5739,7 @@ pub fn translateC(
5729 }5739 }
57305740
5731 var stdout: []u8 = undefined;5741 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
5734 if (out_dep_path) |dep_file_path| add_deps: {5744 if (out_dep_path) |dep_file_path| add_deps: {
5735 if (comp.verbose_cimport) log.info("processing dep file at {s}", .{dep_file_path});5745 if (comp.verbose_cimport) log.info("processing dep file at {s}", .{dep_file_path});
...@@ -5765,7 +5775,7 @@ pub fn translateC(...@@ -5765,7 +5775,7 @@ pub fn translateC(
5765 fatal("unable to read {}-byte translate-c message body: {s}", .{ header.bytes_len, @errorName(err) });5775 fatal("unable to read {}-byte translate-c message body: {s}", .{ header.bytes_len, @errorName(err) });
5766 switch (header.tag) {5776 switch (header.tag) {
5767 .error_bundle => {5777 .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);
5769 return .{5779 return .{
5770 .digest = undefined,5780 .digest = undefined,
5771 .cache_hit = false,5781 .cache_hit = false,
...@@ -6152,6 +6162,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr...@@ -6152,6 +6162,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
6152 log.debug("updating C object: {s}", .{c_object.src.src_path});6162 log.debug("updating C object: {s}", .{c_object.src.src_path});
61536163
6154 const gpa = comp.gpa;6164 const gpa = comp.gpa;
6165 const io = comp.io;
61556166
6156 if (c_object.clearStatus(gpa)) {6167 if (c_object.clearStatus(gpa)) {
6157 // There was previous failure.6168 // There was previous failure.
...@@ -6351,7 +6362,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr...@@ -6351,7 +6362,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
63516362
6352 try child.spawn();6363 try child.spawn();
63536364
6354 var stderr_reader = child.stderr.?.readerStreaming(&.{});6365 var stderr_reader = child.stderr.?.readerStreaming(io, &.{});
6355 const stderr = try stderr_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32)));6366 const stderr = try stderr_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32)));
63566367
6357 const term = child.wait() catch |err| {6368 const term = child.wait() catch |err| {
...@@ -6360,7 +6371,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr...@@ -6360,7 +6371,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
63606371
6361 switch (term) {6372 switch (term) {
6362 .Exited => |code| if (code != 0) if (out_diag_path) |diag_file_path| {6373 .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| {
6364 log.err("{}: failed to parse clang diagnostics: {s}", .{ err, stderr });6375 log.err("{}: failed to parse clang diagnostics: {s}", .{ err, stderr });
6365 return comp.failCObj(c_object, "clang exited with code {d}", .{code});6376 return comp.failCObj(c_object, "clang exited with code {d}", .{code});
6366 };6377 };
...@@ -7805,6 +7816,7 @@ fn buildOutputFromZig(...@@ -7805,6 +7816,7 @@ fn buildOutputFromZig(
7805 defer tracy_trace.end();7816 defer tracy_trace.end();
78067817
7807 const gpa = comp.gpa;7818 const gpa = comp.gpa;
7819 const io = comp.io;
7808 var arena_allocator = std.heap.ArenaAllocator.init(gpa);7820 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
7809 defer arena_allocator.deinit();7821 defer arena_allocator.deinit();
7810 const arena = arena_allocator.allocator();7822 const arena = arena_allocator.allocator();
...@@ -7878,7 +7890,7 @@ fn buildOutputFromZig(...@@ -7878,7 +7890,7 @@ fn buildOutputFromZig(
7878 };7890 };
78797891
7880 var sub_create_diag: CreateDiagnostic = undefined;7892 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, .{
7882 .dirs = comp.dirs.withoutLocalCache(),7894 .dirs = comp.dirs.withoutLocalCache(),
7883 .cache_mode = .whole,7895 .cache_mode = .whole,
7884 .parent_whole_cache = parent_whole_cache,7896 .parent_whole_cache = parent_whole_cache,
...@@ -7946,6 +7958,7 @@ pub fn build_crt_file(...@@ -7946,6 +7958,7 @@ pub fn build_crt_file(
7946 defer tracy_trace.end();7958 defer tracy_trace.end();
79477959
7948 const gpa = comp.gpa;7960 const gpa = comp.gpa;
7961 const io = comp.io;
7949 var arena_allocator = std.heap.ArenaAllocator.init(gpa);7962 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
7950 defer arena_allocator.deinit();7963 defer arena_allocator.deinit();
7951 const arena = arena_allocator.allocator();7964 const arena = arena_allocator.allocator();
...@@ -8014,7 +8027,7 @@ pub fn build_crt_file(...@@ -8014,7 +8027,7 @@ pub fn build_crt_file(
8014 }8027 }
80158028
8016 var sub_create_diag: CreateDiagnostic = undefined;8029 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, .{
8018 .dirs = comp.dirs.withoutLocalCache(),8031 .dirs = comp.dirs.withoutLocalCache(),
8019 .self_exe_path = comp.self_exe_path,8032 .self_exe_path = comp.self_exe_path,
8020 .cache_mode = .whole,8033 .cache_mode = .whole,
src/IncrementalDebugServer.zig+13-10
...@@ -44,22 +44,24 @@ pub fn spawn(ids: *IncrementalDebugServer) void {...@@ -44,22 +44,24 @@ pub fn spawn(ids: *IncrementalDebugServer) void {
44}44}
45fn runThread(ids: *IncrementalDebugServer) void {45fn runThread(ids: *IncrementalDebugServer) void {
46 const gpa = ids.zcu.gpa;46 const gpa = ids.zcu.gpa;
47 const io = ids.zcu.comp.io;
4748
48 var cmd_buf: [1024]u8 = undefined;49 var cmd_buf: [1024]u8 = undefined;
49 var text_out: std.ArrayListUnmanaged(u8) = .empty;50 var text_out: std.ArrayListUnmanaged(u8) = .empty;
50 defer text_out.deinit(gpa);51 defer text_out.deinit(gpa);
5152
52 const addr = std.net.Address.parseIp6("::", port) catch unreachable;53 const addr: std.Io.net.IpAddress = .{ .ip6 = .loopback(port) };
53 var server = addr.listen(.{}) catch @panic("IncrementalDebugServer: failed to listen");54 var server = addr.listen(io, .{}) catch @panic("IncrementalDebugServer: failed to listen");
54 defer server.deinit();55 defer server.deinit(io);
55 const conn = server.accept() catch @panic("IncrementalDebugServer: failed to accept");56 var stream = server.accept(io) catch @panic("IncrementalDebugServer: failed to accept");
56 defer conn.stream.close();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
60 while (ids.running.load(.monotonic)) {62 while (ids.running.load(.monotonic)) {
61 conn.stream.writeAll("zig> ") catch @panic("IncrementalDebugServer: failed to write");63 stream_writer.interface.writeAll("zig> ") catch @panic("IncrementalDebugServer: failed to write");
62 const untrimmed = stream_reader.interface().takeSentinel('\n') catch |err| switch (err) {64 const untrimmed = stream_reader.interface.takeSentinel('\n') catch |err| switch (err) {
63 error.EndOfStream => break,65 error.EndOfStream => break,
64 else => @panic("IncrementalDebugServer: failed to read command"),66 else => @panic("IncrementalDebugServer: failed to read command"),
65 };67 };
...@@ -72,7 +74,7 @@ fn runThread(ids: *IncrementalDebugServer) void {...@@ -72,7 +74,7 @@ fn runThread(ids: *IncrementalDebugServer) void {
72 text_out.clearRetainingCapacity();74 text_out.clearRetainingCapacity();
73 {75 {
74 if (!ids.mutex.tryLock()) {76 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");
76 ids.mutex.lock();78 ids.mutex.lock();
77 }79 }
78 defer ids.mutex.unlock();80 defer ids.mutex.unlock();
...@@ -81,7 +83,7 @@ fn runThread(ids: *IncrementalDebugServer) void {...@@ -81,7 +83,7 @@ fn runThread(ids: *IncrementalDebugServer) void {
81 handleCommand(ids.zcu, &allocating.writer, cmd, arg) catch @panic("IncrementalDebugServer: out of memory");83 handleCommand(ids.zcu, &allocating.writer, cmd, arg) catch @panic("IncrementalDebugServer: out of memory");
82 }84 }
83 text_out.append(gpa, '\n') catch @panic("IncrementalDebugServer: out of memory");85 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");
85 }87 }
86 std.debug.print("closing incremental debug server\n", .{});88 std.debug.print("closing incremental debug server\n", .{});
87}89}
...@@ -373,6 +375,7 @@ fn printType(ty: Type, zcu: *const Zcu, w: anytype) !void {...@@ -373,6 +375,7 @@ fn printType(ty: Type, zcu: *const Zcu, w: anytype) !void {
373}375}
374376
375const std = @import("std");377const std = @import("std");
378const Io = std.Io;
376const Allocator = std.mem.Allocator;379const Allocator = std.mem.Allocator;
377380
378const Compilation = @import("Compilation.zig");381const Compilation = @import("Compilation.zig");
src/Package/Fetch.zig+40-20
...@@ -26,9 +26,13 @@...@@ -26,9 +26,13 @@
26//!26//!
27//! All of this must be done with only referring to the state inside this struct27//! All of this must be done with only referring to the state inside this struct
28//! because this work will be done in a dedicated thread.28//! because this work will be done in a dedicated thread.
29const Fetch = @This();
2930
30const builtin = @import("builtin");31const builtin = @import("builtin");
32const native_os = builtin.os.tag;
33
31const std = @import("std");34const std = @import("std");
35const Io = std.Io;
32const fs = std.fs;36const fs = std.fs;
33const assert = std.debug.assert;37const assert = std.debug.assert;
34const ascii = std.ascii;38const ascii = std.ascii;
...@@ -36,14 +40,13 @@ const Allocator = std.mem.Allocator;...@@ -36,14 +40,13 @@ const Allocator = std.mem.Allocator;
36const Cache = std.Build.Cache;40const Cache = std.Build.Cache;
37const ThreadPool = std.Thread.Pool;41const ThreadPool = std.Thread.Pool;
38const WaitGroup = std.Thread.WaitGroup;42const WaitGroup = std.Thread.WaitGroup;
39const Fetch = @This();
40const git = @import("Fetch/git.zig");43const git = @import("Fetch/git.zig");
41const Package = @import("../Package.zig");44const Package = @import("../Package.zig");
42const Manifest = Package.Manifest;45const Manifest = Package.Manifest;
43const ErrorBundle = std.zig.ErrorBundle;46const ErrorBundle = std.zig.ErrorBundle;
44const native_os = builtin.os.tag;
4547
46arena: std.heap.ArenaAllocator,48arena: std.heap.ArenaAllocator,
49io: Io,
47location: Location,50location: Location,
48location_tok: std.zig.Ast.TokenIndex,51location_tok: std.zig.Ast.TokenIndex,
49hash_tok: std.zig.Ast.OptionalTokenIndex,52hash_tok: std.zig.Ast.OptionalTokenIndex,
...@@ -323,6 +326,7 @@ pub const RunError = error{...@@ -323,6 +326,7 @@ pub const RunError = error{
323};326};
324327
325pub fn run(f: *Fetch) RunError!void {328pub fn run(f: *Fetch) RunError!void {
329 const io = f.io;
326 const eb = &f.error_bundle;330 const eb = &f.error_bundle;
327 const arena = f.arena.allocator();331 const arena = f.arena.allocator();
328 const gpa = f.arena.child_allocator;332 const gpa = f.arena.child_allocator;
...@@ -389,7 +393,7 @@ pub fn run(f: *Fetch) RunError!void {...@@ -389,7 +393,7 @@ pub fn run(f: *Fetch) RunError!void {
389393
390 const file_err = if (dir_err == error.NotDir) e: {394 const file_err = if (dir_err == error.NotDir) e: {
391 if (fs.cwd().openFile(path_or_url, .{})) |file| {395 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) };
393 return f.runResource(path_or_url, &resource, null);397 return f.runResource(path_or_url, &resource, null);
394 } else |err| break :e err;398 } else |err| break :e err;
395 } else dir_err;399 } else dir_err;
...@@ -484,7 +488,8 @@ fn runResource(...@@ -484,7 +488,8 @@ fn runResource(
484 resource: *Resource,488 resource: *Resource,
485 remote_hash: ?Package.Hash,489 remote_hash: ?Package.Hash,
486) RunError!void {490) RunError!void {
487 defer resource.deinit();491 const io = f.io;
492 defer resource.deinit(io);
488 const arena = f.arena.allocator();493 const arena = f.arena.allocator();
489 const eb = &f.error_bundle;494 const eb = &f.error_bundle;
490 const s = fs.path.sep_str;495 const s = fs.path.sep_str;
...@@ -697,6 +702,7 @@ fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void {...@@ -697,6 +702,7 @@ fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void {
697}702}
698703
699fn queueJobsForDeps(f: *Fetch) RunError!void {704fn queueJobsForDeps(f: *Fetch) RunError!void {
705 const io = f.io;
700 assert(f.job_queue.recursive);706 assert(f.job_queue.recursive);
701707
702 // If the package does not have a build.zig.zon file then there are no dependencies.708 // 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 {...@@ -786,6 +792,7 @@ fn queueJobsForDeps(f: *Fetch) RunError!void {
786 f.job_queue.all_fetches.appendAssumeCapacity(new_fetch);792 f.job_queue.all_fetches.appendAssumeCapacity(new_fetch);
787 }793 }
788 new_fetch.* = .{794 new_fetch.* = .{
795 .io = io,
789 .arena = std.heap.ArenaAllocator.init(gpa),796 .arena = std.heap.ArenaAllocator.init(gpa),
790 .location = location,797 .location = location,
791 .location_tok = dep.location_tok,798 .location_tok = dep.location_tok,
...@@ -897,9 +904,9 @@ const Resource = union(enum) {...@@ -897,9 +904,9 @@ const Resource = union(enum) {
897 decompress_buffer: []u8,904 decompress_buffer: []u8,
898 };905 };
899906
900 fn deinit(resource: *Resource) void {907 fn deinit(resource: *Resource, io: Io) void {
901 switch (resource.*) {908 switch (resource.*) {
902 .file => |*file_reader| file_reader.file.close(),909 .file => |*file_reader| file_reader.file.close(io),
903 .http_request => |*http_request| http_request.request.deinit(),910 .http_request => |*http_request| http_request.request.deinit(),
904 .git => |*git_resource| {911 .git => |*git_resource| {
905 git_resource.fetch_stream.deinit();912 git_resource.fetch_stream.deinit();
...@@ -909,7 +916,7 @@ const Resource = union(enum) {...@@ -909,7 +916,7 @@ const Resource = union(enum) {
909 resource.* = undefined;916 resource.* = undefined;
910 }917 }
911918
912 fn reader(resource: *Resource) *std.Io.Reader {919 fn reader(resource: *Resource) *Io.Reader {
913 return switch (resource.*) {920 return switch (resource.*) {
914 .file => |*file_reader| return &file_reader.interface,921 .file => |*file_reader| return &file_reader.interface,
915 .http_request => |*http_request| return http_request.response.readerDecompressing(922 .http_request => |*http_request| return http_request.response.readerDecompressing(
...@@ -985,6 +992,7 @@ const FileType = enum {...@@ -985,6 +992,7 @@ const FileType = enum {
985const init_resource_buffer_size = git.Packet.max_data_length;992const init_resource_buffer_size = git.Packet.max_data_length;
986993
987fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u8) RunError!void {994fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u8) RunError!void {
995 const io = f.io;
988 const arena = f.arena.allocator();996 const arena = f.arena.allocator();
989 const eb = &f.error_bundle;997 const eb = &f.error_bundle;
990998
...@@ -995,7 +1003,7 @@ fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u...@@ -995,7 +1003,7 @@ fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u
995 f.parent_package_root, path, err,1003 f.parent_package_root, path, err,
996 }));1004 }));
997 };1005 };
998 resource.* = .{ .file = file.reader(reader_buffer) };1006 resource.* = .{ .file = file.reader(io, reader_buffer) };
999 return;1007 return;
1000 }1008 }
10011009
...@@ -1242,7 +1250,7 @@ fn unpackResource(...@@ -1242,7 +1250,7 @@ fn unpackResource(
1242 }1250 }
1243}1251}
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 {
1246 const eb = &f.error_bundle;1254 const eb = &f.error_bundle;
1247 const arena = f.arena.allocator();1255 const arena = f.arena.allocator();
12481256
...@@ -1273,11 +1281,12 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: *std.Io.Reader) RunError!Un...@@ -1273,11 +1281,12 @@ fn unpackTarball(f: *Fetch, out_dir: fs.Dir, reader: *std.Io.Reader) RunError!Un
1273 return res;1281 return res;
1274}1282}
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 {
1277 // We write the entire contents to a file first because zip files1285 // We write the entire contents to a file first because zip files
1278 // must be processed back to front and they could be too large to1286 // must be processed back to front and they could be too large to
1279 // load into memory.1287 // load into memory.
12801288
1289 const io = f.io;
1281 const cache_root = f.job_queue.global_cache;1290 const cache_root = f.job_queue.global_cache;
1282 const prefix = "tmp/";1291 const prefix = "tmp/";
1283 const suffix = ".zip";1292 const suffix = ".zip";
...@@ -1319,7 +1328,7 @@ fn unzip(f: *Fetch, out_dir: fs.Dir, reader: *std.Io.Reader) error{ ReadFailed,...@@ -1319,7 +1328,7 @@ fn unzip(f: *Fetch, out_dir: fs.Dir, reader: *std.Io.Reader) error{ ReadFailed,
1319 f.location_tok,1328 f.location_tok,
1320 try eb.printString("failed writing temporary zip file: {t}", .{err}),1329 try eb.printString("failed writing temporary zip file: {t}", .{err}),
1321 );1330 );
1322 break :b zip_file_writer.moveToReader();1331 break :b zip_file_writer.moveToReader(io);
1323 };1332 };
13241333
1325 var diagnostics: std.zip.Diagnostics = .{ .allocator = f.arena.allocator() };1334 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,...@@ -1339,7 +1348,10 @@ fn unzip(f: *Fetch, out_dir: fs.Dir, reader: *std.Io.Reader) error{ ReadFailed,
1339}1348}
13401349
1341fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!UnpackResult {1350fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!UnpackResult {
1351 const io = f.io;
1342 const arena = f.arena.allocator();1352 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
1343 const gpa = f.arena.child_allocator;1355 const gpa = f.arena.child_allocator;
1344 const object_format: git.Oid.Format = resource.want_oid;1356 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...@@ -1358,7 +1370,7 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!U
1358 const fetch_reader = &resource.fetch_stream.reader;1370 const fetch_reader = &resource.fetch_stream.reader;
1359 _ = try fetch_reader.streamRemaining(&pack_file_writer.interface);1371 _ = try fetch_reader.streamRemaining(&pack_file_writer.interface);
1360 try pack_file_writer.interface.flush();1372 try pack_file_writer.interface.flush();
1361 break :b pack_file_writer.moveToReader();1373 break :b pack_file_writer.moveToReader(io);
1362 };1374 };
13631375
1364 var index_file = try pack_dir.createFile("pkg.idx", .{ .read = true });1376 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...@@ -1372,7 +1384,7 @@ fn unpackGitPack(f: *Fetch, out_dir: fs.Dir, resource: *Resource.Git) anyerror!U
1372 }1384 }
13731385
1374 {1386 {
1375 var index_file_reader = index_file.reader(&index_file_buffer);1387 var index_file_reader = index_file.reader(io, &index_file_buffer);
1376 const checkout_prog_node = f.prog_node.start("Checkout", 0);1388 const checkout_prog_node = f.prog_node.start("Checkout", 0);
1377 defer checkout_prog_node.end();1389 defer checkout_prog_node.end();
1378 var repository: git.Repository = undefined;1390 var repository: git.Repository = undefined;
...@@ -2029,7 +2041,7 @@ const UnpackResult = struct {...@@ -2029,7 +2041,7 @@ const UnpackResult = struct {
2029 // output errors to string2041 // output errors to string
2030 var errors = try fetch.error_bundle.toOwnedBundle("");2042 var errors = try fetch.error_bundle.toOwnedBundle("");
2031 defer errors.deinit(gpa);2043 defer errors.deinit(gpa);
2032 var aw: std.Io.Writer.Allocating = .init(gpa);2044 var aw: Io.Writer.Allocating = .init(gpa);
2033 defer aw.deinit();2045 defer aw.deinit();
2034 try errors.renderToWriter(.{ .ttyconf = .no_color }, &aw.writer);2046 try errors.renderToWriter(.{ .ttyconf = .no_color }, &aw.writer);
2035 try std.testing.expectEqualStrings(2047 try std.testing.expectEqualStrings(
...@@ -2057,6 +2069,7 @@ test "tarball with duplicate paths" {...@@ -2057,6 +2069,7 @@ test "tarball with duplicate paths" {
2057 //2069 //
20582070
2059 const gpa = std.testing.allocator;2071 const gpa = std.testing.allocator;
2072 const io = std.testing.io;
2060 var tmp = std.testing.tmpDir(.{});2073 var tmp = std.testing.tmpDir(.{});
2061 defer tmp.cleanup();2074 defer tmp.cleanup();
20622075
...@@ -2067,7 +2080,7 @@ test "tarball with duplicate paths" {...@@ -2067,7 +2080,7 @@ test "tarball with duplicate paths" {
20672080
2068 // Run tarball fetch, expect to fail2081 // Run tarball fetch, expect to fail
2069 var fb: TestFetchBuilder = undefined;2082 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);
2071 defer fb.deinit();2084 defer fb.deinit();
2072 try std.testing.expectError(error.FetchFailed, fetch.run());2085 try std.testing.expectError(error.FetchFailed, fetch.run());
20732086
...@@ -2089,6 +2102,7 @@ test "tarball with excluded duplicate paths" {...@@ -2089,6 +2102,7 @@ test "tarball with excluded duplicate paths" {
2089 //2102 //
20902103
2091 const gpa = std.testing.allocator;2104 const gpa = std.testing.allocator;
2105 const io = std.testing.io;
2092 var tmp = std.testing.tmpDir(.{});2106 var tmp = std.testing.tmpDir(.{});
2093 defer tmp.cleanup();2107 defer tmp.cleanup();
20942108
...@@ -2099,7 +2113,7 @@ test "tarball with excluded duplicate paths" {...@@ -2099,7 +2113,7 @@ test "tarball with excluded duplicate paths" {
20992113
2100 // Run tarball fetch, should succeed2114 // Run tarball fetch, should succeed
2101 var fb: TestFetchBuilder = undefined;2115 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);
2103 defer fb.deinit();2117 defer fb.deinit();
2104 try fetch.run();2118 try fetch.run();
21052119
...@@ -2133,6 +2147,8 @@ test "tarball without root folder" {...@@ -2133,6 +2147,8 @@ test "tarball without root folder" {
2133 //2147 //
21342148
2135 const gpa = std.testing.allocator;2149 const gpa = std.testing.allocator;
2150 const io = std.testing.io;
2151
2136 var tmp = std.testing.tmpDir(.{});2152 var tmp = std.testing.tmpDir(.{});
2137 defer tmp.cleanup();2153 defer tmp.cleanup();
21382154
...@@ -2143,7 +2159,7 @@ test "tarball without root folder" {...@@ -2143,7 +2159,7 @@ test "tarball without root folder" {
21432159
2144 // Run tarball fetch, should succeed2160 // Run tarball fetch, should succeed
2145 var fb: TestFetchBuilder = undefined;2161 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);
2147 defer fb.deinit();2163 defer fb.deinit();
2148 try fetch.run();2164 try fetch.run();
21492165
...@@ -2164,6 +2180,8 @@ test "tarball without root folder" {...@@ -2164,6 +2180,8 @@ test "tarball without root folder" {
2164test "set executable bit based on file content" {2180test "set executable bit based on file content" {
2165 if (!std.fs.has_executable_bit) return error.SkipZigTest;2181 if (!std.fs.has_executable_bit) return error.SkipZigTest;
2166 const gpa = std.testing.allocator;2182 const gpa = std.testing.allocator;
2183 const io = std.testing.io;
2184
2167 var tmp = std.testing.tmpDir(.{});2185 var tmp = std.testing.tmpDir(.{});
2168 defer tmp.cleanup();2186 defer tmp.cleanup();
21692187
...@@ -2182,7 +2200,7 @@ test "set executable bit based on file content" {...@@ -2182,7 +2200,7 @@ test "set executable bit based on file content" {
2182 // -rwxrwxr-x 17 executables/script2200 // -rwxrwxr-x 17 executables/script
21832201
2184 var fb: TestFetchBuilder = undefined;2202 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);
2186 defer fb.deinit();2204 defer fb.deinit();
21872205
2188 try fetch.run();2206 try fetch.run();
...@@ -2232,13 +2250,14 @@ const TestFetchBuilder = struct {...@@ -2232,13 +2250,14 @@ const TestFetchBuilder = struct {
2232 fn build(2250 fn build(
2233 self: *TestFetchBuilder,2251 self: *TestFetchBuilder,
2234 allocator: std.mem.Allocator,2252 allocator: std.mem.Allocator,
2253 io: Io,
2235 cache_parent_dir: std.fs.Dir,2254 cache_parent_dir: std.fs.Dir,
2236 path_or_url: []const u8,2255 path_or_url: []const u8,
2237 ) !*Fetch {2256 ) !*Fetch {
2238 const cache_dir = try cache_parent_dir.makeOpenPath("zig-global-cache", .{});2257 const cache_dir = try cache_parent_dir.makeOpenPath("zig-global-cache", .{});
22392258
2240 try self.thread_pool.init(.{ .allocator = allocator });2259 try self.thread_pool.init(.{ .allocator = allocator });
2241 self.http_client = .{ .allocator = allocator };2260 self.http_client = .{ .allocator = allocator, .io = io };
2242 self.global_cache_directory = .{ .handle = cache_dir, .path = null };2261 self.global_cache_directory = .{ .handle = cache_dir, .path = null };
22432262
2244 self.job_queue = .{2263 self.job_queue = .{
...@@ -2254,6 +2273,7 @@ const TestFetchBuilder = struct {...@@ -2254,6 +2273,7 @@ const TestFetchBuilder = struct {
22542273
2255 self.fetch = .{2274 self.fetch = .{
2256 .arena = std.heap.ArenaAllocator.init(allocator),2275 .arena = std.heap.ArenaAllocator.init(allocator),
2276 .io = io,
2257 .location = .{ .path_or_url = path_or_url },2277 .location = .{ .path_or_url = path_or_url },
2258 .location_tok = 0,2278 .location_tok = 0,
2259 .hash_tok = .none,2279 .hash_tok = .none,
...@@ -2338,7 +2358,7 @@ const TestFetchBuilder = struct {...@@ -2338,7 +2358,7 @@ const TestFetchBuilder = struct {
2338 if (notes_len > 0) {2358 if (notes_len > 0) {
2339 try std.testing.expectEqual(notes_len, em.notes_len);2359 try std.testing.expectEqual(notes_len, em.notes_len);
2340 }2360 }
2341 var aw: std.Io.Writer.Allocating = .init(std.testing.allocator);2361 var aw: Io.Writer.Allocating = .init(std.testing.allocator);
2342 defer aw.deinit();2362 defer aw.deinit();
2343 try errors.renderToWriter(.{ .ttyconf = .no_color }, &aw.writer);2363 try errors.renderToWriter(.{ .ttyconf = .no_color }, &aw.writer);
2344 try std.testing.expectEqualStrings(msg, aw.written());2364 try std.testing.expectEqualStrings(msg, aw.written());
src/Package/Fetch/git.zig+37-32
...@@ -5,6 +5,7 @@...@@ -5,6 +5,7 @@
5//! a package.5//! a package.
66
7const std = @import("std");7const std = @import("std");
8const Io = std.Io;
8const mem = std.mem;9const mem = std.mem;
9const testing = std.testing;10const testing = std.testing;
10const Allocator = mem.Allocator;11const Allocator = mem.Allocator;
...@@ -67,8 +68,8 @@ pub const Oid = union(Format) {...@@ -67,8 +68,8 @@ pub const Oid = union(Format) {
67 };68 };
6869
69 const Hashing = union(Format) {70 const Hashing = union(Format) {
70 sha1: std.Io.Writer.Hashing(Sha1),71 sha1: Io.Writer.Hashing(Sha1),
71 sha256: std.Io.Writer.Hashing(Sha256),72 sha256: Io.Writer.Hashing(Sha256),
7273
73 fn init(oid_format: Format, buffer: []u8) Hashing {74 fn init(oid_format: Format, buffer: []u8) Hashing {
74 return switch (oid_format) {75 return switch (oid_format) {
...@@ -77,7 +78,7 @@ pub const Oid = union(Format) {...@@ -77,7 +78,7 @@ pub const Oid = union(Format) {
77 };78 };
78 }79 }
7980
80 fn writer(h: *@This()) *std.Io.Writer {81 fn writer(h: *@This()) *Io.Writer {
81 return switch (h.*) {82 return switch (h.*) {
82 inline else => |*inner| &inner.writer,83 inline else => |*inner| &inner.writer,
83 };84 };
...@@ -100,7 +101,7 @@ pub const Oid = union(Format) {...@@ -100,7 +101,7 @@ pub const Oid = union(Format) {
100 };101 };
101 }102 }
102103
103 pub fn readBytes(oid_format: Format, reader: *std.Io.Reader) !Oid {104 pub fn readBytes(oid_format: Format, reader: *Io.Reader) !Oid {
104 return switch (oid_format) {105 return switch (oid_format) {
105 inline else => |tag| @unionInit(Oid, @tagName(tag), (try reader.takeArray(tag.byteLength())).*),106 inline else => |tag| @unionInit(Oid, @tagName(tag), (try reader.takeArray(tag.byteLength())).*),
106 };107 };
...@@ -146,7 +147,7 @@ pub const Oid = union(Format) {...@@ -146,7 +147,7 @@ pub const Oid = union(Format) {
146 } else error.InvalidOid;147 } else error.InvalidOid;
147 }148 }
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 {
150 try writer.print("{x}", .{oid.slice()});151 try writer.print("{x}", .{oid.slice()});
151 }152 }
152153
...@@ -594,7 +595,7 @@ pub const Packet = union(enum) {...@@ -594,7 +595,7 @@ pub const Packet = union(enum) {
594 pub const max_data_length = 65516;595 pub const max_data_length = 65516;
595596
596 /// Reads a packet in pkt-line format.597 /// Reads a packet in pkt-line format.
597 fn read(reader: *std.Io.Reader) !Packet {598 fn read(reader: *Io.Reader) !Packet {
598 const packet: Packet = try .peek(reader);599 const packet: Packet = try .peek(reader);
599 switch (packet) {600 switch (packet) {
600 .data => |data| reader.toss(data.len),601 .data => |data| reader.toss(data.len),
...@@ -605,7 +606,7 @@ pub const Packet = union(enum) {...@@ -605,7 +606,7 @@ pub const Packet = union(enum) {
605606
606 /// Consumes the header of a pkt-line packet and reads any associated data607 /// Consumes the header of a pkt-line packet and reads any associated data
607 /// into the reader's buffer, but does not consume the data.608 /// 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 {
609 const length = std.fmt.parseUnsigned(u16, try reader.take(4), 16) catch return error.InvalidPacket;610 const length = std.fmt.parseUnsigned(u16, try reader.take(4), 16) catch return error.InvalidPacket;
610 switch (length) {611 switch (length) {
611 0 => return .flush,612 0 => return .flush,
...@@ -618,7 +619,7 @@ pub const Packet = union(enum) {...@@ -618,7 +619,7 @@ pub const Packet = union(enum) {
618 }619 }
619620
620 /// Writes a packet in pkt-line format.621 /// 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 {
622 switch (packet) {623 switch (packet) {
623 .flush => try writer.writeAll("0000"),624 .flush => try writer.writeAll("0000"),
624 .delimiter => try writer.writeAll("0001"),625 .delimiter => try writer.writeAll("0001"),
...@@ -812,7 +813,7 @@ pub const Session = struct {...@@ -812,7 +813,7 @@ pub const Session = struct {
812813
813 const CapabilityIterator = struct {814 const CapabilityIterator = struct {
814 request: std.http.Client.Request,815 request: std.http.Client.Request,
815 reader: *std.Io.Reader,816 reader: *Io.Reader,
816 decompress: std.http.Decompress,817 decompress: std.http.Decompress,
817818
818 const Capability = struct {819 const Capability = struct {
...@@ -869,7 +870,7 @@ pub const Session = struct {...@@ -869,7 +870,7 @@ pub const Session = struct {
869 upload_pack_uri.query = null;870 upload_pack_uri.query = null;
870 upload_pack_uri.fragment = null;871 upload_pack_uri.fragment = null;
871872
872 var body: std.Io.Writer = .fixed(options.buffer);873 var body: Io.Writer = .fixed(options.buffer);
873 try Packet.write(.{ .data = "command=ls-refs\n" }, &body);874 try Packet.write(.{ .data = "command=ls-refs\n" }, &body);
874 if (session.supports_agent) {875 if (session.supports_agent) {
875 try Packet.write(.{ .data = agent_capability }, &body);876 try Packet.write(.{ .data = agent_capability }, &body);
...@@ -918,7 +919,7 @@ pub const Session = struct {...@@ -918,7 +919,7 @@ pub const Session = struct {
918 pub const RefIterator = struct {919 pub const RefIterator = struct {
919 format: Oid.Format,920 format: Oid.Format,
920 request: std.http.Client.Request,921 request: std.http.Client.Request,
921 reader: *std.Io.Reader,922 reader: *Io.Reader,
922 decompress: std.http.Decompress,923 decompress: std.http.Decompress,
923924
924 pub const Ref = struct {925 pub const Ref = struct {
...@@ -986,7 +987,7 @@ pub const Session = struct {...@@ -986,7 +987,7 @@ pub const Session = struct {
986 upload_pack_uri.query = null;987 upload_pack_uri.query = null;
987 upload_pack_uri.fragment = null;988 upload_pack_uri.fragment = null;
988989
989 var body: std.Io.Writer = .fixed(response_buffer);990 var body: Io.Writer = .fixed(response_buffer);
990 try Packet.write(.{ .data = "command=fetch\n" }, &body);991 try Packet.write(.{ .data = "command=fetch\n" }, &body);
991 if (session.supports_agent) {992 if (session.supports_agent) {
992 try Packet.write(.{ .data = agent_capability }, &body);993 try Packet.write(.{ .data = agent_capability }, &body);
...@@ -1068,8 +1069,8 @@ pub const Session = struct {...@@ -1068,8 +1069,8 @@ pub const Session = struct {
10681069
1069 pub const FetchStream = struct {1070 pub const FetchStream = struct {
1070 request: std.http.Client.Request,1071 request: std.http.Client.Request,
1071 input: *std.Io.Reader,1072 input: *Io.Reader,
1072 reader: std.Io.Reader,1073 reader: Io.Reader,
1073 err: ?Error = null,1074 err: ?Error = null,
1074 remaining_len: usize,1075 remaining_len: usize,
1075 decompress: std.http.Decompress,1076 decompress: std.http.Decompress,
...@@ -1094,7 +1095,7 @@ pub const Session = struct {...@@ -1094,7 +1095,7 @@ pub const Session = struct {
1094 _,1095 _,
1095 };1096 };
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 {
1098 const fs: *FetchStream = @alignCast(@fieldParentPtr("reader", r));1099 const fs: *FetchStream = @alignCast(@fieldParentPtr("reader", r));
1099 const input = fs.input;1100 const input = fs.input;
1100 if (fs.remaining_len == 0) {1101 if (fs.remaining_len == 0) {
...@@ -1139,7 +1140,7 @@ const PackHeader = struct {...@@ -1139,7 +1140,7 @@ const PackHeader = struct {
1139 const signature = "PACK";1140 const signature = "PACK";
1140 const supported_version = 2;1141 const supported_version = 2;
11411142
1142 fn read(reader: *std.Io.Reader) !PackHeader {1143 fn read(reader: *Io.Reader) !PackHeader {
1143 const actual_signature = reader.take(4) catch |e| switch (e) {1144 const actual_signature = reader.take(4) catch |e| switch (e) {
1144 error.EndOfStream => return error.InvalidHeader,1145 error.EndOfStream => return error.InvalidHeader,
1145 else => |other| return other,1146 else => |other| return other,
...@@ -1202,7 +1203,7 @@ const EntryHeader = union(Type) {...@@ -1202,7 +1203,7 @@ const EntryHeader = union(Type) {
1202 };1203 };
1203 }1204 }
12041205
1205 fn read(format: Oid.Format, reader: *std.Io.Reader) !EntryHeader {1206 fn read(format: Oid.Format, reader: *Io.Reader) !EntryHeader {
1206 const InitialByte = packed struct { len: u4, type: u3, has_next: bool };1207 const InitialByte = packed struct { len: u4, type: u3, has_next: bool };
1207 const initial: InitialByte = @bitCast(reader.takeByte() catch |e| switch (e) {1208 const initial: InitialByte = @bitCast(reader.takeByte() catch |e| switch (e) {
1208 error.EndOfStream => return error.InvalidFormat,1209 error.EndOfStream => return error.InvalidFormat,
...@@ -1231,7 +1232,7 @@ const EntryHeader = union(Type) {...@@ -1231,7 +1232,7 @@ const EntryHeader = union(Type) {
1231 }1232 }
1232};1233};
12331234
1234fn readOffsetVarInt(r: *std.Io.Reader) !u64 {1235fn readOffsetVarInt(r: *Io.Reader) !u64 {
1235 const Byte = packed struct { value: u7, has_next: bool };1236 const Byte = packed struct { value: u7, has_next: bool };
1236 var b: Byte = @bitCast(try r.takeByte());1237 var b: Byte = @bitCast(try r.takeByte());
1237 var value: u64 = b.value;1238 var value: u64 = b.value;
...@@ -1250,7 +1251,7 @@ const IndexHeader = struct {...@@ -1250,7 +1251,7 @@ const IndexHeader = struct {
1250 const supported_version = 2;1251 const supported_version = 2;
1251 const size = 4 + 4 + @sizeOf([256]u32);1252 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 {
1254 const sig = try reader.take(4);1255 const sig = try reader.take(4);
1255 if (!mem.eql(u8, sig, signature)) return error.InvalidHeader;1256 if (!mem.eql(u8, sig, signature)) return error.InvalidHeader;
1256 const version = try reader.takeInt(u32, .big);1257 const version = try reader.takeInt(u32, .big);
...@@ -1324,7 +1325,7 @@ pub fn indexPack(...@@ -1324,7 +1325,7 @@ pub fn indexPack(
1324 }1325 }
1325 @memset(fan_out_table[fan_out_index..], count);1326 @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), &.{});
1328 const writer = &index_hashed_writer.writer;1329 const writer = &index_hashed_writer.writer;
1329 try writer.writeAll(IndexHeader.signature);1330 try writer.writeAll(IndexHeader.signature);
1330 try writer.writeInt(u32, IndexHeader.supported_version, .big);1331 try writer.writeInt(u32, IndexHeader.supported_version, .big);
...@@ -1489,14 +1490,14 @@ fn resolveDeltaChain(...@@ -1489,14 +1490,14 @@ fn resolveDeltaChain(
1489 const delta_header = try EntryHeader.read(format, &pack.interface);1490 const delta_header = try EntryHeader.read(format, &pack.interface);
1490 const delta_data = try readObjectRaw(allocator, &pack.interface, delta_header.uncompressedLength());1491 const delta_data = try readObjectRaw(allocator, &pack.interface, delta_header.uncompressedLength());
1491 defer allocator.free(delta_data);1492 defer allocator.free(delta_data);
1492 var delta_reader: std.Io.Reader = .fixed(delta_data);1493 var delta_reader: Io.Reader = .fixed(delta_data);
1493 _ = try delta_reader.takeLeb128(u64); // base object size1494 _ = try delta_reader.takeLeb128(u64); // base object size
1494 const expanded_size = try delta_reader.takeLeb128(u64);1495 const expanded_size = try delta_reader.takeLeb128(u64);
14951496
1496 const expanded_alloc_size = std.math.cast(usize, expanded_size) orelse return error.ObjectTooLarge;1497 const expanded_alloc_size = std.math.cast(usize, expanded_size) orelse return error.ObjectTooLarge;
1497 const expanded_data = try allocator.alloc(u8, expanded_alloc_size);1498 const expanded_data = try allocator.alloc(u8, expanded_alloc_size);
1498 errdefer allocator.free(expanded_data);1499 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);
1500 try expandDelta(base_data, &delta_reader, &expanded_delta_stream);1501 try expandDelta(base_data, &delta_reader, &expanded_delta_stream);
1501 if (expanded_delta_stream.end != expanded_size) return error.InvalidObject;1502 if (expanded_delta_stream.end != expanded_size) return error.InvalidObject;
15021503
...@@ -1509,9 +1510,9 @@ fn resolveDeltaChain(...@@ -1509,9 +1510,9 @@ fn resolveDeltaChain(
1509/// Reads the complete contents of an object from `reader`. This function may1510/// Reads the complete contents of an object from `reader`. This function may
1510/// read more bytes than required from `reader`, so the reader position after1511/// read more bytes than required from `reader`, so the reader position after
1511/// returning is not reliable.1512/// 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 {
1513 const alloc_size = std.math.cast(usize, size) orelse return error.ObjectTooLarge;1514 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);
1515 try aw.ensureTotalCapacity(alloc_size + std.compress.flate.max_window_len);1516 try aw.ensureTotalCapacity(alloc_size + std.compress.flate.max_window_len);
1516 defer aw.deinit();1517 defer aw.deinit();
1517 var decompress: std.compress.flate.Decompress = .init(reader, .zlib, &.{});1518 var decompress: std.compress.flate.Decompress = .init(reader, .zlib, &.{});
...@@ -1523,7 +1524,7 @@ fn readObjectRaw(allocator: Allocator, reader: *std.Io.Reader, size: u64) ![]u8...@@ -1523,7 +1524,7 @@ fn readObjectRaw(allocator: Allocator, reader: *std.Io.Reader, size: u64) ![]u8
1523///1524///
1524/// The format of the delta data is documented in1525/// The format of the delta data is documented in
1525/// [pack-format](https://git-scm.com/docs/pack-format).1526/// [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 {
1527 while (true) {1528 while (true) {
1528 const inst: packed struct { value: u7, copy: bool } = @bitCast(delta_reader.takeByte() catch |e| switch (e) {1529 const inst: packed struct { value: u7, copy: bool } = @bitCast(delta_reader.takeByte() catch |e| switch (e) {
1529 error.EndOfStream => return,1530 error.EndOfStream => return,
...@@ -1576,7 +1577,7 @@ fn expandDelta(base_object: []const u8, delta_reader: *std.Io.Reader, writer: *s...@@ -1576,7 +1577,7 @@ fn expandDelta(base_object: []const u8, delta_reader: *std.Io.Reader, writer: *s
1576/// - SHA-1: `dd582c0720819ab7130b103635bd7271b9fd4feb`1577/// - SHA-1: `dd582c0720819ab7130b103635bd7271b9fd4feb`
1577/// - SHA-256: `7f444a92bd4572ee4a28b2c63059924a9ca1829138553ef3e7c41ee159afae7a`1578/// - SHA-256: `7f444a92bd4572ee4a28b2c63059924a9ca1829138553ef3e7c41ee159afae7a`
1578/// 4. `git checkout $commit`1579/// 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 {
1580 const testrepo_pack = @embedFile("git/testdata/testrepo-" ++ @tagName(format) ++ ".pack");1581 const testrepo_pack = @embedFile("git/testdata/testrepo-" ++ @tagName(format) ++ ".pack");
15811582
1582 var git_dir = testing.tmpDir(.{});1583 var git_dir = testing.tmpDir(.{});
...@@ -1586,7 +1587,7 @@ fn runRepositoryTest(comptime format: Oid.Format, head_commit: []const u8) !void...@@ -1586,7 +1587,7 @@ fn runRepositoryTest(comptime format: Oid.Format, head_commit: []const u8) !void
1586 try pack_file.writeAll(testrepo_pack);1587 try pack_file.writeAll(testrepo_pack);
15871588
1588 var pack_file_buffer: [2000]u8 = undefined;1589 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
1591 var index_file = try git_dir.dir.createFile("testrepo.idx", .{ .read = true });1592 var index_file = try git_dir.dir.createFile("testrepo.idx", .{ .read = true });
1592 defer index_file.close();1593 defer index_file.close();
...@@ -1608,7 +1609,7 @@ fn runRepositoryTest(comptime format: Oid.Format, head_commit: []const u8) !void...@@ -1608,7 +1609,7 @@ fn runRepositoryTest(comptime format: Oid.Format, head_commit: []const u8) !void
1608 try testing.expectEqualSlices(u8, testrepo_idx, index_file_data);1609 try testing.expectEqualSlices(u8, testrepo_idx, index_file_data);
1609 }1610 }
16101611
1611 var index_file_reader = index_file.reader(&index_file_buffer);1612 var index_file_reader = index_file.reader(io, &index_file_buffer);
1612 var repository: Repository = undefined;1613 var repository: Repository = undefined;
1613 try repository.init(testing.allocator, format, &pack_file_reader, &index_file_reader);1614 try repository.init(testing.allocator, format, &pack_file_reader, &index_file_reader);
1614 defer repository.deinit();1615 defer repository.deinit();
...@@ -1687,11 +1688,11 @@ fn runRepositoryTest(comptime format: Oid.Format, head_commit: []const u8) !void...@@ -1687,11 +1688,11 @@ fn runRepositoryTest(comptime format: Oid.Format, head_commit: []const u8) !void
1687const skip_checksums = true;1688const skip_checksums = true;
16881689
1689test "SHA-1 packfile indexing and checkout" {1690test "SHA-1 packfile indexing and checkout" {
1690 try runRepositoryTest(.sha1, "dd582c0720819ab7130b103635bd7271b9fd4feb");1691 try runRepositoryTest(std.testing.io, .sha1, "dd582c0720819ab7130b103635bd7271b9fd4feb");
1691}1692}
16921693
1693test "SHA-256 packfile indexing and checkout" {1694test "SHA-256 packfile indexing and checkout" {
1694 try runRepositoryTest(.sha256, "7f444a92bd4572ee4a28b2c63059924a9ca1829138553ef3e7c41ee159afae7a");1695 try runRepositoryTest(std.testing.io, .sha256, "7f444a92bd4572ee4a28b2c63059924a9ca1829138553ef3e7c41ee159afae7a");
1695}1696}
16961697
1697/// Checks out a commit of a packfile. Intended for experimenting with and1698/// Checks out a commit of a packfile. Intended for experimenting with and
...@@ -1699,6 +1700,10 @@ test "SHA-256 packfile indexing and checkout" {...@@ -1699,6 +1700,10 @@ test "SHA-256 packfile indexing and checkout" {
1699pub fn main() !void {1700pub fn main() !void {
1700 const allocator = std.heap.smp_allocator;1701 const allocator = std.heap.smp_allocator;
17011702
1703 var threaded: Io.Threaded = .init(allocator);
1704 defer threaded.deinit();
1705 const io = threaded.io();
1706
1702 const args = try std.process.argsAlloc(allocator);1707 const args = try std.process.argsAlloc(allocator);
1703 defer std.process.argsFree(allocator, args);1708 defer std.process.argsFree(allocator, args);
1704 if (args.len != 5) {1709 if (args.len != 5) {
...@@ -1710,7 +1715,7 @@ pub fn main() !void {...@@ -1710,7 +1715,7 @@ pub fn main() !void {
1710 var pack_file = try std.fs.cwd().openFile(args[2], .{});1715 var pack_file = try std.fs.cwd().openFile(args[2], .{});
1711 defer pack_file.close();1716 defer pack_file.close();
1712 var pack_file_buffer: [4096]u8 = undefined;1717 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
1715 const commit = try Oid.parse(format, args[3]);1720 const commit = try Oid.parse(format, args[3]);
1716 var worktree = try std.fs.cwd().makeOpenPath(args[4], .{});1721 var worktree = try std.fs.cwd().makeOpenPath(args[4], .{});
...@@ -1727,7 +1732,7 @@ pub fn main() !void {...@@ -1727,7 +1732,7 @@ pub fn main() !void {
1727 try indexPack(allocator, format, &pack_file_reader, &index_file_writer);1732 try indexPack(allocator, format, &pack_file_reader, &index_file_writer);
17281733
1729 std.debug.print("Starting checkout...\n", .{});1734 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);
1731 var repository: Repository = undefined;1736 var repository: Repository = undefined;
1732 try repository.init(allocator, format, &pack_file_reader, &index_file_reader);1737 try repository.init(allocator, format, &pack_file_reader, &index_file_reader);
1733 defer repository.deinit();1738 defer repository.deinit();
src/Zcu.zig+19-13
...@@ -4,9 +4,12 @@...@@ -4,9 +4,12 @@
4//!4//!
5//! Each `Compilation` has exactly one or zero `Zcu`, depending on whether5//! Each `Compilation` has exactly one or zero `Zcu`, depending on whether
6//! there is or is not any zig source code, respectively.6//! there is or is not any zig source code, respectively.
7const Zcu = @This();
8const builtin = @import("builtin");
79
8const std = @import("std");10const std = @import("std");
9const builtin = @import("builtin");11const Io = std.Io;
12const Writer = std.Io.Writer;
10const mem = std.mem;13const mem = std.mem;
11const Allocator = std.mem.Allocator;14const Allocator = std.mem.Allocator;
12const assert = std.debug.assert;15const assert = std.debug.assert;
...@@ -15,9 +18,7 @@ const BigIntConst = std.math.big.int.Const;...@@ -15,9 +18,7 @@ const BigIntConst = std.math.big.int.Const;
15const BigIntMutable = std.math.big.int.Mutable;18const BigIntMutable = std.math.big.int.Mutable;
16const Target = std.Target;19const Target = std.Target;
17const Ast = std.zig.Ast;20const Ast = std.zig.Ast;
18const Writer = std.Io.Writer;
1921
20const Zcu = @This();
21const Compilation = @import("Compilation.zig");22const Compilation = @import("Compilation.zig");
22const Cache = std.Build.Cache;23const Cache = std.Build.Cache;
23pub const Value = @import("Value.zig");24pub const Value = @import("Value.zig");
...@@ -1037,10 +1038,15 @@ pub const File = struct {...@@ -1037,10 +1038,15 @@ pub const File = struct {
1037 stat: Cache.File.Stat,1038 stat: Cache.File.Stat,
1038 };1039 };
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
1042 pub fn getSource(file: *File, zcu: *const Zcu) GetSourceError!Source {1047 pub fn getSource(file: *File, zcu: *const Zcu) GetSourceError!Source {
1043 const gpa = zcu.gpa;1048 const gpa = zcu.gpa;
1049 const io = zcu.comp.io;
10441050
1045 if (file.source) |source| return .{1051 if (file.source) |source| return .{
1046 .bytes = source,1052 .bytes = source,
...@@ -1061,7 +1067,7 @@ pub const File = struct {...@@ -1061,7 +1067,7 @@ pub const File = struct {
1061 const source = try gpa.allocSentinel(u8, @intCast(stat.size), 0);1067 const source = try gpa.allocSentinel(u8, @intCast(stat.size), 0);
1062 errdefer gpa.free(source);1068 errdefer gpa.free(source);
10631069
1064 var file_reader = f.reader(&.{});1070 var file_reader = f.reader(io, &.{});
1065 file_reader.size = stat.size;1071 file_reader.size = stat.size;
1066 file_reader.interface.readSliceAll(source) catch return file_reader.err.?;1072 file_reader.interface.readSliceAll(source) catch return file_reader.err.?;
10671073
...@@ -2859,9 +2865,9 @@ comptime {...@@ -2859,9 +2865,9 @@ comptime {
2859 }2865 }
2860}2866}
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 {
2863 var buffer: [2000]u8 = undefined;2869 var buffer: [2000]u8 = undefined;
2864 var file_reader = cache_file.reader(&buffer);2870 var file_reader = cache_file.reader(io, &buffer);
2865 return result: {2871 return result: {
2866 const header = file_reader.interface.takeStructPointer(Zir.Header) catch |err| break :result err;2872 const header = file_reader.interface.takeStructPointer(Zir.Header) catch |err| break :result err;
2867 break :result loadZirCacheBody(gpa, header.*, &file_reader.interface);2873 break :result loadZirCacheBody(gpa, header.*, &file_reader.interface);
...@@ -2871,7 +2877,7 @@ pub fn loadZirCache(gpa: Allocator, cache_file: std.fs.File) !Zir {...@@ -2871,7 +2877,7 @@ pub fn loadZirCache(gpa: Allocator, cache_file: std.fs.File) !Zir {
2871 };2877 };
2872}2878}
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 {
2875 var instructions: std.MultiArrayList(Zir.Inst) = .{};2881 var instructions: std.MultiArrayList(Zir.Inst) = .{};
2876 errdefer instructions.deinit(gpa);2882 errdefer instructions.deinit(gpa);
28772883
...@@ -2940,7 +2946,7 @@ pub fn saveZirCache(gpa: Allocator, cache_file: std.fs.File, stat: std.fs.File.S...@@ -2940,7 +2946,7 @@ pub fn saveZirCache(gpa: Allocator, cache_file: std.fs.File, stat: std.fs.File.S
29402946
2941 .stat_size = stat.size,2947 .stat_size = stat.size,
2942 .stat_inode = stat.inode,2948 .stat_inode = stat.inode,
2943 .stat_mtime = stat.mtime,2949 .stat_mtime = stat.mtime.toNanoseconds(),
2944 };2950 };
2945 var vecs = [_][]const u8{2951 var vecs = [_][]const u8{
2946 @ptrCast((&header)[0..1]),2952 @ptrCast((&header)[0..1]),
...@@ -2969,7 +2975,7 @@ pub fn saveZoirCache(cache_file: std.fs.File, stat: std.fs.File.Stat, zoir: Zoir...@@ -2969,7 +2975,7 @@ pub fn saveZoirCache(cache_file: std.fs.File, stat: std.fs.File.Stat, zoir: Zoir
29692975
2970 .stat_size = stat.size,2976 .stat_size = stat.size,
2971 .stat_inode = stat.inode,2977 .stat_inode = stat.inode,
2972 .stat_mtime = stat.mtime,2978 .stat_mtime = stat.mtime.toNanoseconds(),
2973 };2979 };
2974 var vecs = [_][]const u8{2980 var vecs = [_][]const u8{
2975 @ptrCast((&header)[0..1]),2981 @ptrCast((&header)[0..1]),
...@@ -2988,7 +2994,7 @@ pub fn saveZoirCache(cache_file: std.fs.File, stat: std.fs.File.Stat, zoir: Zoir...@@ -2988,7 +2994,7 @@ pub fn saveZoirCache(cache_file: std.fs.File, stat: std.fs.File.Stat, zoir: Zoir
2988 };2994 };
2989}2995}
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 {
2992 var zoir: Zoir = .{2998 var zoir: Zoir = .{
2993 .nodes = .empty,2999 .nodes = .empty,
2994 .extra = &.{},3000 .extra = &.{},
...@@ -4283,7 +4289,7 @@ const FormatAnalUnit = struct {...@@ -4283,7 +4289,7 @@ const FormatAnalUnit = struct {
4283 zcu: *Zcu,4289 zcu: *Zcu,
4284};4290};
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 {
4287 const zcu = data.zcu;4293 const zcu = data.zcu;
4288 const ip = &zcu.intern_pool;4294 const ip = &zcu.intern_pool;
4289 switch (data.unit.unwrap()) {4295 switch (data.unit.unwrap()) {
...@@ -4309,7 +4315,7 @@ fn formatAnalUnit(data: FormatAnalUnit, writer: *std.Io.Writer) std.Io.Writer.Er...@@ -4309,7 +4315,7 @@ fn formatAnalUnit(data: FormatAnalUnit, writer: *std.Io.Writer) std.Io.Writer.Er
43094315
4310const FormatDependee = struct { dependee: InternPool.Dependee, zcu: *Zcu };4316const 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 {
4313 const zcu = data.zcu;4319 const zcu = data.zcu;
4314 const ip = &zcu.intern_pool;4320 const ip = &zcu.intern_pool;
4315 switch (data.dependee) {4321 switch (data.dependee) {
src/Zcu/PerThread.zig+9-8
...@@ -87,6 +87,7 @@ pub fn updateFile(...@@ -87,6 +87,7 @@ pub fn updateFile(
87 const zcu = pt.zcu;87 const zcu = pt.zcu;
88 const comp = zcu.comp;88 const comp = zcu.comp;
89 const gpa = zcu.gpa;89 const gpa = zcu.gpa;
90 const io = comp.io;
9091
91 // In any case we need to examine the stat of the file to determine the course of action.92 // In any case we need to examine the stat of the file to determine the course of action.
92 var source_file = f: {93 var source_file = f: {
...@@ -127,7 +128,7 @@ pub fn updateFile(...@@ -127,7 +128,7 @@ pub fn updateFile(
127 .astgen_failure, .success => lock: {128 .astgen_failure, .success => lock: {
128 const unchanged_metadata =129 const unchanged_metadata =
129 stat.size == file.stat.size and130 stat.size == file.stat.size and
130 stat.mtime == file.stat.mtime and131 stat.mtime.nanoseconds == file.stat.mtime.nanoseconds and
131 stat.inode == file.stat.inode;132 stat.inode == file.stat.inode;
132133
133 if (unchanged_metadata) {134 if (unchanged_metadata) {
...@@ -173,8 +174,6 @@ pub fn updateFile(...@@ -173,8 +174,6 @@ pub fn updateFile(
173 .lock = lock,174 .lock = lock,
174 }) catch |err| switch (err) {175 }) catch |err| switch (err) {
175 error.NotDir => unreachable, // no dir components176 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
178 error.BadPathName => unreachable, // it's a hex encoded name177 error.BadPathName => unreachable, // it's a hex encoded name
179 error.NameTooLong => unreachable, // it's a fixed size name178 error.NameTooLong => unreachable, // it's a fixed size name
180 error.PipeBusy => unreachable, // it's not a pipe179 error.PipeBusy => unreachable, // it's not a pipe
...@@ -255,7 +254,7 @@ pub fn updateFile(...@@ -255,7 +254,7 @@ pub fn updateFile(
255254
256 const source = try gpa.allocSentinel(u8, @intCast(stat.size), 0);255 const source = try gpa.allocSentinel(u8, @intCast(stat.size), 0);
257 defer if (file.source == null) gpa.free(source);256 defer if (file.source == null) gpa.free(source);
258 var source_fr = source_file.reader(&.{});257 var source_fr = source_file.reader(io, &.{});
259 source_fr.size = stat.size;258 source_fr.size = stat.size;
260 source_fr.interface.readSliceAll(source) catch |err| switch (err) {259 source_fr.interface.readSliceAll(source) catch |err| switch (err) {
261 error.ReadFailed => return source_fr.err.?,260 error.ReadFailed => return source_fr.err.?,
...@@ -353,6 +352,7 @@ fn loadZirZoirCache(...@@ -353,6 +352,7 @@ fn loadZirZoirCache(
353 assert(file.getMode() == mode);352 assert(file.getMode() == mode);
354353
355 const gpa = zcu.gpa;354 const gpa = zcu.gpa;
355 const io = zcu.comp.io;
356356
357 const Header = switch (mode) {357 const Header = switch (mode) {
358 .zig => Zir.Header,358 .zig => Zir.Header,
...@@ -360,7 +360,7 @@ fn loadZirZoirCache(...@@ -360,7 +360,7 @@ fn loadZirZoirCache(
360 };360 };
361361
362 var buffer: [2000]u8 = undefined;362 var buffer: [2000]u8 = undefined;
363 var cache_fr = cache_file.reader(&buffer);363 var cache_fr = cache_file.reader(io, &buffer);
364 cache_fr.size = stat.size;364 cache_fr.size = stat.size;
365 const cache_br = &cache_fr.interface;365 const cache_br = &cache_fr.interface;
366366
...@@ -375,7 +375,7 @@ fn loadZirZoirCache(...@@ -375,7 +375,7 @@ fn loadZirZoirCache(
375375
376 const unchanged_metadata =376 const unchanged_metadata =
377 stat.size == header.stat_size and377 stat.size == header.stat_size and
378 stat.mtime == header.stat_mtime and378 stat.mtime.nanoseconds == header.stat_mtime and
379 stat.inode == header.stat_inode;379 stat.inode == header.stat_inode;
380380
381 if (!unchanged_metadata) {381 if (!unchanged_metadata) {
...@@ -2436,6 +2436,7 @@ fn updateEmbedFileInner(...@@ -2436,6 +2436,7 @@ fn updateEmbedFileInner(
2436 const tid = pt.tid;2436 const tid = pt.tid;
2437 const zcu = pt.zcu;2437 const zcu = pt.zcu;
2438 const gpa = zcu.gpa;2438 const gpa = zcu.gpa;
2439 const io = zcu.comp.io;
2439 const ip = &zcu.intern_pool;2440 const ip = &zcu.intern_pool;
24402441
2441 var file = f: {2442 var file = f: {
...@@ -2450,7 +2451,7 @@ fn updateEmbedFileInner(...@@ -2450,7 +2451,7 @@ fn updateEmbedFileInner(
2450 const old_stat = ef.stat;2451 const old_stat = ef.stat;
2451 const unchanged_metadata =2452 const unchanged_metadata =
2452 stat.size == old_stat.size and2453 stat.size == old_stat.size and
2453 stat.mtime == old_stat.mtime and2454 stat.mtime.nanoseconds == old_stat.mtime.nanoseconds and
2454 stat.inode == old_stat.inode;2455 stat.inode == old_stat.inode;
2455 if (unchanged_metadata) return;2456 if (unchanged_metadata) return;
2456 }2457 }
...@@ -2464,7 +2465,7 @@ fn updateEmbedFileInner(...@@ -2464,7 +2465,7 @@ fn updateEmbedFileInner(
2464 const old_len = string_bytes.mutate.len;2465 const old_len = string_bytes.mutate.len;
2465 errdefer string_bytes.shrinkRetainingCapacity(old_len);2466 errdefer string_bytes.shrinkRetainingCapacity(old_len);
2466 const bytes = (try string_bytes.addManyAsSlice(size_plus_one))[0];2467 const bytes = (try string_bytes.addManyAsSlice(size_plus_one))[0];
2467 var fr = file.reader(&.{});2468 var fr = file.reader(io, &.{});
2468 fr.size = stat.size;2469 fr.size = stat.size;
2469 fr.interface.readSliceAll(bytes[0..size]) catch |err| switch (err) {2470 fr.interface.readSliceAll(bytes[0..size]) catch |err| switch (err) {
2470 error.ReadFailed => return fr.err.?,2471 error.ReadFailed => return fr.err.?,
src/codegen/llvm.zig+8-8
...@@ -782,10 +782,10 @@ pub const Object = struct {...@@ -782,10 +782,10 @@ pub const Object = struct {
782 pub const EmitOptions = struct {782 pub const EmitOptions = struct {
783 pre_ir_path: ?[]const u8,783 pre_ir_path: ?[]const u8,
784 pre_bc_path: ?[]const u8,784 pre_bc_path: ?[]const u8,
785 bin_path: ?[*:0]const u8,785 bin_path: ?[:0]const u8,
786 asm_path: ?[*:0]const u8,786 asm_path: ?[:0]const u8,
787 post_ir_path: ?[*:0]const u8,787 post_ir_path: ?[:0]const u8,
788 post_bc_path: ?[*:0]const u8,788 post_bc_path: ?[]const u8,
789789
790 is_debug: bool,790 is_debug: bool,
791 is_small: bool,791 is_small: bool,
...@@ -989,7 +989,7 @@ pub const Object = struct {...@@ -989,7 +989,7 @@ pub const Object = struct {
989 options.post_ir_path == null and options.post_bc_path == null) return;989 options.post_ir_path == null and options.post_bc_path == null) return;
990990
991 if (options.post_bc_path) |path| {991 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|
993 return diags.fail("failed to create '{s}': {s}", .{ path, @errorName(err) });993 return diags.fail("failed to create '{s}': {s}", .{ path, @errorName(err) });
994 defer file.close();994 defer file.close();
995995
...@@ -1098,8 +1098,8 @@ pub const Object = struct {...@@ -1098,8 +1098,8 @@ pub const Object = struct {
1098 // though it's clearly not ready and produces multiple miscompilations in our std tests.1098 // though it's clearly not ready and produces multiple miscompilations in our std tests.
1099 .allow_machine_outliner = !comp.root_mod.resolved_target.result.cpu.arch.isRISCV(),1099 .allow_machine_outliner = !comp.root_mod.resolved_target.result.cpu.arch.isRISCV(),
1100 .asm_filename = null,1100 .asm_filename = null,
1101 .bin_filename = options.bin_path,1101 .bin_filename = if (options.bin_path) |x| x.ptr else null,
1102 .llvm_ir_filename = options.post_ir_path,1102 .llvm_ir_filename = if (options.post_ir_path) |x| x.ptr else null,
1103 .bitcode_filename = null,1103 .bitcode_filename = null,
11041104
1105 // `.coverage` value is only used when `.sancov` is enabled.1105 // `.coverage` value is only used when `.sancov` is enabled.
...@@ -1146,7 +1146,7 @@ pub const Object = struct {...@@ -1146,7 +1146,7 @@ pub const Object = struct {
1146 lowered_options.time_report_out = &time_report_c_str;1146 lowered_options.time_report_out = &time_report_c_str;
1147 }1147 }
11481148
1149 lowered_options.asm_filename = options.asm_path;1149 lowered_options.asm_filename = if (options.asm_path) |x| x.ptr else null;
1150 if (target_machine.emitToFile(module, &error_message, &lowered_options)) {1150 if (target_machine.emitToFile(module, &error_message, &lowered_options)) {
1151 defer llvm.disposeMessage(error_message);1151 defer llvm.disposeMessage(error_message);
1152 return diags.fail("LLVM failed to emit asm={s} bin={s} ir={s} bc={s}: {s}", .{1152 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 {...@@ -188,8 +188,8 @@ pub fn lowerToCode(emit: *Emit) Error!void {
188 .fromInterned(fn_info.return_type),188 .fromInterned(fn_info.return_type),
189 target,189 target,
190 ).?;190 ).?;
191 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.call_indirect));
192 if (is_obj) {191 if (is_obj) {
192 code.appendAssumeCapacity(@intFromEnum(std.wasm.Opcode.call_indirect));
193 try wasm.out_relocs.append(gpa, .{193 try wasm.out_relocs.append(gpa, .{
194 .offset = @intCast(code.items.len),194 .offset = @intCast(code.items.len),
195 .pointee = .{ .type_index = func_ty_index },195 .pointee = .{ .type_index = func_ty_index },
...@@ -198,7 +198,19 @@ pub fn lowerToCode(emit: *Emit) Error!void {...@@ -198,7 +198,19 @@ pub fn lowerToCode(emit: *Emit) Error!void {
198 });198 });
199 code.appendNTimesAssumeCapacity(0, 5);199 code.appendNTimesAssumeCapacity(0, 5);
200 } else {200 } 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));
202 writeUleb128(code, @intFromEnum(index));214 writeUleb128(code, @intFromEnum(index));
203 }215 }
204 writeUleb128(code, @as(u32, 0)); // table index216 writeUleb128(code, @as(u32, 0)); // table index
src/fmt.zig+12-4
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const mem = std.mem;3const mem = std.mem;
3const fs = std.fs;4const fs = std.fs;
4const process = std.process;5const process = std.process;
...@@ -34,13 +35,14 @@ const Fmt = struct {...@@ -34,13 +35,14 @@ const Fmt = struct {
34 color: Color,35 color: Color,
35 gpa: Allocator,36 gpa: Allocator,
36 arena: Allocator,37 arena: Allocator,
38 io: Io,
37 out_buffer: std.Io.Writer.Allocating,39 out_buffer: std.Io.Writer.Allocating,
38 stdout_writer: *fs.File.Writer,40 stdout_writer: *fs.File.Writer,
3941
40 const SeenMap = std.AutoHashMap(fs.File.INode, void);42 const SeenMap = std.AutoHashMap(fs.File.INode, void);
41};43};
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 {
44 var color: Color = .auto;46 var color: Color = .auto;
45 var stdin_flag = false;47 var stdin_flag = false;
46 var check_flag = false;48 var check_flag = false;
...@@ -99,7 +101,7 @@ pub fn run(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -99,7 +101,7 @@ pub fn run(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
99101
100 const stdin: fs.File = .stdin();102 const stdin: fs.File = .stdin();
101 var stdio_buffer: [1024]u8 = undefined;103 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);
103 const source_code = std.zig.readSourceFileToEndAlloc(gpa, &file_reader) catch |err| {105 const source_code = std.zig.readSourceFileToEndAlloc(gpa, &file_reader) catch |err| {
104 fatal("unable to read stdin: {}", .{err});106 fatal("unable to read stdin: {}", .{err});
105 };107 };
...@@ -165,6 +167,7 @@ pub fn run(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -165,6 +167,7 @@ pub fn run(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
165 var fmt: Fmt = .{167 var fmt: Fmt = .{
166 .gpa = gpa,168 .gpa = gpa,
167 .arena = arena,169 .arena = arena,
170 .io = io,
168 .seen = .init(gpa),171 .seen = .init(gpa),
169 .any_error = false,172 .any_error = false,
170 .check_ast = check_ast_flag,173 .check_ast = check_ast_flag,
...@@ -255,6 +258,8 @@ fn fmtPathFile(...@@ -255,6 +258,8 @@ fn fmtPathFile(
255 dir: fs.Dir,258 dir: fs.Dir,
256 sub_path: []const u8,259 sub_path: []const u8,
257) !void {260) !void {
261 const io = fmt.io;
262
258 const source_file = try dir.openFile(sub_path, .{});263 const source_file = try dir.openFile(sub_path, .{});
259 var file_closed = false;264 var file_closed = false;
260 errdefer if (!file_closed) source_file.close();265 errdefer if (!file_closed) source_file.close();
...@@ -265,7 +270,7 @@ fn fmtPathFile(...@@ -265,7 +270,7 @@ fn fmtPathFile(
265 return error.IsDir;270 return error.IsDir;
266271
267 var read_buffer: [1024]u8 = undefined;272 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);
269 file_reader.size = stat.size;274 file_reader.size = stat.size;
270275
271 const gpa = fmt.gpa;276 const gpa = fmt.gpa;
...@@ -363,5 +368,8 @@ pub fn main() !void {...@@ -363,5 +368,8 @@ pub fn main() !void {
363 var arena_instance = std.heap.ArenaAllocator.init(gpa);368 var arena_instance = std.heap.ArenaAllocator.init(gpa);
364 const arena = arena_instance.allocator();369 const arena = arena_instance.allocator();
365 const args = try process.argsAlloc(arena);370 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..]);
367}375}
src/libs/freebsd.zig+4-1
...@@ -426,6 +426,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -426,6 +426,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
426 }426 }
427427
428 const gpa = comp.gpa;428 const gpa = comp.gpa;
429 const io = comp.io;
429430
430 var arena_allocator = std.heap.ArenaAllocator.init(gpa);431 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
431 defer arena_allocator.deinit();432 defer arena_allocator.deinit();
...@@ -438,6 +439,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -438,6 +439,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
438 // Use the global cache directory.439 // Use the global cache directory.
439 var cache: Cache = .{440 var cache: Cache = .{
440 .gpa = gpa,441 .gpa = gpa,
442 .io = io,
441 .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}),443 .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}),
442 };444 };
443 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });445 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
...@@ -1017,6 +1019,7 @@ fn buildSharedLib(...@@ -1017,6 +1019,7 @@ fn buildSharedLib(
1017 const tracy = trace(@src());1019 const tracy = trace(@src());
1018 defer tracy.end();1020 defer tracy.end();
10191021
1022 const io = comp.io;
1020 const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover });1023 const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover });
1021 const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 };1024 const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 };
1022 const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?);1025 const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?);
...@@ -1071,7 +1074,7 @@ fn buildSharedLib(...@@ -1071,7 +1074,7 @@ fn buildSharedLib(
1071 const misc_task: Compilation.MiscTask = .@"freebsd libc shared object";1074 const misc_task: Compilation.MiscTask = .@"freebsd libc shared object";
10721075
1073 var sub_create_diag: Compilation.CreateDiagnostic = undefined;1076 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, .{
1075 .dirs = comp.dirs.withoutLocalCache(),1078 .dirs = comp.dirs.withoutLocalCache(),
1076 .thread_pool = comp.thread_pool,1079 .thread_pool = comp.thread_pool,
1077 .self_exe_path = comp.self_exe_path,1080 .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...@@ -666,6 +666,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
666 }666 }
667667
668 const gpa = comp.gpa;668 const gpa = comp.gpa;
669 const io = comp.io;
669670
670 var arena_allocator = std.heap.ArenaAllocator.init(gpa);671 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
671 defer arena_allocator.deinit();672 defer arena_allocator.deinit();
...@@ -677,6 +678,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -677,6 +678,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
677 // Use the global cache directory.678 // Use the global cache directory.
678 var cache: Cache = .{679 var cache: Cache = .{
679 .gpa = gpa,680 .gpa = gpa,
681 .io = io,
680 .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}),682 .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}),
681 };683 };
682 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });684 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
...@@ -1175,6 +1177,7 @@ fn buildSharedLib(...@@ -1175,6 +1177,7 @@ fn buildSharedLib(
1175 const tracy = trace(@src());1177 const tracy = trace(@src());
1176 defer tracy.end();1178 defer tracy.end();
11771179
1180 const io = comp.io;
1178 const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover });1181 const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover });
1179 const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 };1182 const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 };
1180 const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?);1183 const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?);
...@@ -1229,7 +1232,7 @@ fn buildSharedLib(...@@ -1229,7 +1232,7 @@ fn buildSharedLib(
1229 const misc_task: Compilation.MiscTask = .@"glibc shared object";1232 const misc_task: Compilation.MiscTask = .@"glibc shared object";
12301233
1231 var sub_create_diag: Compilation.CreateDiagnostic = undefined;1234 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, .{
1233 .dirs = comp.dirs.withoutLocalCache(),1236 .dirs = comp.dirs.withoutLocalCache(),
1234 .thread_pool = comp.thread_pool,1237 .thread_pool = comp.thread_pool,
1235 .self_exe_path = comp.self_exe_path,1238 .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!...@@ -120,6 +120,7 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError!
120 defer arena_allocator.deinit();120 defer arena_allocator.deinit();
121 const arena = arena_allocator.allocator();121 const arena = arena_allocator.allocator();
122122
123 const io = comp.io;
123 const root_name = "c++";124 const root_name = "c++";
124 const output_mode = .Lib;125 const output_mode = .Lib;
125 const link_mode = .static;126 const link_mode = .static;
...@@ -254,7 +255,7 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError!...@@ -254,7 +255,7 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError!
254 const misc_task: Compilation.MiscTask = .libcxx;255 const misc_task: Compilation.MiscTask = .libcxx;
255256
256 var sub_create_diag: Compilation.CreateDiagnostic = undefined;257 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, .{
258 .dirs = comp.dirs.withoutLocalCache(),259 .dirs = comp.dirs.withoutLocalCache(),
259 .self_exe_path = comp.self_exe_path,260 .self_exe_path = comp.self_exe_path,
260 .cache_mode = .whole,261 .cache_mode = .whole,
...@@ -309,6 +310,7 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr...@@ -309,6 +310,7 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
309 defer arena_allocator.deinit();310 defer arena_allocator.deinit();
310 const arena = arena_allocator.allocator();311 const arena = arena_allocator.allocator();
311312
313 const io = comp.io;
312 const root_name = "c++abi";314 const root_name = "c++abi";
313 const output_mode = .Lib;315 const output_mode = .Lib;
314 const link_mode = .static;316 const link_mode = .static;
...@@ -446,7 +448,7 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr...@@ -446,7 +448,7 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
446 const misc_task: Compilation.MiscTask = .libcxxabi;448 const misc_task: Compilation.MiscTask = .libcxxabi;
447449
448 var sub_create_diag: Compilation.CreateDiagnostic = undefined;450 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, .{
450 .dirs = comp.dirs.withoutLocalCache(),452 .dirs = comp.dirs.withoutLocalCache(),
451 .self_exe_path = comp.self_exe_path,453 .self_exe_path = comp.self_exe_path,
452 .cache_mode = .whole,454 .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...@@ -25,6 +25,7 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo
25 defer arena_allocator.deinit();25 defer arena_allocator.deinit();
26 const arena = arena_allocator.allocator();26 const arena = arena_allocator.allocator();
2727
28 const io = comp.io;
28 const target = comp.getTarget();29 const target = comp.getTarget();
29 const root_name = switch (target.os.tag) {30 const root_name = switch (target.os.tag) {
30 // On Apple platforms, we use the same name as LLVM because the31 // 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...@@ -277,7 +278,7 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo
277 const misc_task: Compilation.MiscTask = .libtsan;278 const misc_task: Compilation.MiscTask = .libtsan;
278279
279 var sub_create_diag: Compilation.CreateDiagnostic = undefined;280 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, .{
281 .dirs = comp.dirs.withoutLocalCache(),282 .dirs = comp.dirs.withoutLocalCache(),
282 .thread_pool = comp.thread_pool,283 .thread_pool = comp.thread_pool,
283 .self_exe_path = comp.self_exe_path,284 .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...@@ -26,6 +26,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
26 defer arena_allocator.deinit();26 defer arena_allocator.deinit();
27 const arena = arena_allocator.allocator();27 const arena = arena_allocator.allocator();
2828
29 const io = comp.io;
29 const output_mode = .Lib;30 const output_mode = .Lib;
30 const target = &comp.root_mod.resolved_target.result;31 const target = &comp.root_mod.resolved_target.result;
31 const unwind_tables: std.builtin.UnwindTables =32 const unwind_tables: std.builtin.UnwindTables =
...@@ -143,7 +144,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr...@@ -143,7 +144,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
143 const misc_task: Compilation.MiscTask = .libunwind;144 const misc_task: Compilation.MiscTask = .libunwind;
144145
145 var sub_create_diag: Compilation.CreateDiagnostic = undefined;146 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, .{
147 .dirs = comp.dirs.withoutLocalCache(),148 .dirs = comp.dirs.withoutLocalCache(),
148 .self_exe_path = comp.self_exe_path,149 .self_exe_path = comp.self_exe_path,
149 .config = config,150 .config = config,
src/libs/mingw.zig+3-1
...@@ -235,6 +235,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {...@@ -235,6 +235,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
235 dev.check(.build_import_lib);235 dev.check(.build_import_lib);
236236
237 const gpa = comp.gpa;237 const gpa = comp.gpa;
238 const io = comp.io;
238239
239 var arena_allocator = std.heap.ArenaAllocator.init(gpa);240 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
240 defer arena_allocator.deinit();241 defer arena_allocator.deinit();
...@@ -255,6 +256,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {...@@ -255,6 +256,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
255 // Use the global cache directory.256 // Use the global cache directory.
256 var cache: Cache = .{257 var cache: Cache = .{
257 .gpa = gpa,258 .gpa = gpa,
259 .io = io,
258 .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}),260 .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}),
259 };261 };
260 cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });262 cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
...@@ -302,7 +304,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {...@@ -302,7 +304,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
302 .output = .{ .to_list = .{ .arena = .init(gpa) } },304 .output = .{ .to_list = .{ .arena = .init(gpa) } },
303 };305 };
304 defer diagnostics.deinit();306 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());
306 defer aro_comp.deinit();308 defer aro_comp.deinit();
307309
308 aro_comp.target = target.*;310 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...@@ -26,6 +26,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro
26 var arena_allocator = std.heap.ArenaAllocator.init(gpa);26 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
27 defer arena_allocator.deinit();27 defer arena_allocator.deinit();
28 const arena = arena_allocator.allocator();28 const arena = arena_allocator.allocator();
29 const io = comp.io;
2930
30 switch (in_crt_file) {31 switch (in_crt_file) {
31 .crt1_o => {32 .crt1_o => {
...@@ -246,7 +247,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro...@@ -246,7 +247,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro
246 const misc_task: Compilation.MiscTask = .@"musl libc.so";247 const misc_task: Compilation.MiscTask = .@"musl libc.so";
247248
248 var sub_create_diag: Compilation.CreateDiagnostic = undefined;249 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, .{
250 .dirs = comp.dirs.withoutLocalCache(),251 .dirs = comp.dirs.withoutLocalCache(),
251 .self_exe_path = comp.self_exe_path,252 .self_exe_path = comp.self_exe_path,
252 .cache_mode = .whole,253 .cache_mode = .whole,
src/libs/netbsd.zig+4-1
...@@ -372,6 +372,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -372,6 +372,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
372 }372 }
373373
374 const gpa = comp.gpa;374 const gpa = comp.gpa;
375 const io = comp.io;
375376
376 var arena_allocator = std.heap.ArenaAllocator.init(gpa);377 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
377 defer arena_allocator.deinit();378 defer arena_allocator.deinit();
...@@ -383,6 +384,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye...@@ -383,6 +384,7 @@ pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anye
383 // Use the global cache directory.384 // Use the global cache directory.
384 var cache: Cache = .{385 var cache: Cache = .{
385 .gpa = gpa,386 .gpa = gpa,
387 .io = io,
386 .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}),388 .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}),
387 };389 };
388 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });390 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
...@@ -680,6 +682,7 @@ fn buildSharedLib(...@@ -680,6 +682,7 @@ fn buildSharedLib(
680 const tracy = trace(@src());682 const tracy = trace(@src());
681 defer tracy.end();683 defer tracy.end();
682684
685 const io = comp.io;
683 const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover });686 const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover });
684 const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 };687 const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 };
685 const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?);688 const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?);
...@@ -733,7 +736,7 @@ fn buildSharedLib(...@@ -733,7 +736,7 @@ fn buildSharedLib(
733 const misc_task: Compilation.MiscTask = .@"netbsd libc shared object";736 const misc_task: Compilation.MiscTask = .@"netbsd libc shared object";
734737
735 var sub_create_diag: Compilation.CreateDiagnostic = undefined;738 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, .{
737 .dirs = comp.dirs.withoutLocalCache(),740 .dirs = comp.dirs.withoutLocalCache(),
738 .thread_pool = comp.thread_pool,741 .thread_pool = comp.thread_pool,
739 .self_exe_path = comp.self_exe_path,742 .self_exe_path = comp.self_exe_path,
src/link.zig+11-20
...@@ -1,19 +1,22 @@...@@ -1,19 +1,22 @@
1const std = @import("std");
2const build_options = @import("build_options");
3const builtin = @import("builtin");1const builtin = @import("builtin");
2const build_options = @import("build_options");
3
4const std = @import("std");
5const Io = std.Io;
4const assert = std.debug.assert;6const assert = std.debug.assert;
5const fs = std.fs;7const fs = std.fs;
6const mem = std.mem;8const mem = std.mem;
7const log = std.log.scoped(.link);9const log = std.log.scoped(.link);
8const trace = @import("tracy.zig").trace;
9const wasi_libc = @import("libs/wasi_libc.zig");
10
11const Allocator = std.mem.Allocator;10const Allocator = std.mem.Allocator;
12const Cache = std.Build.Cache;11const Cache = std.Build.Cache;
13const Path = std.Build.Cache.Path;12const Path = std.Build.Cache.Path;
14const Directory = std.Build.Cache.Directory;13const Directory = std.Build.Cache.Directory;
15const Compilation = @import("Compilation.zig");14const Compilation = @import("Compilation.zig");
16const LibCInstallation = std.zig.LibCInstallation;15const LibCInstallation = std.zig.LibCInstallation;
16
17const trace = @import("tracy.zig").trace;
18const wasi_libc = @import("libs/wasi_libc.zig");
19
17const Zcu = @import("Zcu.zig");20const Zcu = @import("Zcu.zig");
18const InternPool = @import("InternPool.zig");21const InternPool = @import("InternPool.zig");
19const Type = @import("Type.zig");22const Type = @import("Type.zig");
...@@ -572,6 +575,7 @@ pub const File = struct {...@@ -572,6 +575,7 @@ pub const File = struct {
572 dev.check(.make_writable);575 dev.check(.make_writable);
573 const comp = base.comp;576 const comp = base.comp;
574 const gpa = comp.gpa;577 const gpa = comp.gpa;
578 const io = comp.io;
575 switch (base.tag) {579 switch (base.tag) {
576 .lld => assert(base.file == null),580 .lld => assert(base.file == null),
577 .elf, .macho, .wasm => {581 .elf, .macho, .wasm => {
...@@ -616,22 +620,9 @@ pub const File = struct {...@@ -616,22 +620,9 @@ pub const File = struct {
616 &coff.mf620 &coff.mf
617 else621 else
618 unreachable;622 unreachable;
619 var attempt: u5 = 0;623 mf.file = .adaptFromNewApi(try Io.Dir.openFile(base.emit.root_dir.handle.adaptToNewApi(), io, base.emit.sub_path, .{
620 mf.file = while (true) break base.emit.root_dir.handle.openFile(base.emit.sub_path, .{
621 .mode = .read_write,624 .mode = .read_write,
622 }) catch |err| switch (err) {625 }));
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 };
635 base.file = mf.file;626 base.file = mf.file;
636 try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1]));627 try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1]));
637 },628 },
src/link/Coff.zig+1-1
...@@ -610,7 +610,7 @@ fn create(...@@ -610,7 +610,7 @@ fn create(
610 .Obj => false,610 .Obj => false,
611 };611 };
612 const machine = target.toCoffMachine();612 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;
614 const major_subsystem_version = options.major_subsystem_version orelse 6;614 const major_subsystem_version = options.major_subsystem_version orelse 6;
615 const minor_subsystem_version = options.minor_subsystem_version orelse 0;615 const minor_subsystem_version = options.minor_subsystem_version orelse 0;
616 const magic: std.coff.OptionalHeader.Magic = switch (target.ptrBitWidth()) {616 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 {...@@ -1613,11 +1613,9 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {
1613 }1613 }
1614}1614}
16151615
1616fn spawnLld(1616fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !void {
1617 comp: *Compilation,1617 const io = comp.io;
1618 arena: Allocator,1618
1619 argv: []const []const u8,
1620) !void {
1621 if (comp.verbose_link) {1619 if (comp.verbose_link) {
1622 // Skip over our own name so that the LLD linker name is the first argv item.1620 // Skip over our own name so that the LLD linker name is the first argv item.
1623 Compilation.dump_argv(argv[1..]);1621 Compilation.dump_argv(argv[1..]);
...@@ -1649,7 +1647,7 @@ fn spawnLld(...@@ -1649,7 +1647,7 @@ fn spawnLld(
1649 child.stderr_behavior = .Pipe;1647 child.stderr_behavior = .Pipe;
16501648
1651 child.spawn() catch |err| break :term err;1649 child.spawn() catch |err| break :term err;
1652 var stderr_reader = child.stderr.?.readerStreaming(&.{});1650 var stderr_reader = child.stderr.?.readerStreaming(io, &.{});
1653 stderr = try stderr_reader.interface.allocRemaining(comp.gpa, .unlimited);1651 stderr = try stderr_reader.interface.allocRemaining(comp.gpa, .unlimited);
1654 break :term child.wait();1652 break :term child.wait();
1655 }) catch |first_err| term: {1653 }) catch |first_err| term: {
...@@ -1659,7 +1657,7 @@ fn spawnLld(...@@ -1659,7 +1657,7 @@ fn spawnLld(
1659 const rand_int = std.crypto.random.int(u64);1657 const rand_int = std.crypto.random.int(u64);
1660 const rsp_path = "tmp" ++ s ++ std.fmt.hex(rand_int) ++ ".rsp";1658 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, .{});
1663 defer comp.dirs.local_cache.handle.deleteFileZ(rsp_path) catch |err|1661 defer comp.dirs.local_cache.handle.deleteFileZ(rsp_path) catch |err|
1664 log.warn("failed to delete response file {s}: {s}", .{ rsp_path, @errorName(err) });1662 log.warn("failed to delete response file {s}: {s}", .{ rsp_path, @errorName(err) });
1665 {1663 {
...@@ -1699,7 +1697,7 @@ fn spawnLld(...@@ -1699,7 +1697,7 @@ fn spawnLld(
1699 rsp_child.stderr_behavior = .Pipe;1697 rsp_child.stderr_behavior = .Pipe;
17001698
1701 rsp_child.spawn() catch |err| break :err err;1699 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, &.{});
1703 stderr = try stderr_reader.interface.allocRemaining(comp.gpa, .unlimited);1701 stderr = try stderr_reader.interface.allocRemaining(comp.gpa, .unlimited);
1704 break :term rsp_child.wait() catch |err| break :err err;1702 break :term rsp_child.wait() catch |err| break :err err;
1705 }1703 }
src/link/MachO.zig+7-9
...@@ -915,7 +915,7 @@ pub fn readArMagic(file: std.fs.File, offset: usize, buffer: *[Archive.SARMAG]u8...@@ -915,7 +915,7 @@ pub fn readArMagic(file: std.fs.File, offset: usize, buffer: *[Archive.SARMAG]u8
915 return buffer[0..Archive.SARMAG];915 return buffer[0..Archive.SARMAG];
916}916}
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 {
919 const tracy = trace(@src());919 const tracy = trace(@src());
920 defer tracy.end();920 defer tracy.end();
921921
...@@ -929,17 +929,15 @@ fn addObject(self: *MachO, path: Path, handle: File.HandleIndex, offset: u64) !v...@@ -929,17 +929,15 @@ fn addObject(self: *MachO, path: Path, handle: File.HandleIndex, offset: u64) !v
929 });929 });
930 errdefer gpa.free(abs_path);930 errdefer gpa.free(abs_path);
931931
932 const mtime: u64 = mtime: {932 const file = self.getFileHandle(handle_index);
933 const file = self.getFileHandle(handle);933 const stat = try file.stat();
934 const stat = file.stat() catch break :mtime 0;934 const mtime = stat.mtime.toSeconds();
935 break :mtime @as(u64, @intCast(@divFloor(stat.mtime, 1_000_000_000)));935 const index: File.Index = @intCast(try self.files.addOne(gpa));
936 };
937 const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
938 self.files.set(index, .{ .object = .{936 self.files.set(index, .{ .object = .{
939 .offset = offset,937 .offset = offset,
940 .path = abs_path,938 .path = abs_path,
941 .file_handle = handle,939 .file_handle = handle_index,
942 .mtime = mtime,940 .mtime = @intCast(mtime),
943 .index = index,941 .index = index,
944 } });942 } });
945 try self.objects.append(gpa, index);943 try self.objects.append(gpa, index);
src/link/MappedFile.zig+8-6
...@@ -16,11 +16,13 @@ writers: std.SinglyLinkedList,...@@ -16,11 +16,13 @@ writers: std.SinglyLinkedList,
1616
17pub const growth_factor = 4;17pub const growth_factor = 4;
1818
19pub const Error = std.posix.MMapError ||19pub const Error = std.posix.MMapError || std.posix.MRemapError || std.fs.File.SetEndPosError || error{
20 std.posix.MRemapError ||20 NotFile,
21 std.fs.File.SetEndPosError ||21 SystemResources,
22 std.fs.File.CopyRangeError ||22 IsDir,
23 error{NotFile};23 Unseekable,
24 NoSpaceLeft,
25};
2426
25pub fn init(file: std.fs.File, gpa: std.mem.Allocator) !MappedFile {27pub fn init(file: std.fs.File, gpa: std.mem.Allocator) !MappedFile {
26 var mf: MappedFile = .{28 var mf: MappedFile = .{
...@@ -402,7 +404,7 @@ pub const Node = extern struct {...@@ -402,7 +404,7 @@ pub const Node = extern struct {
402404
403 const w: *Writer = @fieldParentPtr("interface", interface);405 const w: *Writer = @fieldParentPtr("interface", interface);
404 const copy_size: usize = @intCast(w.mf.copyFileRange(406 const copy_size: usize = @intCast(w.mf.copyFileRange(
405 file_reader.file,407 .adaptFromNewApi(file_reader.file),
406 file_reader.pos,408 file_reader.pos,
407 w.ni.fileLocation(w.mf, true).offset + interface.end,409 w.ni.fileLocation(w.mf, true).offset + interface.end,
408 limit.minInt(interface.unusedCapacityLen()),410 limit.minInt(interface.unusedCapacityLen()),
src/link/Wasm.zig+14-6
...@@ -3029,18 +3029,22 @@ fn openParseObjectReportingFailure(wasm: *Wasm, path: Path) void {...@@ -3029,18 +3029,22 @@ fn openParseObjectReportingFailure(wasm: *Wasm, path: Path) void {
3029fn parseObject(wasm: *Wasm, obj: link.Input.Object) !void {3029fn parseObject(wasm: *Wasm, obj: link.Input.Object) !void {
3030 log.debug("parseObject {f}", .{obj.path});3030 log.debug("parseObject {f}", .{obj.path});
3031 const gpa = wasm.base.comp.gpa;3031 const gpa = wasm.base.comp.gpa;
3032 const io = wasm.base.comp.io;
3032 const gc_sections = wasm.base.gc_sections;3033 const gc_sections = wasm.base.gc_sections;
30333034
3034 defer obj.file.close();3035 defer obj.file.close();
30353036
3037 var file_reader = obj.file.reader(io, &.{});
3038
3036 try wasm.objects.ensureUnusedCapacity(gpa, 1);3039 try wasm.objects.ensureUnusedCapacity(gpa, 1);
3037 const stat = try obj.file.stat();3040 const size = std.math.cast(usize, try file_reader.getSize()) orelse return error.FileTooBig;
3038 const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig;
30393041
3040 const file_contents = try gpa.alloc(u8, size);3042 const file_contents = try gpa.alloc(u8, size);
3041 defer gpa.free(file_contents);3043 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 };
3044 if (n != file_contents.len) return error.UnexpectedEndOfFile;3048 if (n != file_contents.len) return error.UnexpectedEndOfFile;
30453049
3046 var ss: Object.ScratchSpace = .{};3050 var ss: Object.ScratchSpace = .{};
...@@ -3053,17 +3057,21 @@ fn parseObject(wasm: *Wasm, obj: link.Input.Object) !void {...@@ -3053,17 +3057,21 @@ fn parseObject(wasm: *Wasm, obj: link.Input.Object) !void {
3053fn parseArchive(wasm: *Wasm, obj: link.Input.Object) !void {3057fn parseArchive(wasm: *Wasm, obj: link.Input.Object) !void {
3054 log.debug("parseArchive {f}", .{obj.path});3058 log.debug("parseArchive {f}", .{obj.path});
3055 const gpa = wasm.base.comp.gpa;3059 const gpa = wasm.base.comp.gpa;
3060 const io = wasm.base.comp.io;
3056 const gc_sections = wasm.base.gc_sections;3061 const gc_sections = wasm.base.gc_sections;
30573062
3058 defer obj.file.close();3063 defer obj.file.close();
30593064
3060 const stat = try obj.file.stat();3065 var file_reader = obj.file.reader(io, &.{});
3061 const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig;3066
3067 const size = std.math.cast(usize, try file_reader.getSize()) orelse return error.FileTooBig;
30623068
3063 const file_contents = try gpa.alloc(u8, size);3069 const file_contents = try gpa.alloc(u8, size);
3064 defer gpa.free(file_contents);3070 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 };
3067 if (n != file_contents.len) return error.UnexpectedEndOfFile;3075 if (n != file_contents.len) return error.UnexpectedEndOfFile;
30683076
3069 var archive = try Archive.parse(gpa, file_contents);3077 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 {...@@ -1064,9 +1064,14 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void {
1064 }1064 }
10651065
1066 // Finally, write the entire binary into the file.1066 // Finally, write the entire binary into the file.
1067 const file = wasm.base.file.?;1067 var file_writer = wasm.base.file.?.writer(&.{});
1068 try file.pwriteAll(binary_bytes.items, 0);1068 file_writer.interface.writeAll(binary_bytes.items) catch |err| switch (err) {
1069 try file.setEndPos(binary_bytes.items.len);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 };
1070}1075}
10711076
1072const VirtualAddrs = struct {1077const VirtualAddrs = struct {
src/main.zig+88-75
...@@ -1,5 +1,8 @@...@@ -1,5 +1,8 @@
1const std = @import("std");
2const builtin = @import("builtin");1const builtin = @import("builtin");
2const native_os = builtin.os.tag;
3
4const std = @import("std");
5const Io = std.Io;
3const assert = std.debug.assert;6const assert = std.debug.assert;
4const fs = std.fs;7const fs = std.fs;
5const mem = std.mem;8const mem = std.mem;
...@@ -10,7 +13,6 @@ const Color = std.zig.Color;...@@ -10,7 +13,6 @@ const Color = std.zig.Color;
10const warn = std.log.warn;13const warn = std.log.warn;
11const ThreadPool = std.Thread.Pool;14const ThreadPool = std.Thread.Pool;
12const cleanExit = std.process.cleanExit;15const cleanExit = std.process.cleanExit;
13const native_os = builtin.os.tag;
14const Cache = std.Build.Cache;16const Cache = std.Build.Cache;
15const Path = std.Build.Cache.Path;17const Path = std.Build.Cache.Path;
16const Directory = std.Build.Cache.Directory;18const Directory = std.Build.Cache.Directory;
...@@ -245,26 +247,30 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -245,26 +247,30 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
245 }247 }
246 }248 }
247249
250 var threaded: Io.Threaded = .init(gpa);
251 defer threaded.deinit();
252 const io = threaded.io();
253
248 const cmd = args[1];254 const cmd = args[1];
249 const cmd_args = args[2..];255 const cmd_args = args[2..];
250 if (mem.eql(u8, cmd, "build-exe")) {256 if (mem.eql(u8, cmd, "build-exe")) {
251 dev.check(.build_exe_command);257 dev.check(.build_exe_command);
252 return buildOutputType(gpa, arena, args, .{ .build = .Exe });258 return buildOutputType(gpa, arena, io, args, .{ .build = .Exe });
253 } else if (mem.eql(u8, cmd, "build-lib")) {259 } else if (mem.eql(u8, cmd, "build-lib")) {
254 dev.check(.build_lib_command);260 dev.check(.build_lib_command);
255 return buildOutputType(gpa, arena, args, .{ .build = .Lib });261 return buildOutputType(gpa, arena, io, args, .{ .build = .Lib });
256 } else if (mem.eql(u8, cmd, "build-obj")) {262 } else if (mem.eql(u8, cmd, "build-obj")) {
257 dev.check(.build_obj_command);263 dev.check(.build_obj_command);
258 return buildOutputType(gpa, arena, args, .{ .build = .Obj });264 return buildOutputType(gpa, arena, io, args, .{ .build = .Obj });
259 } else if (mem.eql(u8, cmd, "test")) {265 } else if (mem.eql(u8, cmd, "test")) {
260 dev.check(.test_command);266 dev.check(.test_command);
261 return buildOutputType(gpa, arena, args, .zig_test);267 return buildOutputType(gpa, arena, io, args, .zig_test);
262 } else if (mem.eql(u8, cmd, "test-obj")) {268 } else if (mem.eql(u8, cmd, "test-obj")) {
263 dev.check(.test_command);269 dev.check(.test_command);
264 return buildOutputType(gpa, arena, args, .zig_test_obj);270 return buildOutputType(gpa, arena, io, args, .zig_test_obj);
265 } else if (mem.eql(u8, cmd, "run")) {271 } else if (mem.eql(u8, cmd, "run")) {
266 dev.check(.run_command);272 dev.check(.run_command);
267 return buildOutputType(gpa, arena, args, .run);273 return buildOutputType(gpa, arena, io, args, .run);
268 } else if (mem.eql(u8, cmd, "dlltool") or274 } else if (mem.eql(u8, cmd, "dlltool") or
269 mem.eql(u8, cmd, "ranlib") or275 mem.eql(u8, cmd, "ranlib") or
270 mem.eql(u8, cmd, "lib") or276 mem.eql(u8, cmd, "lib") or
...@@ -274,7 +280,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -274,7 +280,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
274 return process.exit(try llvmArMain(arena, args));280 return process.exit(try llvmArMain(arena, args));
275 } else if (mem.eql(u8, cmd, "build")) {281 } else if (mem.eql(u8, cmd, "build")) {
276 dev.check(.build_command);282 dev.check(.build_command);
277 return cmdBuild(gpa, arena, cmd_args);283 return cmdBuild(gpa, arena, io, cmd_args);
278 } else if (mem.eql(u8, cmd, "clang") or284 } else if (mem.eql(u8, cmd, "clang") or
279 mem.eql(u8, cmd, "-cc1") or mem.eql(u8, cmd, "-cc1as"))285 mem.eql(u8, cmd, "-cc1") or mem.eql(u8, cmd, "-cc1as"))
280 {286 {
...@@ -288,16 +294,16 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -288,16 +294,16 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
288 return process.exit(try lldMain(arena, args, true));294 return process.exit(try lldMain(arena, args, true));
289 } else if (mem.eql(u8, cmd, "cc")) {295 } else if (mem.eql(u8, cmd, "cc")) {
290 dev.check(.cc_command);296 dev.check(.cc_command);
291 return buildOutputType(gpa, arena, args, .cc);297 return buildOutputType(gpa, arena, io, args, .cc);
292 } else if (mem.eql(u8, cmd, "c++")) {298 } else if (mem.eql(u8, cmd, "c++")) {
293 dev.check(.cc_command);299 dev.check(.cc_command);
294 return buildOutputType(gpa, arena, args, .cpp);300 return buildOutputType(gpa, arena, io, args, .cpp);
295 } else if (mem.eql(u8, cmd, "translate-c")) {301 } else if (mem.eql(u8, cmd, "translate-c")) {
296 dev.check(.translate_c_command);302 dev.check(.translate_c_command);
297 return buildOutputType(gpa, arena, args, .translate_c);303 return buildOutputType(gpa, arena, io, args, .translate_c);
298 } else if (mem.eql(u8, cmd, "rc")) {304 } else if (mem.eql(u8, cmd, "rc")) {
299 const use_server = cmd_args.len > 0 and std.mem.eql(u8, cmd_args[0], "--zig-integration");305 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, .{
301 .cmd_name = "resinator",307 .cmd_name = "resinator",
302 .root_src_path = "resinator/main.zig",308 .root_src_path = "resinator/main.zig",
303 .depend_on_aro = true,309 .depend_on_aro = true,
...@@ -306,22 +312,22 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -306,22 +312,22 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
306 });312 });
307 } else if (mem.eql(u8, cmd, "fmt")) {313 } else if (mem.eql(u8, cmd, "fmt")) {
308 dev.check(.fmt_command);314 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);
310 } else if (mem.eql(u8, cmd, "objcopy")) {316 } else if (mem.eql(u8, cmd, "objcopy")) {
311 return jitCmd(gpa, arena, cmd_args, .{317 return jitCmd(gpa, arena, io, cmd_args, .{
312 .cmd_name = "objcopy",318 .cmd_name = "objcopy",
313 .root_src_path = "objcopy.zig",319 .root_src_path = "objcopy.zig",
314 });320 });
315 } else if (mem.eql(u8, cmd, "fetch")) {321 } else if (mem.eql(u8, cmd, "fetch")) {
316 return cmdFetch(gpa, arena, cmd_args);322 return cmdFetch(gpa, arena, io, cmd_args);
317 } else if (mem.eql(u8, cmd, "libc")) {323 } else if (mem.eql(u8, cmd, "libc")) {
318 return jitCmd(gpa, arena, cmd_args, .{324 return jitCmd(gpa, arena, io, cmd_args, .{
319 .cmd_name = "libc",325 .cmd_name = "libc",
320 .root_src_path = "libc.zig",326 .root_src_path = "libc.zig",
321 .prepend_zig_lib_dir_path = true,327 .prepend_zig_lib_dir_path = true,
322 });328 });
323 } else if (mem.eql(u8, cmd, "std")) {329 } else if (mem.eql(u8, cmd, "std")) {
324 return jitCmd(gpa, arena, cmd_args, .{330 return jitCmd(gpa, arena, io, cmd_args, .{
325 .cmd_name = "std",331 .cmd_name = "std",
326 .root_src_path = "std-docs.zig",332 .root_src_path = "std-docs.zig",
327 .prepend_zig_lib_dir_path = true,333 .prepend_zig_lib_dir_path = true,
...@@ -332,7 +338,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -332,7 +338,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
332 return cmdInit(gpa, arena, cmd_args);338 return cmdInit(gpa, arena, cmd_args);
333 } else if (mem.eql(u8, cmd, "targets")) {339 } else if (mem.eql(u8, cmd, "targets")) {
334 dev.check(.targets_command);340 dev.check(.targets_command);
335 const host = std.zig.resolveTargetQueryOrFatal(.{});341 const host = std.zig.resolveTargetQueryOrFatal(io, .{});
336 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);342 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
337 try @import("print_targets.zig").cmdTargets(arena, cmd_args, &stdout_writer.interface, &host);343 try @import("print_targets.zig").cmdTargets(arena, cmd_args, &stdout_writer.interface, &host);
338 return stdout_writer.interface.flush();344 return stdout_writer.interface.flush();
...@@ -342,16 +348,18 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -342,16 +348,18 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
342 return;348 return;
343 } else if (mem.eql(u8, cmd, "env")) {349 } else if (mem.eql(u8, cmd, "env")) {
344 dev.check(.env_command);350 dev.check(.env_command);
351 const host = std.zig.resolveTargetQueryOrFatal(io, .{});
345 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);352 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
346 try @import("print_env.zig").cmdEnv(353 try @import("print_env.zig").cmdEnv(
347 arena,354 arena,
348 &stdout_writer.interface,355 &stdout_writer.interface,
349 args,356 args,
350 if (native_os == .wasi) wasi_preopens,357 if (native_os == .wasi) wasi_preopens,
358 &host,
351 );359 );
352 return stdout_writer.interface.flush();360 return stdout_writer.interface.flush();
353 } else if (mem.eql(u8, cmd, "reduce")) {361 } else if (mem.eql(u8, cmd, "reduce")) {
354 return jitCmd(gpa, arena, cmd_args, .{362 return jitCmd(gpa, arena, io, cmd_args, .{
355 .cmd_name = "reduce",363 .cmd_name = "reduce",
356 .root_src_path = "reduce.zig",364 .root_src_path = "reduce.zig",
357 });365 });
...@@ -362,13 +370,13 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -362,13 +370,13 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
362 dev.check(.help_command);370 dev.check(.help_command);
363 return fs.File.stdout().writeAll(usage);371 return fs.File.stdout().writeAll(usage);
364 } else if (mem.eql(u8, cmd, "ast-check")) {372 } else if (mem.eql(u8, cmd, "ast-check")) {
365 return cmdAstCheck(arena, cmd_args);373 return cmdAstCheck(arena, io, cmd_args);
366 } else if (mem.eql(u8, cmd, "detect-cpu")) {374 } else if (mem.eql(u8, cmd, "detect-cpu")) {
367 return cmdDetectCpu(cmd_args);375 return cmdDetectCpu(io, cmd_args);
368 } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "changelist")) {376 } 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);
370 } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "dump-zir")) {378 } 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);
372 } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "llvm-ints")) {380 } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "llvm-ints")) {
373 return cmdDumpLlvmInts(gpa, arena, cmd_args);381 return cmdDumpLlvmInts(gpa, arena, cmd_args);
374 } else {382 } else {
...@@ -735,7 +743,7 @@ const ArgMode = union(enum) {...@@ -735,7 +743,7 @@ const ArgMode = union(enum) {
735const Listen = union(enum) {743const Listen = union(enum) {
736 none,744 none,
737 stdio: if (dev.env.supports(.stdio_listen)) void else noreturn,745 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,
739};747};
740748
741const ArgsIterator = struct {749const ArgsIterator = struct {
...@@ -792,6 +800,7 @@ const CliModule = struct {...@@ -792,6 +800,7 @@ const CliModule = struct {
792fn buildOutputType(800fn buildOutputType(
793 gpa: Allocator,801 gpa: Allocator,
794 arena: Allocator,802 arena: Allocator,
803 io: Io,
795 all_args: []const []const u8,804 all_args: []const []const u8,
796 arg_mode: ArgMode,805 arg_mode: ArgMode,
797) !void {806) !void {
...@@ -1328,7 +1337,7 @@ fn buildOutputType(...@@ -1328,7 +1337,7 @@ fn buildOutputType(
1328 const host, const port_text = mem.cutScalar(u8, next_arg, ':') orelse .{ next_arg, "14735" };1337 const host, const port_text = mem.cutScalar(u8, next_arg, ':') orelse .{ next_arg, "14735" };
1329 const port = std.fmt.parseInt(u16, port_text, 10) catch |err|1338 const port = std.fmt.parseInt(u16, port_text, 10) catch |err|
1330 fatal("invalid port number: '{s}': {s}", .{ port_text, @errorName(err) });1339 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|
1332 fatal("invalid host: '{s}': {s}", .{ host, @errorName(err) }) };1341 fatal("invalid host: '{s}': {s}", .{ host, @errorName(err) }) };
1333 }1342 }
1334 } else if (mem.eql(u8, arg, "--listen=-")) {1343 } else if (mem.eql(u8, arg, "--listen=-")) {
...@@ -3017,7 +3026,7 @@ fn buildOutputType(...@@ -3017,7 +3026,7 @@ fn buildOutputType(
3017 create_module.opts.emit_bin = emit_bin != .no;3026 create_module.opts.emit_bin = emit_bin != .no;
3018 create_module.opts.any_c_source_files = create_module.c_source_files.items.len != 0;3027 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);
3021 for (create_module.modules.keys(), create_module.modules.values()) |key, cli_mod| {3030 for (create_module.modules.keys(), create_module.modules.values()) |key, cli_mod| {
3022 if (cli_mod.resolved == null)3031 if (cli_mod.resolved == null)
3023 fatal("module '{s}' declared but not used", .{key});3032 fatal("module '{s}' declared but not used", .{key});
...@@ -3311,7 +3320,7 @@ fn buildOutputType(...@@ -3311,7 +3320,7 @@ fn buildOutputType(
3311 var file_writer = f.writer(&.{});3320 var file_writer = f.writer(&.{});
3312 var buffer: [1000]u8 = undefined;3321 var buffer: [1000]u8 = undefined;
3313 var hasher = file_writer.interface.hashed(Cache.Hasher.init("0123456789abcdef"), &buffer);3322 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, &.{});
3315 _ = hasher.writer.sendFileAll(&stdin_reader, .unlimited) catch |err| switch (err) {3324 _ = hasher.writer.sendFileAll(&stdin_reader, .unlimited) catch |err| switch (err) {
3316 error.WriteFailed => fatal("failed to write {s}: {t}", .{ dump_path, file_writer.err.? }),3325 error.WriteFailed => fatal("failed to write {s}: {t}", .{ dump_path, file_writer.err.? }),
3317 else => fatal("failed to pipe stdin to {s}: {t}", .{ dump_path, err }),3326 else => fatal("failed to pipe stdin to {s}: {t}", .{ dump_path, err }),
...@@ -3367,7 +3376,7 @@ fn buildOutputType(...@@ -3367,7 +3376,7 @@ fn buildOutputType(
3367 try create_module.rpath_list.appendSlice(arena, rpath_dedup.keys());3376 try create_module.rpath_list.appendSlice(arena, rpath_dedup.keys());
33683377
3369 var create_diag: Compilation.CreateDiagnostic = undefined;3378 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, .{
3371 .dirs = dirs,3380 .dirs = dirs,
3372 .thread_pool = &thread_pool,3381 .thread_pool = &thread_pool,
3373 .self_exe_path = switch (native_os) {3382 .self_exe_path = switch (native_os) {
...@@ -3542,7 +3551,7 @@ fn buildOutputType(...@@ -3542,7 +3551,7 @@ fn buildOutputType(
3542 switch (listen) {3551 switch (listen) {
3543 .none => {},3552 .none => {},
3544 .stdio => {3553 .stdio => {
3545 var stdin_reader = fs.File.stdin().reader(&stdin_buffer);3554 var stdin_reader = fs.File.stdin().reader(io, &stdin_buffer);
3546 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);3555 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
3547 try serve(3556 try serve(
3548 comp,3557 comp,
...@@ -3557,22 +3566,22 @@ fn buildOutputType(...@@ -3557,22 +3566,22 @@ fn buildOutputType(
3557 return cleanExit();3566 return cleanExit();
3558 },3567 },
3559 .ip4 => |ip4_addr| {3568 .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, .{
3563 .reuse_address = true,3572 .reuse_address = true,
3564 });3573 });
3565 defer server.deinit();3574 defer server.deinit(io);
35663575
3567 const conn = try server.accept();3576 var stream = try server.accept(io);
3568 defer conn.stream.close();3577 defer stream.close(io);
35693578
3570 var input = conn.stream.reader(&stdin_buffer);3579 var input = stream.reader(io, &stdin_buffer);
3571 var output = conn.stream.writer(&stdout_buffer);3580 var output = stream.writer(io, &stdout_buffer);
35723581
3573 try serve(3582 try serve(
3574 comp,3583 comp,
3575 input.interface(),3584 &input.interface,
3576 &output.interface,3585 &output.interface,
3577 test_exec_args.items,3586 test_exec_args.items,
3578 self_exe_path,3587 self_exe_path,
...@@ -3646,6 +3655,7 @@ fn buildOutputType(...@@ -3646,6 +3655,7 @@ fn buildOutputType(
3646 comp,3655 comp,
3647 gpa,3656 gpa,
3648 arena,3657 arena,
3658 io,
3649 test_exec_args.items,3659 test_exec_args.items,
3650 self_exe_path,3660 self_exe_path,
3651 arg_mode,3661 arg_mode,
...@@ -3704,6 +3714,7 @@ const CreateModule = struct {...@@ -3704,6 +3714,7 @@ const CreateModule = struct {
3704fn createModule(3714fn createModule(
3705 gpa: Allocator,3715 gpa: Allocator,
3706 arena: Allocator,3716 arena: Allocator,
3717 io: Io,
3707 create_module: *CreateModule,3718 create_module: *CreateModule,
3708 index: usize,3719 index: usize,
3709 parent: ?*Package.Module,3720 parent: ?*Package.Module,
...@@ -3777,7 +3788,7 @@ fn createModule(...@@ -3777,7 +3788,7 @@ fn createModule(
3777 }3788 }
37783789
3779 const target_query = std.zig.parseTargetQueryOrReportFatalError(arena, target_parse_options);3790 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);
3781 break :t .{3792 break :t .{
3782 .result = target,3793 .result = target,
3783 .is_native_os = target_query.isNativeOs(),3794 .is_native_os = target_query.isNativeOs(),
...@@ -4022,7 +4033,7 @@ fn createModule(...@@ -4022,7 +4033,7 @@ fn createModule(
4022 for (cli_mod.deps) |dep| {4033 for (cli_mod.deps) |dep| {
4023 const dep_index = create_module.modules.getIndex(dep.value) orelse4034 const dep_index = create_module.modules.getIndex(dep.value) orelse
4024 fatal("module '{s}' depends on non-existent module '{s}'", .{ name, dep.key });4035 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);
4026 try mod.deps.put(arena, dep.key, dep_mod);4037 try mod.deps.put(arena, dep.key, dep_mod);
4027 }4038 }
40284039
...@@ -4039,8 +4050,8 @@ fn saveState(comp: *Compilation, incremental: bool) void {...@@ -4039,8 +4050,8 @@ fn saveState(comp: *Compilation, incremental: bool) void {
40394050
4040fn serve(4051fn serve(
4041 comp: *Compilation,4052 comp: *Compilation,
4042 in: *std.Io.Reader,4053 in: *Io.Reader,
4043 out: *std.Io.Writer,4054 out: *Io.Writer,
4044 test_exec_args: []const ?[]const u8,4055 test_exec_args: []const ?[]const u8,
4045 self_exe_path: ?[]const u8,4056 self_exe_path: ?[]const u8,
4046 arg_mode: ArgMode,4057 arg_mode: ArgMode,
...@@ -4126,6 +4137,7 @@ fn serve(...@@ -4126,6 +4137,7 @@ fn serve(
4126 // comp,4137 // comp,
4127 // gpa,4138 // gpa,
4128 // arena,4139 // arena,
4140 // io,
4129 // test_exec_args,4141 // test_exec_args,
4130 // self_exe_path.?,4142 // self_exe_path.?,
4131 // arg_mode,4143 // arg_mode,
...@@ -4280,6 +4292,7 @@ fn runOrTest(...@@ -4280,6 +4292,7 @@ fn runOrTest(
4280 comp: *Compilation,4292 comp: *Compilation,
4281 gpa: Allocator,4293 gpa: Allocator,
4282 arena: Allocator,4294 arena: Allocator,
4295 io: Io,
4283 test_exec_args: []const ?[]const u8,4296 test_exec_args: []const ?[]const u8,
4284 self_exe_path: []const u8,4297 self_exe_path: []const u8,
4285 arg_mode: ArgMode,4298 arg_mode: ArgMode,
...@@ -4334,7 +4347,7 @@ fn runOrTest(...@@ -4334,7 +4347,7 @@ fn runOrTest(
4334 std.debug.lockStdErr();4347 std.debug.lockStdErr();
4335 const err = process.execve(gpa, argv.items, &env_map);4348 const err = process.execve(gpa, argv.items, &env_map);
4336 std.debug.unlockStdErr();4349 std.debug.unlockStdErr();
4337 try warnAboutForeignBinaries(arena, arg_mode, target, link_libc);4350 try warnAboutForeignBinaries(io, arena, arg_mode, target, link_libc);
4338 const cmd = try std.mem.join(arena, " ", argv.items);4351 const cmd = try std.mem.join(arena, " ", argv.items);
4339 fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd });4352 fatal("the following command failed to execve with '{s}':\n{s}", .{ @errorName(err), cmd });
4340 } else if (process.can_spawn) {4353 } else if (process.can_spawn) {
...@@ -4355,7 +4368,7 @@ fn runOrTest(...@@ -4355,7 +4368,7 @@ fn runOrTest(
4355 break :t child.spawnAndWait();4368 break :t child.spawnAndWait();
4356 };4369 };
4357 const term = term_result catch |err| {4370 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);
4359 const cmd = try std.mem.join(arena, " ", argv.items);4372 const cmd = try std.mem.join(arena, " ", argv.items);
4360 fatal("the following command failed with '{s}':\n{s}", .{ @errorName(err), cmd });4373 fatal("the following command failed with '{s}':\n{s}", .{ @errorName(err), cmd });
4361 };4374 };
...@@ -4521,6 +4534,8 @@ fn cmdTranslateC(...@@ -4521,6 +4534,8 @@ fn cmdTranslateC(
4521) !void {4534) !void {
4522 dev.check(.translate_c_command);4535 dev.check(.translate_c_command);
45234536
4537 const io = comp.io;
4538
4524 assert(comp.c_source_files.len == 1);4539 assert(comp.c_source_files.len == 1);
4525 const c_source_file = comp.c_source_files[0];4540 const c_source_file = comp.c_source_files[0];
45264541
...@@ -4584,7 +4599,7 @@ fn cmdTranslateC(...@@ -4584,7 +4599,7 @@ fn cmdTranslateC(
4584 };4599 };
4585 defer zig_file.close();4600 defer zig_file.close();
4586 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);4601 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
4587 var file_reader = zig_file.reader(&.{});4602 var file_reader = zig_file.reader(io, &.{});
4588 _ = try stdout_writer.interface.sendFileAll(&file_reader, .unlimited);4603 _ = try stdout_writer.interface.sendFileAll(&file_reader, .unlimited);
4589 try stdout_writer.interface.flush();4604 try stdout_writer.interface.flush();
4590 return cleanExit();4605 return cleanExit();
...@@ -4594,11 +4609,12 @@ fn cmdTranslateC(...@@ -4594,11 +4609,12 @@ fn cmdTranslateC(
4594pub fn translateC(4609pub fn translateC(
4595 gpa: Allocator,4610 gpa: Allocator,
4596 arena: Allocator,4611 arena: Allocator,
4612 io: Io,
4597 argv: []const []const u8,4613 argv: []const []const u8,
4598 prog_node: std.Progress.Node,4614 prog_node: std.Progress.Node,
4599 capture: ?*[]u8,4615 capture: ?*[]u8,
4600) !void {4616) !void {
4601 try jitCmd(gpa, arena, argv, .{4617 try jitCmd(gpa, arena, io, argv, .{
4602 .cmd_name = "translate-c",4618 .cmd_name = "translate-c",
4603 .root_src_path = "translate-c/main.zig",4619 .root_src_path = "translate-c/main.zig",
4604 .depend_on_aro = true,4620 .depend_on_aro = true,
...@@ -4755,7 +4771,7 @@ test sanitizeExampleName {...@@ -4755,7 +4771,7 @@ test sanitizeExampleName {
4755 try std.testing.expectEqualStrings("test_project", try sanitizeExampleName(arena, "test project"));4771 try std.testing.expectEqualStrings("test_project", try sanitizeExampleName(arena, "test project"));
4756}4772}
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 {
4759 dev.check(.build_command);4775 dev.check(.build_command);
47604776
4761 var build_file: ?[]const u8 = null;4777 var build_file: ?[]const u8 = null;
...@@ -4983,7 +4999,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4983,7 +4999,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4983 .arch_os_abi = triple,4999 .arch_os_abi = triple,
4984 });5000 });
4985 break :t .{5001 break :t .{
4986 .result = std.zig.resolveTargetQueryOrFatal(target_query),5002 .result = std.zig.resolveTargetQueryOrFatal(io, target_query),
4987 .is_native_os = false,5003 .is_native_os = false,
4988 .is_native_abi = false,5004 .is_native_abi = false,
4989 .is_explicit_dynamic_linker = false,5005 .is_explicit_dynamic_linker = false,
...@@ -4991,7 +5007,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4991,7 +5007,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4991 }5007 }
4992 }5008 }
4993 break :t .{5009 break :t .{
4994 .result = std.zig.resolveTargetQueryOrFatal(.{}),5010 .result = std.zig.resolveTargetQueryOrFatal(io, .{}),
4995 .is_native_os = true,5011 .is_native_os = true,
4996 .is_native_abi = true,5012 .is_native_abi = true,
4997 .is_explicit_dynamic_linker = false,5013 .is_explicit_dynamic_linker = false,
...@@ -5046,8 +5062,9 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -5046,8 +5062,9 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
5046 // Prevents bootstrap from depending on a bunch of unnecessary stuff.5062 // Prevents bootstrap from depending on a bunch of unnecessary stuff.
5047 var http_client: if (dev.env.supports(.fetch_command)) std.http.Client else struct {5063 var http_client: if (dev.env.supports(.fetch_command)) std.http.Client else struct {
5048 allocator: Allocator,5064 allocator: Allocator,
5065 io: Io,
5049 fn deinit(_: @This()) void {}5066 fn deinit(_: @This()) void {}
5050 } = .{ .allocator = gpa };5067 } = .{ .allocator = gpa, .io = io };
5051 defer http_client.deinit();5068 defer http_client.deinit();
50525069
5053 var unlazy_set: Package.Fetch.JobQueue.UnlazySet = .{};5070 var unlazy_set: Package.Fetch.JobQueue.UnlazySet = .{};
...@@ -5139,6 +5156,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -5139,6 +5156,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
51395156
5140 var fetch: Package.Fetch = .{5157 var fetch: Package.Fetch = .{
5141 .arena = std.heap.ArenaAllocator.init(gpa),5158 .arena = std.heap.ArenaAllocator.init(gpa),
5159 .io = io,
5142 .location = .{ .relative_path = phantom_package_root },5160 .location = .{ .relative_path = phantom_package_root },
5143 .location_tok = 0,5161 .location_tok = 0,
5144 .hash_tok = .none,5162 .hash_tok = .none,
...@@ -5261,7 +5279,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -5261,7 +5279,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
5261 try root_mod.deps.put(arena, "@build", build_mod);5279 try root_mod.deps.put(arena, "@build", build_mod);
52625280
5263 var create_diag: Compilation.CreateDiagnostic = undefined;5281 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, .{
5265 .libc_installation = libc_installation,5283 .libc_installation = libc_installation,
5266 .dirs = dirs,5284 .dirs = dirs,
5267 .root_name = "build",5285 .root_name = "build",
...@@ -5400,6 +5418,7 @@ const JitCmdOptions = struct {...@@ -5400,6 +5418,7 @@ const JitCmdOptions = struct {
5400fn jitCmd(5418fn jitCmd(
5401 gpa: Allocator,5419 gpa: Allocator,
5402 arena: Allocator,5420 arena: Allocator,
5421 io: Io,
5403 args: []const []const u8,5422 args: []const []const u8,
5404 options: JitCmdOptions,5423 options: JitCmdOptions,
5405) !void {5424) !void {
...@@ -5412,7 +5431,7 @@ fn jitCmd(...@@ -5412,7 +5431,7 @@ fn jitCmd(
54125431
5413 const target_query: std.Target.Query = .{};5432 const target_query: std.Target.Query = .{};
5414 const resolved_target: Package.Module.ResolvedTarget = .{5433 const resolved_target: Package.Module.ResolvedTarget = .{
5415 .result = std.zig.resolveTargetQueryOrFatal(target_query),5434 .result = std.zig.resolveTargetQueryOrFatal(io, target_query),
5416 .is_native_os = true,5435 .is_native_os = true,
5417 .is_native_abi = true,5436 .is_native_abi = true,
5418 .is_explicit_dynamic_linker = false,5437 .is_explicit_dynamic_linker = false,
...@@ -5504,7 +5523,7 @@ fn jitCmd(...@@ -5504,7 +5523,7 @@ fn jitCmd(
5504 }5523 }
55055524
5506 var create_diag: Compilation.CreateDiagnostic = undefined;5525 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, .{
5508 .dirs = dirs,5527 .dirs = dirs,
5509 .root_name = options.cmd_name,5528 .root_name = options.cmd_name,
5510 .config = config,5529 .config = config,
...@@ -5584,7 +5603,7 @@ fn jitCmd(...@@ -5584,7 +5603,7 @@ fn jitCmd(
5584 try child.spawn();5603 try child.spawn();
55855604
5586 if (options.capture) |ptr| {5605 if (options.capture) |ptr| {
5587 var stdout_reader = child.stdout.?.readerStreaming(&.{});5606 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
5588 ptr.* = try stdout_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32)));5607 ptr.* = try stdout_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32)));
5589 }5608 }
55905609
...@@ -6039,10 +6058,7 @@ const usage_ast_check =...@@ -6039,10 +6058,7 @@ const usage_ast_check =
6039 \\6058 \\
6040;6059;
60416060
6042fn cmdAstCheck(6061fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8) !void {
6043 arena: Allocator,
6044 args: []const []const u8,
6045) !void {
6046 dev.check(.ast_check_command);6062 dev.check(.ast_check_command);
60476063
6048 const Zir = std.zig.Zir;6064 const Zir = std.zig.Zir;
...@@ -6090,7 +6106,7 @@ fn cmdAstCheck(...@@ -6090,7 +6106,7 @@ fn cmdAstCheck(
6090 };6106 };
6091 } else fs.File.stdin();6107 } else fs.File.stdin();
6092 defer if (zig_source_path != null) f.close();6108 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);
6094 break :s std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err| {6110 break :s std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err| {
6095 fatal("unable to load file '{s}' for ast-check: {s}", .{ display_path, @errorName(err) });6111 fatal("unable to load file '{s}' for ast-check: {s}", .{ display_path, @errorName(err) });
6096 };6112 };
...@@ -6209,7 +6225,7 @@ fn cmdAstCheck(...@@ -6209,7 +6225,7 @@ fn cmdAstCheck(
6209 }6225 }
6210}6226}
62116227
6212fn cmdDetectCpu(args: []const []const u8) !void {6228fn cmdDetectCpu(io: Io, args: []const []const u8) !void {
6213 dev.check(.detect_cpu_command);6229 dev.check(.detect_cpu_command);
62146230
6215 const detect_cpu_usage =6231 const detect_cpu_usage =
...@@ -6254,7 +6270,7 @@ fn cmdDetectCpu(args: []const []const u8) !void {...@@ -6254,7 +6270,7 @@ fn cmdDetectCpu(args: []const []const u8) !void {
6254 const cpu = try detectNativeCpuWithLLVM(builtin.cpu.arch, name, features);6270 const cpu = try detectNativeCpuWithLLVM(builtin.cpu.arch, name, features);
6255 try printCpu(cpu);6271 try printCpu(cpu);
6256 } else {6272 } else {
6257 const host_target = std.zig.resolveTargetQueryOrFatal(.{});6273 const host_target = std.zig.resolveTargetQueryOrFatal(io, .{});
6258 try printCpu(host_target.cpu);6274 try printCpu(host_target.cpu);
6259 }6275 }
6260}6276}
...@@ -6385,10 +6401,7 @@ fn cmdDumpLlvmInts(...@@ -6385,10 +6401,7 @@ fn cmdDumpLlvmInts(
6385}6401}
63866402
6387/// This is only enabled for debug builds.6403/// This is only enabled for debug builds.
6388fn cmdDumpZir(6404fn cmdDumpZir(arena: Allocator, io: Io, args: []const []const u8) !void {
6389 arena: Allocator,
6390 args: []const []const u8,
6391) !void {
6392 dev.check(.dump_zir_command);6405 dev.check(.dump_zir_command);
63936406
6394 const Zir = std.zig.Zir;6407 const Zir = std.zig.Zir;
...@@ -6400,7 +6413,7 @@ fn cmdDumpZir(...@@ -6400,7 +6413,7 @@ fn cmdDumpZir(
6400 };6413 };
6401 defer f.close();6414 defer f.close();
64026415
6403 const zir = try Zcu.loadZirCache(arena, f);6416 const zir = try Zcu.loadZirCache(arena, io, f);
6404 var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer);6417 var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer);
6405 const stdout_bw = &stdout_writer.interface;6418 const stdout_bw = &stdout_writer.interface;
6406 {6419 {
...@@ -6432,10 +6445,7 @@ fn cmdDumpZir(...@@ -6432,10 +6445,7 @@ fn cmdDumpZir(
6432}6445}
64336446
6434/// This is only enabled for debug builds.6447/// This is only enabled for debug builds.
6435fn cmdChangelist(6448fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8) !void {
6436 arena: Allocator,
6437 args: []const []const u8,
6438) !void {
6439 dev.check(.changelist_command);6449 dev.check(.changelist_command);
64406450
6441 const color: Color = .auto;6451 const color: Color = .auto;
...@@ -6448,7 +6458,7 @@ fn cmdChangelist(...@@ -6448,7 +6458,7 @@ fn cmdChangelist(
6448 var f = fs.cwd().openFile(old_source_path, .{}) catch |err|6458 var f = fs.cwd().openFile(old_source_path, .{}) catch |err|
6449 fatal("unable to open old source file '{s}': {s}", .{ old_source_path, @errorName(err) });6459 fatal("unable to open old source file '{s}': {s}", .{ old_source_path, @errorName(err) });
6450 defer f.close();6460 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);
6452 break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err|6462 break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err|
6453 fatal("unable to read old source file '{s}': {s}", .{ old_source_path, @errorName(err) });6463 fatal("unable to read old source file '{s}': {s}", .{ old_source_path, @errorName(err) });
6454 };6464 };
...@@ -6456,7 +6466,7 @@ fn cmdChangelist(...@@ -6456,7 +6466,7 @@ fn cmdChangelist(
6456 var f = fs.cwd().openFile(new_source_path, .{}) catch |err|6466 var f = fs.cwd().openFile(new_source_path, .{}) catch |err|
6457 fatal("unable to open new source file '{s}': {s}", .{ new_source_path, @errorName(err) });6467 fatal("unable to open new source file '{s}': {s}", .{ new_source_path, @errorName(err) });
6458 defer f.close();6468 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);
6460 break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err|6470 break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err|
6461 fatal("unable to read new source file '{s}': {s}", .{ new_source_path, @errorName(err) });6471 fatal("unable to read new source file '{s}': {s}", .{ new_source_path, @errorName(err) });
6462 };6472 };
...@@ -6521,13 +6531,14 @@ fn prefixedIntArg(arg: []const u8, prefix: []const u8) ?u64 {...@@ -6521,13 +6531,14 @@ fn prefixedIntArg(arg: []const u8, prefix: []const u8) ?u64 {
6521}6531}
65226532
6523fn warnAboutForeignBinaries(6533fn warnAboutForeignBinaries(
6534 io: Io,
6524 arena: Allocator,6535 arena: Allocator,
6525 arg_mode: ArgMode,6536 arg_mode: ArgMode,
6526 target: *const std.Target,6537 target: *const std.Target,
6527 link_libc: bool,6538 link_libc: bool,
6528) !void {6539) !void {
6529 const host_query: std.Target.Query = .{};6540 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
6532 switch (std.zig.system.getExternalExecutor(&host_target, target, .{ .link_libc = link_libc })) {6543 switch (std.zig.system.getExternalExecutor(&host_target, target, .{ .link_libc = link_libc })) {
6533 .native => return,6544 .native => return,
...@@ -6812,6 +6823,7 @@ const usage_fetch =...@@ -6812,6 +6823,7 @@ const usage_fetch =
6812fn cmdFetch(6823fn cmdFetch(
6813 gpa: Allocator,6824 gpa: Allocator,
6814 arena: Allocator,6825 arena: Allocator,
6826 io: Io,
6815 args: []const []const u8,6827 args: []const []const u8,
6816) !void {6828) !void {
6817 dev.check(.fetch_command);6829 dev.check(.fetch_command);
...@@ -6867,7 +6879,7 @@ fn cmdFetch(...@@ -6867,7 +6879,7 @@ fn cmdFetch(
6867 try thread_pool.init(.{ .allocator = gpa });6879 try thread_pool.init(.{ .allocator = gpa });
6868 defer thread_pool.deinit();6880 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 };
6871 defer http_client.deinit();6883 defer http_client.deinit();
68726884
6873 try http_client.initDefaultProxies(arena);6885 try http_client.initDefaultProxies(arena);
...@@ -6900,6 +6912,7 @@ fn cmdFetch(...@@ -6900,6 +6912,7 @@ fn cmdFetch(
69006912
6901 var fetch: Package.Fetch = .{6913 var fetch: Package.Fetch = .{
6902 .arena = std.heap.ArenaAllocator.init(gpa),6914 .arena = std.heap.ArenaAllocator.init(gpa),
6915 .io = io,
6903 .location = .{ .path_or_url = path_or_url },6916 .location = .{ .path_or_url = path_or_url },
6904 .location_tok = 0,6917 .location_tok = 0,
6905 .hash_tok = .none,6918 .hash_tok = .none,
...@@ -7080,7 +7093,7 @@ fn cmdFetch(...@@ -7080,7 +7093,7 @@ fn cmdFetch(
7080 try fixups.append_string_after_node.put(gpa, manifest.version_node, dependencies_text);7093 try fixups.append_string_after_node.put(gpa, manifest.version_node, dependencies_text);
7081 }7094 }
70827095
7083 var aw: std.Io.Writer.Allocating = .init(gpa);7096 var aw: Io.Writer.Allocating = .init(gpa);
7084 defer aw.deinit();7097 defer aw.deinit();
7085 try ast.render(gpa, &aw.writer, fixups);7098 try ast.render(gpa, &aw.writer, fixups);
7086 const rendered = aw.written();7099 const rendered = aw.written();
src/print_env.zig+1-2
...@@ -14,6 +14,7 @@ pub fn cmdEnv(...@@ -14,6 +14,7 @@ pub fn cmdEnv(
14 .wasi => std.fs.wasi.Preopens,14 .wasi => std.fs.wasi.Preopens,
15 else => void,15 else => void,
16 },16 },
17 host: *const std.Target,
17) !void {18) !void {
18 const override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena);19 const override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena);
19 const override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena);20 const override_global_cache_dir: ?[]const u8 = try EnvVar.ZIG_GLOBAL_CACHE_DIR.get(arena);
...@@ -38,8 +39,6 @@ pub fn cmdEnv(...@@ -38,8 +39,6 @@ pub fn cmdEnv(
38 const zig_lib_dir = dirs.zig_lib.path orelse "";39 const zig_lib_dir = dirs.zig_lib.path orelse "";
39 const zig_std_dir = try dirs.zig_lib.join(arena, &.{"std"});40 const zig_std_dir = try dirs.zig_lib.join(arena, &.{"std"});
40 const global_cache_dir = dirs.global_cache.path orelse "";41 const global_cache_dir = dirs.global_cache.path orelse "";
41
42 const host = try std.zig.system.resolveTargetQuery(.{});
43 const triple = try host.zigTriple(arena);42 const triple = try host.zigTriple(arena);
4443
45 var serializer: std.zon.Serializer = .{ .writer = out };44 var serializer: std.zon.Serializer = .{ .writer = out };
test/src/Cases.zig+6-11
...@@ -370,6 +370,10 @@ fn addFromDirInner(...@@ -370,6 +370,10 @@ fn addFromDirInner(
370 const resolved_target = b.resolveTargetQuery(target_query);370 const resolved_target = b.resolveTargetQuery(target_query);
371 const target = &resolved_target.result;371 const target = &resolved_target.result;
372 for (backends) |backend| {372 for (backends) |backend| {
373 if (backend == .selfhosted and target.cpu.arch == .wasm32) {
374 // https://github.com/ziglang/zig/issues/25684
375 continue;
376 }
373 if (backend == .selfhosted and377 if (backend == .selfhosted and
374 target.cpu.arch != .aarch64 and target.cpu.arch != .wasm32 and target.cpu.arch != .x86_64 and target.cpu.arch != .spirv64)378 target.cpu.arch != .aarch64 and target.cpu.arch != .wasm32 and target.cpu.arch != .x86_64 and target.cpu.arch != .spirv64)
375 {379 {
...@@ -455,8 +459,7 @@ pub fn lowerToBuildSteps(...@@ -455,8 +459,7 @@ pub fn lowerToBuildSteps(
455 parent_step: *std.Build.Step,459 parent_step: *std.Build.Step,
456 options: CaseTestOptions,460 options: CaseTestOptions,
457) void {461) void {
458 const host = std.zig.system.resolveTargetQuery(.{}) catch |err|462 const host = b.resolveTargetQuery(.{});
459 std.debug.panic("unable to detect native host: {s}\n", .{@errorName(err)});
460 const cases_dir_path = b.build_root.join(b.allocator, &.{ "test", "cases" }) catch @panic("OOM");463 const cases_dir_path = b.build_root.join(b.allocator, &.{ "test", "cases" }) catch @panic("OOM");
461464
462 for (self.cases.items) |case| {465 for (self.cases.items) |case| {
...@@ -587,7 +590,7 @@ pub fn lowerToBuildSteps(...@@ -587,7 +590,7 @@ pub fn lowerToBuildSteps(
587 },590 },
588 .Execution => |expected_stdout| no_exec: {591 .Execution => |expected_stdout| no_exec: {
589 const run = if (case.target.result.ofmt == .c) run_step: {592 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) {
591 // We wouldn't be able to run the compiled C code.594 // We wouldn't be able to run the compiled C code.
592 break :no_exec;595 break :no_exec;
593 }596 }
...@@ -972,14 +975,6 @@ const TestManifest = struct {...@@ -972,14 +975,6 @@ const TestManifest = struct {
972 }975 }
973};976};
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
983fn knownFileExtension(filename: []const u8) bool {978fn knownFileExtension(filename: []const u8) bool {
984 // List taken from `Compilation.classifyFileExt` in the compiler.979 // List taken from `Compilation.classifyFileExt` in the compiler.
985 for ([_][]const u8{980 for ([_][]const u8{
test/src/convert-stack-trace.zig+7-1
...@@ -32,6 +32,12 @@ pub fn main() !void {...@@ -32,6 +32,12 @@ pub fn main() !void {
32 const args = try std.process.argsAlloc(arena);32 const args = try std.process.argsAlloc(arena);
33 if (args.len != 2) std.process.fatal("usage: convert-stack-trace path/to/test/output", .{});33 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
35 var read_buf: [1024]u8 = undefined;41 var read_buf: [1024]u8 = undefined;
36 var write_buf: [1024]u8 = undefined;42 var write_buf: [1024]u8 = undefined;
3743
...@@ -40,7 +46,7 @@ pub fn main() !void {...@@ -40,7 +46,7 @@ pub fn main() !void {
4046
41 const out_file: std.fs.File = .stdout();47 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);
44 var out_fw = out_file.writer(&write_buf);50 var out_fw = out_file.writer(&write_buf);
4551
46 const w = &out_fw.interface;52 const w = &out_fw.interface;
test/standalone/child_process/child.zig+10-3
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
23
3// 42 is expected by parent; other values result in test failure4// 42 is expected by parent; other values result in test failure
4var exit_code: u8 = 42;5var exit_code: u8 = 42;
...@@ -6,12 +7,17 @@ var exit_code: u8 = 42;...@@ -6,12 +7,17 @@ var exit_code: u8 = 42;
6pub fn main() !void {7pub fn main() !void {
7 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);8 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
8 const arena = arena_state.allocator();9 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);
10 arena_state.deinit();16 arena_state.deinit();
11 std.process.exit(exit_code);17 std.process.exit(exit_code);
12}18}
1319
14fn run(allocator: std.mem.Allocator) !void {20fn run(allocator: std.mem.Allocator, io: Io) !void {
15 var args = try std.process.argsWithAllocator(allocator);21 var args = try std.process.argsWithAllocator(allocator);
16 defer args.deinit();22 defer args.deinit();
17 _ = args.next() orelse unreachable; // skip binary name23 _ = args.next() orelse unreachable; // skip binary name
...@@ -33,7 +39,8 @@ fn run(allocator: std.mem.Allocator) !void {...@@ -33,7 +39,8 @@ fn run(allocator: std.mem.Allocator) !void {
33 const hello_stdin = "hello from stdin";39 const hello_stdin = "hello from stdin";
34 var buf: [hello_stdin.len]u8 = undefined;40 var buf: [hello_stdin.len]u8 = undefined;
35 const stdin: std.fs.File = .stdin();41 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);
37 if (!std.mem.eql(u8, buf[0..n], hello_stdin)) {44 if (!std.mem.eql(u8, buf[0..n], hello_stdin)) {
38 testError("stdin: '{s}'; want '{s}'", .{ buf[0..n], hello_stdin });45 testError("stdin: '{s}'; want '{s}'", .{ buf[0..n], hello_stdin });
39 }46 }
test/standalone/child_process/main.zig+5-1
...@@ -20,6 +20,10 @@ pub fn main() !void {...@@ -20,6 +20,10 @@ pub fn main() !void {
20 };20 };
21 defer if (needs_free) gpa.free(child_path);21 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
23 var child = std.process.Child.init(&.{ child_path, "hello arg" }, gpa);27 var child = std.process.Child.init(&.{ child_path, "hello arg" }, gpa);
24 child.stdin_behavior = .Pipe;28 child.stdin_behavior = .Pipe;
25 child.stdout_behavior = .Pipe;29 child.stdout_behavior = .Pipe;
...@@ -32,7 +36,7 @@ pub fn main() !void {...@@ -32,7 +36,7 @@ pub fn main() !void {
3236
33 const hello_stdout = "hello from stdout";37 const hello_stdout = "hello from stdout";
34 var buf: [hello_stdout.len]u8 = undefined;38 var buf: [hello_stdout.len]u8 = undefined;
35 var stdout_reader = child.stdout.?.readerStreaming(&.{});39 var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
36 const n = try stdout_reader.interface.readSliceShort(&buf);40 const n = try stdout_reader.interface.readSliceShort(&buf);
37 if (!std.mem.eql(u8, buf[0..n], hello_stdout)) {41 if (!std.mem.eql(u8, buf[0..n], hello_stdout)) {
38 testError("child stdout: '{s}'; want '{s}'", .{ buf[0..n], hello_stdout });42 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 {...@@ -11,10 +11,14 @@ pub fn main() void {
11 var di: std.debug.SelfInfo = .init;11 var di: std.debug.SelfInfo = .init;
12 defer di.deinit(gpa);12 defer di.deinit(gpa);
1313
14 var threaded: std.Io.Threaded = .init(gpa);
15 defer threaded.deinit();
16 const io = threaded.io();
17
14 var add_addr: usize = undefined;18 var add_addr: usize = undefined;
15 _ = add(1, 2, &add_addr);19 _ = 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});
18 defer if (symbol.source_location) |sl| gpa.free(sl.file_name);22 defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
1923
20 if (symbol.name == null) fatal("failed to resolve symbol name", .{});24 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 {...@@ -15,6 +15,10 @@ pub fn main() !void {
15 defer args.deinit();15 defer args.deinit();
16 _ = args.skip(); // executable name16 _ = args.skip(); // executable name
1717
18 var threaded: std.Io.Threaded = .init(gpa);
19 defer threaded.deinit();
20 const io = threaded.io();
21
18 const cache_dir_path = args.next() orelse @panic("expected cache directory path argument");22 const cache_dir_path = args.next() orelse @panic("expected cache directory path argument");
19 var cache_dir = try std.fs.cwd().openDir(cache_dir_path, .{});23 var cache_dir = try std.fs.cwd().openDir(cache_dir_path, .{});
20 defer cache_dir.close();24 defer cache_dir.close();
...@@ -30,7 +34,7 @@ pub fn main() !void {...@@ -30,7 +34,7 @@ pub fn main() !void {
30 defer coverage_file.close();34 defer coverage_file.close();
3135
32 var read_buf: [@sizeOf(abi.SeenPcsHeader)]u8 = undefined;36 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);
34 const pcs_header = r.interface.takeStruct(abi.SeenPcsHeader, native_endian) catch return r.err.?;38 const pcs_header = r.interface.takeStruct(abi.SeenPcsHeader, native_endian) catch return r.err.?;
3539
36 if (pcs_header.pcs_len == 0)40 if (pcs_header.pcs_len == 0)
test/standalone/posix/sigaction.zig+10-14
...@@ -17,12 +17,12 @@ fn test_sigaction() !void {...@@ -17,12 +17,12 @@ fn test_sigaction() !void {
17 return; // https://github.com/ziglang/zig/issues/1538117 return; // https://github.com/ziglang/zig/issues/15381
18 }18 }
1919
20 const test_signo = std.posix.SIG.URG; // URG only because it is ignored by default in debuggers20 const test_signo: std.posix.SIG = .URG; // URG only because it is ignored by default in debuggers
2121
22 const S = struct {22 const S = struct {
23 var handler_called_count: u32 = 0;23 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 {
26 _ = ctx_ptr;26 _ = ctx_ptr;
27 // Check that we received the correct signal.27 // Check that we received the correct signal.
28 const info_sig = switch (native_os) {28 const info_sig = switch (native_os) {
...@@ -80,20 +80,18 @@ fn test_sigaction() !void {...@@ -80,20 +80,18 @@ fn test_sigaction() !void {
80}80}
8181
82fn test_sigset_bits() !void {82fn test_sigset_bits() !void {
83 const NO_SIG: i32 = 0;
84
85 const S = struct {83 const S = struct {
86 var expected_sig: i32 = undefined;84 var expected_sig: std.posix.SIG = undefined;
87 var seen_sig: i32 = NO_SIG;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 {
90 _ = ctx_ptr;88 _ = ctx_ptr;
9189
92 const info_sig = switch (native_os) {90 const info_sig = switch (native_os) {
93 .netbsd => info.info.signo,91 .netbsd => info.info.signo,
94 else => info.signo,92 else => info.signo,
95 };93 };
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) {
97 seen_sig = sig;95 seen_sig = sig;
98 }96 }
99 }97 }
...@@ -107,11 +105,9 @@ fn test_sigset_bits() !void {...@@ -107,11 +105,9 @@ fn test_sigset_bits() !void {
107 // big-endian), try sending a blocked signal to make sure the mask matches the105 // big-endian), try sending a blocked signal to make sure the mask matches the
108 // signal. (Send URG and CHLD because they're ignored by default in the106 // signal. (Send URG and CHLD because they're ignored by default in the
109 // debugger, vs. USR1 or other named signals)107 // debugger, vs. USR1 or other named signals)
110 inline for ([_]i32{ std.posix.SIG.URG, std.posix.SIG.CHLD, 62, 94, 126 }) |test_signo| {108 inline for ([_]std.posix.SIG{ .URG, .CHLD }) |test_signo| {
111 if (test_signo >= std.posix.NSIG) continue;
112
113 S.expected_sig = test_signo;109 S.expected_sig = test_signo;
114 S.seen_sig = NO_SIG;110 S.seen_sig = null;
115111
116 const sa: std.posix.Sigaction = .{112 const sa: std.posix.Sigaction = .{
117 .handler = .{ .sigaction = &S.handler },113 .handler = .{ .sigaction = &S.handler },
...@@ -135,14 +131,14 @@ fn test_sigset_bits() !void {...@@ -135,14 +131,14 @@ fn test_sigset_bits() !void {
135 switch (std.posix.errno(rc)) {131 switch (std.posix.errno(rc)) {
136 .SUCCESS => {132 .SUCCESS => {
137 // See that the signal is blocked, then unblocked133 // 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);
139 std.posix.sigprocmask(std.posix.SIG.UNBLOCK, &block_one, null);135 std.posix.sigprocmask(std.posix.SIG.UNBLOCK, &block_one, null);
140 try std.testing.expectEqual(test_signo, S.seen_sig);136 try std.testing.expectEqual(test_signo, S.seen_sig);
141 },137 },
142 .INVAL => {138 .INVAL => {
143 // Signal won't get delviered. Just clean up.139 // Signal won't get delviered. Just clean up.
144 std.posix.sigprocmask(std.posix.SIG.UNBLOCK, &block_one, null);140 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);
146 },142 },
147 else => |errno| return std.posix.unexpectedErrno(errno),143 else => |errno| return std.posix.unexpectedErrno(errno),
148 }144 }
test/standalone/simple/cat/main.zig+6-2
...@@ -9,6 +9,10 @@ pub fn main() !void {...@@ -9,6 +9,10 @@ pub fn main() !void {
9 defer arena_instance.deinit();9 defer arena_instance.deinit();
10 const arena = arena_instance.allocator();10 const arena = arena_instance.allocator();
1111
12 var threaded: std.Io.Threaded = .init(arena);
13 defer threaded.deinit();
14 const io = threaded.io();
15
12 const args = try std.process.argsAlloc(arena);16 const args = try std.process.argsAlloc(arena);
1317
14 const exe = args[0];18 const exe = args[0];
...@@ -16,7 +20,7 @@ pub fn main() !void {...@@ -16,7 +20,7 @@ pub fn main() !void {
16 var stdout_buffer: [4096]u8 = undefined;20 var stdout_buffer: [4096]u8 = undefined;
17 var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer);21 var stdout_writer = fs.File.stdout().writerStreaming(&stdout_buffer);
18 const stdout = &stdout_writer.interface;22 const stdout = &stdout_writer.interface;
19 var stdin_reader = fs.File.stdin().readerStreaming(&.{});23 var stdin_reader = fs.File.stdin().readerStreaming(io, &.{});
2024
21 const cwd = fs.cwd();25 const cwd = fs.cwd();
2226
...@@ -32,7 +36,7 @@ pub fn main() !void {...@@ -32,7 +36,7 @@ pub fn main() !void {
32 defer file.close();36 defer file.close();
3337
34 catted_anything = true;38 catted_anything = true;
35 var file_reader = file.reader(&.{});39 var file_reader = file.reader(io, &.{});
36 _ = try stdout.sendFileAll(&file_reader, .unlimited);40 _ = try stdout.sendFileAll(&file_reader, .unlimited);
37 try stdout.flush();41 try stdout.flush();
38 }42 }
test/standalone/test_obj_link_run/build.zig+1
...@@ -11,6 +11,7 @@ pub fn build(b: *std.Build) void {...@@ -11,6 +11,7 @@ pub fn build(b: *std.Build) void {
11 if (is_windows) {11 if (is_windows) {
12 test_obj.linkSystemLibrary("ntdll");12 test_obj.linkSystemLibrary("ntdll");
13 test_obj.linkSystemLibrary("kernel32");13 test_obj.linkSystemLibrary("kernel32");
14 test_obj.linkSystemLibrary("ws2_32");
14 }15 }
1516
16 const test_exe_mod = b.createModule(.{17 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...@@ -224,7 +224,7 @@ fn renameExe(dir: std.fs.Dir, old_sub_path: []const u8, new_sub_path: []const u8
224 error.AccessDenied => {224 error.AccessDenied => {
225 if (attempt == 13) return error.AccessDenied;225 if (attempt == 13) return error.AccessDenied;
226 // give the kernel a chance to finish closing the executable handle226 // 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);
228 attempt += 1;228 attempt += 1;
229 continue;229 continue;
230 },230 },
tools/docgen.zig+7-1
...@@ -36,6 +36,12 @@ pub fn main() !void {...@@ -36,6 +36,12 @@ pub fn main() !void {
36 var args_it = try process.argsWithAllocator(arena);36 var args_it = try process.argsWithAllocator(arena);
37 if (!args_it.skip()) @panic("expected self arg");37 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
39 var opt_code_dir: ?[]const u8 = null;45 var opt_code_dir: ?[]const u8 = null;
40 var opt_input: ?[]const u8 = null;46 var opt_input: ?[]const u8 = null;
41 var opt_output: ?[]const u8 = null;47 var opt_output: ?[]const u8 = null;
...@@ -77,7 +83,7 @@ pub fn main() !void {...@@ -77,7 +83,7 @@ pub fn main() !void {
77 var code_dir = try fs.cwd().openDir(code_dir_path, .{});83 var code_dir = try fs.cwd().openDir(code_dir_path, .{});
78 defer code_dir.close();84 defer code_dir.close();
7985
80 var in_file_reader = in_file.reader(&.{});86 var in_file_reader = in_file.reader(io, &.{});
81 const input_file_bytes = try in_file_reader.interface.allocRemaining(arena, .limited(max_doc_file_size));87 const input_file_bytes = try in_file_reader.interface.allocRemaining(arena, .limited(max_doc_file_size));
8288
83 var tokenizer = Tokenizer.init(input_path, input_file_bytes);89 var tokenizer = Tokenizer.init(input_path, input_file_bytes);
tools/doctest.zig+26-18
...@@ -1,5 +1,8 @@...@@ -1,5 +1,8 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2
2const std = @import("std");3const std = @import("std");
4const Io = std.Io;
5const Writer = std.Io.Writer;
3const fatal = std.process.fatal;6const fatal = std.process.fatal;
4const mem = std.mem;7const mem = std.mem;
5const fs = std.fs;8const fs = std.fs;
...@@ -7,7 +10,6 @@ const process = std.process;...@@ -7,7 +10,6 @@ const process = std.process;
7const Allocator = std.mem.Allocator;10const Allocator = std.mem.Allocator;
8const testing = std.testing;11const testing = std.testing;
9const getExternalExecutor = std.zig.system.getExternalExecutor;12const getExternalExecutor = std.zig.system.getExternalExecutor;
10const Writer = std.Io.Writer;
1113
12const max_doc_file_size = 10 * 1024 * 1024;14const max_doc_file_size = 10 * 1024 * 1024;
1315
...@@ -36,6 +38,12 @@ pub fn main() !void {...@@ -36,6 +38,12 @@ pub fn main() !void {
36 var args_it = try process.argsWithAllocator(arena);38 var args_it = try process.argsWithAllocator(arena);
37 if (!args_it.skip()) fatal("missing argv[0]", .{});39 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
39 var opt_input: ?[]const u8 = null;47 var opt_input: ?[]const u8 = null;
40 var opt_output: ?[]const u8 = null;48 var opt_output: ?[]const u8 = null;
41 var opt_zig: ?[]const u8 = null;49 var opt_zig: ?[]const u8 = null;
...@@ -93,6 +101,7 @@ pub fn main() !void {...@@ -93,6 +101,7 @@ pub fn main() !void {
93 try printSourceBlock(arena, out, source, fs.path.basename(input_path));101 try printSourceBlock(arena, out, source, fs.path.basename(input_path));
94 try printOutput(102 try printOutput(
95 arena,103 arena,
104 io,
96 out,105 out,
97 code,106 code,
98 tmp_dir_path,107 tmp_dir_path,
...@@ -109,6 +118,7 @@ pub fn main() !void {...@@ -109,6 +118,7 @@ pub fn main() !void {
109118
110fn printOutput(119fn printOutput(
111 arena: Allocator,120 arena: Allocator,
121 io: Io,
112 out: *Writer,122 out: *Writer,
113 code: Code,123 code: Code,
114 /// Relative to this process' cwd.124 /// Relative to this process' cwd.
...@@ -123,11 +133,11 @@ fn printOutput(...@@ -123,11 +133,11 @@ fn printOutput(
123 var env_map = try process.getEnvMap(arena);133 var env_map = try process.getEnvMap(arena);
124 try env_map.put("CLICOLOR_FORCE", "1");134 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, .{});
127 const obj_ext = builtin.object_format.fileExt(builtin.cpu.arch);137 const obj_ext = builtin.object_format.fileExt(builtin.cpu.arch);
128 const print = std.debug.print;138 const print = std.debug.print;
129139
130 var shell_buffer: std.Io.Writer.Allocating = .init(arena);140 var shell_buffer: Writer.Allocating = .init(arena);
131 defer shell_buffer.deinit();141 defer shell_buffer.deinit();
132 const shell_out = &shell_buffer.writer;142 const shell_out = &shell_buffer.writer;
133143
...@@ -238,7 +248,7 @@ fn printOutput(...@@ -238,7 +248,7 @@ fn printOutput(
238 const target_query = try std.Target.Query.parse(.{248 const target_query = try std.Target.Query.parse(.{
239 .arch_os_abi = code.target_str orelse "native",249 .arch_os_abi = code.target_str orelse "native",
240 });250 });
241 const target = try std.zig.system.resolveTargetQuery(target_query);251 const target = try std.zig.system.resolveTargetQuery(io, target_query);
242252
243 const path_to_exe = try std.fmt.allocPrint(arena, "./{s}{s}", .{253 const path_to_exe = try std.fmt.allocPrint(arena, "./{s}{s}", .{
244 code_name, target.exeFileExt(),254 code_name, target.exeFileExt(),
...@@ -316,9 +326,7 @@ fn printOutput(...@@ -316,9 +326,7 @@ fn printOutput(
316 const target_query = try std.Target.Query.parse(.{326 const target_query = try std.Target.Query.parse(.{
317 .arch_os_abi = triple,327 .arch_os_abi = triple,
318 });328 });
319 const target = try std.zig.system.resolveTargetQuery(329 const target = try std.zig.system.resolveTargetQuery(io, target_query);
320 target_query,
321 );
322 switch (getExternalExecutor(&host, &target, .{330 switch (getExternalExecutor(&host, &target, .{
323 .link_libc = code.link_libc,331 .link_libc = code.link_libc,
324 })) {332 })) {
...@@ -1397,7 +1405,7 @@ test "printShell" {...@@ -1397,7 +1405,7 @@ test "printShell" {
1397 \\</samp></pre></figure>1405 \\</samp></pre></figure>
1398 ;1406 ;
13991407
1400 var buffer: std.Io.Writer.Allocating = .init(test_allocator);1408 var buffer: Writer.Allocating = .init(test_allocator);
1401 defer buffer.deinit();1409 defer buffer.deinit();
14021410
1403 try printShell(&buffer.writer, shell_out, false);1411 try printShell(&buffer.writer, shell_out, false);
...@@ -1414,7 +1422,7 @@ test "printShell" {...@@ -1414,7 +1422,7 @@ test "printShell" {
1414 \\</samp></pre></figure>1422 \\</samp></pre></figure>
1415 ;1423 ;
14161424
1417 var buffer: std.Io.Writer.Allocating = .init(test_allocator);1425 var buffer: Writer.Allocating = .init(test_allocator);
1418 defer buffer.deinit();1426 defer buffer.deinit();
14191427
1420 try printShell(&buffer.writer, shell_out, false);1428 try printShell(&buffer.writer, shell_out, false);
...@@ -1428,7 +1436,7 @@ test "printShell" {...@@ -1428,7 +1436,7 @@ test "printShell" {
1428 \\</samp></pre></figure>1436 \\</samp></pre></figure>
1429 ;1437 ;
14301438
1431 var buffer: std.Io.Writer.Allocating = .init(test_allocator);1439 var buffer: Writer.Allocating = .init(test_allocator);
1432 defer buffer.deinit();1440 defer buffer.deinit();
14331441
1434 try printShell(&buffer.writer, shell_out, false);1442 try printShell(&buffer.writer, shell_out, false);
...@@ -1447,7 +1455,7 @@ test "printShell" {...@@ -1447,7 +1455,7 @@ test "printShell" {
1447 \\</samp></pre></figure>1455 \\</samp></pre></figure>
1448 ;1456 ;
14491457
1450 var buffer: std.Io.Writer.Allocating = .init(test_allocator);1458 var buffer: Writer.Allocating = .init(test_allocator);
1451 defer buffer.deinit();1459 defer buffer.deinit();
14521460
1453 try printShell(&buffer.writer, shell_out, false);1461 try printShell(&buffer.writer, shell_out, false);
...@@ -1468,7 +1476,7 @@ test "printShell" {...@@ -1468,7 +1476,7 @@ test "printShell" {
1468 \\</samp></pre></figure>1476 \\</samp></pre></figure>
1469 ;1477 ;
14701478
1471 var buffer: std.Io.Writer.Allocating = .init(test_allocator);1479 var buffer: Writer.Allocating = .init(test_allocator);
1472 defer buffer.deinit();1480 defer buffer.deinit();
14731481
1474 try printShell(&buffer.writer, shell_out, false);1482 try printShell(&buffer.writer, shell_out, false);
...@@ -1487,7 +1495,7 @@ test "printShell" {...@@ -1487,7 +1495,7 @@ test "printShell" {
1487 \\</samp></pre></figure>1495 \\</samp></pre></figure>
1488 ;1496 ;
14891497
1490 var buffer: std.Io.Writer.Allocating = .init(test_allocator);1498 var buffer: Writer.Allocating = .init(test_allocator);
1491 defer buffer.deinit();1499 defer buffer.deinit();
14921500
1493 try printShell(&buffer.writer, shell_out, false);1501 try printShell(&buffer.writer, shell_out, false);
...@@ -1510,7 +1518,7 @@ test "printShell" {...@@ -1510,7 +1518,7 @@ test "printShell" {
1510 \\</samp></pre></figure>1518 \\</samp></pre></figure>
1511 ;1519 ;
15121520
1513 var buffer: std.Io.Writer.Allocating = .init(test_allocator);1521 var buffer: Writer.Allocating = .init(test_allocator);
1514 defer buffer.deinit();1522 defer buffer.deinit();
15151523
1516 try printShell(&buffer.writer, shell_out, false);1524 try printShell(&buffer.writer, shell_out, false);
...@@ -1532,7 +1540,7 @@ test "printShell" {...@@ -1532,7 +1540,7 @@ test "printShell" {
1532 \\</samp></pre></figure>1540 \\</samp></pre></figure>
1533 ;1541 ;
15341542
1535 var buffer: std.Io.Writer.Allocating = .init(test_allocator);1543 var buffer: Writer.Allocating = .init(test_allocator);
1536 defer buffer.deinit();1544 defer buffer.deinit();
15371545
1538 try printShell(&buffer.writer, shell_out, false);1546 try printShell(&buffer.writer, shell_out, false);
...@@ -1549,7 +1557,7 @@ test "printShell" {...@@ -1549,7 +1557,7 @@ test "printShell" {
1549 \\</samp></pre></figure>1557 \\</samp></pre></figure>
1550 ;1558 ;
15511559
1552 var buffer: std.Io.Writer.Allocating = .init(test_allocator);1560 var buffer: Writer.Allocating = .init(test_allocator);
1553 defer buffer.deinit();1561 defer buffer.deinit();
15541562
1555 try printShell(&buffer.writer, shell_out, false);1563 try printShell(&buffer.writer, shell_out, false);
...@@ -1568,7 +1576,7 @@ test "printShell" {...@@ -1568,7 +1576,7 @@ test "printShell" {
1568 \\</samp></pre></figure>1576 \\</samp></pre></figure>
1569 ;1577 ;
15701578
1571 var buffer: std.Io.Writer.Allocating = .init(test_allocator);1579 var buffer: Writer.Allocating = .init(test_allocator);
1572 defer buffer.deinit();1580 defer buffer.deinit();
15731581
1574 try printShell(&buffer.writer, shell_out, false);1582 try printShell(&buffer.writer, shell_out, false);
...@@ -1583,7 +1591,7 @@ test "printShell" {...@@ -1583,7 +1591,7 @@ test "printShell" {
1583 \\</samp></pre></figure>1591 \\</samp></pre></figure>
1584 ;1592 ;
15851593
1586 var buffer: std.Io.Writer.Allocating = .init(test_allocator);1594 var buffer: Writer.Allocating = .init(test_allocator);
1587 defer buffer.deinit();1595 defer buffer.deinit();
15881596
1589 try printShell(&buffer.writer, shell_out, false);1597 try printShell(&buffer.writer, shell_out, false);
tools/fetch_them_macos_headers.zig+11-5
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const fs = std.fs;3const fs = std.fs;
3const mem = std.mem;4const mem = std.mem;
4const process = std.process;5const process = std.process;
...@@ -85,8 +86,12 @@ pub fn main() anyerror!void {...@@ -85,8 +86,12 @@ pub fn main() anyerror!void {
85 } else try argv.append(arg);86 } else try argv.append(arg);
86 }87 }
8788
89 var threaded: Io.Threaded = .init(gpa);
90 defer threaded.deinit();
91 const io = threaded.io();
92
88 const sysroot_path = sysroot orelse blk: {93 const sysroot_path = sysroot orelse blk: {
89 const target = try std.zig.system.resolveTargetQuery(.{});94 const target = try std.zig.system.resolveTargetQuery(io, .{});
90 break :blk std.zig.system.darwin.getSdk(allocator, &target) orelse95 break :blk std.zig.system.darwin.getSdk(allocator, &target) orelse
91 fatal("no SDK found; you can provide one explicitly with '--sysroot' flag", .{});96 fatal("no SDK found; you can provide one explicitly with '--sysroot' flag", .{});
92 };97 };
...@@ -114,12 +119,13 @@ pub fn main() anyerror!void {...@@ -114,12 +119,13 @@ pub fn main() anyerror!void {
114 .arch = arch,119 .arch = arch,
115 .os_ver = os_ver,120 .os_ver = os_ver,
116 };121 };
117 try fetchTarget(allocator, argv.items, sysroot_path, target, version, tmp);122 try fetchTarget(allocator, io, argv.items, sysroot_path, target, version, tmp);
118 }123 }
119}124}
120125
121fn fetchTarget(126fn fetchTarget(
122 arena: Allocator,127 arena: Allocator,
128 io: Io,
123 args: []const []const u8,129 args: []const []const u8,
124 sysroot: []const u8,130 sysroot: []const u8,
125 target: Target,131 target: Target,
...@@ -190,7 +196,7 @@ fn fetchTarget(...@@ -190,7 +196,7 @@ fn fetchTarget(
190 var dirs = std.StringHashMap(fs.Dir).init(arena);196 var dirs = std.StringHashMap(fs.Dir).init(arena);
191 try dirs.putNoClobber(".", dest_dir);197 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, &.{});
194 const headers_list_str = try headers_list_file_reader.interface.allocRemaining(arena, .unlimited);200 const headers_list_str = try headers_list_file_reader.interface.allocRemaining(arena, .unlimited);
195 const prefix = "/usr/include";201 const prefix = "/usr/include";
196202
...@@ -263,8 +269,8 @@ const Version = struct {...@@ -263,8 +269,8 @@ const Version = struct {
263269
264 pub fn format(270 pub fn format(
265 v: Version,271 v: Version,
266 writer: *std.Io.Writer,272 writer: *Io.Writer,
267 ) std.Io.Writer.Error!void {273 ) Io.Writer.Error!void {
268 try writer.print("{d}.{d}.{d}", .{ v.major, v.minor, v.patch });274 try writer.print("{d}.{d}.{d}", .{ v.major, v.minor, v.patch });
269 }275 }
270};276};
tools/gen_macos_headers_c.zig+2-2
...@@ -33,7 +33,7 @@ pub fn main() anyerror!void {...@@ -33,7 +33,7 @@ pub fn main() anyerror!void {
3333
34 if (positionals.items.len != 1) fatal("expected one positional argument: [dir]", .{});34 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 });
37 defer dir.close();37 defer dir.close();
38 var paths = std.array_list.Managed([]const u8).init(arena);38 var paths = std.array_list.Managed([]const u8).init(arena);
39 try findHeaders(arena, dir, "", &paths);39 try findHeaders(arena, dir, "", &paths);
...@@ -73,7 +73,7 @@ fn findHeaders(...@@ -73,7 +73,7 @@ fn findHeaders(
73 switch (entry.kind) {73 switch (entry.kind) {
74 .directory => {74 .directory => {
75 const path = try std.fs.path.join(arena, &.{ prefix, entry.name });75 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 });
77 defer subdir.close();77 defer subdir.close();
78 try findHeaders(arena, subdir, path, paths);78 try findHeaders(arena, subdir, path, paths);
79 },79 },
tools/generate_c_size_and_align_checks.zig+5-1
...@@ -39,8 +39,12 @@ pub fn main() !void {...@@ -39,8 +39,12 @@ pub fn main() !void {
39 std.process.exit(1);39 std.process.exit(1);
40 }40 }
4141
42 var threaded: std.Io.Threaded = .init(gpa);
43 defer threaded.deinit();
44 const io = threaded.io();
45
42 const query = try std.Target.Query.parse(.{ .arch_os_abi = args[1] });46 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
45 var buffer: [2000]u8 = undefined;49 var buffer: [2000]u8 = undefined;
46 var stdout_writer = std.fs.File.stdout().writerStreaming(&buffer);50 var stdout_writer = std.fs.File.stdout().writerStreaming(&buffer);
tools/incr-check.zig+23-11
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const Io = std.Io;
2const Allocator = std.mem.Allocator;3const Allocator = std.mem.Allocator;
3const Cache = std.Build.Cache;4const Cache = std.Build.Cache;
45
...@@ -11,6 +12,12 @@ pub fn main() !void {...@@ -11,6 +12,12 @@ pub fn main() !void {
11 defer arena_instance.deinit();12 defer arena_instance.deinit();
12 const arena = arena_instance.allocator();13 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
14 var opt_zig_exe: ?[]const u8 = null;21 var opt_zig_exe: ?[]const u8 = null;
15 var opt_input_file_name: ?[]const u8 = null;22 var opt_input_file_name: ?[]const u8 = null;
16 var opt_lib_dir: ?[]const u8 = null;23 var opt_lib_dir: ?[]const u8 = null;
...@@ -53,7 +60,7 @@ pub fn main() !void {...@@ -53,7 +60,7 @@ pub fn main() !void {
53 const input_file_name = opt_input_file_name orelse fatal("missing input file\n{s}", .{usage});60 const input_file_name = opt_input_file_name orelse fatal("missing input file\n{s}", .{usage});
5461
55 const input_file_bytes = try std.fs.cwd().readFileAlloc(input_file_name, arena, .limited(std.math.maxInt(u32)));62 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
58 // Check now: if there are any targets using the `cbe` backend, we need the lib dir.65 // Check now: if there are any targets using the `cbe` backend, we need the lib dir.
59 if (opt_lib_dir == null) {66 if (opt_lib_dir == null) {
...@@ -86,22 +93,21 @@ pub fn main() !void {...@@ -86,22 +93,21 @@ pub fn main() !void {
86 else93 else
87 null;94 null;
8895
89 const host = try std.zig.system.resolveTargetQuery(.{});96 const host = try std.zig.system.resolveTargetQuery(io, .{});
9097
91 const debug_log_verbose = debug_zcu or debug_dwarf or debug_link;98 const debug_log_verbose = debug_zcu or debug_dwarf or debug_link;
9299
93 for (case.targets) |target| {100 for (case.targets) |target| {
94 const target_prog_node = node: {101 const target_prog_node = node: {
95 var name_buf: [std.Progress.Node.max_name_len]u8 = undefined;102 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;
97 break :node prog_node.start(name, case.updates.len);104 break :node prog_node.start(name, case.updates.len);
98 };105 };
99 defer target_prog_node.end();106 defer target_prog_node.end();
100107
101 if (debug_log_verbose) {108 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 });
103 }110 }
104
105 var child_args: std.ArrayListUnmanaged([]const u8) = .empty;111 var child_args: std.ArrayListUnmanaged([]const u8) = .empty;
106 try child_args.appendSlice(arena, &.{112 try child_args.appendSlice(arena, &.{
107 resolved_zig_exe,113 resolved_zig_exe,
...@@ -114,8 +120,10 @@ pub fn main() !void {...@@ -114,8 +120,10 @@ pub fn main() !void {
114 ".local-cache",120 ".local-cache",
115 "--global-cache-dir",121 "--global-cache-dir",
116 ".global-cache",122 ".global-cache",
117 "--listen=-",
118 });123 });
124 if (target.resolved.os.tag == .windows) try child_args.append(arena, "-lws2_32");
125 try child_args.append(arena, "--listen=-");
126
119 if (opt_resolved_lib_dir) |resolved_lib_dir| {127 if (opt_resolved_lib_dir) |resolved_lib_dir| {
120 try child_args.appendSlice(arena, &.{ "--zig-lib-dir", resolved_lib_dir });128 try child_args.appendSlice(arena, &.{ "--zig-lib-dir", resolved_lib_dir });
121 }129 }
...@@ -167,8 +175,12 @@ pub fn main() !void {...@@ -167,8 +175,12 @@ pub fn main() !void {
167 target.query,175 target.query,
168 "-I",176 "-I",
169 opt_resolved_lib_dir.?, // verified earlier177 opt_resolved_lib_dir.?, // verified earlier
170 "-o",
171 });178 });
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");
172 }184 }
173185
174 var eval: Eval = .{186 var eval: Eval = .{
...@@ -186,7 +198,7 @@ pub fn main() !void {...@@ -186,7 +198,7 @@ pub fn main() !void {
186198
187 try child.spawn();199 try child.spawn();
188200
189 var poller = std.Io.poll(arena, Eval.StreamEnum, .{201 var poller = Io.poll(arena, Eval.StreamEnum, .{
190 .stdout = child.stdout.?,202 .stdout = child.stdout.?,
191 .stderr = child.stderr.?,203 .stderr = child.stderr.?,
192 });204 });
...@@ -226,7 +238,7 @@ const Eval = struct {...@@ -226,7 +238,7 @@ const Eval = struct {
226 cc_child_args: *std.ArrayListUnmanaged([]const u8),238 cc_child_args: *std.ArrayListUnmanaged([]const u8),
227239
228 const StreamEnum = enum { stdout, stderr };240 const StreamEnum = enum { stdout, stderr };
229 const Poller = std.Io.Poller(StreamEnum);241 const Poller = Io.Poller(StreamEnum);
230242
231 /// Currently this function assumes the previous updates have already been written.243 /// Currently this function assumes the previous updates have already been written.
232 fn write(eval: *Eval, update: Case.Update) void {244 fn write(eval: *Eval, update: Case.Update) void {
...@@ -647,7 +659,7 @@ const Case = struct {...@@ -647,7 +659,7 @@ const Case = struct {
647 msg: []const u8,659 msg: []const u8,
648 };660 };
649661
650 fn parse(arena: Allocator, bytes: []const u8) !Case {662 fn parse(arena: Allocator, io: Io, bytes: []const u8) !Case {
651 const fatal = std.process.fatal;663 const fatal = std.process.fatal;
652664
653 var targets: std.ArrayListUnmanaged(Target) = .empty;665 var targets: std.ArrayListUnmanaged(Target) = .empty;
...@@ -683,7 +695,7 @@ const Case = struct {...@@ -683,7 +695,7 @@ const Case = struct {
683 },695 },
684 }) catch fatal("line {d}: invalid target query '{s}'", .{ line_n, query });696 }) 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
688 try targets.append(arena, .{700 try targets.append(arena, .{
689 .query = query,701 .query = query,
tools/migrate_langref.zig+7-1
...@@ -13,10 +13,16 @@ pub fn main() !void {...@@ -13,10 +13,16 @@ pub fn main() !void {
13 defer arena_instance.deinit();13 defer arena_instance.deinit();
14 const arena = arena_instance.allocator();14 const arena = arena_instance.allocator();
1515
16 const gpa = arena;
17
16 const args = try std.process.argsAlloc(arena);18 const args = try std.process.argsAlloc(arena);
17 const input_file = args[1];19 const input_file = args[1];
18 const output_file = args[2];20 const output_file = args[2];
1921
22 var threaded: std.Io.Threaded = .init(gpa);
23 defer threaded.deinit();
24 const io = threaded.io();
25
20 var in_file = try fs.cwd().openFile(input_file, .{ .mode = .read_only });26 var in_file = try fs.cwd().openFile(input_file, .{ .mode = .read_only });
21 defer in_file.close();27 defer in_file.close();
2228
...@@ -28,7 +34,7 @@ pub fn main() !void {...@@ -28,7 +34,7 @@ pub fn main() !void {
28 var out_dir = try fs.cwd().openDir(fs.path.dirname(output_file).?, .{});34 var out_dir = try fs.cwd().openDir(fs.path.dirname(output_file).?, .{});
29 defer out_dir.close();35 defer out_dir.close();
3036
31 var in_file_reader = in_file.reader(&.{});37 var in_file_reader = in_file.reader(io, &.{});
32 const input_file_bytes = try in_file_reader.interface.allocRemaining(arena, .unlimited);38 const input_file_bytes = try in_file_reader.interface.allocRemaining(arena, .unlimited);
3339
34 var tokenizer = Tokenizer.init(input_file, input_file_bytes);40 var tokenizer = Tokenizer.init(input_file, input_file_bytes);