Commit 47aa5a70a5
Changed files (34)
lib
compiler
std
Build
crypto
tls
debug
SelfInfo
http
os
linux
posix
process
tar
zig
test
src
lib/compiler/build_runner.zig
@@ -1,5 +1,8 @@
-const std = @import("std");
+const runner = @This();
const builtin = @import("builtin");
+
+const std = @import("std");
+const Io = std.Io;
const assert = std.debug.assert;
const fmt = std.fmt;
const mem = std.mem;
@@ -11,7 +14,6 @@ const WebServer = std.Build.WebServer;
const Allocator = std.mem.Allocator;
const fatal = std.process.fatal;
const Writer = std.Io.Writer;
-const runner = @This();
const tty = std.Io.tty;
pub const root = @import("@build");
@@ -75,6 +77,7 @@ pub fn main() !void {
.io = io,
.arena = arena,
.cache = .{
+ .io = io,
.gpa = arena,
.manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),
},
@@ -84,7 +87,7 @@ pub fn main() !void {
.zig_lib_directory = zig_lib_directory,
.host = .{
.query = .{},
- .result = try std.zig.system.resolveTargetQuery(.{}),
+ .result = try std.zig.system.resolveTargetQuery(io, .{}),
},
.time_report = false,
};
@@ -121,7 +124,7 @@ pub fn main() !void {
var watch = false;
var fuzz: ?std.Build.Fuzz.Mode = null;
var debounce_interval_ms: u16 = 50;
- var webui_listen: ?std.net.Address = null;
+ var webui_listen: ?Io.net.IpAddress = null;
if (try std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(arena)) |str| {
if (std.meta.stringToEnum(ErrorStyle, str)) |style| {
@@ -288,11 +291,11 @@ pub fn main() !void {
});
};
} else if (mem.eql(u8, arg, "--webui")) {
- webui_listen = std.net.Address.parseIp("::1", 0) catch unreachable;
+ if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
} else if (mem.startsWith(u8, arg, "--webui=")) {
const addr_str = arg["--webui=".len..];
if (std.mem.eql(u8, addr_str, "-")) fatal("web interface cannot listen on stdio", .{});
- webui_listen = std.net.Address.parseIpAndPort(addr_str) catch |err| {
+ webui_listen = Io.net.IpAddress.parseLiteral(addr_str) catch |err| {
fatal("invalid web UI address '{s}': {s}", .{ addr_str, @errorName(err) });
};
} else if (mem.eql(u8, arg, "--debug-log")) {
@@ -334,14 +337,10 @@ pub fn main() !void {
watch = true;
} else if (mem.eql(u8, arg, "--time-report")) {
graph.time_report = true;
- if (webui_listen == null) {
- webui_listen = std.net.Address.parseIp("::1", 0) catch unreachable;
- }
+ if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
} else if (mem.eql(u8, arg, "--fuzz")) {
fuzz = .{ .forever = undefined };
- if (webui_listen == null) {
- webui_listen = std.net.Address.parseIp("::1", 0) catch unreachable;
- }
+ if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) };
} else if (mem.startsWith(u8, arg, "--fuzz=")) {
const value = arg["--fuzz=".len..];
if (value.len == 0) fatal("missing argument to --fuzz", .{});
@@ -550,13 +549,15 @@ pub fn main() !void {
var w: Watch = w: {
if (!watch) break :w undefined;
- if (!Watch.have_impl) fatal("--watch not yet implemented for {s}", .{@tagName(builtin.os.tag)});
+ if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{builtin.os.tag});
break :w try .init();
};
try run.thread_pool.init(thread_pool_options);
defer run.thread_pool.deinit();
+ const now = Io.Timestamp.now(io, .awake) catch |err| fatal("failed to collect timestamp: {t}", .{err});
+
run.web_server = if (webui_listen) |listen_address| ws: {
if (builtin.single_threaded) unreachable; // `fatal` above
break :ws .init(.{
@@ -568,11 +569,12 @@ pub fn main() !void {
.root_prog_node = main_progress_node,
.watch = watch,
.listen_address = listen_address,
+ .base_timestamp = now,
});
} else null;
if (run.web_server) |*ws| {
- ws.start() catch |err| fatal("failed to start web server: {s}", .{@errorName(err)});
+ ws.start() catch |err| fatal("failed to start web server: {t}", .{err});
}
rebuild: while (true) : (if (run.error_style.clearOnUpdate()) {
@@ -755,6 +757,7 @@ fn runStepNames(
fuzz: ?std.Build.Fuzz.Mode,
) !void {
const gpa = run.gpa;
+ const io = b.graph.io;
const step_stack = &run.step_stack;
const thread_pool = &run.thread_pool;
@@ -858,6 +861,7 @@ fn runStepNames(
assert(mode == .limit);
var f = std.Build.Fuzz.init(
gpa,
+ io,
thread_pool,
step_stack.keys(),
parent_prog_node,
lib/compiler/test_runner.zig
@@ -2,6 +2,7 @@
const builtin = @import("builtin");
const std = @import("std");
+const Io = std.Io;
const fatal = std.process.fatal;
const testing = std.testing;
const assert = std.debug.assert;
@@ -16,6 +17,7 @@ var fba: std.heap.FixedBufferAllocator = .init(&fba_buffer);
var fba_buffer: [8192]u8 = undefined;
var stdin_buffer: [4096]u8 = undefined;
var stdout_buffer: [4096]u8 = undefined;
+var runner_threaded_io: Io.Threaded = .init_single_threaded;
/// Keep in sync with logic in `std.Build.addRunArtifact` which decides whether
/// the test runner will communicate with the build runner via `std.zig.Server`.
@@ -63,8 +65,6 @@ pub fn main() void {
fuzz_abi.fuzzer_init(.fromSlice(cache_dir));
}
- fba.reset();
-
if (listen) {
return mainServer() catch @panic("internal test runner failure");
} else {
@@ -74,7 +74,7 @@ pub fn main() void {
fn mainServer() !void {
@disableInstrumentation();
- var stdin_reader = std.fs.File.stdin().readerStreaming(&stdin_buffer);
+ var stdin_reader = std.fs.File.stdin().readerStreaming(runner_threaded_io.io(), &stdin_buffer);
var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer);
var server = try std.zig.Server.init(.{
.in = &stdin_reader.interface,
@@ -131,7 +131,7 @@ fn mainServer() !void {
.run_test => {
testing.allocator_instance = .{};
- testing.io_instance = .init(fba.allocator());
+ testing.io_instance = .init(testing.allocator);
log_err_count = 0;
const index = try server.receiveBody_u32();
const test_fn = builtin.test_functions[index];
@@ -154,7 +154,6 @@ fn mainServer() !void {
},
};
testing.io_instance.deinit();
- fba.reset();
const leak_count = testing.allocator_instance.detectLeaks();
testing.allocator_instance.deinitWithoutLeakChecks();
try server.serveTestResults(.{
@@ -234,10 +233,10 @@ fn mainTerminal() void {
var leaks: usize = 0;
for (test_fn_list, 0..) |test_fn, i| {
testing.allocator_instance = .{};
- testing.io_instance = .init(fba.allocator());
+ testing.io_instance = .init(testing.allocator);
defer {
- if (testing.allocator_instance.deinit() == .leak) leaks += 1;
testing.io_instance.deinit();
+ if (testing.allocator_instance.deinit() == .leak) leaks += 1;
}
testing.log_level = .warn;
@@ -324,7 +323,7 @@ pub fn mainSimple() anyerror!void {
.stage2_aarch64, .stage2_riscv64 => true,
else => false,
};
- // is the backend capable of calling `std.Io.Writer.print`?
+ // is the backend capable of calling `Io.Writer.print`?
const enable_print = switch (builtin.zig_backend) {
.stage2_aarch64, .stage2_riscv64 => true,
else => false,
lib/std/Build/Step/Options.zig
@@ -538,8 +538,10 @@ test Options {
defer arena.deinit();
var graph: std.Build.Graph = .{
+ .io = io,
.arena = arena.allocator(),
.cache = .{
+ .io = io,
.gpa = arena.allocator(),
.manifest_dir = std.fs.cwd(),
},
lib/std/Build/Step/Run.zig
@@ -761,6 +761,7 @@ const IndexedOutput = struct {
};
fn make(step: *Step, options: Step.MakeOptions) !void {
const b = step.owner;
+ const io = b.graph.io;
const arena = b.allocator;
const run: *Run = @fieldParentPtr("step", step);
const has_side_effects = run.hasSideEffects();
@@ -834,7 +835,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
defer file.close();
var buf: [1024]u8 = undefined;
- var file_reader = file.reader(&buf);
+ var file_reader = file.reader(io, &buf);
_ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {
error.ReadFailed => return step.fail(
"failed to read from '{f}': {t}",
@@ -1067,6 +1068,7 @@ pub fn rerunInFuzzMode(
) !void {
const step = &run.step;
const b = step.owner;
+ const io = b.graph.io;
const arena = b.allocator;
var argv_list: std.ArrayList([]const u8) = .empty;
for (run.argv.items) |arg| {
@@ -1093,7 +1095,7 @@ pub fn rerunInFuzzMode(
defer file.close();
var buf: [1024]u8 = undefined;
- var file_reader = file.reader(&buf);
+ var file_reader = file.reader(io, &buf);
_ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) {
error.ReadFailed => return file_reader.err.?,
error.WriteFailed => return error.OutOfMemory,
@@ -2090,6 +2092,7 @@ fn sendRunFuzzTestMessage(
fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {
const b = run.step.owner;
+ const io = b.graph.io;
const arena = b.allocator;
try child.spawn();
@@ -2113,7 +2116,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {
defer file.close();
// TODO https://github.com/ziglang/zig/issues/23955
var read_buffer: [1024]u8 = undefined;
- var file_reader = file.reader(&read_buffer);
+ var file_reader = file.reader(io, &read_buffer);
var write_buffer: [1024]u8 = undefined;
var stdin_writer = child.stdin.?.writer(&write_buffer);
_ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) {
@@ -2159,7 +2162,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {
stdout_bytes = try poller.toOwnedSlice(.stdout);
stderr_bytes = try poller.toOwnedSlice(.stderr);
} else {
- var stdout_reader = stdout.readerStreaming(&.{});
+ var stdout_reader = stdout.readerStreaming(io, &.{});
stdout_bytes = stdout_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReadFailed => return stdout_reader.err.?,
@@ -2167,7 +2170,7 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult {
};
}
} else if (child.stderr) |stderr| {
- var stderr_reader = stderr.readerStreaming(&.{});
+ var stderr_reader = stderr.readerStreaming(io, &.{});
stderr_bytes = stderr_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReadFailed => return stderr_reader.err.?,
lib/std/Build/Step/UpdateSourceFiles.zig
@@ -3,11 +3,13 @@
//! not be used during the normal build process, but as a utility run by a
//! developer with intention to update source files, which will then be
//! committed to version control.
+const UpdateSourceFiles = @This();
+
const std = @import("std");
+const Io = std.Io;
const Step = std.Build.Step;
const fs = std.fs;
const ArrayList = std.ArrayList;
-const UpdateSourceFiles = @This();
step: Step,
output_source_files: std.ArrayListUnmanaged(OutputSourceFile),
@@ -70,22 +72,21 @@ pub fn addBytesToSource(usf: *UpdateSourceFiles, bytes: []const u8, sub_path: []
fn make(step: *Step, options: Step.MakeOptions) !void {
_ = options;
const b = step.owner;
+ const io = b.graph.io;
const usf: *UpdateSourceFiles = @fieldParentPtr("step", step);
var any_miss = false;
for (usf.output_source_files.items) |output_source_file| {
if (fs.path.dirname(output_source_file.sub_path)) |dirname| {
b.build_root.handle.makePath(dirname) catch |err| {
- return step.fail("unable to make path '{f}{s}': {s}", .{
- b.build_root, dirname, @errorName(err),
- });
+ return step.fail("unable to make path '{f}{s}': {t}", .{ b.build_root, dirname, err });
};
}
switch (output_source_file.contents) {
.bytes => |bytes| {
b.build_root.handle.writeFile(.{ .sub_path = output_source_file.sub_path, .data = bytes }) catch |err| {
- return step.fail("unable to write file '{f}{s}': {s}", .{
- b.build_root, output_source_file.sub_path, @errorName(err),
+ return step.fail("unable to write file '{f}{s}': {t}", .{
+ b.build_root, output_source_file.sub_path, err,
});
};
any_miss = true;
@@ -94,15 +95,16 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
if (!step.inputs.populated()) try step.addWatchInput(file_source);
const source_path = file_source.getPath2(b, step);
- const prev_status = fs.Dir.updateFile(
- fs.cwd(),
+ const prev_status = Io.Dir.updateFile(
+ .cwd(),
+ io,
source_path,
- b.build_root.handle,
+ b.build_root.handle.adaptToNewApi(),
output_source_file.sub_path,
.{},
) catch |err| {
- return step.fail("unable to update file from '{s}' to '{f}{s}': {s}", .{
- source_path, b.build_root, output_source_file.sub_path, @errorName(err),
+ return step.fail("unable to update file from '{s}' to '{f}{s}': {t}", .{
+ source_path, b.build_root, output_source_file.sub_path, err,
});
};
any_miss = any_miss or prev_status == .stale;
lib/std/Build/Step/WriteFile.zig
@@ -2,6 +2,7 @@
//! the local cache which has a set of files that have either been generated
//! during the build, or are copied from the source package.
const std = @import("std");
+const Io = std.Io;
const Step = std.Build.Step;
const fs = std.fs;
const ArrayList = std.ArrayList;
@@ -174,6 +175,7 @@ fn maybeUpdateName(write_file: *WriteFile) void {
fn make(step: *Step, options: Step.MakeOptions) !void {
_ = options;
const b = step.owner;
+ const io = b.graph.io;
const arena = b.allocator;
const gpa = arena;
const write_file: *WriteFile = @fieldParentPtr("step", step);
@@ -264,40 +266,27 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
};
defer cache_dir.close();
- const cwd = fs.cwd();
-
for (write_file.files.items) |file| {
if (fs.path.dirname(file.sub_path)) |dirname| {
cache_dir.makePath(dirname) catch |err| {
- return step.fail("unable to make path '{f}{s}{c}{s}': {s}", .{
- b.cache_root, cache_path, fs.path.sep, dirname, @errorName(err),
+ return step.fail("unable to make path '{f}{s}{c}{s}': {t}", .{
+ b.cache_root, cache_path, fs.path.sep, dirname, err,
});
};
}
switch (file.contents) {
.bytes => |bytes| {
cache_dir.writeFile(.{ .sub_path = file.sub_path, .data = bytes }) catch |err| {
- return step.fail("unable to write file '{f}{s}{c}{s}': {s}", .{
- b.cache_root, cache_path, fs.path.sep, file.sub_path, @errorName(err),
+ return step.fail("unable to write file '{f}{s}{c}{s}': {t}", .{
+ b.cache_root, cache_path, fs.path.sep, file.sub_path, err,
});
};
},
.copy => |file_source| {
const source_path = file_source.getPath2(b, step);
- const prev_status = fs.Dir.updateFile(
- cwd,
- source_path,
- cache_dir,
- file.sub_path,
- .{},
- ) catch |err| {
- return step.fail("unable to update file from '{s}' to '{f}{s}{c}{s}': {s}", .{
- source_path,
- b.cache_root,
- cache_path,
- fs.path.sep,
- file.sub_path,
- @errorName(err),
+ const prev_status = Io.Dir.updateFile(.cwd(), io, source_path, cache_dir.adaptToNewApi(), file.sub_path, .{}) catch |err| {
+ return step.fail("unable to update file from '{s}' to '{f}{s}{c}{s}': {t}", .{
+ source_path, b.cache_root, cache_path, fs.path.sep, file.sub_path, err,
});
};
// 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 {
switch (entry.kind) {
.directory => try cache_dir.makePath(dest_path),
.file => {
- const prev_status = fs.Dir.updateFile(
- src_entry_path.root_dir.handle,
+ const prev_status = Io.Dir.updateFile(
+ src_entry_path.root_dir.handle.adaptToNewApi(),
+ io,
src_entry_path.sub_path,
- cache_dir,
+ cache_dir.adaptToNewApi(),
dest_path,
.{},
) catch |err| {
lib/std/Build/Cache.zig
@@ -3,8 +3,10 @@
//! not to withstand attacks using specially-crafted input.
const Cache = @This();
-const std = @import("std");
const builtin = @import("builtin");
+
+const std = @import("std");
+const Io = std.Io;
const crypto = std.crypto;
const fs = std.fs;
const assert = std.debug.assert;
@@ -15,6 +17,7 @@ const Allocator = std.mem.Allocator;
const log = std.log.scoped(.cache);
gpa: Allocator,
+io: Io,
manifest_dir: fs.Dir,
hash: HashHelper = .{},
/// This value is accessed from multiple threads, protected by mutex.
@@ -661,9 +664,10 @@ pub const Manifest = struct {
},
} {
const gpa = self.cache.gpa;
+ const io = self.cache.io;
const input_file_count = self.files.entries.len;
var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded
- var manifest_reader = self.manifest_file.?.reader(&tiny_buffer); // Reads positionally from zero.
+ var manifest_reader = self.manifest_file.?.reader(io, &tiny_buffer); // Reads positionally from zero.
const limit: std.Io.Limit = .limited(manifest_file_size_max);
const file_contents = manifest_reader.interface.allocRemaining(gpa, limit) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
@@ -1337,7 +1341,8 @@ test "cache file and then recall it" {
var digest2: HexDigest = undefined;
{
- var cache = Cache{
+ var cache: Cache = .{
+ .io = io,
.gpa = testing.allocator,
.manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
};
@@ -1402,7 +1407,8 @@ test "check that changing a file makes cache fail" {
var digest2: HexDigest = undefined;
{
- var cache = Cache{
+ var cache: Cache = .{
+ .io = io,
.gpa = testing.allocator,
.manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
};
@@ -1451,6 +1457,8 @@ test "check that changing a file makes cache fail" {
}
test "no file inputs" {
+ const io = testing.io;
+
var tmp = testing.tmpDir(.{});
defer tmp.cleanup();
@@ -1459,7 +1467,8 @@ test "no file inputs" {
var digest1: HexDigest = undefined;
var digest2: HexDigest = undefined;
- var cache = Cache{
+ var cache: Cache = .{
+ .io = io,
.gpa = testing.allocator,
.manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
};
@@ -1517,7 +1526,8 @@ test "Manifest with files added after initial hash work" {
var digest3: HexDigest = undefined;
{
- var cache = Cache{
+ var cache: Cache = .{
+ .io = io,
.gpa = testing.allocator,
.manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
};
lib/std/Build/Fuzz.zig
@@ -1,4 +1,5 @@
const std = @import("../std.zig");
+const Io = std.Io;
const Build = std.Build;
const Cache = Build.Cache;
const Step = std.Build.Step;
@@ -14,6 +15,7 @@ const Fuzz = @This();
const build_runner = @import("root");
gpa: Allocator,
+io: Io,
mode: Mode,
/// Allocated into `gpa`.
@@ -75,6 +77,7 @@ const CoverageMap = struct {
pub fn init(
gpa: Allocator,
+ io: Io,
thread_pool: *std.Thread.Pool,
all_steps: []const *Build.Step,
root_prog_node: std.Progress.Node,
@@ -111,6 +114,7 @@ pub fn init(
return .{
.gpa = gpa,
+ .io = io,
.mode = mode,
.run_steps = run_steps,
.wait_group = .{},
@@ -484,6 +488,7 @@ fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReporte
pub fn waitAndPrintReport(fuzz: *Fuzz) void {
assert(fuzz.mode == .limit);
+ const io = fuzz.io;
fuzz.wait_group.wait();
fuzz.wait_group.reset();
@@ -506,7 +511,7 @@ pub fn waitAndPrintReport(fuzz: *Fuzz) void {
const fuzz_abi = std.Build.abi.fuzz;
var rbuf: [0x1000]u8 = undefined;
- var r = coverage_file.reader(&rbuf);
+ var r = coverage_file.reader(io, &rbuf);
var header: fuzz_abi.SeenPcsHeader = undefined;
r.interface.readSliceAll(std.mem.asBytes(&header)) catch |err| {
lib/std/Build/Step.zig
@@ -1,9 +1,11 @@
const Step = @This();
+const builtin = @import("builtin");
+
const std = @import("../std.zig");
+const Io = std.Io;
const Build = std.Build;
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
-const builtin = @import("builtin");
const Cache = Build.Cache;
const Path = Cache.Path;
const ArrayList = std.ArrayList;
@@ -327,7 +329,7 @@ pub fn cast(step: *Step, comptime T: type) ?*T {
}
/// For debugging purposes, prints identifying information about this Step.
-pub fn dump(step: *Step, w: *std.Io.Writer, tty_config: std.Io.tty.Config) void {
+pub fn dump(step: *Step, w: *Io.Writer, tty_config: Io.tty.Config) void {
if (step.debug_stack_trace.instruction_addresses.len > 0) {
w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {};
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
pub const ZigProcess = struct {
child: std.process.Child,
- poller: std.Io.Poller(StreamEnum),
+ poller: Io.Poller(StreamEnum),
progress_ipc_fd: if (std.Progress.have_ipc) ?std.posix.fd_t else void,
pub const StreamEnum = enum { stdout, stderr };
@@ -458,7 +460,7 @@ pub fn evalZigProcess(
const zp = try gpa.create(ZigProcess);
zp.* = .{
.child = child,
- .poller = std.Io.poll(gpa, ZigProcess.StreamEnum, .{
+ .poller = Io.poll(gpa, ZigProcess.StreamEnum, .{
.stdout = child.stdout.?,
.stderr = child.stderr.?,
}),
@@ -505,11 +507,12 @@ pub fn evalZigProcess(
}
/// Wrapper around `std.fs.Dir.updateFile` that handles verbose and error output.
-pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u8) !std.fs.Dir.PrevStatus {
+pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u8) !Io.Dir.PrevStatus {
const b = s.owner;
+ const io = b.graph.io;
const src_path = src_lazy_path.getPath3(b, s);
try handleVerbose(b, null, &.{ "install", "-C", b.fmt("{f}", .{src_path}), dest_path });
- return src_path.root_dir.handle.updateFile(src_path.sub_path, std.fs.cwd(), dest_path, .{}) catch |err| {
+ return Io.Dir.updateFile(src_path.root_dir.handle.adaptToNewApi(), io, src_path.sub_path, .cwd(), dest_path, .{}) catch |err| {
return s.fail("unable to update file from '{f}' to '{s}': {s}", .{
src_path, dest_path, @errorName(err),
});
@@ -738,7 +741,7 @@ pub fn allocPrintCmd2(
argv: []const []const u8,
) Allocator.Error![]u8 {
const shell = struct {
- fn escape(writer: *std.Io.Writer, string: []const u8, is_argv0: bool) !void {
+ fn escape(writer: *Io.Writer, string: []const u8, is_argv0: bool) !void {
for (string) |c| {
if (switch (c) {
else => true,
@@ -772,7 +775,7 @@ pub fn allocPrintCmd2(
}
};
- var aw: std.Io.Writer.Allocating = .init(gpa);
+ var aw: Io.Writer.Allocating = .init(gpa);
defer aw.deinit();
const writer = &aw.writer;
if (opt_cwd) |cwd| writer.print("cd {s} && ", .{cwd}) catch return error.OutOfMemory;
lib/std/Build/WebServer.zig
@@ -3,14 +3,15 @@ thread_pool: *std.Thread.Pool,
graph: *const Build.Graph,
all_steps: []const *Build.Step,
listen_address: net.IpAddress,
-ttyconf: std.Io.tty.Config,
+ttyconf: Io.tty.Config,
root_prog_node: std.Progress.Node,
watch: bool,
tcp_server: ?net.Server,
serve_thread: ?std.Thread,
-base_timestamp: i128,
+/// Uses `Io.Clock.awake`.
+base_timestamp: i96,
/// The "step name" data which trails `abi.Hello`, for the steps in `all_steps`.
step_names_trailing: []u8,
@@ -53,15 +54,17 @@ pub const Options = struct {
thread_pool: *std.Thread.Pool,
graph: *const std.Build.Graph,
all_steps: []const *Build.Step,
- ttyconf: std.Io.tty.Config,
+ ttyconf: Io.tty.Config,
root_prog_node: std.Progress.Node,
watch: bool,
listen_address: net.IpAddress,
+ base_timestamp: Io.Timestamp,
};
pub fn init(opts: Options) WebServer {
- // The upcoming `std.Io` interface should allow us to use `Io.async` and `Io.concurrent`
+ // The upcoming `Io` interface should allow us to use `Io.async` and `Io.concurrent`
// instead of threads, so that the web server can function in single-threaded builds.
comptime assert(!builtin.single_threaded);
+ assert(opts.base_timestamp.clock == .awake);
const all_steps = opts.all_steps;
@@ -106,7 +109,7 @@ pub fn init(opts: Options) WebServer {
.tcp_server = null,
.serve_thread = null,
- .base_timestamp = std.time.nanoTimestamp(),
+ .base_timestamp = opts.base_timestamp.nanoseconds,
.step_names_trailing = step_names_trailing,
.step_status_bits = step_status_bits,
@@ -147,32 +150,34 @@ pub fn deinit(ws: *WebServer) void {
pub fn start(ws: *WebServer) error{AlreadyReported}!void {
assert(ws.tcp_server == null);
assert(ws.serve_thread == null);
+ const io = ws.graph.io;
- ws.tcp_server = ws.listen_address.listen(.{ .reuse_address = true }) catch |err| {
+ ws.tcp_server = ws.listen_address.listen(io, .{ .reuse_address = true }) catch |err| {
log.err("failed to listen to port {d}: {s}", .{ ws.listen_address.getPort(), @errorName(err) });
return error.AlreadyReported;
};
ws.serve_thread = std.Thread.spawn(.{}, serve, .{ws}) catch |err| {
log.err("unable to spawn web server thread: {s}", .{@errorName(err)});
- ws.tcp_server.?.deinit();
+ ws.tcp_server.?.deinit(io);
ws.tcp_server = null;
return error.AlreadyReported;
};
- log.info("web interface listening at http://{f}/", .{ws.tcp_server.?.listen_address});
+ log.info("web interface listening at http://{f}/", .{ws.tcp_server.?.socket.address});
if (ws.listen_address.getPort() == 0) {
- log.info("hint: pass '--webui={f}' to use the same port next time", .{ws.tcp_server.?.listen_address});
+ log.info("hint: pass '--webui={f}' to use the same port next time", .{ws.tcp_server.?.socket.address});
}
}
fn serve(ws: *WebServer) void {
+ const io = ws.graph.io;
while (true) {
- const connection = ws.tcp_server.?.accept() catch |err| {
+ var stream = ws.tcp_server.?.accept(io) catch |err| {
log.err("failed to accept connection: {s}", .{@errorName(err)});
return;
};
- _ = std.Thread.spawn(.{}, accept, .{ ws, connection }) catch |err| {
+ _ = std.Thread.spawn(.{}, accept, .{ ws, stream }) catch |err| {
log.err("unable to spawn connection thread: {s}", .{@errorName(err)});
- connection.stream.close();
+ stream.close(io);
continue;
};
}
@@ -227,6 +232,7 @@ pub fn finishBuild(ws: *WebServer, opts: struct {
ws.fuzz = Fuzz.init(
ws.gpa,
+ ws.graph.io,
ws.thread_pool,
ws.all_steps,
ws.root_prog_node,
@@ -241,17 +247,25 @@ pub fn finishBuild(ws: *WebServer, opts: struct {
}
pub fn now(s: *const WebServer) i64 {
- return @intCast(std.time.nanoTimestamp() - s.base_timestamp);
+ const io = s.graph.io;
+ const base: Io.Timestamp = .{ .nanoseconds = s.base_timestamp, .clock = .awake };
+ const ts = Io.Timestamp.now(io, base.clock) catch base;
+ return @intCast(base.durationTo(ts).toNanoseconds());
}
-fn accept(ws: *WebServer, connection: net.Server.Connection) void {
- defer connection.stream.close();
-
+fn accept(ws: *WebServer, stream: net.Stream) void {
+ const io = ws.graph.io;
+ defer {
+ // `net.Stream.close` wants to helpfully overwrite `stream` with
+ // `undefined`, but it cannot do so since it is an immutable parameter.
+ var copy = stream;
+ copy.close(io);
+ }
var send_buffer: [4096]u8 = undefined;
var recv_buffer: [4096]u8 = undefined;
- var connection_reader = connection.stream.reader(&recv_buffer);
- var connection_writer = connection.stream.writer(&send_buffer);
- var server: http.Server = .init(connection_reader.interface(), &connection_writer.interface);
+ var connection_reader = stream.reader(io, &recv_buffer);
+ var connection_writer = stream.writer(io, &send_buffer);
+ var server: http.Server = .init(&connection_reader.interface, &connection_writer.interface);
while (true) {
var request = server.receiveHead() catch |err| switch (err) {
@@ -466,12 +480,9 @@ pub fn serveFile(
},
});
}
-pub fn serveTarFile(
- ws: *WebServer,
- request: *http.Server.Request,
- paths: []const Cache.Path,
-) !void {
+pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []const Cache.Path) !void {
const gpa = ws.gpa;
+ const io = ws.graph.io;
var send_buffer: [0x4000]u8 = undefined;
var response = try request.respondStreaming(&send_buffer, .{
@@ -496,7 +507,7 @@ pub fn serveTarFile(
defer file.close();
const stat = try file.stat();
var read_buffer: [1024]u8 = undefined;
- var file_reader: std.fs.File.Reader = .initSize(file, &read_buffer, stat.size);
+ var file_reader: Io.File.Reader = .initSize(file.adaptToNewApi(), io, &read_buffer, stat.size);
// TODO: this logic is completely bogus -- obviously so, because `path.root_dir.path` can
// be cwd-relative. This is also related to why linkification doesn't work in the fuzzer UI:
@@ -566,7 +577,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
child.stderr_behavior = .Pipe;
try child.spawn();
- var poller = std.Io.poll(gpa, enum { stdout, stderr }, .{
+ var poller = Io.poll(gpa, enum { stdout, stderr }, .{
.stdout = child.stdout.?,
.stderr = child.stderr.?,
});
@@ -842,7 +853,10 @@ const cache_control_header: http.Header = .{
};
const builtin = @import("builtin");
+
const std = @import("std");
+const Io = std.Io;
+const net = std.Io.net;
const assert = std.debug.assert;
const mem = std.mem;
const log = std.log.scoped(.web_server);
@@ -852,6 +866,5 @@ const Cache = Build.Cache;
const Fuzz = Build.Fuzz;
const abi = Build.abi;
const http = std.http;
-const net = std.Io.net;
const WebServer = @This();
lib/std/crypto/tls/Client.zig
@@ -105,6 +105,14 @@ pub const Options = struct {
/// Verify that the server certificate is authorized by a given ca bundle.
bundle: Certificate.Bundle,
},
+ write_buffer: []u8,
+ read_buffer: []u8,
+ /// Cryptographically secure random bytes. The pointer is not captured; data is only
+ /// read during `init`.
+ entropy: *const [176]u8,
+ /// Current time according to the wall clock / calendar, in seconds.
+ realtime_now_seconds: i64,
+
/// If non-null, ssl secrets are logged to this stream. Creating such a log file allows
/// other programs with access to that file to decrypt all traffic over this connection.
///
@@ -120,8 +128,6 @@ pub const Options = struct {
/// application layer itself verifies that the amount of data received equals
/// the amount of data expected, such as HTTP with the Content-Length header.
allow_truncation_attacks: bool = false,
- write_buffer: []u8,
- read_buffer: []u8,
/// Populated when `error.TlsAlert` is returned from `init`.
alert: ?*tls.Alert = null,
};
@@ -189,14 +195,12 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
};
const host_len: u16 = @intCast(host.len);
- var random_buffer: [176]u8 = undefined;
- crypto.random.bytes(&random_buffer);
- const client_hello_rand = random_buffer[0..32].*;
+ const client_hello_rand = options.entropy[0..32].*;
var key_seq: u64 = 0;
var server_hello_rand: [32]u8 = undefined;
- const legacy_session_id = random_buffer[32..64].*;
+ const legacy_session_id = options.entropy[32..64].*;
- var key_share = KeyShare.init(random_buffer[64..176].*) catch |err| switch (err) {
+ var key_share = KeyShare.init(options.entropy[64..176].*) catch |err| switch (err) {
// Only possible to happen if the seed is all zeroes.
error.IdentityElement => return error.InsufficientEntropy,
};
@@ -321,7 +325,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
var handshake_cipher: tls.HandshakeCipher = undefined;
var main_cert_pub_key: CertificatePublicKey = undefined;
var tls12_negotiated_group: ?tls.NamedGroup = null;
- const now_sec = std.time.timestamp();
+ const now_sec = options.realtime_now_seconds;
var cleartext_fragment_start: usize = 0;
var cleartext_fragment_end: usize = 0;
lib/std/debug/SelfInfo/Windows.zig
@@ -434,7 +434,7 @@ const Module = struct {
};
errdefer pdb_file.close();
- const pdb_reader = try arena.create(std.fs.File.Reader);
+ const pdb_reader = try arena.create(Io.File.Reader);
pdb_reader.* = pdb_file.reader(try arena.alloc(u8, 4096));
var pdb = Pdb.init(gpa, pdb_reader) catch |err| switch (err) {
@@ -544,6 +544,7 @@ fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) error{ MissingDebug
}
const std = @import("std");
+const Io = std.Io;
const Allocator = std.mem.Allocator;
const Dwarf = std.debug.Dwarf;
const Pdb = std.debug.Pdb;
lib/std/fs/Dir.zig
@@ -1,6 +1,11 @@
+//! Deprecated in favor of `Io.Dir`.
const Dir = @This();
+
const builtin = @import("builtin");
+const native_os = builtin.os.tag;
+
const std = @import("../std.zig");
+const Io = std.Io;
const File = std.fs.File;
const AtomicFile = std.fs.AtomicFile;
const base64_encoder = fs.base64_encoder;
@@ -12,7 +17,6 @@ const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const linux = std.os.linux;
const windows = std.os.windows;
-const native_os = builtin.os.tag;
const have_flock = @TypeOf(posix.system.flock) != void;
fd: Handle,
@@ -1189,84 +1193,41 @@ pub fn createFileW(self: Dir, sub_path_w: []const u16, flags: File.CreateFlags)
return file;
}
-pub const MakeError = posix.MakeDirError;
+/// Deprecated in favor of `Io.Dir.MakeError`.
+pub const MakeError = Io.Dir.MakeError;
-/// Creates a single directory with a relative or absolute path.
-/// To create multiple directories to make an entire path, see `makePath`.
-/// To operate on only absolute paths, see `makeDirAbsolute`.
-/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
-/// On WASI, `sub_path` should be encoded as valid UTF-8.
-/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
+/// Deprecated in favor of `Io.Dir.makeDir`.
pub fn makeDir(self: Dir, sub_path: []const u8) MakeError!void {
- try posix.mkdirat(self.fd, sub_path, default_mode);
+ var threaded: Io.Threaded = .init_single_threaded;
+ const io = threaded.io();
+ return Io.Dir.makeDir(.{ .handle = self.fd }, io, sub_path);
}
-/// Same as `makeDir`, but `sub_path` is null-terminated.
-/// To create multiple directories to make an entire path, see `makePath`.
-/// To operate on only absolute paths, see `makeDirAbsoluteZ`.
+/// Deprecated in favor of `Io.Dir.makeDir`.
pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) MakeError!void {
try posix.mkdiratZ(self.fd, sub_path, default_mode);
}
-/// Creates a single directory with a relative or absolute null-terminated WTF-16 LE-encoded path.
-/// To create multiple directories to make an entire path, see `makePath`.
-/// To operate on only absolute paths, see `makeDirAbsoluteW`.
+/// Deprecated in favor of `Io.Dir.makeDir`.
pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) MakeError!void {
try posix.mkdiratW(self.fd, mem.span(sub_path), default_mode);
}
-/// Calls makeDir iteratively to make an entire path
-/// (i.e. creating any parent directories that do not exist).
-/// Returns success if the path already exists and is a directory.
-/// This function is not atomic, and if it returns an error, the file system may
-/// have been modified regardless.
-/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
-/// On WASI, `sub_path` should be encoded as valid UTF-8.
-/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
-/// Fails on an empty path with `error.BadPathName` as that is not a path that can be created.
-///
-/// Paths containing `..` components are handled differently depending on the platform:
-/// - On Windows, `..` are resolved before the path is passed to NtCreateFile, meaning
-/// a `sub_path` like "first/../second" will resolve to "second" and only a
-/// `./second` directory will be created.
-/// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`,
-/// meaning a `sub_path` like "first/../second" will create both a `./first`
-/// and a `./second` directory.
-pub fn makePath(self: Dir, sub_path: []const u8) (MakeError || StatFileError)!void {
+/// Deprecated in favor of `Io.Dir.makePath`.
+pub fn makePath(self: Dir, sub_path: []const u8) MakePathError!void {
_ = try self.makePathStatus(sub_path);
}
-pub const MakePathStatus = enum { existed, created };
-/// Same as `makePath` except returns whether the path already existed or was successfully created.
-pub fn makePathStatus(self: Dir, sub_path: []const u8) (MakeError || StatFileError)!MakePathStatus {
- var it = try fs.path.componentIterator(sub_path);
- var status: MakePathStatus = .existed;
- var component = it.last() orelse return error.BadPathName;
- while (true) {
- if (self.makeDir(component.path)) |_| {
- status = .created;
- } else |err| switch (err) {
- error.PathAlreadyExists => {
- // stat the file and return an error if it's not a directory
- // this is important because otherwise a dangling symlink
- // could cause an infinite loop
- check_dir: {
- // workaround for windows, see https://github.com/ziglang/zig/issues/16738
- const fstat = self.statFile(component.path) catch |stat_err| switch (stat_err) {
- error.IsDir => break :check_dir,
- else => |e| return e,
- };
- if (fstat.kind != .directory) return error.NotDir;
- }
- },
- error.FileNotFound => |e| {
- component = it.previous() orelse return e;
- continue;
- },
- else => |e| return e,
- }
- component = it.next() orelse return status;
- }
+/// Deprecated in favor of `Io.Dir.MakePathStatus`.
+pub const MakePathStatus = Io.Dir.MakePathStatus;
+/// Deprecated in favor of `Io.Dir.MakePathError`.
+pub const MakePathError = Io.Dir.MakePathError;
+
+/// Deprecated in favor of `Io.Dir.makePathStatus`.
+pub fn makePathStatus(self: Dir, sub_path: []const u8) MakePathError!MakePathStatus {
+ var threaded: Io.Threaded = .init_single_threaded;
+ const io = threaded.io();
+ return Io.Dir.makePathStatus(.{ .handle = self.fd }, io, sub_path);
}
/// Windows only. Calls makeOpenDirAccessMaskW iteratively to make an entire path
@@ -2052,20 +2013,11 @@ pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
return windows.ReadLink(self.fd, sub_path_w, buffer);
}
-/// Read all of file contents using a preallocated buffer.
-/// The returned slice has the same pointer as `buffer`. If the length matches `buffer.len`
-/// the situation is ambiguous. It could either mean that the entire file was read, and
-/// it exactly fits the buffer, or it could mean the buffer was not big enough for the
-/// entire file.
-/// On Windows, `file_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
-/// On WASI, `file_path` should be encoded as valid UTF-8.
-/// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
+/// Deprecated in favor of `Io.Dir.readFile`.
pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 {
- var file = try self.openFile(file_path, .{});
- defer file.close();
-
- const end_index = try file.readAll(buffer);
- return buffer[0..end_index];
+ var threaded: Io.Threaded = .init_single_threaded;
+ const io = threaded.io();
+ return Io.Dir.readFile(.{ .handle = self.fd }, io, file_path, buffer);
}
pub const ReadFileAllocError = File.OpenError || File.ReadError || Allocator.Error || error{
@@ -2091,7 +2043,7 @@ pub fn readFileAlloc(
/// Used to allocate the result.
gpa: Allocator,
/// If reached or exceeded, `error.StreamTooLong` is returned instead.
- limit: std.Io.Limit,
+ limit: Io.Limit,
) ReadFileAllocError![]u8 {
return readFileAllocOptions(dir, sub_path, gpa, limit, .of(u8), null);
}
@@ -2101,6 +2053,8 @@ pub fn readFileAlloc(
///
/// If the file size is already known, a better alternative is to initialize a
/// `File.Reader`.
+///
+/// TODO move this function to Io.Dir
pub fn readFileAllocOptions(
dir: Dir,
/// On Windows, should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
@@ -2110,13 +2064,16 @@ pub fn readFileAllocOptions(
/// Used to allocate the result.
gpa: Allocator,
/// If reached or exceeded, `error.StreamTooLong` is returned instead.
- limit: std.Io.Limit,
+ limit: Io.Limit,
comptime alignment: std.mem.Alignment,
comptime sentinel: ?u8,
) ReadFileAllocError!(if (sentinel) |s| [:s]align(alignment.toByteUnits()) u8 else []align(alignment.toByteUnits()) u8) {
+ var threaded: Io.Threaded = .init_single_threaded;
+ const io = threaded.io();
+
var file = try dir.openFile(sub_path, .{});
defer file.close();
- var file_reader = file.reader(&.{});
+ var file_reader = file.reader(io, &.{});
return file_reader.interface.allocRemainingAlignedSentinel(gpa, limit, alignment, sentinel) catch |err| switch (err) {
error.ReadFailed => return file_reader.err.?,
error.OutOfMemory, error.StreamTooLong => |e| return e,
@@ -2647,6 +2604,8 @@ pub const CopyFileError = File.OpenError || File.StatError ||
/// [WTF-8](https://wtf-8.codeberg.page/). On WASI, both paths should be
/// encoded as valid UTF-8. On other platforms, both paths are an opaque
/// sequence of bytes with no particular encoding.
+///
+/// TODO move this function to Io.Dir
pub fn copyFile(
source_dir: Dir,
source_path: []const u8,
@@ -2654,11 +2613,15 @@ pub fn copyFile(
dest_path: []const u8,
options: CopyFileOptions,
) CopyFileError!void {
- var file_reader: File.Reader = .init(try source_dir.openFile(source_path, .{}), &.{});
- defer file_reader.file.close();
+ var threaded: Io.Threaded = .init_single_threaded;
+ const io = threaded.io();
+
+ const file = try source_dir.openFile(source_path, .{});
+ var file_reader: File.Reader = .init(.{ .handle = file.handle }, io, &.{});
+ defer file_reader.file.close(io);
const mode = options.override_mode orelse blk: {
- const st = try file_reader.file.stat();
+ const st = try file_reader.file.stat(io);
file_reader.size = st.size;
break :blk st.mode;
};
@@ -2708,6 +2671,7 @@ pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions)
pub const Stat = File.Stat;
pub const StatError = File.StatError;
+/// Deprecated in favor of `Io.Dir.stat`.
pub fn stat(self: Dir) StatError!Stat {
const file: File = .{ .handle = self.fd };
return file.stat();
@@ -2715,17 +2679,7 @@ pub fn stat(self: Dir) StatError!Stat {
pub const StatFileError = File.OpenError || File.StatError || posix.FStatAtError;
-/// Returns metadata for a file inside the directory.
-///
-/// On Windows, this requires three syscalls. On other operating systems, it
-/// only takes one.
-///
-/// Symlinks are followed.
-///
-/// `sub_path` may be absolute, in which case `self` is ignored.
-/// On Windows, `sub_path` should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
-/// On WASI, `sub_path` should be encoded as valid UTF-8.
-/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
+/// Deprecated in favor of `Io.Dir.statPath`.
pub fn statFile(self: Dir, sub_path: []const u8) StatFileError!Stat {
if (native_os == .windows) {
var file = try self.openFile(sub_path, .{});
@@ -2799,3 +2753,11 @@ pub fn setPermissions(self: Dir, permissions: Permissions) SetPermissionsError!v
const file: File = .{ .handle = self.fd };
try file.setPermissions(permissions);
}
+
+pub fn adaptToNewApi(dir: Dir) Io.Dir {
+ return .{ .handle = dir.fd };
+}
+
+pub fn adaptFromNewApi(dir: Io.Dir) Dir {
+ return .{ .fd = dir.handle };
+}
lib/std/fs/File.zig
@@ -858,10 +858,12 @@ pub const Writer = struct {
};
}
- pub fn moveToReader(w: *Writer) Reader {
+ /// TODO when this logic moves from fs.File to Io.File the io parameter should be deleted
+ pub fn moveToReader(w: *Writer, io: std.Io) Reader {
defer w.* = undefined;
return .{
- .file = w.file,
+ .io = io,
+ .file = .{ .handle = w.file.handle },
.mode = w.mode,
.pos = w.pos,
.interface = Reader.initInterface(w.interface.buffer),
@@ -1350,15 +1352,15 @@ pub const Writer = struct {
///
/// Positional is more threadsafe, since the global seek position is not
/// affected.
-pub fn reader(file: File, buffer: []u8) Reader {
- return .init(file, buffer);
+pub fn reader(file: File, io: std.Io, buffer: []u8) Reader {
+ return .init(.{ .handle = file.handle }, io, buffer);
}
/// Positional is more threadsafe, since the global seek position is not
/// affected, but when such syscalls are not available, preemptively
/// initializing in streaming mode skips a failed syscall.
-pub fn readerStreaming(file: File, buffer: []u8) Reader {
- return .initStreaming(file, buffer);
+pub fn readerStreaming(file: File, io: std.Io, buffer: []u8) Reader {
+ return .initStreaming(.{ .handle = file.handle }, io, buffer);
}
/// Defaults to positional reading; falls back to streaming.
@@ -1538,3 +1540,11 @@ pub fn downgradeLock(file: File) LockError!void {
};
}
}
+
+pub fn adaptToNewApi(file: File) std.Io.File {
+ return .{ .handle = file.handle };
+}
+
+pub fn adaptFromNewApi(file: std.Io.File) File {
+ return .{ .handle = file.handle };
+}
lib/std/fs/test.zig
@@ -1,10 +1,12 @@
-const std = @import("../std.zig");
const builtin = @import("builtin");
+const native_os = builtin.os.tag;
+
+const std = @import("../std.zig");
+const Io = std.Io;
const testing = std.testing;
const fs = std.fs;
const mem = std.mem;
const wasi = std.os.wasi;
-const native_os = builtin.os.tag;
const windows = std.os.windows;
const posix = std.posix;
@@ -73,6 +75,7 @@ const PathType = enum {
};
const TestContext = struct {
+ io: Io,
path_type: PathType,
path_sep: u8,
arena: ArenaAllocator,
@@ -83,6 +86,7 @@ const TestContext = struct {
pub fn init(path_type: PathType, path_sep: u8, allocator: mem.Allocator, transform_fn: *const PathType.TransformFn) TestContext {
const tmp = tmpDir(.{ .iterate = true });
return .{
+ .io = testing.io,
.path_type = path_type,
.path_sep = path_sep,
.arena = ArenaAllocator.init(allocator),
@@ -1319,6 +1323,8 @@ test "max file name component lengths" {
}
test "writev, readv" {
+ const io = testing.io;
+
var tmp = tmpDir(.{});
defer tmp.cleanup();
@@ -1327,78 +1333,55 @@ test "writev, readv" {
var buf1: [line1.len]u8 = undefined;
var buf2: [line2.len]u8 = undefined;
- var write_vecs = [_]posix.iovec_const{
- .{
- .base = line1,
- .len = line1.len,
- },
- .{
- .base = line2,
- .len = line2.len,
- },
- };
- var read_vecs = [_]posix.iovec{
- .{
- .base = &buf2,
- .len = buf2.len,
- },
- .{
- .base = &buf1,
- .len = buf1.len,
- },
- };
+ var write_vecs: [2][]const u8 = .{ line1, line2 };
+ var read_vecs: [2][]u8 = .{ &buf2, &buf1 };
var src_file = try tmp.dir.createFile("test.txt", .{ .read = true });
defer src_file.close();
- try src_file.writevAll(&write_vecs);
+ var writer = src_file.writerStreaming(&.{});
+
+ try writer.interface.writeVecAll(&write_vecs);
+ try writer.interface.flush();
try testing.expectEqual(@as(u64, line1.len + line2.len), try src_file.getEndPos());
- try src_file.seekTo(0);
- const read = try src_file.readvAll(&read_vecs);
- try testing.expectEqual(@as(usize, line1.len + line2.len), read);
+
+ var reader = writer.moveToReader(io);
+ try reader.seekTo(0);
+ try reader.interface.readVecAll(&read_vecs);
try testing.expectEqualStrings(&buf1, "line2\n");
try testing.expectEqualStrings(&buf2, "line1\n");
+ try testing.expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1));
}
test "pwritev, preadv" {
+ const io = testing.io;
+
var tmp = tmpDir(.{});
defer tmp.cleanup();
const line1 = "line1\n";
const line2 = "line2\n";
-
+ var lines: [2][]const u8 = .{ line1, line2 };
var buf1: [line1.len]u8 = undefined;
var buf2: [line2.len]u8 = undefined;
- var write_vecs = [_]posix.iovec_const{
- .{
- .base = line1,
- .len = line1.len,
- },
- .{
- .base = line2,
- .len = line2.len,
- },
- };
- var read_vecs = [_]posix.iovec{
- .{
- .base = &buf2,
- .len = buf2.len,
- },
- .{
- .base = &buf1,
- .len = buf1.len,
- },
- };
+ var read_vecs: [2][]u8 = .{ &buf2, &buf1 };
var src_file = try tmp.dir.createFile("test.txt", .{ .read = true });
defer src_file.close();
- try src_file.pwritevAll(&write_vecs, 16);
+ var writer = src_file.writer(&.{});
+
+ try writer.seekTo(16);
+ try writer.interface.writeVecAll(&lines);
+ try writer.interface.flush();
try testing.expectEqual(@as(u64, 16 + line1.len + line2.len), try src_file.getEndPos());
- const read = try src_file.preadvAll(&read_vecs, 16);
- try testing.expectEqual(@as(usize, line1.len + line2.len), read);
+
+ var reader = writer.moveToReader(io);
+ try reader.seekTo(16);
+ try reader.interface.readVecAll(&read_vecs);
try testing.expectEqualStrings(&buf1, "line2\n");
try testing.expectEqualStrings(&buf2, "line1\n");
+ try testing.expectError(error.EndOfStream, reader.interface.readSliceAll(&buf1));
}
test "setEndPos" {
@@ -1406,6 +1389,8 @@ test "setEndPos" {
if (native_os == .wasi and builtin.link_libc) return error.SkipZigTest;
if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23806
+ const io = testing.io;
+
var tmp = tmpDir(.{});
defer tmp.cleanup();
@@ -1416,11 +1401,13 @@ test "setEndPos" {
const initial_size = try f.getEndPos();
var buffer: [32]u8 = undefined;
+ var reader = f.reader(io, &.{});
{
try f.setEndPos(initial_size);
try testing.expectEqual(initial_size, try f.getEndPos());
- try testing.expectEqual(initial_size, try f.preadAll(&buffer, 0));
+ try reader.seekTo(0);
+ try testing.expectEqual(initial_size, reader.interface.readSliceShort(&buffer));
try testing.expectEqualStrings("ninebytes", buffer[0..@intCast(initial_size)]);
}
@@ -1428,7 +1415,8 @@ test "setEndPos" {
const larger = initial_size + 4;
try f.setEndPos(larger);
try testing.expectEqual(larger, try f.getEndPos());
- try testing.expectEqual(larger, try f.preadAll(&buffer, 0));
+ try reader.seekTo(0);
+ try testing.expectEqual(larger, reader.interface.readSliceShort(&buffer));
try testing.expectEqualStrings("ninebytes\x00\x00\x00\x00", buffer[0..@intCast(larger)]);
}
@@ -1436,25 +1424,21 @@ test "setEndPos" {
const smaller = initial_size - 5;
try f.setEndPos(smaller);
try testing.expectEqual(smaller, try f.getEndPos());
- try testing.expectEqual(smaller, try f.preadAll(&buffer, 0));
+ try reader.seekTo(0);
+ try testing.expectEqual(smaller, try reader.interface.readSliceShort(&buffer));
try testing.expectEqualStrings("nine", buffer[0..@intCast(smaller)]);
}
try f.setEndPos(0);
try testing.expectEqual(0, try f.getEndPos());
- try testing.expectEqual(0, try f.preadAll(&buffer, 0));
+ try reader.seekTo(0);
+ try testing.expectEqual(0, try reader.interface.readSliceShort(&buffer));
// Invalid file length should error gracefully. Actual limit is host
// and file-system dependent, but 1PB should fail on filesystems like
// EXT4 and NTFS. But XFS or Btrfs support up to 8EiB files.
- f.setEndPos(0x4_0000_0000_0000) catch |err| if (err != error.FileTooBig) {
- return err;
- };
-
- f.setEndPos(std.math.maxInt(u63)) catch |err| if (err != error.FileTooBig) {
- return err;
- };
-
+ try testing.expectError(error.FileTooBig, f.setEndPos(0x4_0000_0000_0000));
+ try testing.expectError(error.FileTooBig, f.setEndPos(std.math.maxInt(u63)));
try testing.expectError(error.FileTooBig, f.setEndPos(std.math.maxInt(u63) + 1));
try testing.expectError(error.FileTooBig, f.setEndPos(std.math.maxInt(u64)));
}
@@ -1560,31 +1544,6 @@ test "sendfile with buffered data" {
try std.testing.expectEqualSlices(u8, "AAAA", written_buf[0..amt]);
}
-test "copyRangeAll" {
- var tmp = tmpDir(.{});
- defer tmp.cleanup();
-
- try tmp.dir.makePath("os_test_tmp");
-
- var dir = try tmp.dir.openDir("os_test_tmp", .{});
- defer dir.close();
-
- var src_file = try dir.createFile("file1.txt", .{ .read = true });
- defer src_file.close();
-
- const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP";
- try src_file.writeAll(data);
-
- var dest_file = try dir.createFile("file2.txt", .{ .read = true });
- defer dest_file.close();
-
- var written_buf: [100]u8 = undefined;
- _ = try src_file.copyRangeAll(0, dest_file, 0, data.len);
-
- const amt = try dest_file.preadAll(&written_buf, 0);
- try testing.expectEqualStrings(data, written_buf[0..amt]);
-}
-
test "copyFile" {
try testWithAllSupportedPathTypes(struct {
fn impl(ctx: *TestContext) !void {
@@ -1708,8 +1667,8 @@ test "open file with exclusive lock twice, make sure second lock waits" {
}
};
- var started = std.Thread.ResetEvent{};
- var locked = std.Thread.ResetEvent{};
+ var started: std.Thread.ResetEvent = .unset;
+ var locked: std.Thread.ResetEvent = .unset;
const t = try std.Thread.spawn(.{}, S.checkFn, .{
&ctx.dir,
@@ -1773,7 +1732,7 @@ test "read from locked file" {
const f = try ctx.dir.createFile(filename, .{ .read = true });
defer f.close();
var buffer: [1]u8 = undefined;
- _ = try f.readAll(&buffer);
+ _ = try f.read(&buffer);
}
{
const f = try ctx.dir.createFile(filename, .{
@@ -1785,9 +1744,9 @@ test "read from locked file" {
defer f2.close();
var buffer: [1]u8 = undefined;
if (builtin.os.tag == .windows) {
- try std.testing.expectError(error.LockViolation, f2.readAll(&buffer));
+ try std.testing.expectError(error.LockViolation, f2.read(&buffer));
} else {
- try std.testing.expectEqual(0, f2.readAll(&buffer));
+ try std.testing.expectEqual(0, f2.read(&buffer));
}
}
}
@@ -1944,6 +1903,7 @@ test "'.' and '..' in fs.Dir functions" {
try testWithAllSupportedPathTypes(struct {
fn impl(ctx: *TestContext) !void {
+ const io = ctx.io;
const subdir_path = try ctx.transformPath("./subdir");
const file_path = try ctx.transformPath("./subdir/../file");
const copy_path = try ctx.transformPath("./subdir/../copy");
@@ -1966,7 +1926,8 @@ test "'.' and '..' in fs.Dir functions" {
try ctx.dir.deleteFile(rename_path);
try ctx.dir.writeFile(.{ .sub_path = update_path, .data = "something" });
- const prev_status = try ctx.dir.updateFile(file_path, ctx.dir, update_path, .{});
+ var dir = ctx.dir.adaptToNewApi();
+ const prev_status = try dir.updateFile(io, file_path, dir, update_path, .{});
try testing.expectEqual(fs.Dir.PrevStatus.stale, prev_status);
try ctx.dir.deleteDir(subdir_path);
@@ -2005,13 +1966,6 @@ test "'.' and '..' in absolute functions" {
renamed_file.close();
try fs.deleteFileAbsolute(renamed_file_path);
- const update_file_path = try fs.path.join(allocator, &.{ subdir_path, "../update" });
- const update_file = try fs.createFileAbsolute(update_file_path, .{});
- try update_file.writeAll("something");
- update_file.close();
- const prev_status = try fs.updateFileAbsolute(created_file_path, update_file_path, .{});
- try testing.expectEqual(fs.Dir.PrevStatus.stale, prev_status);
-
try fs.deleteDirAbsolute(subdir_path);
}
@@ -2079,6 +2033,7 @@ test "invalid UTF-8/WTF-8 paths" {
try testWithAllSupportedPathTypes(struct {
fn impl(ctx: *TestContext) !void {
+ const io = ctx.io;
// This is both invalid UTF-8 and WTF-8, since \xFF is an invalid start byte
const invalid_path = try ctx.transformPath("\xFF");
@@ -2129,7 +2084,8 @@ test "invalid UTF-8/WTF-8 paths" {
try testing.expectError(expected_err, ctx.dir.access(invalid_path, .{}));
try testing.expectError(expected_err, ctx.dir.accessZ(invalid_path, .{}));
- try testing.expectError(expected_err, ctx.dir.updateFile(invalid_path, ctx.dir, invalid_path, .{}));
+ var dir = ctx.dir.adaptToNewApi();
+ try testing.expectError(expected_err, dir.updateFile(io, invalid_path, dir, invalid_path, .{}));
try testing.expectError(expected_err, ctx.dir.copyFile(invalid_path, ctx.dir, invalid_path, .{}));
try testing.expectError(expected_err, ctx.dir.statFile(invalid_path));
@@ -2144,7 +2100,6 @@ test "invalid UTF-8/WTF-8 paths" {
try testing.expectError(expected_err, fs.renameZ(ctx.dir, invalid_path, ctx.dir, invalid_path));
if (native_os != .wasi and ctx.path_type != .relative) {
- try testing.expectError(expected_err, fs.updateFileAbsolute(invalid_path, invalid_path, .{}));
try testing.expectError(expected_err, fs.copyFileAbsolute(invalid_path, invalid_path, .{}));
try testing.expectError(expected_err, fs.makeDirAbsolute(invalid_path));
try testing.expectError(expected_err, fs.makeDirAbsoluteZ(invalid_path));
@@ -2175,6 +2130,8 @@ test "invalid UTF-8/WTF-8 paths" {
}
test "read file non vectored" {
+ const io = std.testing.io;
+
var tmp_dir = testing.tmpDir(.{});
defer tmp_dir.cleanup();
@@ -2188,7 +2145,7 @@ test "read file non vectored" {
try file_writer.interface.flush();
}
- var file_reader: std.fs.File.Reader = .init(file, &.{});
+ var file_reader: std.Io.File.Reader = .initAdapted(file, io, &.{});
var write_buffer: [100]u8 = undefined;
var w: std.Io.Writer = .fixed(&write_buffer);
@@ -2205,6 +2162,8 @@ test "read file non vectored" {
}
test "seek keeping partial buffer" {
+ const io = std.testing.io;
+
var tmp_dir = testing.tmpDir(.{});
defer tmp_dir.cleanup();
@@ -2219,7 +2178,7 @@ test "seek keeping partial buffer" {
}
var read_buffer: [3]u8 = undefined;
- var file_reader: std.fs.File.Reader = .init(file, &read_buffer);
+ var file_reader: Io.File.Reader = .initAdapted(file, io, &read_buffer);
try testing.expectEqual(0, file_reader.logicalPos());
@@ -2246,13 +2205,15 @@ test "seek keeping partial buffer" {
}
test "seekBy" {
+ const io = testing.io;
+
var tmp_dir = testing.tmpDir(.{});
defer tmp_dir.cleanup();
try tmp_dir.dir.writeFile(.{ .sub_path = "blah.txt", .data = "let's test seekBy" });
const f = try tmp_dir.dir.openFile("blah.txt", .{ .mode = .read_only });
defer f.close();
- var reader = f.readerStreaming(&.{});
+ var reader = f.readerStreaming(io, &.{});
try reader.seekBy(2);
var buffer: [20]u8 = undefined;
lib/std/http/Client.zig
@@ -247,6 +247,7 @@ pub const Connection = struct {
port: u16,
stream: Io.net.Stream,
) error{OutOfMemory}!*Plain {
+ const io = client.io;
const gpa = client.allocator;
const alloc_len = allocLen(client, remote_host.bytes.len);
const base = try gpa.alignedAlloc(u8, .of(Plain), alloc_len);
@@ -260,8 +261,8 @@ pub const Connection = struct {
plain.* = .{
.connection = .{
.client = client,
- .stream_writer = stream.writer(socket_write_buffer),
- .stream_reader = stream.reader(socket_read_buffer),
+ .stream_writer = stream.writer(io, socket_write_buffer),
+ .stream_reader = stream.reader(io, socket_read_buffer),
.pool_node = .{},
.port = port,
.host_len = @intCast(remote_host.bytes.len),
@@ -300,6 +301,7 @@ pub const Connection = struct {
port: u16,
stream: Io.net.Stream,
) error{ OutOfMemory, TlsInitializationFailed }!*Tls {
+ const io = client.io;
const gpa = client.allocator;
const alloc_len = allocLen(client, remote_host.bytes.len);
const base = try gpa.alignedAlloc(u8, .of(Tls), alloc_len);
@@ -316,11 +318,14 @@ pub const Connection = struct {
assert(base.ptr + alloc_len == socket_read_buffer.ptr + socket_read_buffer.len);
@memcpy(host_buffer, remote_host.bytes);
const tls: *Tls = @ptrCast(base);
+ var random_buffer: [176]u8 = undefined;
+ std.crypto.random.bytes(&random_buffer);
+ const now_ts = if (Io.Timestamp.now(io, .real)) |ts| ts.toSeconds() else |_| return error.TlsInitializationFailed;
tls.* = .{
.connection = .{
.client = client,
- .stream_writer = stream.writer(tls_write_buffer),
- .stream_reader = stream.reader(socket_read_buffer),
+ .stream_writer = stream.writer(io, tls_write_buffer),
+ .stream_reader = stream.reader(io, socket_read_buffer),
.pool_node = .{},
.port = port,
.host_len = @intCast(remote_host.bytes.len),
@@ -338,6 +343,8 @@ pub const Connection = struct {
.ssl_key_log = client.ssl_key_log,
.read_buffer = tls_read_buffer,
.write_buffer = socket_write_buffer,
+ .entropy = &random_buffer,
+ .realtime_now_seconds = now_ts,
// This is appropriate for HTTPS because the HTTP headers contain
// the content length which is used to detect truncation attacks.
.allow_truncation_attacks = true,
@@ -1390,16 +1397,8 @@ pub const basic_authorization = struct {
};
pub const ConnectTcpError = error{
- ConnectionRefused,
- NetworkUnreachable,
- ConnectionTimedOut,
- ConnectionResetByPeer,
- TemporaryNameServerFailure,
- NameServerFailure,
- UnknownHostName,
- UnexpectedConnectFailure,
TlsInitializationFailed,
-} || Allocator.Error || Io.Cancelable;
+} || Allocator.Error || HostName.ConnectError;
/// Reuses a `Connection` if one matching `host` and `port` is already open.
///
@@ -1424,6 +1423,7 @@ pub const ConnectTcpOptions = struct {
};
pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcpError!*Connection {
+ const io = client.io;
const host = options.host;
const port = options.port;
const protocol = options.protocol;
@@ -1437,22 +1437,17 @@ pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcp
.protocol = protocol,
})) |conn| return conn;
- const stream = host.connect(client.io, port, .{ .mode = .stream }) catch |err| switch (err) {
- error.ConnectionRefused => return error.ConnectionRefused,
- error.NetworkUnreachable => return error.NetworkUnreachable,
- error.ConnectionTimedOut => return error.ConnectionTimedOut,
- error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
- error.NameServerFailure => return error.NameServerFailure,
- error.UnknownHostName => return error.UnknownHostName,
- error.Canceled => return error.Canceled,
- //else => return error.UnexpectedConnectFailure,
- };
- errdefer stream.close();
+ var stream = try host.connect(io, port, .{ .mode = .stream });
+ errdefer stream.close(io);
switch (protocol) {
.tls => {
if (disable_tls) return error.TlsInitializationFailed;
- const tc = try Connection.Tls.create(client, proxied_host, proxied_port, stream);
+ const tc = Connection.Tls.create(client, proxied_host, proxied_port, stream) catch |err| switch (err) {
+ error.OutOfMemory => |e| return e,
+ error.Unexpected => |e| return e,
+ error.UnsupportedClock => return error.TlsInitializationFailed,
+ };
client.connection_pool.addUsed(&tc.connection);
return &tc.connection;
},
lib/std/Io/net/HostName.zig
@@ -77,7 +77,9 @@ pub const LookupError = error{
InvalidDnsAAAARecord,
InvalidDnsCnameRecord,
NameServerFailure,
-} || Io.Timestamp.Error || IpAddress.BindError || Io.File.OpenError || Io.File.Reader.Error || Io.Cancelable;
+ /// Failed to open or read "/etc/hosts" or "/etc/resolv.conf".
+ DetectingNetworkConfigurationFailed,
+} || Io.Timestamp.Error || IpAddress.BindError || Io.Cancelable;
pub const LookupResult = struct {
/// How many `LookupOptions.addresses_buffer` elements are populated.
@@ -428,14 +430,25 @@ fn lookupHosts(host_name: HostName, io: Io, options: LookupOptions) !LookupResul
error.AccessDenied,
=> return .empty,
- else => |e| return e,
+ error.Canceled => |e| return e,
+
+ else => {
+ // TODO populate optional diagnostic struct
+ return error.DetectingNetworkConfigurationFailed;
+ },
};
defer file.close(io);
var line_buf: [512]u8 = undefined;
var file_reader = file.reader(io, &line_buf);
return lookupHostsReader(host_name, options, &file_reader.interface) catch |err| switch (err) {
- error.ReadFailed => return file_reader.err.?,
+ error.ReadFailed => switch (file_reader.err.?) {
+ error.Canceled => |e| return e,
+ else => {
+ // TODO populate optional diagnostic struct
+ return error.DetectingNetworkConfigurationFailed;
+ },
+ },
};
}
lib/std/Io/net/test.zig
@@ -211,11 +211,11 @@ test "listen on a port, send bytes, receive bytes" {
const t = try std.Thread.spawn(.{}, S.clientFn, .{server.socket.address});
defer t.join();
- var client = try server.accept(io);
- defer client.stream.close(io);
+ var stream = try server.accept(io);
+ defer stream.close(io);
var buf: [16]u8 = undefined;
- var stream_reader = client.stream.reader(io, &.{});
- const n = try stream_reader.interface().readSliceShort(&buf);
+ var stream_reader = stream.reader(io, &.{});
+ const n = try stream_reader.interface.readSliceShort(&buf);
try testing.expectEqual(@as(usize, 12), n);
try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
@@ -267,10 +267,9 @@ fn testServer(server: *net.Server) anyerror!void {
const io = testing.io;
- var client = try server.accept(io);
-
- const stream = client.stream.writer(io);
- try stream.print("hello from server\n", .{});
+ var stream = try server.accept(io);
+ var writer = stream.writer(io, &.{});
+ try writer.interface.print("hello from server\n", .{});
}
test "listen on a unix socket, send bytes, receive bytes" {
@@ -310,11 +309,11 @@ test "listen on a unix socket, send bytes, receive bytes" {
const t = try std.Thread.spawn(.{}, S.clientFn, .{socket_path});
defer t.join();
- var client = try server.accept(io);
- defer client.stream.close(io);
+ var stream = try server.accept(io);
+ defer stream.close(io);
var buf: [16]u8 = undefined;
- var stream_reader = client.stream.reader(io, &.{});
- const n = try stream_reader.interface().readSliceShort(&buf);
+ var stream_reader = stream.reader(io, &.{});
+ const n = try stream_reader.interface.readSliceShort(&buf);
try testing.expectEqual(@as(usize, 12), n);
try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
@@ -366,10 +365,10 @@ test "non-blocking tcp server" {
const socket_file = try net.tcpConnectToAddress(server.socket.address);
defer socket_file.close();
- var client = try server.accept(io);
- defer client.stream.close(io);
- const stream = client.stream.writer(io);
- try stream.print("hello from server\n", .{});
+ var stream = try server.accept(io);
+ defer stream.close(io);
+ var writer = stream.writer(io, .{});
+ try writer.interface.print("hello from server\n", .{});
var buf: [100]u8 = undefined;
const len = try socket_file.read(&buf);
lib/std/Io/Dir.zig
@@ -6,6 +6,9 @@ const File = Io.File;
handle: Handle,
+pub const Mode = Io.File.Mode;
+pub const default_mode: Mode = 0o755;
+
pub fn cwd() Dir {
return .{ .handle = std.fs.cwd().fd };
}
@@ -47,8 +50,9 @@ pub const UpdateFileError = File.OpenError;
/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If
/// they are equal, does nothing. Otherwise, atomically copies `source_path` to
-/// `dest_path`. The destination file gains the mtime, atime, and mode of the
-/// source file so that the next call to `updateFile` will not need a copy.
+/// `dest_path`, creating the parent directory hierarchy as needed. The
+/// destination file gains the mtime, atime, and mode of the source file so
+/// that the next call to `updateFile` will not need a copy.
///
/// Returns the previous status of the file before updating.
///
@@ -65,7 +69,7 @@ pub fn updateFile(
options: std.fs.Dir.CopyFileOptions,
) !PrevStatus {
var src_file = try source_dir.openFile(io, source_path, .{});
- defer src_file.close();
+ defer src_file.close(io);
const src_stat = try src_file.stat(io);
const actual_mode = options.override_mode orelse src_stat.mode;
@@ -93,13 +97,13 @@ pub fn updateFile(
}
var buffer: [1000]u8 = undefined; // Used only when direct fd-to-fd is not available.
- var atomic_file = try dest_dir.atomicFile(io, dest_path, .{
+ var atomic_file = try std.fs.Dir.atomicFile(.adaptFromNewApi(dest_dir), dest_path, .{
.mode = actual_mode,
.write_buffer = &buffer,
});
defer atomic_file.deinit();
- var src_reader: File.Reader = .initSize(io, src_file, &.{}, src_stat.size);
+ var src_reader: File.Reader = .initSize(src_file, io, &.{}, src_stat.size);
const dest_writer = &atomic_file.file_writer.interface;
_ = dest_writer.sendFileAll(&src_reader, .unlimited) catch |err| switch (err) {
@@ -111,3 +115,154 @@ pub fn updateFile(
try atomic_file.renameIntoPlace();
return .stale;
}
+
+pub const ReadFileError = File.OpenError || File.Reader.Error;
+
+/// Read all of file contents using a preallocated buffer.
+///
+/// The returned slice has the same pointer as `buffer`. If the length matches `buffer.len`
+/// the situation is ambiguous. It could either mean that the entire file was read, and
+/// it exactly fits the buffer, or it could mean the buffer was not big enough for the
+/// entire file.
+///
+/// * On Windows, `file_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// * On WASI, `file_path` should be encoded as valid UTF-8.
+/// * On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding.
+pub fn readFile(dir: Dir, io: Io, file_path: []const u8, buffer: []u8) ReadFileError![]u8 {
+ var file = try dir.openFile(io, file_path, .{});
+ defer file.close(io);
+
+ var reader = file.reader(io, &.{});
+ const n = reader.interface.readSliceShort(buffer) catch |err| switch (err) {
+ error.ReadFailed => return reader.err.?,
+ };
+
+ return buffer[0..n];
+}
+
+pub const MakeError = error{
+ /// In WASI, this error may occur when the file descriptor does
+ /// not hold the required rights to create a new directory relative to it.
+ AccessDenied,
+ PermissionDenied,
+ DiskQuota,
+ PathAlreadyExists,
+ SymLinkLoop,
+ LinkQuotaExceeded,
+ NameTooLong,
+ FileNotFound,
+ SystemResources,
+ NoSpaceLeft,
+ NotDir,
+ ReadOnlyFileSystem,
+ /// WASI-only; file paths must be valid UTF-8.
+ InvalidUtf8,
+ /// Windows-only; file paths provided by the user must be valid WTF-8.
+ /// https://simonsapin.github.io/wtf-8/
+ InvalidWtf8,
+ BadPathName,
+ NoDevice,
+ /// On Windows, `\\server` or `\\server\share` was not found.
+ NetworkNotFound,
+} || Io.Cancelable || Io.UnexpectedError;
+
+/// Creates a single directory with a relative or absolute path.
+///
+/// * On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// * On WASI, `sub_path` should be encoded as valid UTF-8.
+/// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
+///
+/// Related:
+/// * `makePath`
+/// * `makeDirAbsolute`
+pub fn makeDir(dir: Dir, io: Io, sub_path: []const u8) MakeError!void {
+ return io.vtable.dirMake(io.userdata, dir, sub_path, default_mode);
+}
+
+pub const MakePathError = MakeError || StatPathError;
+
+/// Calls makeDir iteratively to make an entire path, creating any parent
+/// directories that do not exist.
+///
+/// Returns success if the path already exists and is a directory.
+///
+/// This function is not atomic, and if it returns an error, the file system
+/// may have been modified regardless.
+///
+/// Fails on an empty path with `error.BadPathName` as that is not a path that
+/// can be created.
+///
+/// On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// On WASI, `sub_path` should be encoded as valid UTF-8.
+/// On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
+///
+/// Paths containing `..` components are handled differently depending on the platform:
+/// - On Windows, `..` are resolved before the path is passed to NtCreateFile, meaning
+/// a `sub_path` like "first/../second" will resolve to "second" and only a
+/// `./second` directory will be created.
+/// - On other platforms, `..` are not resolved before the path is passed to `mkdirat`,
+/// meaning a `sub_path` like "first/../second" will create both a `./first`
+/// and a `./second` directory.
+pub fn makePath(dir: Dir, io: Io, sub_path: []const u8) MakePathError!void {
+ _ = try makePathStatus(dir, io, sub_path);
+}
+
+pub const MakePathStatus = enum { existed, created };
+
+/// Same as `makePath` except returns whether the path already existed or was
+/// successfully created.
+pub fn makePathStatus(dir: Dir, io: Io, sub_path: []const u8) MakePathError!MakePathStatus {
+ var it = try std.fs.path.componentIterator(sub_path);
+ var status: MakePathStatus = .existed;
+ var component = it.last() orelse return error.BadPathName;
+ while (true) {
+ if (makeDir(dir, io, component.path)) |_| {
+ status = .created;
+ } else |err| switch (err) {
+ error.PathAlreadyExists => {
+ // stat the file and return an error if it's not a directory
+ // this is important because otherwise a dangling symlink
+ // could cause an infinite loop
+ check_dir: {
+ // workaround for windows, see https://github.com/ziglang/zig/issues/16738
+ const fstat = statPath(dir, io, component.path) catch |stat_err| switch (stat_err) {
+ error.IsDir => break :check_dir,
+ else => |e| return e,
+ };
+ if (fstat.kind != .directory) return error.NotDir;
+ }
+ },
+ error.FileNotFound => |e| {
+ component = it.previous() orelse return e;
+ continue;
+ },
+ else => |e| return e,
+ }
+ component = it.next() orelse return status;
+ }
+}
+
+pub const Stat = File.Stat;
+pub const StatError = File.StatError;
+
+pub fn stat(dir: Dir, io: Io) StatError!Stat {
+ return io.vtable.dirStat(io.userdata, dir);
+}
+
+pub const StatPathError = File.OpenError || File.StatError;
+
+/// Returns metadata for a file inside the directory.
+///
+/// On Windows, this requires three syscalls. On other operating systems, it
+/// only takes one.
+///
+/// Symlinks are followed.
+///
+/// `sub_path` may be absolute, in which case `self` is ignored.
+///
+/// * On Windows, `sub_path` should be encoded as [WTF-8](https://simonsapin.github.io/wtf-8/).
+/// * On WASI, `sub_path` should be encoded as valid UTF-8.
+/// * On other platforms, `sub_path` is an opaque sequence of bytes with no particular encoding.
+pub fn statPath(dir: Dir, io: Io, sub_path: []const u8) StatPathError!File.Stat {
+ return io.vtable.dirStatPath(io.userdata, dir, sub_path);
+}
lib/std/Io/File.zig
@@ -446,7 +446,11 @@ pub const Reader = struct {
fn stream(io_reader: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
- switch (r.mode) {
+ return streamMode(r, w, limit, r.mode);
+ }
+
+ pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Reader.Mode) Io.Reader.StreamError!usize {
+ switch (mode) {
.positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {
error.Unimplemented => {
r.mode = r.mode.toReading();
lib/std/Io/net.zig
@@ -57,6 +57,39 @@ pub const IpAddress = union(enum) {
pub const Family = @typeInfo(IpAddress).@"union".tag_type.?;
+ pub const ParseLiteralError = error{ InvalidAddress, InvalidPort };
+
+ /// Parse an IP address which may include a port.
+ ///
+ /// For IPv4, this is written `address:port`.
+ ///
+ /// For IPv6, RFC 3986 defines this as an "IP literal", and the port is
+ /// differentiated from the address by surrounding the address part in
+ /// brackets "[addr]:port". Even if the port is not given, the brackets are
+ /// mandatory.
+ pub fn parseLiteral(text: []const u8) ParseLiteralError!IpAddress {
+ if (text.len == 0) return error.InvalidAddress;
+ if (text[0] == '[') {
+ const addr_end = std.mem.indexOfScalar(u8, text, ']') orelse
+ return error.InvalidAddress;
+ const addr_text = text[1..addr_end];
+ const port: u16 = p: {
+ if (addr_end == text.len - 1) break :p 0;
+ if (text[addr_end + 1] != ':') return error.InvalidAddress;
+ break :p std.fmt.parseInt(u16, text[addr_end + 2 ..], 10) catch return error.InvalidPort;
+ };
+ return parseIp6(addr_text, port) catch error.InvalidAddress;
+ }
+ if (std.mem.indexOfScalar(u8, text, ':')) |i| {
+ const addr = Ip4Address.parse(text[0..i], 0) catch return error.InvalidAddress;
+ return .{ .ip4 = .{
+ .bytes = addr.bytes,
+ .port = std.fmt.parseInt(u16, text[i + 1 ..], 10) catch return error.InvalidPort,
+ } };
+ }
+ return parseIp4(text, 0) catch error.InvalidAddress;
+ }
+
/// Parse the given IP address string into an `IpAddress` value.
///
/// This is a pure function but it cannot handle IPv6 addresses that have
lib/std/Io/Threaded.zig
@@ -63,7 +63,17 @@ const Closure = struct {
pub const InitError = std.Thread.CpuCountError || Allocator.Error;
-pub fn init(gpa: Allocator) Pool {
+/// Related:
+/// * `init_single_threaded`
+pub fn init(
+ /// Must be threadsafe. Only used for the following functions:
+ /// * `Io.VTable.async`
+ /// * `Io.VTable.concurrent`
+ /// * `Io.VTable.groupAsync`
+ /// If these functions are avoided, then `Allocator.failing` may be passed
+ /// here.
+ gpa: Allocator,
+) Pool {
var pool: Pool = .{
.allocator = gpa,
.threads = .empty,
@@ -77,6 +87,20 @@ pub fn init(gpa: Allocator) Pool {
return pool;
}
+/// Statically initialize such that any call to the following functions will
+/// fail with `error.OutOfMemory`:
+/// * `Io.VTable.async`
+/// * `Io.VTable.concurrent`
+/// * `Io.VTable.groupAsync`
+/// When initialized this way, `deinit` is safe, but unnecessary to call.
+pub const init_single_threaded: Pool = .{
+ .allocator = .failing,
+ .threads = .empty,
+ .stack_size = std.Thread.SpawnConfig.default_stack_size,
+ .cpu_count = 1,
+ .concurrent_count = 0,
+};
+
pub fn deinit(pool: *Pool) void {
const gpa = pool.allocator;
pool.join();
@@ -136,6 +160,10 @@ pub fn io(pool: *Pool) Io {
.conditionWait = conditionWait,
.conditionWake = conditionWake,
+ .dirMake = dirMake,
+ .dirStat = dirStat,
+ .dirStatPath = dirStatPath,
+ .fileStat = fileStat,
.createFile = createFile,
.fileOpen = fileOpen,
.fileClose = fileClose,
@@ -520,10 +548,11 @@ fn groupAsync(
fn groupWait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void {
const pool: *Pool = @ptrCast(@alignCast(userdata));
- _ = pool;
+ const gpa = pool.allocator;
if (builtin.single_threaded) return;
+ // TODO these primitives are too high level, need to check cancel on EINTR
const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
const reset_event: *ResetEvent = @ptrCast(&group.context);
std.Thread.WaitGroup.waitStateless(group_state, reset_event);
@@ -531,8 +560,9 @@ fn groupWait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void {
var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token));
while (true) {
const gc: *GroupClosure = @fieldParentPtr("node", node);
- gc.closure.requestCancel();
- node = node.next orelse break;
+ const node_next = node.next;
+ gc.free(gpa);
+ node = node_next orelse break;
}
}
@@ -724,6 +754,41 @@ fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.
}
}
+fn dirMake(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void {
+ const pool: *Pool = @ptrCast(@alignCast(userdata));
+ try pool.checkCancel();
+
+ _ = dir;
+ _ = sub_path;
+ _ = mode;
+ @panic("TODO");
+}
+
+fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat {
+ const pool: *Pool = @ptrCast(@alignCast(userdata));
+ try pool.checkCancel();
+
+ _ = dir;
+ @panic("TODO");
+}
+
+fn dirStatPath(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8) Io.Dir.StatError!Io.File.Stat {
+ const pool: *Pool = @ptrCast(@alignCast(userdata));
+ try pool.checkCancel();
+
+ _ = dir;
+ _ = sub_path;
+ @panic("TODO");
+}
+
+fn fileStat(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat {
+ const pool: *Pool = @ptrCast(@alignCast(userdata));
+ try pool.checkCancel();
+
+ _ = file;
+ @panic("TODO");
+}
+
fn createFile(
userdata: ?*anyopaque,
dir: Io.Dir,
lib/std/Io/Writer.zig
@@ -2827,6 +2827,8 @@ pub const Allocating = struct {
};
test "discarding sendFile" {
+ const io = testing.io;
+
var tmp_dir = testing.tmpDir(.{});
defer tmp_dir.cleanup();
@@ -2837,7 +2839,7 @@ test "discarding sendFile" {
try file_writer.interface.writeByte('h');
try file_writer.interface.flush();
- var file_reader = file_writer.moveToReader();
+ var file_reader = file_writer.moveToReader(io);
try file_reader.seekTo(0);
var w_buffer: [256]u8 = undefined;
@@ -2847,6 +2849,8 @@ test "discarding sendFile" {
}
test "allocating sendFile" {
+ const io = testing.io;
+
var tmp_dir = testing.tmpDir(.{});
defer tmp_dir.cleanup();
@@ -2857,7 +2861,7 @@ test "allocating sendFile" {
try file_writer.interface.writeAll("abcd");
try file_writer.interface.flush();
- var file_reader = file_writer.moveToReader();
+ var file_reader = file_writer.moveToReader(io);
try file_reader.seekTo(0);
try file_reader.interface.fill(2);
lib/std/os/linux/IoUring.zig
@@ -3,7 +3,7 @@ const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const mem = std.mem;
-const net = std.net;
+const net = std.Io.net;
const posix = std.posix;
const linux = std.os.linux;
const testing = std.testing;
@@ -2361,19 +2361,22 @@ test "sendmsg/recvmsg" {
};
defer ring.deinit();
- var address_server = try net.Address.parseIp4("127.0.0.1", 0);
+ var address_server: linux.sockaddr.in = .{
+ .port = 0,
+ .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
+ };
- const server = try posix.socket(address_server.any.family, posix.SOCK.DGRAM, 0);
+ const server = try posix.socket(address_server.family, posix.SOCK.DGRAM, 0);
defer posix.close(server);
try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEPORT, &mem.toBytes(@as(c_int, 1)));
try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));
- try posix.bind(server, &address_server.any, address_server.getOsSockLen());
+ try posix.bind(server, addrAny(&address_server), @sizeOf(linux.sockaddr.in));
// set address_server to the OS-chosen IP/port.
- var slen: posix.socklen_t = address_server.getOsSockLen();
- try posix.getsockname(server, &address_server.any, &slen);
+ var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in);
+ try posix.getsockname(server, addrAny(&address_server), &slen);
- const client = try posix.socket(address_server.any.family, posix.SOCK.DGRAM, 0);
+ const client = try posix.socket(address_server.family, posix.SOCK.DGRAM, 0);
defer posix.close(client);
const buffer_send = [_]u8{42} ** 128;
@@ -2381,8 +2384,8 @@ test "sendmsg/recvmsg" {
posix.iovec_const{ .base = &buffer_send, .len = buffer_send.len },
};
const msg_send: posix.msghdr_const = .{
- .name = &address_server.any,
- .namelen = address_server.getOsSockLen(),
+ .name = addrAny(&address_server),
+ .namelen = @sizeOf(linux.sockaddr.in),
.iov = &iovecs_send,
.iovlen = 1,
.control = null,
@@ -2398,11 +2401,13 @@ test "sendmsg/recvmsg" {
var iovecs_recv = [_]posix.iovec{
posix.iovec{ .base = &buffer_recv, .len = buffer_recv.len },
};
- const addr = [_]u8{0} ** 4;
- var address_recv = net.Address.initIp4(addr, 0);
+ var address_recv: linux.sockaddr.in = .{
+ .port = 0,
+ .addr = 0,
+ };
var msg_recv: posix.msghdr = .{
- .name = &address_recv.any,
- .namelen = address_recv.getOsSockLen(),
+ .name = addrAny(&address_recv),
+ .namelen = @sizeOf(linux.sockaddr.in),
.iov = &iovecs_recv,
.iovlen = 1,
.control = null,
@@ -2441,6 +2446,8 @@ test "sendmsg/recvmsg" {
test "timeout (after a relative time)" {
if (!is_linux) return error.SkipZigTest;
+ const io = testing.io;
+
var ring = IoUring.init(1, 0) catch |err| switch (err) {
error.SystemOutdated => return error.SkipZigTest,
error.PermissionDenied => return error.SkipZigTest,
@@ -2452,12 +2459,12 @@ test "timeout (after a relative time)" {
const margin = 5;
const ts: linux.kernel_timespec = .{ .sec = 0, .nsec = ms * 1000000 };
- const started = std.time.milliTimestamp();
+ const started = try std.Io.Timestamp.now(io, .awake);
const sqe = try ring.timeout(0x55555555, &ts, 0, 0);
try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe.opcode);
try testing.expectEqual(@as(u32, 1), try ring.submit());
const cqe = try ring.copy_cqe();
- const stopped = std.time.milliTimestamp();
+ const stopped = try std.Io.Timestamp.now(io, .awake);
try testing.expectEqual(linux.io_uring_cqe{
.user_data = 0x55555555,
@@ -2466,7 +2473,8 @@ test "timeout (after a relative time)" {
}, cqe);
// Tests should not depend on timings: skip test if outside margin.
- if (!std.math.approxEqAbs(f64, ms, @as(f64, @floatFromInt(stopped - started)), margin)) return error.SkipZigTest;
+ const ms_elapsed = started.durationTo(stopped).toMilliseconds();
+ if (ms_elapsed > margin) return error.SkipZigTest;
}
test "timeout (after a number of completions)" {
@@ -2861,19 +2869,22 @@ test "shutdown" {
};
defer ring.deinit();
- var address = try net.Address.parseIp4("127.0.0.1", 0);
+ var address: linux.sockaddr.in = .{
+ .port = 0,
+ .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
+ };
// Socket bound, expect shutdown to work
{
- const server = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
+ const server = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
defer posix.close(server);
try posix.setsockopt(server, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));
- try posix.bind(server, &address.any, address.getOsSockLen());
+ try posix.bind(server, addrAny(&address), @sizeOf(linux.sockaddr.in));
try posix.listen(server, 1);
// set address to the OS-chosen IP/port.
- var slen: posix.socklen_t = address.getOsSockLen();
- try posix.getsockname(server, &address.any, &slen);
+ var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in);
+ try posix.getsockname(server, addrAny(&address), &slen);
const shutdown_sqe = try ring.shutdown(0x445445445, server, linux.SHUT.RD);
try testing.expectEqual(linux.IORING_OP.SHUTDOWN, shutdown_sqe.opcode);
@@ -2898,7 +2909,7 @@ test "shutdown" {
// Socket not bound, expect to fail with ENOTCONN
{
- const server = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
+ const server = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
defer posix.close(server);
const shutdown_sqe = ring.shutdown(0x445445445, server, linux.SHUT.RD) catch |err| switch (err) {
@@ -2966,22 +2977,11 @@ test "renameat" {
}, cqe);
// Validate that the old file doesn't exist anymore
- {
- _ = tmp.dir.openFile(old_path, .{}) catch |err| switch (err) {
- error.FileNotFound => {},
- else => std.debug.panic("unexpected error: {}", .{err}),
- };
- }
+ try testing.expectError(error.FileNotFound, tmp.dir.openFile(old_path, .{}));
// Validate that the new file exists with the proper content
- {
- const new_file = try tmp.dir.openFile(new_path, .{});
- defer new_file.close();
-
- var new_file_data: [16]u8 = undefined;
- const bytes_read = try new_file.readAll(&new_file_data);
- try testing.expectEqualStrings("hello", new_file_data[0..bytes_read]);
- }
+ var new_file_data: [16]u8 = undefined;
+ try testing.expectEqualStrings("hello", try tmp.dir.readFile(new_path, &new_file_data));
}
test "unlinkat" {
@@ -3179,12 +3179,8 @@ test "linkat" {
}, cqe);
// Validate the second file
- const second_file = try tmp.dir.openFile(second_path, .{});
- defer second_file.close();
-
var second_file_data: [16]u8 = undefined;
- const bytes_read = try second_file.readAll(&second_file_data);
- try testing.expectEqualStrings("hello", second_file_data[0..bytes_read]);
+ try testing.expectEqualStrings("hello", try tmp.dir.readFile(second_path, &second_file_data));
}
test "provide_buffers: read" {
@@ -3588,7 +3584,10 @@ const SocketTestHarness = struct {
fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness {
// Create a TCP server socket
- var address = try net.Address.parseIp4("127.0.0.1", 0);
+ var address: linux.sockaddr.in = .{
+ .port = 0,
+ .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
+ };
const listener_socket = try createListenerSocket(&address);
errdefer posix.close(listener_socket);
@@ -3598,9 +3597,9 @@ fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness {
_ = try ring.accept(0xaaaaaaaa, listener_socket, &accept_addr, &accept_addr_len, 0);
// Create a TCP client socket
- const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
+ const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
errdefer posix.close(client);
- _ = try ring.connect(0xcccccccc, client, &address.any, address.getOsSockLen());
+ _ = try ring.connect(0xcccccccc, client, addrAny(&address), @sizeOf(linux.sockaddr.in));
try testing.expectEqual(@as(u32, 2), try ring.submit());
@@ -3636,18 +3635,18 @@ fn createSocketTestHarness(ring: *IoUring) !SocketTestHarness {
};
}
-fn createListenerSocket(address: *net.Address) !posix.socket_t {
+fn createListenerSocket(address: *linux.sockaddr.in) !posix.socket_t {
const kernel_backlog = 1;
- const listener_socket = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
+ const listener_socket = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
errdefer posix.close(listener_socket);
try posix.setsockopt(listener_socket, posix.SOL.SOCKET, posix.SO.REUSEADDR, &mem.toBytes(@as(c_int, 1)));
- try posix.bind(listener_socket, &address.any, address.getOsSockLen());
+ try posix.bind(listener_socket, addrAny(address), @sizeOf(linux.sockaddr.in));
try posix.listen(listener_socket, kernel_backlog);
// set address to the OS-chosen IP/port.
- var slen: posix.socklen_t = address.getOsSockLen();
- try posix.getsockname(listener_socket, &address.any, &slen);
+ var slen: posix.socklen_t = @sizeOf(linux.sockaddr.in);
+ try posix.getsockname(listener_socket, addrAny(address), &slen);
return listener_socket;
}
@@ -3662,7 +3661,10 @@ test "accept multishot" {
};
defer ring.deinit();
- var address = try net.Address.parseIp4("127.0.0.1", 0);
+ var address: linux.sockaddr.in = .{
+ .port = 0,
+ .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
+ };
const listener_socket = try createListenerSocket(&address);
defer posix.close(listener_socket);
@@ -3676,9 +3678,9 @@ test "accept multishot" {
var nr: usize = 4; // number of clients to connect
while (nr > 0) : (nr -= 1) {
// connect client
- const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
+ const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
errdefer posix.close(client);
- try posix.connect(client, &address.any, address.getOsSockLen());
+ try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
// test accept completion
var cqe = try ring.copy_cqe();
@@ -3756,7 +3758,10 @@ test "accept_direct" {
else => return err,
};
defer ring.deinit();
- var address = try net.Address.parseIp4("127.0.0.1", 0);
+ var address: linux.sockaddr.in = .{
+ .port = 0,
+ .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
+ };
// register direct file descriptors
var registered_fds = [_]posix.fd_t{-1} ** 2;
@@ -3779,8 +3784,8 @@ test "accept_direct" {
try testing.expectEqual(@as(u32, 1), try ring.submit());
// connect
- const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
- try posix.connect(client, &address.any, address.getOsSockLen());
+ const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
+ try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
defer posix.close(client);
// accept completion
@@ -3813,8 +3818,8 @@ test "accept_direct" {
_ = try ring.accept_direct(accept_userdata, listener_socket, null, null, 0);
try testing.expectEqual(@as(u32, 1), try ring.submit());
// connect
- const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
- try posix.connect(client, &address.any, address.getOsSockLen());
+ const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
+ try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
defer posix.close(client);
// completion with error
const cqe_accept = try ring.copy_cqe();
@@ -3837,7 +3842,10 @@ test "accept_multishot_direct" {
};
defer ring.deinit();
- var address = try net.Address.parseIp4("127.0.0.1", 0);
+ var address: linux.sockaddr.in = .{
+ .port = 0,
+ .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
+ };
var registered_fds = [_]posix.fd_t{-1} ** 2;
try ring.register_files(registered_fds[0..]);
@@ -3855,8 +3863,8 @@ test "accept_multishot_direct" {
for (registered_fds) |_| {
// connect
- const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
- try posix.connect(client, &address.any, address.getOsSockLen());
+ const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
+ try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
defer posix.close(client);
// accept completion
@@ -3870,8 +3878,8 @@ test "accept_multishot_direct" {
// Multishot is terminated (more flag is not set).
{
// connect
- const client = try posix.socket(address.any.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
- try posix.connect(client, &address.any, address.getOsSockLen());
+ const client = try posix.socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0);
+ try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in));
defer posix.close(client);
// completion with error
const cqe_accept = try ring.copy_cqe();
@@ -3944,7 +3952,10 @@ test "socket_direct/socket_direct_alloc/close_direct" {
try testing.expect(cqe_socket.res == 2); // returns registered file index
// use sockets from registered_fds in connect operation
- var address = try net.Address.parseIp4("127.0.0.1", 0);
+ var address: linux.sockaddr.in = .{
+ .port = 0,
+ .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
+ };
const listener_socket = try createListenerSocket(&address);
defer posix.close(listener_socket);
const accept_userdata: u64 = 0xaaaaaaaa;
@@ -3954,7 +3965,7 @@ test "socket_direct/socket_direct_alloc/close_direct" {
// prepare accept
_ = try ring.accept(accept_userdata, listener_socket, null, null, 0);
// prepare connect with fixed socket
- const connect_sqe = try ring.connect(connect_userdata, @intCast(fd_index), &address.any, address.getOsSockLen());
+ const connect_sqe = try ring.connect(connect_userdata, @intCast(fd_index), addrAny(&address), @sizeOf(linux.sockaddr.in));
connect_sqe.flags |= linux.IOSQE_FIXED_FILE; // fd is fixed file index
// submit both
try testing.expectEqual(@as(u32, 2), try ring.submit());
@@ -4483,12 +4494,15 @@ test "bind/listen/connect" {
// LISTEN is higher required operation
if (!probe.is_supported(.LISTEN)) return error.SkipZigTest;
- var addr = net.Address.initIp4([4]u8{ 127, 0, 0, 1 }, 0);
- const proto: u32 = if (addr.any.family == linux.AF.UNIX) 0 else linux.IPPROTO.TCP;
+ var addr: linux.sockaddr.in = .{
+ .port = 0,
+ .addr = @bitCast([4]u8{ 127, 0, 0, 1 }),
+ };
+ const proto: u32 = if (addr.family == linux.AF.UNIX) 0 else linux.IPPROTO.TCP;
const listen_fd = brk: {
// Create socket
- _ = try ring.socket(1, addr.any.family, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, proto, 0);
+ _ = try ring.socket(1, addr.family, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, proto, 0);
try testing.expectEqual(1, try ring.submit());
var cqe = try ring.copy_cqe();
try testing.expectEqual(1, cqe.user_data);
@@ -4500,7 +4514,7 @@ test "bind/listen/connect" {
var optval: u32 = 1;
(try ring.setsockopt(2, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEADDR, mem.asBytes(&optval))).link_next();
(try ring.setsockopt(3, listen_fd, linux.SOL.SOCKET, linux.SO.REUSEPORT, mem.asBytes(&optval))).link_next();
- (try ring.bind(4, listen_fd, &addr.any, addr.getOsSockLen(), 0)).link_next();
+ (try ring.bind(4, listen_fd, addrAny(&addr), @sizeOf(linux.sockaddr.in), 0)).link_next();
_ = try ring.listen(5, listen_fd, 1, 0);
// Submit 4 operations
try testing.expectEqual(4, try ring.submit());
@@ -4521,15 +4535,15 @@ test "bind/listen/connect" {
try testing.expectEqual(1, optval);
// Read system assigned port into addr
- var addr_len: posix.socklen_t = addr.getOsSockLen();
- try posix.getsockname(listen_fd, &addr.any, &addr_len);
+ var addr_len: posix.socklen_t = @sizeOf(linux.sockaddr.in);
+ try posix.getsockname(listen_fd, addrAny(&addr), &addr_len);
break :brk listen_fd;
};
const connect_fd = brk: {
// Create connect socket
- _ = try ring.socket(6, addr.any.family, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, proto, 0);
+ _ = try ring.socket(6, addr.family, linux.SOCK.STREAM | linux.SOCK.CLOEXEC, proto, 0);
try testing.expectEqual(1, try ring.submit());
const cqe = try ring.copy_cqe();
try testing.expectEqual(6, cqe.user_data);
@@ -4542,7 +4556,7 @@ test "bind/listen/connect" {
// Prepare accept/connect operations
_ = try ring.accept(7, listen_fd, null, null, 0);
- _ = try ring.connect(8, connect_fd, &addr.any, addr.getOsSockLen());
+ _ = try ring.connect(8, connect_fd, addrAny(&addr), @sizeOf(linux.sockaddr.in));
try testing.expectEqual(2, try ring.submit());
// Get listener accepted socket
var accept_fd: posix.socket_t = 0;
@@ -4604,3 +4618,7 @@ fn testSendRecv(ring: *IoUring, send_fd: posix.socket_t, recv_fd: posix.socket_t
try testing.expectEqualSlices(u8, buffer_send, buffer_recv[0..buffer_send.len]);
try testing.expectEqualSlices(u8, buffer_send, buffer_recv[buffer_send.len..]);
}
+
+fn addrAny(addr: *linux.sockaddr.in) *linux.sockaddr {
+ return @ptrCast(addr);
+}
lib/std/posix/test.zig
@@ -731,11 +731,8 @@ test "dup & dup2" {
try dup2ed.writeAll("dup2");
}
- var file = try tmp.dir.openFile("os_dup_test", .{});
- defer file.close();
-
- var buf: [7]u8 = undefined;
- try testing.expectEqualStrings("dupdup2", buf[0..try file.readAll(&buf)]);
+ var buffer: [8]u8 = undefined;
+ try testing.expectEqualStrings("dupdup2", try tmp.dir.readFile("os_dup_test", &buffer));
}
test "writev longer than IOV_MAX" {
lib/std/process/Child.zig
@@ -1,5 +1,9 @@
-const std = @import("../std.zig");
+const ChildProcess = @This();
+
const builtin = @import("builtin");
+const native_os = builtin.os.tag;
+
+const std = @import("../std.zig");
const unicode = std.unicode;
const fs = std.fs;
const process = std.process;
@@ -11,9 +15,7 @@ const mem = std.mem;
const EnvMap = std.process.EnvMap;
const maxInt = std.math.maxInt;
const assert = std.debug.assert;
-const native_os = builtin.os.tag;
const Allocator = std.mem.Allocator;
-const ChildProcess = @This();
const ArrayList = std.ArrayList;
pub const Id = switch (native_os) {
@@ -317,16 +319,23 @@ pub fn waitForSpawn(self: *ChildProcess) SpawnError!void {
const err_pipe = self.err_pipe orelse return;
self.err_pipe = null;
-
// Wait for the child to report any errors in or before `execvpe`.
- if (readIntFd(err_pipe)) |child_err_int| {
- posix.close(err_pipe);
+ const report = readIntFd(err_pipe);
+ posix.close(err_pipe);
+ if (report) |child_err_int| {
const child_err: SpawnError = @errorCast(@errorFromInt(child_err_int));
self.term = child_err;
return child_err;
- } else |_| {
- // Write end closed by CLOEXEC at the time of the `execvpe` call, indicating success!
- posix.close(err_pipe);
+ } else |read_err| switch (read_err) {
+ error.EndOfStream => {
+ // Write end closed by CLOEXEC at the time of the `execvpe` call,
+ // indicating success.
+ },
+ else => {
+ // Problem reading the error from the error reporting pipe. We
+ // don't know if the child is alive or dead. Better to assume it is
+ // alive so the resource does not risk being leaked.
+ },
}
}
@@ -1014,8 +1023,14 @@ fn writeIntFd(fd: i32, value: ErrInt) !void {
fn readIntFd(fd: i32) !ErrInt {
var buffer: [8]u8 = undefined;
- var fr: std.fs.File.Reader = .initStreaming(.{ .handle = fd }, &buffer);
- return @intCast(fr.interface.takeInt(u64, .little) catch return error.SystemResources);
+ var i: usize = 0;
+ while (i < buffer.len) {
+ const n = try std.posix.read(fd, buffer[i..]);
+ if (n == 0) return error.EndOfStream;
+ i += n;
+ }
+ const int = mem.readInt(u64, &buffer, .little);
+ return @intCast(int);
}
const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8);
lib/std/tar/Writer.zig
@@ -1,7 +1,9 @@
+const Writer = @This();
+
const std = @import("std");
+const Io = std.Io;
const assert = std.debug.assert;
const testing = std.testing;
-const Writer = @This();
const block_size = @sizeOf(Header);
@@ -14,7 +16,7 @@ pub const Options = struct {
mtime: u64 = 0,
};
-underlying_writer: *std.Io.Writer,
+underlying_writer: *Io.Writer,
prefix: []const u8 = "",
mtime_now: u64 = 0,
@@ -36,12 +38,12 @@ pub fn writeDir(w: *Writer, sub_path: []const u8, options: Options) Error!void {
try w.writeHeader(.directory, sub_path, "", 0, options);
}
-pub const WriteFileError = std.Io.Writer.FileError || Error || std.fs.File.Reader.SizeError;
+pub const WriteFileError = Io.Writer.FileError || Error || Io.File.Reader.SizeError;
pub fn writeFile(
w: *Writer,
sub_path: []const u8,
- file_reader: *std.fs.File.Reader,
+ file_reader: *Io.File.Reader,
stat_mtime: i128,
) WriteFileError!void {
const size = try file_reader.getSize();
@@ -58,7 +60,7 @@ pub fn writeFile(
try w.writePadding64(size);
}
-pub const WriteFileStreamError = Error || std.Io.Reader.StreamError;
+pub const WriteFileStreamError = Error || Io.Reader.StreamError;
/// Writes file reading file content from `reader`. Reads exactly `size` bytes
/// from `reader`, or returns `error.EndOfStream`.
@@ -66,7 +68,7 @@ pub fn writeFileStream(
w: *Writer,
sub_path: []const u8,
size: u64,
- reader: *std.Io.Reader,
+ reader: *Io.Reader,
options: Options,
) WriteFileStreamError!void {
try w.writeHeader(.regular, sub_path, "", size, options);
@@ -136,15 +138,15 @@ fn writeExtendedHeader(w: *Writer, typeflag: Header.FileType, buffers: []const [
try w.writePadding(len);
}
-fn writePadding(w: *Writer, bytes: usize) std.Io.Writer.Error!void {
+fn writePadding(w: *Writer, bytes: usize) Io.Writer.Error!void {
return writePaddingPos(w, bytes % block_size);
}
-fn writePadding64(w: *Writer, bytes: u64) std.Io.Writer.Error!void {
+fn writePadding64(w: *Writer, bytes: u64) Io.Writer.Error!void {
return writePaddingPos(w, @intCast(bytes % block_size));
}
-fn writePaddingPos(w: *Writer, pos: usize) std.Io.Writer.Error!void {
+fn writePaddingPos(w: *Writer, pos: usize) Io.Writer.Error!void {
if (pos == 0) return;
try w.underlying_writer.splatByteAll(0, block_size - pos);
}
@@ -153,7 +155,7 @@ fn writePaddingPos(w: *Writer, pos: usize) std.Io.Writer.Error!void {
/// "reasonable system must not assume that such a block exists when reading an
/// archive". Therefore, the Zig standard library recommends to not call this
/// function.
-pub fn finishPedantically(w: *Writer) std.Io.Writer.Error!void {
+pub fn finishPedantically(w: *Writer) Io.Writer.Error!void {
try w.underlying_writer.splatByteAll(0, block_size * 2);
}
@@ -248,7 +250,7 @@ pub const Header = extern struct {
try octal(&w.checksum, checksum);
}
- pub fn write(h: *Header, bw: *std.Io.Writer) error{ OctalOverflow, WriteFailed }!void {
+ pub fn write(h: *Header, bw: *Io.Writer) error{ OctalOverflow, WriteFailed }!void {
try h.updateChecksum();
try bw.writeAll(std.mem.asBytes(h));
}
@@ -396,14 +398,14 @@ test "write files" {
{
const root = "root";
- var output: std.Io.Writer.Allocating = .init(testing.allocator);
+ var output: Io.Writer.Allocating = .init(testing.allocator);
var w: Writer = .{ .underlying_writer = &output.writer };
defer output.deinit();
try w.setRoot(root);
for (files) |file|
try w.writeFileBytes(file.path, file.content, .{});
- var input: std.Io.Reader = .fixed(output.written());
+ var input: Io.Reader = .fixed(output.written());
var it: std.tar.Iterator = .init(&input, .{
.file_name_buffer = &file_name_buffer,
.link_name_buffer = &link_name_buffer,
@@ -424,7 +426,7 @@ test "write files" {
try testing.expectEqual('/', actual.name[root.len..][0]);
try testing.expectEqualStrings(expected.path, actual.name[root.len + 1 ..]);
- var content: std.Io.Writer.Allocating = .init(testing.allocator);
+ var content: Io.Writer.Allocating = .init(testing.allocator);
defer content.deinit();
try it.streamRemaining(actual, &content.writer);
try testing.expectEqualSlices(u8, expected.content, content.written());
@@ -432,15 +434,15 @@ test "write files" {
}
// without root
{
- var output: std.Io.Writer.Allocating = .init(testing.allocator);
+ var output: Io.Writer.Allocating = .init(testing.allocator);
var w: Writer = .{ .underlying_writer = &output.writer };
defer output.deinit();
for (files) |file| {
- var content: std.Io.Reader = .fixed(file.content);
+ var content: Io.Reader = .fixed(file.content);
try w.writeFileStream(file.path, file.content.len, &content, .{});
}
- var input: std.Io.Reader = .fixed(output.written());
+ var input: Io.Reader = .fixed(output.written());
var it: std.tar.Iterator = .init(&input, .{
.file_name_buffer = &file_name_buffer,
.link_name_buffer = &link_name_buffer,
@@ -452,7 +454,7 @@ test "write files" {
const expected = files[i];
try testing.expectEqualStrings(expected.path, actual.name);
- var content: std.Io.Writer.Allocating = .init(testing.allocator);
+ var content: Io.Writer.Allocating = .init(testing.allocator);
defer content.deinit();
try it.streamRemaining(actual, &content.writer);
try testing.expectEqualSlices(u8, expected.content, content.written());
lib/std/zig/system.zig
@@ -442,6 +442,7 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
error.DeviceBusy,
error.InputOutput,
error.LockViolation,
+ error.FileSystem,
error.UnableToOpenElfFile,
error.UnhelpfulFile,
@@ -542,16 +543,15 @@ fn detectNativeCpuAndFeatures(cpu_arch: Target.Cpu.Arch, os: Target.Os, query: T
return null;
}
-pub const AbiAndDynamicLinkerFromFileError = error{};
-
-pub fn abiAndDynamicLinkerFromFile(
+fn abiAndDynamicLinkerFromFile(
file_reader: *Io.File.Reader,
header: *const elf.Header,
cpu: Target.Cpu,
os: Target.Os,
ld_info_list: []const LdInfo,
query: Target.Query,
-) AbiAndDynamicLinkerFromFileError!Target {
+) !Target {
+ const io = file_reader.io;
var result: Target = .{
.cpu = cpu,
.os = os,
@@ -623,8 +623,8 @@ pub fn abiAndDynamicLinkerFromFile(
try file_reader.seekTo(shstr.sh_offset);
try file_reader.interface.readSliceAll(shstrtab);
const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: {
- var it = header.iterateSectionHeaders(&file_reader.interface);
- while (it.next()) |shdr| {
+ var it = header.iterateSectionHeaders(file_reader);
+ while (try it.next()) |shdr| {
const end = mem.findScalarPos(u8, shstrtab, shdr.sh_name, 0) orelse continue;
const sh_name = shstrtab[shdr.sh_name..end :0];
if (mem.eql(u8, sh_name, ".dynstr")) break :find_dyn_str .{
@@ -645,7 +645,7 @@ pub fn abiAndDynamicLinkerFromFile(
var it = mem.tokenizeScalar(u8, rpath_list, ':');
while (it.next()) |rpath| {
- if (glibcVerFromRPath(rpath)) |ver| {
+ if (glibcVerFromRPath(io, rpath)) |ver| {
result.os.version_range.linux.glibc = ver;
return result;
} else |err| switch (err) {
@@ -660,7 +660,7 @@ pub fn abiAndDynamicLinkerFromFile(
// There is no DT_RUNPATH so we try to find libc.so.6 inside the same
// directory as the dynamic linker.
if (fs.path.dirname(dl_path)) |rpath| {
- if (glibcVerFromRPath(rpath)) |ver| {
+ if (glibcVerFromRPath(io, rpath)) |ver| {
result.os.version_range.linux.glibc = ver;
return result;
} else |err| switch (err) {
@@ -725,7 +725,7 @@ pub fn abiAndDynamicLinkerFromFile(
@memcpy(path_buf[index..][0..abi.len], abi);
index += abi.len;
const rpath = path_buf[0..index];
- if (glibcVerFromRPath(rpath)) |ver| {
+ if (glibcVerFromRPath(io, rpath)) |ver| {
result.os.version_range.linux.glibc = ver;
return result;
} else |err| switch (err) {
@@ -842,18 +842,13 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion {
error.InvalidElfMagic,
error.InvalidElfEndian,
error.InvalidElfClass,
- error.InvalidElfFile,
error.InvalidElfVersion,
error.InvalidGnuLibCVersion,
error.EndOfStream,
=> return error.GLibCNotFound,
- error.SystemResources,
- error.UnableToReadElfFile,
- error.Unexpected,
- error.FileSystem,
- error.ProcessNotFound,
- => |e| return e,
+ error.ReadFailed => return file_reader.err.?,
+ else => |e| return e,
};
}
@@ -867,8 +862,8 @@ fn glibcVerFromSoFile(file_reader: *Io.File.Reader) !std.SemanticVersion {
try file_reader.seekTo(shstr.sh_offset);
try file_reader.interface.readSliceAll(shstrtab);
const dynstr: struct { offset: u64, size: u64 } = find_dyn_str: {
- var it = header.iterateSectionHeaders(&file_reader.interface);
- while (it.next()) |shdr| {
+ var it = header.iterateSectionHeaders(file_reader);
+ while (try it.next()) |shdr| {
const end = mem.findScalarPos(u8, shstrtab, shdr.sh_name, 0) orelse continue;
const sh_name = shstrtab[shdr.sh_name..end :0];
if (mem.eql(u8, sh_name, ".dynstr")) break :find_dyn_str .{
@@ -882,19 +877,25 @@ fn glibcVerFromSoFile(file_reader: *Io.File.Reader) !std.SemanticVersion {
// strings that start with "GLIBC_2." indicate the existence of such a glibc version,
// and furthermore, that the system-installed glibc is at minimum that version.
var max_ver: std.SemanticVersion = .{ .major = 2, .minor = 2, .patch = 5 };
-
+ var offset: u64 = 0;
try file_reader.seekTo(dynstr.offset);
- while (file_reader.interface.takeSentinel(0)) |s| {
- if (mem.startsWith(u8, s, "GLIBC_2.")) {
- const chopped = s["GLIBC_".len..];
- const ver = Target.Query.parseVersion(chopped) catch |err| switch (err) {
- error.Overflow => return error.InvalidGnuLibCVersion,
- error.InvalidVersion => return error.InvalidGnuLibCVersion,
- };
- switch (ver.order(max_ver)) {
- .gt => max_ver = ver,
- .lt, .eq => continue,
+ while (offset < dynstr.size) {
+ if (file_reader.interface.takeSentinel(0)) |s| {
+ if (mem.startsWith(u8, s, "GLIBC_2.")) {
+ const chopped = s["GLIBC_".len..];
+ const ver = Target.Query.parseVersion(chopped) catch |err| switch (err) {
+ error.Overflow => return error.InvalidGnuLibCVersion,
+ error.InvalidVersion => return error.InvalidGnuLibCVersion,
+ };
+ switch (ver.order(max_ver)) {
+ .gt => max_ver = ver,
+ .lt, .eq => continue,
+ }
}
+ offset += s.len + 1;
+ } else |err| switch (err) {
+ error.EndOfStream, error.StreamTooLong => break,
+ error.ReadFailed => |e| return e,
}
}
@@ -1091,22 +1092,12 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ
error.ProcessFdQuotaExceeded,
error.SystemFdQuotaExceeded,
error.ProcessNotFound,
+ error.Canceled,
=> |e| return e,
error.ReadFailed => return file_reader.err.?,
- error.UnableToReadElfFile,
- error.InvalidElfClass,
- error.InvalidElfVersion,
- error.InvalidElfEndian,
- error.InvalidElfFile,
- error.InvalidElfMagic,
- error.Unexpected,
- error.EndOfStream,
- error.NameTooLong,
- error.StaticElfFile,
- // Finally, we fall back on the standard path.
- => |e| {
+ else => |e| {
std.log.warn("encountered {t}; falling back to default ABI and dynamic linker", .{e});
return defaultAbiAndDynamicLinker(cpu, os, query);
},
lib/std/Build.zig
@@ -1837,6 +1837,8 @@ pub fn runAllowFail(
if (!process.can_spawn)
return error.ExecNotSupported;
+ const io = b.graph.io;
+
const max_output_size = 400 * 1024;
var child = std.process.Child.init(argv, b.allocator);
child.stdin_behavior = .Ignore;
@@ -1847,7 +1849,7 @@ pub fn runAllowFail(
try Step.handleVerbose2(b, null, child.env_map, argv);
try child.spawn();
- var stdout_reader = child.stdout.?.readerStreaming(&.{});
+ var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
const stdout = stdout_reader.interface.allocRemaining(b.allocator, .limited(max_output_size)) catch {
return error.ReadFailure;
};
lib/std/elf.zig
@@ -710,7 +710,7 @@ pub const ProgramHeaderIterator = struct {
const offset = it.phoff + size * it.index;
try it.file_reader.seekTo(offset);
- return takeProgramHeader(&it.file_reader.interface, it.is_64, it.endian);
+ return try takeProgramHeader(&it.file_reader.interface, it.is_64, it.endian);
}
};
@@ -731,7 +731,7 @@ pub const ProgramHeaderBufferIterator = struct {
const offset = it.phoff + size * it.index;
var reader = Io.Reader.fixed(it.buf[offset..]);
- return takeProgramHeader(&reader, it.is_64, it.endian);
+ return try takeProgramHeader(&reader, it.is_64, it.endian);
}
};
@@ -771,7 +771,7 @@ pub const SectionHeaderIterator = struct {
const offset = it.shoff + size * it.index;
try it.file_reader.seekTo(offset);
- return takeSectionHeader(&it.file_reader.interface, it.is_64, it.endian);
+ return try takeSectionHeader(&it.file_reader.interface, it.is_64, it.endian);
}
};
@@ -793,7 +793,7 @@ pub const SectionHeaderBufferIterator = struct {
if (offset > it.buf.len) return error.EndOfStream;
var reader = Io.Reader.fixed(it.buf[@intCast(offset)..]);
- return takeSectionHeader(&reader, it.is_64, it.endian);
+ return try takeSectionHeader(&reader, it.is_64, it.endian);
}
};
@@ -826,12 +826,12 @@ pub const DynamicSectionIterator = struct {
file_reader: *Io.File.Reader,
- pub fn next(it: *SectionHeaderIterator) !?Elf64_Dyn {
+ pub fn next(it: *DynamicSectionIterator) !?Elf64_Dyn {
if (it.offset >= it.end_offset) return null;
const size: u64 = if (it.is_64) @sizeOf(Elf64_Dyn) else @sizeOf(Elf32_Dyn);
defer it.offset += size;
try it.file_reader.seekTo(it.offset);
- return takeDynamicSection(&it.file_reader.interface, it.is_64, it.endian);
+ return try takeDynamicSection(&it.file_reader.interface, it.is_64, it.endian);
}
};
lib/std/Io.zig
@@ -654,6 +654,10 @@ pub const VTable = struct {
conditionWait: *const fn (?*anyopaque, cond: *Condition, mutex: *Mutex) Cancelable!void,
conditionWake: *const fn (?*anyopaque, cond: *Condition, wake: Condition.Wake) void,
+ dirMake: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, mode: Dir.Mode) Dir.MakeError!void,
+ dirStat: *const fn (?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat,
+ dirStatPath: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8) Dir.StatError!File.Stat,
+ fileStat: *const fn (?*anyopaque, file: File) File.StatError!File.Stat,
createFile: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File,
fileOpen: *const fn (?*anyopaque, dir: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File,
fileClose: *const fn (?*anyopaque, File) void,
@@ -804,6 +808,10 @@ pub const Timestamp = struct {
assert(lhs.clock == rhs.clock);
return std.math.compare(lhs.nanoseconds, op, rhs.nanoseconds);
}
+
+ pub fn toSeconds(t: Timestamp) i64 {
+ return @intCast(@divTrunc(t.nanoseconds, std.time.ns_per_s));
+ }
};
pub const Duration = struct {
@@ -831,6 +839,10 @@ pub const Duration = struct {
return @intCast(@divTrunc(d.nanoseconds, std.time.ns_per_s));
}
+ pub fn toNanoseconds(d: Duration) i96 {
+ return d.nanoseconds;
+ }
+
pub fn sleep(duration: Duration, io: Io) SleepError!void {
return io.vtable.sleep(io.userdata, .{ .duration = .{ .duration = duration, .clock = .awake } });
}
lib/std/posix.zig
@@ -3000,31 +3000,7 @@ pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: mode_t) MakeDirErro
windows.CloseHandle(sub_dir_handle);
}
-pub const MakeDirError = error{
- /// In WASI, this error may occur when the file descriptor does
- /// not hold the required rights to create a new directory relative to it.
- AccessDenied,
- PermissionDenied,
- DiskQuota,
- PathAlreadyExists,
- SymLinkLoop,
- LinkQuotaExceeded,
- NameTooLong,
- FileNotFound,
- SystemResources,
- NoSpaceLeft,
- NotDir,
- ReadOnlyFileSystem,
- /// WASI-only; file paths must be valid UTF-8.
- InvalidUtf8,
- /// Windows-only; file paths provided by the user must be valid WTF-8.
- /// https://wtf-8.codeberg.page/
- InvalidWtf8,
- BadPathName,
- NoDevice,
- /// On Windows, `\\server` or `\\server\share` was not found.
- NetworkNotFound,
-} || UnexpectedError;
+pub const MakeDirError = std.Io.Dir.MakeError;
/// Create a directory.
/// `mode` is ignored on Windows and WASI.
lib/std/zig.zig
@@ -559,7 +559,7 @@ test isUnderscore {
/// If the source can be UTF-16LE encoded, this function asserts that `gpa`
/// will align a byte-sized allocation to at least 2. Allocators that don't do
/// this are rare.
-pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *std.fs.File.Reader) ![:0]u8 {
+pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *Io.File.Reader) ![:0]u8 {
var buffer: std.ArrayList(u8) = .empty;
defer buffer.deinit(gpa);
test/src/Cases.zig
@@ -455,8 +455,7 @@ pub fn lowerToBuildSteps(
parent_step: *std.Build.Step,
options: CaseTestOptions,
) void {
- const host = std.zig.system.resolveTargetQuery(.{}) catch |err|
- std.debug.panic("unable to detect native host: {s}\n", .{@errorName(err)});
+ const host = b.resolveTargetQuery(.{});
const cases_dir_path = b.build_root.join(b.allocator, &.{ "test", "cases" }) catch @panic("OOM");
for (self.cases.items) |case| {
@@ -587,7 +586,7 @@ pub fn lowerToBuildSteps(
},
.Execution => |expected_stdout| no_exec: {
const run = if (case.target.result.ofmt == .c) run_step: {
- if (getExternalExecutor(&host, &case.target.result, .{ .link_libc = true }) != .native) {
+ if (getExternalExecutor(&host.result, &case.target.result, .{ .link_libc = true }) != .native) {
// We wouldn't be able to run the compiled C code.
break :no_exec;
}
@@ -972,14 +971,6 @@ const TestManifest = struct {
}
};
-fn resolveTargetQuery(query: std.Target.Query) std.Build.ResolvedTarget {
- return .{
- .query = query,
- .target = std.zig.system.resolveTargetQuery(query) catch
- @panic("unable to resolve target query"),
- };
-}
-
fn knownFileExtension(filename: []const u8) bool {
// List taken from `Compilation.classifyFileExt` in the compiler.
for ([_][]const u8{